Process definition
With the module’s skeleton ready, it’s time to edit the process to define the script
to run and its
input
and output
sections. For now, open the main.nf file. We’ll go through the following steps in order :
- Write the script section
- Define the inputs and outputs
- Add dependencies versioning
- Define a stub for quick test
Write the script
Section titled “Write the script”The script section is defined in two parts : the groovy header and the bash script.
script: def GROOVY HEADER GOES HERE ! """ BASH SCRIPT GOES HERE ! """
Groovy header
Section titled “Groovy header”In the groovy header, you can define variables and modify them using the power of the
groovy language. You can see the args
and prefix
variables
already defined for you by the template :
script: def args = task.ext.args ?: '' def prefix = task.ext.prefix ?: "${meta.id}"
Bash script
Section titled “Bash script”The bash script contains the commands that will be run at execution of your module. It is enclosed by triple quotes. In the script, you have access to :
- All terminal commands, from the system (e.g.
cd
,ls
,find
) and those installed by dependencies (e.g. scilpy commands such asscil_denoising_nlmeans.py
) - Environment variables available at runtime (
PATH
and others). Use the prefix\$
(e.g.\$PATH
) - Variables defined in the groovy header, in the
input
andoutput
sections and through thetask
object. Use the prefix$
(e.g.$prefix
or$task.cpus
)
For the Non-Local Means module, replace the whole script section with the following definition :
script: def prefix = task.ext.prefix ?: "${meta.id}" def args = task.ext.args ?: [] """ export ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS=1 export OMP_NUM_THREADS=1 export OPENBLAS_NUM_THREADS=1
scil_denoising_nlmeans.py $image \ ${prefix}__denoised.nii.gz \ ${args.join(" ")} \ --processes $tasks.cpus """
Define the inputs and outputs
Section titled “Define the inputs and outputs”The input
section of the process define how users of your module shape and structure the data passed to it for processing.
In the same manner, the output
section define how you shape and structure the data processed by the module, how the user collects it for usage in other downstream processes.
The input section
Section titled “The input section”The input section is defined on multiple lines, each line stating a different input channel.
input: CHANNEL_1 CHANNEL_2 ...
Each channel describes the structure of the elements that pass through it. From the script
we defined above, we know
we need two variables in input : the meta
and the image
.
We can’t define them in separate channels, since the metadata is associated with the image. Instead, we state we want them
grouped using a tuple :
input: tuple val(meta), path(image)script: def prefix = task.ext.prefix ?: "${meta.id}" def args = task.ext.args ?: [] """ export ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS=1 export OMP_NUM_THREADS=1 export OPENBLAS_NUM_THREADS=1
scil_denoising_nlmeans.py $image \ ${prefix}__denoised.nii.gz \ ${args.join(" ")} \ --processes $tasks.cpus """
Optional input value
Section titled “Optional input value”Denoising with the Non-Local Means algorithm can be accelerated and improved using a mask. This mask is optional, so we need to define it as such. In nextflow, there is no concept of optional inputs. This is a limitation of the language itself, not of the framework. However, we can work around this using default null values.
input: tuple val(meta), path(image), path(mask) /* optional, input = [] */script: def prefix = task.ext.prefix ?: "${meta.id}" def args = task.ext.args ?: [] def input_mask = mask ? "--mask_denoise $mask" : "" """ export ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS=1 export OMP_NUM_THREADS=1 export OPENBLAS_NUM_THREADS=1
scil_denoising_nlmeans.py $image \ ${prefix}__denoised.nii.gz \ $input_mask \ ${args.join(" ")} \ --processes $tasks.cpus """
The output section
Section titled “The output section”Like the input section, the output section is defined on multiple lines, each line defining a different output channel.
The difference is that each channel gets a name, using the identifier emit:
, and can be tagged as optional:
.
output: CHANNEL_1, emit: <name> CHANNEL_2, emit: <name>, optional: true ...
To define an output file, use the path
type, along with a glob pattern
that targets it. For the current module, the output file to target is the denoised image named :
${
prefix
}
__denoised.nii.gz
. The matching glob pattern is ”*__denoised.nii.gz”.
Here, the character *
will match with any ${
prefix
}
, the only part that changes in
the filename. The output section should look like this :
input: tuple val(meta), path(image), path(mask) /* optional, input = [] */output: tuple val(meta), path("*__denoised.nii.gz"), emit: imagescript: def prefix = task.ext.prefix ?: "${meta.id}" def args = task.ext.args ?: [] def input_mask = mask ? "--mask_denoise $mask" : "" """ export ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS=1 export OMP_NUM_THREADS=1 export OPENBLAS_NUM_THREADS=1
scil_denoising_nlmeans.py $image \ ${prefix}__denoised.nii.gz \ $input_mask \ ${args.join(" ")} \ --processes $tasks.cpus """
Obligatory version.yml output
Section titled “Obligatory version.yml output”To ensure reproducibility of software, it is mandatory you export the versions from the full
first layer dependencies used within your module (the ones called inside your script) to a versions.yml
file. For this module, this means exporting the version of scilpy
, which is done by modifying the process
as follows :
input: tuple val(meta), path(image), path(mask) /* optional, input = [] */output: tuple val(meta), path("*__denoised.nii.gz"), emit: image path "versions.yml", emit: versionsscript: def prefix = task.ext.prefix ?: "${meta.id}" def args = task.ext.args ?: [] def input_mask = mask ? "--mask_denoise $mask" : "" """ export ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS=1 export OMP_NUM_THREADS=1 export OPENBLAS_NUM_THREADS=1
scil_denoising_nlmeans.py $image \ ${prefix}__denoised.nii.gz \ $input_mask \ ${args.join(" ")} \ --processes $tasks.cpus
cat <<-END_VERSIONS > versions.yml "${task.process}": scilpy: \$(pip list --disable-pip-version-check --no-python-version-warning | grep scilpy | tr -s ' ' | cut -d' ' -f2) END_VERSIONS """
Define a stub for quick testing
Section titled “Define a stub for quick testing”The stub
section is a special script that does not get executed at runtime. Instead, it is used to
perform quick integration tests, using multiple modules, to verify how well they work together. In
order for a stub to be efficient, it must do three things :
- Guarantee dependencies availablility to the script.
- For most dependencies, calling the command’s usage (usually
-h
or--help
) is enough.
- For most dependencies, calling the command’s usage (usually
- Generate all outputs produced by the script.
- Often done by creating empty files using the
touch
command, but some use-cases might need more.
- Often done by creating empty files using the
- Export dependencies versions to versions.yml.
- Just like in the actual script.
For the Non-Local Means denoising module, we’ll :
- Call
scil_denoising_nlmeans.py -h
to display the usage information. - Create an empty file named
${
prefix
}
__denoised.nii.gz
. - Export the version of
scilpy
to a versions.yml file.
To do so, replace the stub skeleton in your file with the following :
stub: def prefix = task.ext.prefix ?: "${meta.id}" """ scil_denoising_nlmeans.py -h
touch ${prefix}_denoised.nii.gz
cat <<-END_VERSIONS > versions.yml "${task.process}": scilpy: \$(pip list --disable-pip-version-check --no-python-version-warning | grep scilpy | tr -s ' ' | cut -d' ' -f2) END_VERSIONS """
A complete example
Section titled “A complete example”If you followed every step on page, you should now have a complete, guideline-abiding,
and usable main.nf file! Your resulting file should be closely similar to the
denoising/nlmeans
module :
process DENOISING_NLMEANS {
tag "$meta.id"label 'process_medium'
container " ... "
input: tuple val(meta), path(image), path(mask)
output: tuple val(meta), path("*_denoised.nii.gz") , emit: image path "versions.yml" , emit: versions
when: task.ext.when == null || task.ext.when
script: def prefix = task.ext.prefix ?: "${meta.id}" def args = task.ext.args ?: [] def input_mask = mask ? "--mask $mask" : "" """ export ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS=1 export OMP_NUM_THREADS=1 export OPENBLAS_NUM_THREADS=1
scil_denoising_nlmeans.py $image \ ${prefix}__denoised.nii.gz \ $input_mask \ ${args.join(" ")} \ --processes $task.cpus
cat <<-END_VERSIONS > versions.yml "${task.process}": scilpy: \$(pip list --disable-pip-version-check --no-python-version-warning | grep scilpy | tr -s ' ' | cut -d' ' -f2) END_VERSIONS """
stub: def prefix = task.ext.prefix ?: "${meta.id}" """ scil_denoising_nlmeans.py -h
touch ${prefix}_denoised.nii.gz
cat <<-END_VERSIONS > versions.yml "${task.process}": scilpy: \$(pip list --disable-pip-version-check --no-python-version-warning | grep scilpy | tr -s ' ' | cut -d' ' -f2) END_VERSIONS """}
On the next part of the tutorial, you will add configuration options to the module, using the
task.ext
argument namespace. This will enable users to
fine-tune the behavior of your module.