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.config
plugins {
    id 'nf-bids@{plugin-version}'
}
main.nf
include { 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.yaml
loop_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.yaml
loop_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.yaml
loop_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.yaml
loop_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 multiMap or branch when one BIDS stream feeds multiple analyses.

  • Keep migration-only compatibility logic isolated; see BIDS Migration Guide for the legacy tuple format.