Subworkflow configuration
For subworkflows, configuration acts on two fronts :
- Allowing for the subworkflow itself to change behavior, such as selecting between two different algorithms or choosing to run a specific pre-processing part.
- Configuring the behavior of its included components to fit its use-case, and allows end-users to modify it.
Define subworkflow behaviors
Section titled “Define subworkflow behaviors”Subworkflows parameters are defined using the params
configuration scope. This scope is available in all .nf files, but
params
defined there can be overriden by .config files at execution, or by other subworkflows that would include yours.
For the preproc_anat
subworkflow, there are no subworkflow parameters defined for now, but we could, for example, add one to
skip the denoising step :
...
params.preproc_anat_denoise = true
subworkflow preproc_anat {take: ch_anatomical // Structure : [ [id: string] , path(anat_image) ] ...main: ... if (params.preproc_anat_denoise) { ch_denoising_nlmeans = ch_anatomical .join(ch_brain_mask, remainder: true) .map{ meta, image, mask -> [meta, image, [], mask ?: []] }
DENOISING_NLMEANS(ch_denoising_nlmeans) ch_versions = ch_versions.mix(DENOISING_NLMEANS.out.versions) ch_anatomical = DENOISING_NLMEANS.out.image }
ch_betcrop_synthbet = ch_anatomical .join(ch_brain_mask, remainder: true) ...
ch_preproc_n4 = ch_anatomical .join(ch_n4_reference, remainder: true) ...
Change components behaviors
Section titled “Change components behaviors”Changing the behavior of included components is not as straightforward as it seems.
Modules are configured using the task.ext
scope, which is not assignable in the params
or inside any .nf file. Instead,
those configurations have to be defined inside .config files.
Create a new nextflow.config file at the root of your subworkflow directory. While this file is not included at runtime, you will document its existence and still use it profusely in tests :
params { preproc_anat_denoise = true preproc_anat_n4 = true}
As a first, you’ll want to make the number_of_coils
parameter of DENOISING_NLMEANS
configurable by users. This requires
a new configuration parameter in params
associated to task
.
ext
.
number_of_coils
for the module. To do so, in the configuration file created, define a new entry for the process
scope that targets this one
and only module, using a process selector :
params { preproc_anat_denoise = true preproc_anat_n4 = true preproc_anat_nlmeans_number_of_coils = 1}
process { withName: "DENOISING_NLMEANS" { task.ext.number_of_coils = params.preproc_anat_nlmeans_number_of_coils ?: 1 }}
Configuration is done using the params
scope directly in the main.nf file of your subworkflow, after the sub-components inclusion and
before the workflow
definition. Use the same parameter names as the ones defined in the subworkflow you include and they will
overwrite their values.
Now that the subworkflow is configurable, it’s time to document everything. Refer below for a full configuration of all included components :
// MODULESinclude { DENOISING_NLMEANS } from '../../../modules/nf-neuro/denoising/nlmeans/main'include { BETCROP_SYNTHBET } from '../../../modules/nf-neuro/betcrop/synthbet/main'include { BETCROP_ANTSBET } from '../../../modules/nf-neuro/betcrop/antsbet/main'include { PREPROC_N4 } from '../../../modules/nf-neuro/preproc/n4/main'// SUBWORKFLOWSinclude { ANATOMICAL_SEGMENTATION } from '../anatomical_segmentation/main'
params.preproc_anat_denoise = trueparams.preproc_anat_bet_before_n4 = trueparams.preproc_anat_n4 = true
workflow PREPROC_ANAT {take: ch_anatomical // Structure : [ [id: string] , path(anat_image) ] ch_template // Structure : [ path(anat_ref), path(brain_proba) ] ch_brain_mask // Structure : [ [id: string] , path(brain_mask) ], optional ch_synthbet_weights // Structure : [ [id: string] , path(weights) ], optional ch_n4_reference // Structure : [ [id: string] , path(reference) ], optional ch_freesurferseg // Structure : [ [id: string] , path(aparc+aseg) , path(wmparc) ], optional ch_lesion // Structure : [ [id: string] , path(lesion) ], optional ch_fs_license // Structure : [ path(license) ], optionalmain: ch_versions = Channel.empty()
if (params.preproc_anat_denoise) { ch_denoising_nlmeans = ch_anatomical .join(ch_brain_mask, remainder: true) .map{ meta, image, mask -> [meta, image, [], mask ?: []] }
DENOISING_NLMEANS(ch_denoising_nlmeans) ch_versions = ch_versions.mix(DENOISING_NLMEANS.out.versions) ch_anatomical = DENOISING_NLMEANS.out.image }
ch_brain_pre_mask = Channel.empty() if (params.preproc_anat_bet_before_n4) { ch_betcrop_synthbet = ch_anatomical .join(ch_brain_mask, remainder: true) .filter{ meta, image, mask -> !mask } .join(ch_synthbet_weights, remainder: true) .map{ meta, image, mask, weights -> [meta, image, weights ?: []] }
BETCROP_SYNTHBET( ch_betcrop_synthbet ) ch_versions = ch_versions.mix(BETCROP_SYNTHBET.out.versions) ch_brain_pre_mask = ch_brain_mask.mix(BETCROP_SYNTHBET.out.brain_mask) }
if (params.preproc_anat_n4) { ch_preproc_n4 = ch_anatomical .join(ch_n4_reference, remainder: true) .join(ch_brain_pre_mask, remainder: true) .map{ meta, image, reference, mask -> [meta, image, reference ?: [], mask ?: []] }
PREPROC_N4( ch_preproc_n4 ) ch_versions = ch_versions.mix(PREPROC_N4.out.versions) ch_anatomical = PREPROC_N4.out.image }
ch_betcrop_antsbet = ch_anatomical .join(ch_brain_mask, remainder: true) .filter{ meta, image, mask -> !mask } .map{ meta, image, mask -> [meta, image] } .combine(ch_template)
BETCROP_ANTSBET( ch_betcrop_antsbet ) ch_versions = ch_versions.mix(BETCROP_ANTSBET.out.versions)
ANATOMICAL_SEGMENTATION( ch_anatomical, ch_freesurferseg, ch_lesion, ch_license )emit: ch_anatomical = ch_anatomical // channel: [ [id: string] , path(image) ] ch_brain_mask = BETCROP_ANTSBET.out.mask // channel: [ [id: string] , path(brain_mask) ]
wm_mask = ANATOMICAL_SEGMENTATION.out.wm_mask // channel: [ [id: string] , path(wm_mask) ] gm_mask = ANATOMICAL_SEGMENTATION.out.gm_mask // channel: [ [id: string] , path(gm_mask) ] csf_mask = ANATOMICAL_SEGMENTATION.out.csf_mask // channel: [ [id: string] , path(csf_mask) ]
wm_map = ANATOMICAL_SEGMENTATION.out.wm_map // channel: [ [id: string] , path(wm_map) ] gm_map = ANATOMICAL_SEGMENTATION.out.gm_map // channel: [ [id: string] , path(gm_map) ] csf_map = ANATOMICAL_SEGMENTATION.out.csf_map // channel: [ [id: string] , path(csf_map) ] versions = ch_versions // channel: [ path(versions.yml) ]}
params { preproc_anat_denoise = true preproc_anat_bet_before_n4 = true preproc_anat_n4 = true
// Configure DENOISING_NLMEANS preproc_anat_nlmeans_number_of_coils = 1 preproc_anat_nlmeans_sigma = 0.5 preproc_anat_nlmeans_sigma_from_all_voxels = false preproc_anat_nlmeans_gaussian = false preproc_anat_nlmeans_method = "basic_sigma"
// Configure BETCROP_SYNTHBET preproc_anat_betcrop_synthbet_border = null preproc_anat_betcrop_synthbet_nocsf = false
// Configure PREPROC_N4 preproc_anat_n4_knots_per_voxel = 1 preproc_anat_n4_shrink_factor = 1
// Configure ANATOMICAL_SEGMENTATION run_synthbet = false // Reusing the same name, we could have changed if wanted
}
process { withName: "DENOISING_NLMEANS" { task.ext.number_of_coils = params.preproc_anat_nlmeans_number_of_coils ?: 1 task.ext.sigma = params.preproc_anat_nlmeans_sigma ?: 0.5 task.ext.sigma_from_all_voxels = params.preproc_anat_nlmeans_sigma_from_all_voxels ?: false task.ext.gaussian = params.preproc_anat_nlmeans_gaussian ?: false task.ext.method = params.preproc_anat_nlmeans_method ?: "basic_sigma" }
withName: "BETCROP_SYNTHBET" { task.ext.border = params.preproc_anat_betcrop_synthbet_border ?: null task.ext.nocsf = params.preproc_anat_betcrop_synthbet_nocsf ?: false }
withName: "PREPROC_N4" { task.ext.bspline_knot_per_voxel = params.preproc_anat_n4_knots_per_voxel ?: 1 task.ext.shrink_factor = params.preproc_anat_n4_shrink_factor ?: 1 }}