Workflow Examples
Practical nf-bids usage patterns for common neuroimaging workflows. These
examples assume the plugin is enabled in nextflow.config and use the default
flat output format (the standard output shape since 0.1.0-beta.9).
Start with Installation and Configuration Loading & Validation if you need setup details before adapting the examples below.
Shared plugin setup
nextflow.configplugins {
id 'nf-bids@{plugin-version}'
}
main.nfinclude { fromBIDS } from 'plugin/nf-bids'
All examples below assume Channel.fromBIDS() emits flat maps shaped like:
[
meta: [subject: 'sub-01', session: 'ses-01', run: 'NA'],
T1w: [nii: Path('/data/bids/sub-01/anat/sub-01_T1w.nii.gz')],
dwi: [nii: Path('/data/bids/sub-01/dwi/sub-01_dwi.nii.gz')]
]
Sequential sets stay flat too, but their extension values are ordered lists of
Path objects. For example, item.bold.nii is a List<Path>, not a list of
per-echo maps.
For the legacy tuple format, see BIDS Migration Guide.
Basic examples
T1w brain extraction with FSL bet
config.yamlloop_over:
- subject
T1w:
plain_set: {}
main.nf#!/usr/bin/env nextflow
params.bids_dir = '/data/bids'
params.output_dir = 'results'
include { fromBIDS } from 'plugin/nf-bids'
workflow {
Channel.fromBIDS(params.bids_dir, 'config.yaml')
.map { item -> [item.meta.subject, item.T1w.nii] }
| processT1w
}
process processT1w {
publishDir { "${params.output_dir}/${subject}" }, mode: 'copy'
input:
tuple val(subject), path(t1w)
output:
tuple val(subject), path("${subject}_brain.nii.gz"), emit: t1w
script:
"""
bet ${t1w} ${subject}_brain.nii.gz -f 0.5
"""
}
Multi-echo resting-state fMRI combination with tedana
config.yamlloop_over:
- subject
- session
- task
bold:
sequential_set:
by_entity: echo
main.nf#!/usr/bin/env nextflow
params.bids_dir = '/data/bids'
include { fromBIDS } from 'plugin/nf-bids'
workflow {
Channel.fromBIDS(params.bids_dir, 'config.yaml')
.filter { item -> item.meta.task == 'rest' }
.map { item ->
def id = [item.meta.subject, item.meta.session].findAll { it != 'NA' }.join('_')
[id, item.meta.subject, item.meta.session, item.bold.nii]
}
| multiEchoCombine
}
process multiEchoCombine {
input:
tuple val(id), val(subject), val(session), path(echoes)
output:
tuple val(id), path("${id}_combined_bold.nii.gz"), emit: bold
script:
def echoArgs = echoes.collect { "-e ${it}" }.join(' ')
"""
tedana ${echoArgs} -o ${id}_combined_bold.nii.gz
"""
}
Concatenate DWI runs before dtifit
config.yamlloop_over:
- subject
- session
runs:
sequential_set:
by_entity: run
suffix_maps_to: dwi
additional_extensions: [.bval, .bvec]
main.nf#!/usr/bin/env nextflow
params.bids_dir = '/data/bids'
include { fromBIDS } from 'plugin/nf-bids'
workflow {
Channel.fromBIDS(params.bids_dir, 'config.yaml')
.map { item ->
def id = [item.meta.subject, item.meta.session].findAll { it != 'NA' }.join('_')
def meta = [id: id, subject: item.meta.subject, session: item.meta.session]
[meta, item.runs.nii, item.runs.bval, item.runs.bvec]
}
| mergeDWI
}
process mergeDWI {
input:
tuple val(meta), path(dwis), path(bvals), path(bvecs)
output:
tuple val(meta), path("${meta.id}_merged_dwi.nii.gz"), emit: dwi
tuple val(meta), path("${meta.id}_merged_dwi.bval"), emit: bval
tuple val(meta), path("${meta.id}_merged_dwi.bvec"), emit: bvec
script:
def dwiFiles = dwis.join(' ')
def bvalFiles = bvals.join(' ')
def bvecFiles = bvecs.join(' ')
"""
fslmerge -t ${meta.id}_merged_dwi.nii.gz ${dwiFiles}
paste -d ' ' ${bvalFiles} > ${meta.id}_merged_dwi.bval
paste -d ' ' ${bvecFiles} > ${meta.id}_merged_dwi.bvec
"""
}
Advanced workflows
Cross-modal analysis with a T1w reference
Use include_cross_modal when one modality should be bundled alongside another
for downstream alignment or masking.
config.yamlloop_over:
- subject
- session
- task
bold:
plain_set:
include_cross_modal:
- T1w
main.nf#!/usr/bin/env nextflow
params.bids_dir = '/data/bids'
include { fromBIDS } from 'plugin/nf-bids'
workflow {
Channel.fromBIDS(params.bids_dir, 'config.yaml')
.map { item ->
def meta = [
id: [item.meta.subject, item.meta.session, item.meta.task].findAll { it != 'NA' }.join('_'),
subject: item.meta.subject,
session: item.meta.session,
task: item.meta.task,
]
[meta, item.T1w.nii, item.bold.nii]
}
| registerBoldToT1w
}
process registerBoldToT1w {
input:
tuple val(meta), path(t1w), path(bold)
output:
tuple val(meta), path("${meta.id}_registered_bold.nii.gz"), emit: bold
tuple val(meta), path("${meta.id}_transform.mat"), emit: transform
script:
"""
fslroi ${bold} ref_vol.nii.gz 0 1
flirt -in ref_vol.nii.gz \
-ref ${t1w} \
-out ${meta.id}_registered_bold.nii.gz \
-omat ${meta.id}_transform.mat
"""
}
Next Step
If you are migrating from older output formats, continue with BIDS Migration Guide.
Branch processing by task
workflow {
Channel.fromBIDS(params.bids_dir, 'config.yaml')
.map { item -> [item.meta, item.bold.nii] }
.branch { meta, bold ->
rest: meta.task == 'rest'
nback: meta.task == 'nback'
memory: meta.task == 'memory'
}
.with { chTasks ->
processRest(chTasks.rest)
processNBack(chTasks.nback)
processMemory(chTasks.memory)
}
}
DWI distortion correction with JSON sidecars
When JSON metadata is needed alongside images, keep sidecars as paths and join them into a preparation process that derives acquisition parameters.
workflow {
Channel.fromBIDS(params.bids_dir, 'config.yaml')
.map { item ->
def id = [item.meta.subject, item.meta.session, item.meta.run].findAll { it != 'NA' }.join('_')
def meta = [id: id]
[meta, item.dwi.nii, item.dwi.bval, item.dwi.bvec, item.epi.nii, item.dwi.json, item.epi.json]
}
.multiMap { meta, dwi, bval, bvec, epi, dwiJson, epiJson ->
data: [meta, dwi, bval, bvec, epi]
json: [meta, dwiJson, epiJson]
}
}
See Configuration Loading & Validation for
additional_extensions, include_cross_modal, and suffix_maps_to details.
Quality control pattern
A common pattern is to split the BIDS stream by modality, render per-modality QC artifacts, then collect them into a final reporting step such as MultiQC.
workflow {
chBids = Channel.fromBIDS(params.bids_dir, 'config.yaml')
.branch { item ->
dwi: item.dwi
t1w: item.T1w
}
qcDiffusion(chBids.dwi)
qcAnatomical(chBids.t1w)
Channel.empty()
.mix(qcAnatomical.out.mqc)
.mix(qcDiffusion.out.mqc)
.collect()
| generateMultiQC
}
The key design point is that nf-bids already emits absolute Path values, so
QC processes can use them directly in path(…) inputs without extra
file(…) conversion.
Tips and best practices
-
Filter early, before heavy downstream processing.
-
Preserve semantic metadata in small maps when crossing process boundaries.
-
Use
multiMaporbranchwhen one BIDS stream feeds multiple analyses. -
Keep migration-only compatibility logic isolated; see BIDS Migration Guide for the legacy tuple format.