Skip to content

BIDS output `PublishDir` specifications

1. Example for one module

We will integrate specific configurations, including the use of publishDir, saveAs, and path in the nextflow.config file to handle the output of the process BETCROP_SYNTHBET. (This configuration must be applied to each module in your pipeline to ensure all outputs are properly organized).

Follow these two main modifications in the nextflow.config:

  • Add those parameters after the params.output section
  • // Publish BIDS-like configuration
    params.lean_output = true
    params.publish_dir_mode = 'copy'
  • Add those parameters in the process section
  • withName: "BETCROP_SYNTHBET" {
    memory = "4G"
    ext.nocsf = false
    publishDir = [
    mode: params.publish_dir_mode,
    saveAs: {
    filename ->
    def ses = meta.session ? "_${meta.session}" : ""
    if ( filename.contains("bet_image.nii.gz") ) { "${meta.id}_${ses}_desc-t1_bet.nii.gz" }
    else if ( filename.contains("brain_mask.nii.gz") ) { "${meta.id}_${ses}_desc-t1_mask.nii.gz" }
    else if ( filename.contains("versions.yml") ) { null }
    else { params.lean_output ? null : filename }
    },
    path: { meta.session ? "${params.output}/${meta.id}/${meta.session}/anat/" : "${params.output}/${meta.id}/anat/" },
    ]
    }

    Make sure to apply this structure to every process in your pipeline that produces output to ensure that all data is organized consistently, making it easier to integrate with other BIDS-compliant tools and workflows. Additionally, by defining the appropriate metadata and passing the required parameters, you can easily reorganize your output files and make them ready for further analysis.

    2. Verify your files

    Your nextflow.config file should look like this.

    profiles {
    docker {
    docker.enabled = true
    conda.enabled = false
    singularity.enabled = false
    podman.enabled = false
    shifter.enabled = false
    charliecloud.enabled = false
    apptainer.enabled = false
    docker.runOptions = '-u $(id -u):$(id -g)'
    }
    }
    manifest {
    name = 'scilus/nf-neuro-tutorial'
    description = """nf-neuro-tutorial is a Nextflow pipeline for processing neuroimaging data."""
    version = '0.1dev'
    }
    params.input = false
    params.output = 'result'
    // Publish BIDS-like configuration
    params.lean_output = true
    params.publish_dir_mode = 'copy'
    // ** subworkflow PREPROC_DIFF **
    params.preproc_dwi_run_denoising = true
    // ** Subworkflow PREPROC T1 **
    params.preproc_t1_run_denoising = true
    params.preproc_t1_run_N4 = false
    params.preproc_t1_run_resampling = false
    params.preproc_t1_run_ants_bet = false
    params.preproc_t1_run_synthbet = true
    params.preproc_t1_run_crop = false
    process {
    //publishDir = { "${params.output}/$meta.id/${task.process.replaceAll(':', '-')}" }
    withName: "DENOISING_MPPCA" {
    ext.extent = 3
    publishDir = [
    mode: params.publish_dir_mode,
    saveAs: {
    filename ->
    def ses = meta.session ? "_${meta.session}" : ""
    if ( filename.contains("denoised.nii.gz") ) { "${meta.id}_${ses}_desc-denoised_dwi.nii.gz" }
    else if ( filename.contains("versions.yml") ) { null }
    },
    path: { meta.session ? "${params.output}/${meta.id}/${meta.session}/dwi/" : "${params.output}/${meta.id}/dwi/" }
    ]
    }
    withName: "BETCROP_SYNTHBET" {
    memory = "4G"
    ext.nocsf = false
    publishDir = [
    mode: params.publish_dir_mode,
    saveAs: {
    filename ->
    def ses = meta.session ? "_${meta.session}" : ""
    if ( filename.contains("bet_image.nii.gz") ) { "${meta.id}_${ses}_desc-t1_bet.nii.gz" }
    else if ( filename.contains("brain_mask.nii.gz") ) { "${meta.id}_${ses}_desc-t1_mask.nii.gz" }
    else if ( filename.contains("versions.yml") ) { null }
    else { params.lean_output ? null : filename }
    },
    path: { meta.session ? "${params.output}/${meta.id}/${meta.session}/anat/" : "${params.output}/${meta.id}/anat/" },
    ]
    }
    withName: "RECONST_DTIMETRICS" {
    ext.ad = false
    ext.evecs = false
    ext.evals = false
    ext.fa = true
    ext.ga = false
    ext.rgb = false
    ext.md = true
    ext.mode = false
    ext.norm = false
    ext.rd = false
    ext.tensor = false
    ext.nonphysical = false
    ext.pulsation = false
    ext.residual = false
    ext.b0_thr_extract_b0 = 10
    ext.dwi_shell_tolerance = 50
    ext.max_dti_shell_value = 1200
    ext.run_qc = false
    publishDir = [
    mode: params.publish_dir_mode,
    saveAs: {
    filename ->
    def ses = meta.session ? "_${meta.session}" : ""
    if ( filename.contains("md.nii.gz") ) { "${meta.id}_${ses}_desc-md.nii.gz" }
    else if ( filename.contains("fa.nii.gz") ) { "${meta.id}_${ses}_desc-fa.nii.gz" }
    else if ( filename.contains("versions.yml") ) { null }
    else { params.lean_output ? null : filename }
    },
    path: { meta.session ? "${params.output}/${meta.id}/${meta.session}/dwi/" : "${params.output}/${meta.id}/dwi/" }
    ]
    }
    // Here is an example where you want to store output with two different datatypes (stats + dwi)
    withName: "STATS_METRICSINROI" {
    ext.bin = true
    ext.normalize_weights = false
    publishDir = [
    [
    mode: params.publish_dir_mode,
    saveAs: {
    filename ->
    def ses = meta.session ? "_${meta.session}" : ""
    if ( filename.contains("stats.json") ) { "${meta.id}_${ses}_desc-dti_stats.json" }
    else { params.lean_output ? null : filename }
    },
    path: { meta.session ? "${params.output}/${meta.id}/${meta.session}/stats/" : "${params.output}/${meta.id}/stats/" }
    ],
    [
    mode: params.publish_dir_mode,
    saveAs: {
    filename ->
    def ses = meta.session ? "_${meta.session}" : ""
    if ( filename.contains("map_csf.nii.gz") ) { "${meta.id}_${ses}_desc-t1_map_csf.nii.gz" }
    else if ( filename.contains("map_wm.nii.gz") ) { "${meta.id}_${ses}_desc-t1_map_wm.nii.gz" }
    else if ( filename.contains("map_gm.nii.gz") ) { "${meta.id}_${ses}_desc-t1_map_gm.nii.gz" }
    else if ( filename.contains("mask_csf.nii.gz") ) { "${meta.id}_${ses}_desc-t1_mask_csf.nii.gz" }
    else if ( filename.contains("mask_wm.nii.gz") ) { "${meta.id}_${ses}_desc-t1_mask_wm.nii.gz" }
    else if ( filename.contains("mask_gm.nii.gz") ) { "${meta.id}_${ses}_desc-t1_mask_gm.nii.gz" }
    else { params.lean_output ? null : filename }
    },
    path: { meta.session ? "${params.output}/${meta.id}/${meta.session}/anat/" : "${params.output}/${meta.id}/anat/" }
    ]
    ]
    }
    }

    3. Run nextflow

    Now, you can run nextflow..

    Terminal window
    nextflow run main.nf --input data -profile docker -resume

    4. Verify your output structure

    Your result folder should look like this:

    • Directoryresults
      • Directorysub-03
        • Directoryses-01
          • Directoryanat
            • sub-03_ses-01_desc-t1_bet.nii.gz
            • sub-03_ses-01_desc-t1_map_csf.nii.gz
            • sub-03_ses-01_desc-t1_map_gm.nii.gz
            • sub-03_ses-01_desc-t1_map_wm.nii.gz
            • sub-03_ses-01_desc-t1_mask.nii.gz
            • sub-03_ses-01_desc-t1_mask_csf.nii.gz
            • sub-03_ses-01_desc-t1_mask_gm.nii.gz
            • sub-03_ses-01_desc-t1_mask_wm.nii.gz
          • Directorydwi
            • sub-03_ses-01_desc-denoised_dwi.nii.gz
            • sub-03_ses-01_desc-fa.nii.gz
            • sub-03_ses-01_desc-md.nii.gz
          • Directorystats
            • sub-03_ses-01_desc-dti_stats.json