diff --git a/main.nf b/main.nf index b1ad6cd..21781ca 100644 --- a/main.nf +++ b/main.nf @@ -11,72 +11,79 @@ nextflow.enable.dsl = 2 -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - GENOME PARAMETER VALUES -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ +params.help = false -// TODO nf-core: Remove this line if you don't need a FASTA file -// This is an example of how to use getGenomeAttribute() to fetch parameters -// from igenomes.config using `--genome` -params.fasta = WorkflowMain.getGenomeAttribute(params, 'fasta') +// Importing modules and processes -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - VALIDATE & PRINT PARAMETER SUMMARY -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ +include { DENOISING_MPPCA } from "./modules/nf-scil/denoising/mppca/main.nf" +include { UTILS_EXTRACTB0 } from "../modules/nf-scil/utils/extractb0/main.nf" +include { BETCROP_FSLBETCROP } from "./modules/nf-scil/betcrop/fslbetcrop/main.nf" +include { PREPROC_N4 } from "./modules/nf-scil/preproc/n4/main.nf" +include { RECONST_DTIMETRICS } from "./modules/nf-scil/reconst/dtimetrics/main.nf" +include { RECONST_FRF } from "./modules/nf-scil/reconst/frf/main.nf" +include { RECONST_FODF } from "./modules/nf-scil/reconst/fodf/main.nf" +include { TRACKING_LOCALTRACKING } from "./modules/nf-scil/tracking/localtracking/main.nf" -include { validateParameters; paramsHelp } from 'plugin/nf-validation' +workflow { -// Print help message if needed -if (params.help) { - def logo = NfcoreTemplate.logo(workflow, params.monochrome_logs) - def citation = '\n' + WorkflowMain.citation(workflow) + '\n' - def String command = "nextflow run ${workflow.manifest.name} --input samplesheet.csv --genome GRCh37 -profile docker" - log.info logo + paramsHelp(command) + citation + NfcoreTemplate.dashedLine(params.monochrome_logs) - System.exit(0) -} + main: -// Validate input parameters -if (params.validate_params) { - validateParameters() -} + dwi_channel = Channel.fromFilePairs("$input/**/*dwi.nii.gz", size: 1, flat: true) + { fetch_id(it.parent, input) } + bval_channel = Channel.fromFilePairs("$input/**/*bval", size: 1, flat: true) + { fetch_id(it.parent, input) } + bvec_channel = Channel.fromFilePairs("$input/**/*bvec", size: 1, flat: true) + { fetch_id(it.parent, input) } -WorkflowMain.initialise(workflow, params, log) + // ** Denoising ** // + DENOISING_MPPCA(dwi_channel) -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - NAMED WORKFLOW FOR PIPELINE -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ + // ** Extract b0 ** // + b0_channel = DENOISING_MPPCA.out.dwi + .combine(bval_channel) + .combine(bvec_channel) + UTILS_EXTRACTB0(b0_channel) -include { FIBERCUP } from './workflows/fibercup' + // ** Bet ** // + bet_channel = DENOISING_MPPCA.out.dwi + .combine(bval_channel) + .combine(bvec_channel) + BETCROP_FSLBETCROP(bet_channel) -// -// WORKFLOW: Run main nf-core/fibercup analysis pipeline -// -workflow NFCORE_FIBERCUP { - FIBERCUP () -} + // ** N4 ** // + n4_channel = BETCROP_FSLBETCROP.out.dwi + .combine(UTILS_EXTRACTB0.out.b0) + .combine(BETCROP_FSLBETCROP.out.mask) + PREPROC_N4(n4_channel) -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - RUN ALL WORKFLOWS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ + // ** DTI ** // + dti_channel = PREPROC_N4.out.dwi + .combine(bval_channel) + .combine(bvec_channel) + RECONST_DTIMETRICS(dti_channel) + + // ** FRF ** // + frf_channel = PREPROC_N4.out.dwi + .combine(bval_channel) + .combine(bvec_channel) + .combine(b0_mask_channel) + RECONST_FRF(frf_channel) + + // ** FODF ** // + fodf_channel = PREPROC_N4.out.dwi + .combine(bval_channel) + .combine(bvec_channel) + .combine(b0_mask_channel) + .combine(RECONST_DTIMETRICS.out.fa) + .combine(RECONST_DTIMETRICS.out.md) + .combine(RECONST_FRF.out.frf) + RECONST_FODF(fodf_channel) + + // ** Local Tracking ** // + tracking_channel = RECONST_FODF.out.fodf + .combine(tracking_mask_channel) + .combine(seed_channel) + TRACKING_LOCALTRACKING(tracking_channel) -// -// WORKFLOW: Execute a single named workflow for the pipeline -// See: https://github.com/nf-core/rnaseq/issues/619 -// -workflow { - NFCORE_FIBERCUP () } -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - THE END -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ diff --git a/modules.json b/modules.json index 0c07c6b..c67f10e 100644 --- a/modules.json +++ b/modules.json @@ -22,6 +22,57 @@ } } } + }, + "https://github.com/scilus/nf-scil.git": { + "modules": { + "nf-scil": { + "betcrop/fslbetcrop": { + "branch": "main", + "git_sha": "701c3dacd90d5129ac11b030dbf461b221dd6937", + "installed_by": ["modules"] + }, + "denoising/mppca": { + "branch": "main", + "git_sha": "98796d3551ad5c0ebf6356e375d3850e7cfc64fe", + "installed_by": ["modules"] + }, + "denoising/nlmeans": { + "branch": "main", + "git_sha": "c91e5d948830cfc5fc83b984060dd22bf269d81b", + "installed_by": ["modules"] + }, + "preproc/n4": { + "branch": "main", + "git_sha": "7eea58371b89dfe254989183fd23262e233d4653", + "installed_by": ["modules"] + }, + "reconst/dtimetrics": { + "branch": "main", + "git_sha": "ef9ea4fd26019062173a9bba52ca4914a4944c0c", + "installed_by": ["modules"] + }, + "reconst/fodf": { + "branch": "main", + "git_sha": "4c325c5be9a4f8d6a45172d5b6a9fc932c945b0f", + "installed_by": ["modules"] + }, + "reconst/frf": { + "branch": "main", + "git_sha": "1c1dda1aae448bc64bc738d13ddcca76ae5448c8", + "installed_by": ["modules"] + }, + "tracking/pfttracking": { + "branch": "main", + "git_sha": "0560d8365bdb14d3722aebbd9f3d3be3a70698bb", + "installed_by": ["modules"] + }, + "utils/extractb0": { + "branch": "main", + "git_sha": "6b200ce524794bd512c5590bba38c2662dce1e46", + "installed_by": ["modules"] + } + } + } } } } diff --git a/modules/nf-scil/betcrop/fslbetcrop/main.nf b/modules/nf-scil/betcrop/fslbetcrop/main.nf new file mode 100644 index 0000000..ba25ac9 --- /dev/null +++ b/modules/nf-scil/betcrop/fslbetcrop/main.nf @@ -0,0 +1,75 @@ + +process BETCROP_FSLBETCROP { + tag "$meta.id" + label 'process_single' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://scil.usherbrooke.ca/containers/scilus_1.6.0.sif': + 'scilus/scilus:1.6.0' }" + + input: + tuple val(meta), path(dwi), path(bval), path(bvec) + + output: + tuple val(meta), path("*dwi_bet_cropped.nii.gz") , emit: dwi + tuple val(meta), path("*dwi_bet_cropped_mask.nii.gz") , emit: mask + tuple val(meta), path("*dwi_boundingBox.pkl") , emit: bbox + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def prefix = task.ext.prefix ?: "${meta.id}" + + def b0_thr = task.ext.b0_thr ? "--b0_thr " + task.ext.b0_thr : "" + def bet_dwi_f = task.ext.bet_dwi_f ? "-f " + task.ext.bet_dwi_f : "" + + """ + export ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS=1 + export OMP_NUM_THREADS=1 + export OPENBLAS_NUM_THREADS=1 + + scil_extract_b0.py $dwi $bval $bvec ${prefix}__b0.nii.gz --mean \ + $b0_thr --force_b0_threshold + bet ${prefix}__b0.nii.gz ${prefix}__b0_bet.nii.gz -m -R $bet_dwi_f + scil_image_math.py convert ${prefix}__b0_bet_mask.nii.gz ${prefix}__b0_bet_mask.nii.gz --data_type uint8 -f + mrcalc $dwi ${prefix}__b0_bet_mask.nii.gz -mult ${prefix}__dwi_bet.nii.gz -quiet -nthreads 1 + + scil_crop_volume.py $dwi ${prefix}__dwi_bet_cropped.nii.gz -f \ + --output_bbox ${prefix}__dwi_boundingBox.pkl -f + scil_crop_volume.py ${prefix}__b0_bet_mask.nii.gz ${prefix}__dwi_bet_cropped_mask.nii.gz -f\ + --input_bbox ${prefix}__dwi_boundingBox.pkl -f + scil_image_math.py convert ${prefix}__dwi_bet_cropped_mask.nii.gz ${prefix}__dwi_bet_cropped_mask.nii.gz \ + --data_type uint8 -f + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + scilpy: 1.6.0 + mrtrix: \$(mrcalc -version 2>&1 | sed -n 's/== mrcalc \\([0-9.]\\+\\).*/\\1/p') + fsl: \$(flirt -version 2>&1 | sed -n 's/FLIRT version \\([0-9.]\\+\\)/\\1/p') + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + scil_extract_b0.py -h + bet -h + scil_image_math.py -h + mrcalc -h + scil_crop_volume.py -h + + touch ${prefix}__dwi_bet_cropped.nii.gz + touch ${prefix}__dwi_bet_cropped_mask.nii.gz + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + scilpy: 1.6.0 + mrtrix: \$(mrcalc -version 2>&1 | sed -n 's/== mrcalc \\([0-9.]\\+\\).*/\\1/p') + fsl: \$(flirt -version 2>&1 | sed -n 's/FLIRT version \\([0-9.]\\+\\)/\\1/p') + END_VERSIONS + """ +} diff --git a/modules/nf-scil/betcrop/fslbetcrop/meta.yml b/modules/nf-scil/betcrop/fslbetcrop/meta.yml new file mode 100644 index 0000000..fe7d76c --- /dev/null +++ b/modules/nf-scil/betcrop/fslbetcrop/meta.yml @@ -0,0 +1,70 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/yaml-schema.json +name: "betcrop_fslbetcrop" +description: Perform Brain extraction using FSL BET followed by cropping empty planes around the data. +keywords: + - DWI + - BET + - Crop +tools: + - "FSL": + description: "FSL Toolbox and Scilpy Toolbox" + homepage: "https://fsl.fmrib.ox.ac.uk/fsl/fslwiki" + - "Scilpy": + description: "The Sherbrooke Connectivity Imaging Lab (SCIL) Python dMRI processing toolbox." + homepage: "https://github.com/scilus/scilpy.git" + - "MRtrix3": + description: "Toolbox for image processing, analysis and visualisation of dMRI." + homepage: "https://mrtrix.readthedocs.io/en/latest/" + +input: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + + - dwi: + type: file + description: Nifti DWI volume to perform BET + crop. + pattern: "*.{nii,nii.gz}" + + - bval: + type: file + description: B-values in FSL format. + pattern: "*.bval" + + - bvec: + type: file + description: B-vectors in FSL format. + pattern: "*.bvec" + +output: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + + - versions: + type: file + description: File containing software versions + pattern: "versions.yml" + + - dwi: + type: file + description: Nifti DWI volume brain-extracted and cropped. + pattern: "*dwi_bet_cropped.{nii,nii.gz}" + + - mask: + type: file + description: DWI mask brain-extracted and cropped. + pattern: "*dwi_bet_cropped_mask.{nii,nii.gz}" + + - bbox: + type: file + description: DWI BoundingBox used for cropping. + pattern: "*dwi_boundingBox.pkl" + +authors: + - "@gagnonanthony" diff --git a/modules/nf-scil/denoising/mppca/main.nf b/modules/nf-scil/denoising/mppca/main.nf new file mode 100644 index 0000000..c287502 --- /dev/null +++ b/modules/nf-scil/denoising/mppca/main.nf @@ -0,0 +1,55 @@ + +process DENOISING_MPPCA { + tag "$meta.id" + label 'process_single' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://scil.usherbrooke.ca/containers/scilus_1.6.0.sif': + 'scilus/scilus:1.6.0' }" + + input: + tuple val(meta), path(dwi) + + output: + tuple val(meta), path("*_dwi_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 extent = task.ext.extent ? "-extent " + task.ext.extent : "" + + """ + export ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS=1 + export OMP_NUM_THREADS=1 + export OPENBLAS_NUM_THREADS=1 + + dwidenoise $dwi ${prefix}__pre_dwi_denoised.nii.gz $extent -nthreads 1 + fslmaths ${prefix}__pre_dwi_denoised.nii.gz -thr 0 ${prefix}_dwi_denoised.nii.gz + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + mrtrix: \$(mrcalc -version 2>&1 | sed -n 's/== mrcalc \\([0-9.]\\+\\).*/\\1/p') + fsl: \$(flirt -version 2>&1 | sed -n 's/FLIRT version \\([0-9.]\\+\\)/\\1/p') + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + dwidenoise -h + fslmaths -h + + touch ${prefix}_dwi_denoised.nii.gz + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + mrtrix: \$(mrcalc -version 2>&1 | sed -n 's/== mrcalc \\([0-9.]\\+\\).*/\\1/p') + fsl: \$(flirt -version 2>&1 | sed -n 's/FLIRT version \\([0-9.]\\+\\)/\\1/p') + END_VERSIONS + """ +} diff --git a/modules/nf-scil/denoising/mppca/meta.yml b/modules/nf-scil/denoising/mppca/meta.yml new file mode 100644 index 0000000..d044773 --- /dev/null +++ b/modules/nf-scil/denoising/mppca/meta.yml @@ -0,0 +1,49 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/yaml-schema.json +name: "denoising_mppca" +description: denoise a dataset with the Marchenko-Pastur principal component analysis +keywords: + - nifti + - denoising + - mppca + - mrtrix + - fsl +tools: + - "MRtrix3": + description: "Toolbox for image processing, analysis and visualisation of dMRI." + homepage: "https://mrtrix.readthedocs.io/en/latest/" + - "FSL": + description: "FSL Toolbox and Scilpy Toolbox" + homepage: "https://fsl.fmrib.ox.ac.uk/fsl/fslwiki" + +input: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + + - dwi: + type: file + description: Nifti dwi file to denoise + pattern: "*.{nii,nii.gz}" + +output: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + + - versions: + type: file + description: File containing software versions + pattern: "versions.yml" + + - image: + type: file + description: Denoised Nifti image file + pattern: "*_dwi_denoised.{nii,nii.gz}" + +authors: + - "@scilus" diff --git a/modules/nf-scil/denoising/nlmeans/main.nf b/modules/nf-scil/denoising/nlmeans/main.nf new file mode 100644 index 0000000..c913d62 --- /dev/null +++ b/modules/nf-scil/denoising/nlmeans/main.nf @@ -0,0 +1,51 @@ + +process DENOISING_NLMEANS { + tag "$meta.id" + label 'process_single' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://scil.usherbrooke.ca/containers/scilus_1.6.0.sif': + 'scilus/scilus:1.6.0' }" + + 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 = [] + if (mask) args += ["--mask $mask"] + + """ + export ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS=1 + export OMP_NUM_THREADS=1 + export OPENBLAS_NUM_THREADS=1 + + scil_run_nlmeans.py $image ${prefix}_denoised.nii.gz 1 ${args.join(" ")} + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + scilpy: 1.6.0 + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + scil_run_nlmeans.py -h + + touch ${prefix}_denoised.nii.gz + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + scilpy: 1.6.0 + END_VERSIONS + """ +} diff --git a/modules/nf-scil/denoising/nlmeans/meta.yml b/modules/nf-scil/denoising/nlmeans/meta.yml new file mode 100644 index 0000000..050958e --- /dev/null +++ b/modules/nf-scil/denoising/nlmeans/meta.yml @@ -0,0 +1,50 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/yaml-schema.json +name: "denoising_nlmeans" +description: denoise a dataset with the Non Local Means algorithm +keywords: + - nifti + - denoising + - nlmeans + - scilpy +tools: + - "Scilpy": + description: "The Sherbrooke Connectivity Imaging Lab (SCIL) Python dMRI processing toolbox." + homepage: "https://github.com/scilus/scilpy.git" + +input: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + + - image: + type: file + description: Nifti image file to denoise + pattern: "*.{nii,nii.gz}" + + - mask: + type: file + description: Nifti image file used to mask the input image + pattern: "*.{nii,nii.gz}" + +output: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + + - versions: + type: file + description: File containing software versions + pattern: "versions.yml" + + - image: + type: file + description: Denoised Nifti image file + pattern: "*_denoised.{nii,nii.gz}" + +authors: + - "@scilus" diff --git a/modules/nf-scil/preproc/n4/main.nf b/modules/nf-scil/preproc/n4/main.nf new file mode 100644 index 0000000..7013793 --- /dev/null +++ b/modules/nf-scil/preproc/n4/main.nf @@ -0,0 +1,57 @@ +process PREPROC_N4 { + tag "$meta.id" + label 'process_single' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'scil.usherbrooke.ca/containers/scilus_1.6.0.sif': + 'scilus/scilus:1.6.0' }" + + input: + tuple val(meta), path(dwi), path(b0), path(b0_mask) + + output: + tuple val(meta), path("*dwi_n4.nii.gz") , emit: dwi + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + """ + export ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS=$task.cpus + export OMP_NUM_THREADS=1 + export OPENBLAS_NUM_THREADS=1 + export ANTS_RANDOM_SEED=1234 + + N4BiasFieldCorrection -i $b0\ + -o [${prefix}__b0_n4.nii.gz, bias_field_b0.nii.gz]\ + -c [300x150x75x50, 1e-6] -v 1 + + scil_apply_bias_field_on_dwi.py $dwi bias_field_b0.nii.gz\ + ${prefix}__dwi_n4.nii.gz --mask $b0_mask -f + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + scilpy: 1.6.0 + N4BiasFieldCorrection: \$(N4BiasFieldCorrection --version 2>&1 | sed -n 's/ANTs Version: v\\([0-9.]\\+\\)/\\1/p') + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + """ + N4BiasFieldCorrection.py -h + scil_apply_bias_field_on_dwi -h + + touch ${prefix}_dwi_n4.nii.gz + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + scilpy: 1.6.0 + N4BiasFieldCorrection: \$(N4BiasFieldCorrection --version 2>&1 | sed -n 's/ANTs Version: v\\([0-9.]\\+\\)/\\1/p') + END_VERSIONS + """ +} diff --git a/modules/nf-scil/preproc/n4/meta.yml b/modules/nf-scil/preproc/n4/meta.yml new file mode 100644 index 0000000..ef2cc8b --- /dev/null +++ b/modules/nf-scil/preproc/n4/meta.yml @@ -0,0 +1,54 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/yaml-schema.json +name: "preproc_n4" +description: Bias field correction using N4 +keywords: + - correction + - N4 + - bias field +tools: + - "Scilpy": + description: "The Sherbrooke Connectivity Imaging Lab (SCIL) Python dMRI processing toolbox." + homepage: "https://github.com/scilus/scilpy.git" + +input: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + + - dwi: + type: file + description: Nifti image file to correct + pattern: "*.{nii,nii.gz}" + + - b0: + type: file + description: Nifti image file - b0 + pattern: "*.{nii,nii.gz}" + + - b0_mask: + type: file + description: Nifti image file mask + pattern: "*.{nii,nii.gz}" + +output: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + + - versions: + type: file + description: File containing software versions + pattern: "versions.yml" + + - dwi: + type: file + description: DWI image N4 corrected + pattern: "*.{nii,nii.gz}" + +authors: + - "@arnaudbore" diff --git a/modules/nf-scil/reconst/dtimetrics/main.nf b/modules/nf-scil/reconst/dtimetrics/main.nf new file mode 100644 index 0000000..1724f4c --- /dev/null +++ b/modules/nf-scil/reconst/dtimetrics/main.nf @@ -0,0 +1,119 @@ + +process RECONST_DTIMETRICS { + tag "$meta.id" + label 'process_single' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://scil.usherbrooke.ca/containers/scilus_1.6.0.sif': + 'scilus/scilus:1.6.0' }" + + input: + tuple val(meta), path(dwi), path(bval), path(bvec), path(b0mask) + + output: + tuple val(meta), path("*__ad.nii.gz") , emit: ad, optional: true + tuple val(meta), path("*__evecs.nii.gz") , emit: evecs, optional: true + tuple val(meta), path("*__evecs_v1.nii.gz") , emit: evecs_v1, optional: true + tuple val(meta), path("*__evecs_v2.nii.gz") , emit: evecs_v2, optional: true + tuple val(meta), path("*__evecs_v3.nii.gz") , emit: evecs_v3, optional: true + tuple val(meta), path("*__evals.nii.gz") , emit: evals, optional: true + tuple val(meta), path("*__evals_e1.nii.gz") , emit: evals_e1, optional: true + tuple val(meta), path("*__evals_e2.nii.gz") , emit: evals_e2, optional: true + tuple val(meta), path("*__evals_e3.nii.gz") , emit: evals_e3, optional: true + tuple val(meta), path("*__fa.nii.gz") , emit: fa, optional: true + tuple val(meta), path("*__ga.nii.gz") , emit: ga, optional: true + tuple val(meta), path("*__rgb.nii.gz") , emit: rgb, optional: true + tuple val(meta), path("*__md.nii.gz") , emit: md, optional: true + tuple val(meta), path("*__mode.nii.gz") , emit: mode, optional: true + tuple val(meta), path("*__norm.nii.gz") , emit: norm, optional: true + tuple val(meta), path("*__rd.nii.gz") , emit: rd, optional: true + tuple val(meta), path("*__tensor.nii.gz") , emit: tensor, optional: true + tuple val(meta), path("*__nonphysical.nii.gz") , emit: nonphysical, optional: true + tuple val(meta), path("*__pulsation_std_dwi.nii.gz") , emit: pulsation_std_dwi, optional: true + tuple val(meta), path("*__residual.nii.gz") , emit: residual, optional: true + tuple val(meta), path("*__residual_iqr_residuals.npy") , emit: residual_iqr_residuals, optional: true + tuple val(meta), path("*__residual_mean_residuals.npy") , emit: residual_mean_residuals, optional: true + tuple val(meta), path("*__residual_q1_residuals.npy") , emit: residual_q1_residuals, optional: true + tuple val(meta), path("*__residual_q3_residuals.npy") , emit: residual_q3_residuals, optional: true + tuple val(meta), path("*__residual_residuals_stats.png") , emit: residual_residuals_stats, optional: true + tuple val(meta), path("*__residual_std_residuals.npy") , emit: residual_std_residuals, optional: true + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def mask =[] + + if (b0mask) mask += ["--mask $b0mask"] + if ( task.ext.ad ) args += " --ad ${prefix}__ad.nii.gz" + if ( task.ext.evecs ) args += " --evecs ${prefix}__evecs.nii.gz" + if ( task.ext.evals ) args += " --evals ${prefix}__evals.nii.gz" + if ( task.ext.fa ) args += " --fa ${prefix}__fa.nii.gz" + if ( task.ext.ga ) args += " --ga ${prefix}__ga.nii.gz" + if ( task.ext.rgb ) args += " --rgb ${prefix}__rgb.nii.gz" + if ( task.ext.md ) args += " --md ${prefix}__md.nii.gz" + if ( task.ext.mode ) args += " --mode ${prefix}__mode.nii.gz" + if ( task.ext.norm ) args += " --norm ${prefix}__norm.nii.gz" + if ( task.ext.rd ) args += " --rd ${prefix}__rd.nii.gz" + if ( task.ext.tensor ) args += " --tensor ${prefix}__tensor.nii.gz" + if ( task.ext.nonphysical ) args += " --non-physical ${prefix}__nonphysical.nii.gz" + if ( task.ext.pulsation ) args += " --pulsation ${prefix}__pulsation_std_dwi.nii.gz" + if ( task.ext.residual ) args += " --residual ${prefix}__residual.nii.gz" + + + """ + export ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS=1 + export OMP_NUM_THREADS=1 + export OPENBLAS_NUM_THREADS=1 + + scil_compute_dti_metrics.py $dwi $bval $bvec ${mask.join(" ")} --not_all $args -f --force_b0_threshold + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + scilpy: 1.6.0 + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + scil_compute_dti_metrics.py -h + + touch ${prefix}__ad.nii.gz + touch ${prefix}__evecs.nii.gz + touch ${prefix}__evecs_v1.nii.gz + touch ${prefix}__evecs_v2.nii.gz + touch ${prefix}__evecs_v3.nii.gz + touch ${prefix}__evals.nii.gz + touch ${prefix}__evals_e1.nii.gz + touch ${prefix}__evals_e2.nii.gz + touch ${prefix}__evals_e3.nii.gz + touch ${prefix}__fa.nii.gz + touch ${prefix}__ga.nii.gz + touch ${prefix}__rgb.nii.gz + touch ${prefix}__md.nii.gz + touch ${prefix}__mode.nii.gz + touch ${prefix}__norm.nii.gz + touch ${prefix}__rd.nii.gz + touch ${prefix}__tensor.nii.gz + touch ${prefix}__nonphysical.nii.gz + touch ${prefix}__pulsation_std_dwi.nii.gz + touch ${prefix}__residual.nii.gz + touch ${prefix}__residual_iqr_residuals.npy + touch ${prefix}__residual_mean_residuals.npy + touch ${prefix}__residual_q1_residuals.npy + touch ${prefix}__residual_q3_residuals.npy + touch ${prefix}__residual_residuals_stats.png + touch ${prefix}__residual_std_residuals.npy + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + scilpy: 1.6.0 + END_VERSIONS + """ +} diff --git a/modules/nf-scil/reconst/dtimetrics/meta.yml b/modules/nf-scil/reconst/dtimetrics/meta.yml new file mode 100644 index 0000000..54a2198 --- /dev/null +++ b/modules/nf-scil/reconst/dtimetrics/meta.yml @@ -0,0 +1,190 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/yaml-schema.json +name: "reconst_dtimetrics" + +description: Script to compute all of the Diffusion Tensor Imaging (DTI) metrics +keywords: + - nifti + - DTI + - tensor + - scilpy +tools: + - "Scilpy": + description: "The Sherbrooke Connectivity Imaging Lab (SCIL) Python dMRI processing toolbox." + homepage: "https://github.com/scilus/scilpy.git" + +input: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + + - dwi: + type: file + description: Nifti DWI volume used to extract DTI metrics. + pattern: "*.{nii,nii.gz}" + + - bval: + type: file + description: B-values in FSL format. + pattern: "*.bval" + + - bvec: + type: file + description: B-vectors in FSL format. + pattern: "*.bvec" + + - b0mask: + type: file + description: Nifti b0 volume file used to mask the input image. + pattern: "*.{nii,nii.gz}" + +output: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + + - versions: + type: file + description: File containing software versions + pattern: "versions.yml" + + - ad: + type: file + description: Output filename for the axial diffusivity. + pattern: "*__ad.{nii,nii.gz}" + + - evecs: + type: file + description: Output filename for the eigenvectors of the tensor. + pattern: "*__evecs.{nii,nii.gz}" + + - evecs_v1: + type: file + description: Output filename for the first eigenvector. + pattern: "*__evecs_v1.{nii,nii.gz}" + + - evecs_v2: + type: file + description: Output filename for the second eigenvector. + pattern: "*__evecs_v2.{nii,nii.gz}" + + - evec_v3: + type: file + description: Output filename for the third eigenvector. + pattern: "*__evecs_v3.{nii,nii.gz}" + + - evals: + type: file + description: Output filename for the eigenvalues of the tensor. + pattern: "*__evals.{nii,nii.gz}" + + - evals_e1: + type: file + description: Output filename for the first eigenvalue. + pattern: "*__evals_e1.{nii,nii.gz}" + + - evals_e2: + type: file + description: Output filename for the second eigenvalue. + pattern: "*__evals_e2.{nii,nii.gz}" + + - evals_e3: + type: file + description: Output filename for the third eigenvalue. + pattern: "*__evals_e3.{nii,nii.gz}" + + - fa: + type: file + description: Output filename for the fractional anisotropy. + pattern: "*__fa.{nii,nii.gz}" + + - ga: + type: file + description: Output filename for the geodesic anisotropy. + pattern: "*__ga.{nii,nii.gz}"Cropped Nifti image file + + - rgb: + type: file + description: Output filename for the colored fractional anisotropy. + pattern: "*__rgb.{nii,nii.gz}" + + - md: + type: file + description: Output filename for the mean diffusivity. + pattern: "*__md.{nii,nii.gz}" + + - mode: + type: file + description: Output filename for the mode. + pattern: "*__mode.{nii,nii.gz}" + + - norm: + type: file + description: Output filename for the tensor norm. + pattern: "*__norm.{nii,nii.gz}" + + - rd: + type: file + description: Output filename for the radial diffusivity. + pattern: "*__rd.{nii,nii.gz}" + + - tensor: + type: file + description: Output filename for the tensor coefficients. + pattern: "*__tensor.{nii,nii.gz}" + + - nonphysical: + type: file + description: Output filename for the voxels with physically implausible + signals where the mean of b=0 images is below one or more + diffusion-weighted images. + pattern: "*__nonphysical.{nii,nii.gz}" + + - pulsation_std_dwi: + type: file + description: Standard deviation map across all diffusion-weighted images + and across b=0 images if more than one is available.Shows + pulsation and misalignment artifacts. + pattern: "*__pulsation_std_dwi.{nii,nii.gz}" + + - residual: + type: file + description: Output filename for the map of the residual of the tensor fit. + pattern: "*__residual.{nii,nii.gz}" + + - residual_iqr_residuals: + type: file + description: Output filename for the interquartile range of the residual of the tensor fit. + pattern: "*__residual_iqr_residuals.npy" + + - residual_mean_residuals: + type: file + description: Output filename for the mean of the residual of the tensor fit. + pattern: "*__residual_mean_residuals.npy" + + - residuall_q1_residuals: + type: file + description: Output filename for the firt quartile of the residual of the tensor fit. + pattern: "*__residual_q1_residuals.npy" + + - residual_q3_residuals: + type: file + description: Output filename for the third quartile of the residual of the tensor fit. + pattern: "*__residual_q3_residuals.npy" + + - residual_residuals_stats: + type: file + description: Output filename for the all statistics of the residual of the tensor fit. + pattern: "*__residual_residuals_stats.png" + + - residual_std_residuals: + type: file + description: Output filename for the standard deviation of the residual of the tensor fit. + pattern: "*__residual_std_residuals.npy" + +authors: + - "@scilus" diff --git a/modules/nf-scil/reconst/fodf/main.nf b/modules/nf-scil/reconst/fodf/main.nf new file mode 100644 index 0000000..a711ad2 --- /dev/null +++ b/modules/nf-scil/reconst/fodf/main.nf @@ -0,0 +1,105 @@ + +process RECONST_FODF { + tag "$meta.id" + label 'process_single' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://scil.usherbrooke.ca/containers/scilus_1.6.0.sif': + 'scilus/scilus:1.6.0' }" + + input: + tuple val(meta), path(dwi), path(bval), path(bvec), path(b0_mask), path(fa), path(md), path(frf) + + output: + tuple val(meta), path("*fodf.nii.gz") , emit: fodf + tuple val(meta), path("*peaks.nii.gz") , emit: peaks, optional: true + tuple val(meta), path("*peak_indices.nii.gz") , emit: peak_indices, optional: true + tuple val(meta), path("*afd_max.nii.gz") , emit: afd_max, optional: true + tuple val(meta), path("*afd_total.nii.gz") , emit: afd_total, optional: true + tuple val(meta), path("*afd_sum.nii.gz") , emit: afd_sum, optional: true + tuple val(meta), path("*nufo.nii.gz") , emit: nufo, optional: true + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def prefix = task.ext.prefix ?: "${meta.id}" + + def dwi_shell_tolerance = task.ext.dwi_shell_tolerance ? "--tolerance " + task.ext.dwi_shell_tolerance : "" + def fodf_shells = task.ext.fodf_shells ?: "\$(cut -d ' ' --output-delimiter=\$'\\n' -f 1- $bval | awk -F' ' '{v=int(\$1)}{if(v>=$task.ext.min_fodf_shell_value|| v<=$task.ext.b0_thr_extract_b0)print v}' | uniq)" + def sh_order = task.ext.sh_order ? "--sh_order " + task.ext.sh_order : "" + def sh_basis = task.ext.sh_basis ? "--sh_basis " + task.ext.sh_basis : "" + def fa_threshold = task.ext.fa_threshold ? "--fa_t " + task.ext.fa_threshold : "" + def md_threshold = task.ext.md_threshold ? "--md_t " + task.ext.md_threshold : "" + def relative_threshold = task.ext.relative_threshold ? "--rt " + task.ext.relative_threshold : "" + def fodf_metrics_a_factor = task.ext.fodf_metrics_a_factor ? task.ext.fodf_metrics_a_factor : 2.0 + def processes = task.ext.processes ? "--processes " + task.ext.processes : "" + + if ( task.ext.peaks ) peaks = "--peaks ${prefix}__peaks.nii.gz" else "" + if ( task.ext.peak_indices ) peak_indices = "--peak_indices ${prefix}__peak_indices.nii.gz" else "" + if ( task.ext.afd_max ) afd_max = "--afd_max ${prefix}__afd_max.nii.gz" else "" + if ( task.ext.afd_total ) afd_total = "--afd_total ${prefix}__afd_total.nii.gz" else "" + if ( task.ext.afd_sum ) afd_sum = "--afd_sum ${prefix}__afd_sum.nii.gz" else "" + if ( task.ext.nufo ) nufo = "--nufo ${prefix}__nufo.nii.gz" else "" + + """ + export ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS=1 + export OMP_NUM_THREADS=1 + export OPENBLAS_NUM_THREADS=1 + + scil_extract_dwi_shell.py $dwi $bval $bvec $fodf_shells \ + dwi_fodf.nii.gz bval_fodf bvec_fodf \ + $dwi_shell_tolerance -f + + scil_compute_ssst_fodf.py dwi_fodf.nii.gz bval_fodf bvec_fodf $frf ${prefix}__fodf.nii.gz \ + $sh_order $sh_basis --force_b0_threshold \ + --mask $b0_mask $processes + + scil_compute_fodf_max_in_ventricles.py ${prefix}__fodf.nii.gz $fa $md \ + --max_value_output ventricles_fodf_max_value.txt $sh_basis \ + $fa_threshold $md_threshold -f + + a_factor=$fodf_metrics_a_factor + v_max=\$(sed -E 's/([+-]?[0-9.]+)[eE]\\+?(-?)([0-9]+)/(\\1*10^\\2\\3)/g' <<<"\$(cat ventricles_fodf_max_value.txt)") + a_threshold=\$(echo "scale=10; \${a_factor} * \${v_max}" | bc) + if (( \$(echo "\${a_threshold} < 0" | bc -l) )); then + a_threshold=0 + fi + + scil_compute_fodf_metrics.py ${prefix}__fodf.nii.gz \ + --mask $b0_mask $sh_basis \ + $peaks $peak_indices \ + $afd_max $afd_total \ + $afd_sum $nufo \ + $relative_threshold --at \${a_threshold} + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + scilpy: 1.6.0 + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + scil_extract_dwi_shell.py -h + scil_compute_ssst_fodf.py -h + scil_compute_fodf_max_in_ventricles.py -h + scil_compute_fodf_metrics.py -h + + touch ${prefix}__fodf.nii.gz + touch ${prefix}__peaks.nii.gz + touch ${prefix}__peak_indices.nii.gz + touch ${prefix}__afd_max.nii.gz + touch ${prefix}__afd_total.nii.gz + touch ${prefix}__afd_sum.nii.gz + touch ${prefix}__nufo.nii.gz + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + scilpy: 1.6.0 + END_VERSIONS + """ +} diff --git a/modules/nf-scil/reconst/fodf/meta.yml b/modules/nf-scil/reconst/fodf/meta.yml new file mode 100644 index 0000000..fd2366b --- /dev/null +++ b/modules/nf-scil/reconst/fodf/meta.yml @@ -0,0 +1,103 @@ +--- +name: "reconst_fodf" +description: Perform FODF reconstruction and compute FODF metrics from a dwi volume for a selected number of shells. +keywords: + - FODF + - Local Model + - Metrics +tools: + - "Scilpy": + description: "The Sherbrooke Connectivity Imaging Lab (SCIL) Python dMRI processing toolbox." + homepage: "https://github.com/scilus/scilpy.git" + +input: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + + - dwi: + type: file + description: Nifti DWI volume to reconstruct FODF from. + pattern: "*.{nii,nii.gz}" + + - bval: + type: file + description: B-values in FSL format. + pattern: "*.bval" + + - bvec: + type: file + description: B-vectors in FSL format. + pattern: "*.bvec" + + - b0_mask: + type: file + description: B0 mask. + pattern: "*.{nii,nii.gz}" + + - fa: + type: file + description: FA map. + pattern: "*.{nii,nii.gz}" + + - md: + type: file + description: MD map. + pattern: "*.{nii,nii.gz}" + + - frf: + type: file + description: Fiber Response Function (FRF). + pattern: "*.txt" + +output: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + + - versions: + type: file + description: File containing software versions + pattern: "versions.yml" + + - fodf: + type: file + description: FODF map. + pattern: "*fodf.nii.gz" + + - peaks: + type: file + description: Peaks file. + pattern: "*peaks.nii.gz" + + - peak_indices: + type: file + description: Peak indices file. + pattern: "*peak_indices.nii.gz" + + - afd_max: + type: file + description: Maximum Apparent Fiber Density (AFDmax) map. + pattern: "*afd_max.nii.gz" + + - afd_total: + type: file + description: Total Apparent Fiber Density (AFDtotal) map. + pattern: "*afd_total.nii.gz" + + - afd_sum: + type: file + description: Sum of all Apparent Fiber Density (Afdsum) map. + pattern: "*afd_sum.nii.gz" + + - nufo: + type: file + description: Number of Fiber Orientation (NuFO) map. + pattern: "*nufo.nii.gz" + +authors: + - "@gagnonanthony" diff --git a/modules/nf-scil/reconst/frf/main.nf b/modules/nf-scil/reconst/frf/main.nf new file mode 100644 index 0000000..7e38569 --- /dev/null +++ b/modules/nf-scil/reconst/frf/main.nf @@ -0,0 +1,64 @@ + + +process RECONST_FRF { + tag "$meta.id" + label 'process_single' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://scil.usherbrooke.ca/containers/scilus_1.6.0.sif': + 'scilus/scilus:1.6.0' }" + + input: + tuple val(meta), path(dwi), path(bval), path(bvec), path(mask) + + output: + tuple val(meta), path("*__frf.txt") , emit: frf + path "versions.yml" , emit: versions + + + when: + task.ext.when == null || task.ext.when + + script: + def prefix = task.ext.prefix ?: "${meta.id}" + def fa = task.ext.fa ? "--fa " + task.ext.fa : "" + def fa_min = task.ext.fa_min ? "--min_fa " + task.ext.fa_min : "" + def nvox_min = task.ext.nvox_min ? "--min_nvox " + task.ext.nvox_min : "" + def roi_radius = task.ext.roi_radius ? "--roi_radii " + task.ext.roi_radius : "" + def fix_frf = task.ext.manual_frf ? task.ext.manual_frf : "" + def set_mask = mask ? "--mask $mask" : "" + + """ + export ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS=1 + export OMP_NUM_THREADS=1 + export OPENBLAS_NUM_THREADS=1 + + scil_compute_ssst_frf.py $dwi $bval $bvec ${prefix}__frf.txt \ + $set_mask $fa $fa_min $nvox_min $roi_radius --force_b0_threshold + + if ( "$task.ext.set_frf" = true ); then + scil_set_response_function.py ${prefix}__frf.txt "${fix_frf}" \ + ${prefix}__frf.txt -f + fi + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + scilpy: 1.6.0 + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + scil_compute_ssst_frf.py -h + scil_set_response_function.py -h + + touch ${prefix}__frf.txt + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + scilpy: 1.6.0 + END_VERSIONS + """ +} diff --git a/modules/nf-scil/reconst/frf/meta.yml b/modules/nf-scil/reconst/frf/meta.yml new file mode 100644 index 0000000..384e835 --- /dev/null +++ b/modules/nf-scil/reconst/frf/meta.yml @@ -0,0 +1,63 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/yaml-schema.json +name: "reconst_frf" +description: Compute a single Fiber Response Function from a DWI. +keywords: + - DWI + - FRF + +tools: + - "DIPY": + description: "DIPY is the paragon 3D/4D+ imaging library in Python." + homepage: https://dipy.org + + - "Scilpy": + description: "The Sherbrooke Connectivity Imaging Lab (SCIL) Python dMRI processing toolbox." + homepage: "https://github.com/scilus/scilpy.git" + +input: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + + - dwi: + type: file + description: Nifti image of DWI. + pattern: "*.{nii,nii.gz}" + + - bval: + type: file + description: B-values in FSL format. + pattern: "*.bval" + + - bvec: + type: file + description: B-vectors in FSL format. + pattern: "*.bvec" + + - mask: + type: file + description: Nifti b0 binary mask. + pattern: "*.{nii,nii.gz}" + +output: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + + - versions: + type: file + description: File containing software versions + pattern: "versions.yml" + + - frf: + type: file + description: Fiber Response Function (FRF). + pattern: "*.txt" + +authors: + - "@Manonedde" diff --git a/modules/nf-scil/tracking/pfttracking/main.nf b/modules/nf-scil/tracking/pfttracking/main.nf new file mode 100644 index 0000000..3e9eb59 --- /dev/null +++ b/modules/nf-scil/tracking/pfttracking/main.nf @@ -0,0 +1,131 @@ + +process TRACKING_PFTTRACKING { + tag "$meta.id" + label 'process_single' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://scil.usherbrooke.ca/containers/scilus_1.6.0.sif': + 'scilus/scilus:1.6.0' }" + + input: + tuple val(meta), path(wm), path(gm), path(csf), path(fodf), path(fa) + + output: + + tuple val(meta), path("*__pft_tracking.trk") , emit: trk + tuple val(meta), path("*__pft_tracking_config.json") , emit: config + tuple val(meta), path("*__map_include.nii.gz") , emit: include + tuple val(meta), path("*__map_exclude.nii.gz") , emit: exclude + tuple val(meta), path("*__pft_seeding_mask.nii.gz") , emit: seeding + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def prefix = task.ext.prefix ?: "${meta.id}" + + def pft_fa_threshold = task.ext.pft_fa_seeding_mask_threshold ? task.ext.pft_fa_seeding_mask_threshold : "" + def pft_seeding_mask = task.ext.pft_seeding_mask_type ? "${task.ext.pft_seeding_mask_type}" : "" + + def pft_random_seed = task.ext.pft_random_seed ? "--seed " + task.ext.pft_random_seed : "" + def compress = task.ext.pft_compress_streamlines ? "--compress " + task.ext.pft_compress_value : "" + def pft_algo = task.ext.pft_algo ? "--algo " + task.ext.pft_algo: "" + def pft_seeding_type = task.ext.pft_seeding ? "--" + task.ext.pft_seeding : "" + def pft_nbr_seeds = task.ext.pft_nbr_seeds ? "" + task.ext.pft_nbr_seeds : "" + def pft_step = task.ext.pft_step ? "--step " + task.ext.pft_step : "" + def pft_theta = task.ext.pft_theta ? "--theta " + task.ext.pft_theta : "" + def pft_sfthres = task.ext.pft_sfthres ? "--sfthres " + task.ext.pft_sfthres : "" + def pft_sfthres_init = task.ext.pft_sfthres_init ? "--sfthres_init " + task.ext.pft_sfthres_init : "" + def pft_min_len = task.ext.pft_min_len ? "--min_length " + task.ext.pft_min_len : "" + def pft_max_len = task.ext.pft_max_len ? "--max_length " + task.ext.pft_max_len : "" + def pft_particles = task.ext.pft_particles ? "--particles " + task.ext.pft_particles : "" + def pft_back = task.ext.pft_back ? "--back " + task.ext.pft_back : "" + def pft_front = task.ext.pft_front ? "--forward " + task.ext.pft_front : "" + def basis = task.ext.basis ? "--sh_basis " + task.ext.basis : "" + + """ + export ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS=1 + export OMP_NUM_THREADS=1 + export OPENBLAS_NUM_THREADS=1 + + scil_compute_maps_for_particle_filter_tracking.py $wm $gm $csf \ + --include ${prefix}__map_include.nii.gz \ + --exclude ${prefix}__map_exclude.nii.gz \ + --interface ${prefix}__interface.nii.gz -f + + if [ "${pft_seeding_mask}" == "wm" ]; then + scil_image_math.py convert $wm ${prefix}__mask_wm.nii.gz \ + --data_type uint8 + scil_image_math.py union ${prefix}__mask_wm.nii.gz \ + ${prefix}__interface.nii.gz ${prefix}__pft_seeding_mask.nii.gz\ + --data_type uint8 + + elif [ "${pft_seeding_mask}" == "interface" ]; then + cp ${prefix}__interface.nii.gz ${prefix}__pft_seeding_mask.nii.gz + + elif [ "${pft_seeding_mask}" == "fa" ]; then + mrcalc $fa $pft_fa_threshold -ge ${prefix}__pft_seeding_mask.nii.gz\ + -datatype uint8 + fi + + scil_compute_pft.py $fodf ${prefix}__pft_seeding_mask.nii.gz \ + ${prefix}__map_include.nii.gz ${prefix}__map_exclude.nii.gz tmp.trk\ + $pft_algo $pft_seeding_type $pft_nbr_seeds \ + $pft_random_seed $pft_step $pft_theta\ + $pft_sfthres $pft_sfthres_init $pft_min_len $pft_max_len\ + $pft_particles $pft_back $pft_front $compress $basis + + scil_remove_invalid_streamlines.py tmp.trk ${prefix}__pft_tracking.trk\ + --remove_single_point + + cat <<-TRACKING_INFO > ${prefix}__pft_tracking_config.json + {"algorithm": "${task.ext.pft_algo}", + "seeding_type": "${task.ext.pft_seeding}", + "nb_seed": $task.ext.pft_nbr_seeds, + "seeding_mask": "${pft_seeding_mask}", + "random_seed": $task.ext.pft_random_seed, + "is_compress": "${task.ext.pft_compress_streamlines}", + "compress_value": $task.ext.pft_compress_value, + "step": $task.ext.pft_step, + "theta": $task.ext.pft_theta, + "sfthres": $task.ext.pft_sfthres, + "sfthres_init": $task.ext.pft_sfthres_init, + "min_len": $task.ext.pft_min_len, + "max_len": $task.ext.pft_max_len, + "particles": $task.ext.pft_particles, + "back": $task.ext.pft_back, + "forward": $task.ext.pft_front, + "sh_basis": "${task.ext.basis}"} + TRACKING_INFO + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + scilpy: 1.6.0 + mrtrix: \$(mrcalc -version 2>&1 | sed -n 's/== mrcalc \\([0-9.]\\+\\).*/\\1/p') + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + scil_compute_pft.py -h + scil_compute_maps_for_particle_filter_tracking.py -h + scil_image_math.py -h + mrcalc -h + scil_remove_invalid_streamlines.py -h + + touch ${prefix}__map_include.nii.gz + touch ${prefix}__map_exclude.nii.gz + touch ${prefix}__pft_seeding_mask.nii.gz + touch ${prefix}__pft_tracking.trk + touch ${prefix}__pft_tracking_config.json + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + scilpy: 1.6.0 + mrtrix: \$(mrcalc -version 2>&1 | sed -n 's/== mrcalc \\([0-9.]\\+\\).*/\\1/p') + END_VERSIONS + """ +} diff --git a/modules/nf-scil/tracking/pfttracking/meta.yml b/modules/nf-scil/tracking/pfttracking/meta.yml new file mode 100644 index 0000000..a7f6e3a --- /dev/null +++ b/modules/nf-scil/tracking/pfttracking/meta.yml @@ -0,0 +1,91 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/yaml-schema.json +name: "tracking_pfttracking" +description: Compute include and exclude maps, and the seeding mask + from partial volume estimation (PVE) maps. + Generates a tractogram using anatomically-constrained particle + filter tracking, Particle Filtering Tractography (PFT). +keywords: + - PFT + - tractography + +tools: + - "DIPY": + description: "DIPY is the paragon 3D/4D+ imaging library in Python." + homepage: https://dipy.org + doi: "10.1016/j.neuroimage.2014.04.074" + - "Scilpy": + description: "The Sherbrooke Connectivity Imaging Lab (SCIL) Python dMRI processing toolbox." + homepage: "https://github.com/scilus/scilpy.git" + +input: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + + - wm: + type: file + description: Nifti white matter probability map. + pattern: "*.{nii,nii.gz}" + + - gm: + type: file + description: Nifti grey matter probability map. + pattern: "*.{nii,nii.gz}" + + - csf: + type: file + description: Nifti cerebrospinal fuild probability map. + pattern: "*.{nii,nii.gz}" + + - fodf: + type: file + description: Nifti image of Spherical harmonic file (fodf). + pattern: "*.{nii,nii.gz}" + + - fa: + type: file + description: Nifti image of FA. + pattern: "*.{nii,nii.gz}" + +output: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + + - versions: + type: file + description: File containing software versions + pattern: "versions.yml" + + - include: + type: file + description: Nifti probability map for tracking of ending the streamline and including it in the output. + pattern: "*.{nii,nii.gz}" + + - exclude: + type: file + description: Nifti probability map for tracking of ending the streamline and excluding it in the output. + pattern: "*.{nii,nii.gz}" + + - seeding: + type: file + description: Nifti seeding mask for tracking. + pattern: "*.{nii,nii.gz}" + + - trk: + type: file + description: Tractogram output file. + pattern: "*.{trk,tck}" + + - config: + type: file + description: Json file containing tracking parameters. + pattern: "*.{json}" + +authors: + - "@Manonedde" diff --git a/modules/nf-scil/utils/extractb0/main.nf b/modules/nf-scil/utils/extractb0/main.nf new file mode 100644 index 0000000..d79b57a --- /dev/null +++ b/modules/nf-scil/utils/extractb0/main.nf @@ -0,0 +1,54 @@ + + +process UTILS_EXTRACTB0 { + tag "$meta.id" + label 'process_single' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://scil.usherbrooke.ca/containers/scilus_1.6.0.sif': + 'scilus/scilus:1.6.0' }" + + input: + tuple val(meta), path(dwi), path(bval), path(bvec) + + output: + tuple val(meta), path("*_b0*.nii.gz"), emit: b0 + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def prefix = task.ext.prefix ?: "${meta.id}" + def extraction_strategy = task.ext.b0_extraction_strategy ? "--$task.ext.b0_extraction_strategy" : "--mean" + def b0_threshold = task.ext.b0_threshold ? "--b0_thr $task.ext.b0_threshold" : "" + def output_series = task.ext.output_series ? "" : "--single-image" + """ + export ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS=1 + export OMP_NUM_THREADS=1 + export OPENBLAS_NUM_THREADS=1 + + scil_extract_b0.py $dwi $bval $bvec ${prefix}_b0.nii.gz \ + $output_series $extraction_strategy $b0_threshold --force_b0_threshold + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + scilpy: 1.6.0 + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + scil_extract_b0.py - h + + touch ${prefix}_b0.nii.gz + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + scilpy: 1.6.0 + END_VERSIONS + """ +} diff --git a/modules/nf-scil/utils/extractb0/meta.yml b/modules/nf-scil/utils/extractb0/meta.yml new file mode 100644 index 0000000..8834184 --- /dev/null +++ b/modules/nf-scil/utils/extractb0/meta.yml @@ -0,0 +1,56 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/yaml-schema.json +name: "utils_extractb0" +description: Extract a b0 volume from a DWI image. +keywords: + - dwi + - extract + - b0 +tools: + - "Scilpy": + description: "The Sherbrooke Connectivity Imaging Lab (SCIL) Python dMRI processing toolbox." + homepage: "https://github.com/scilus/scilpy.git" + +input: + # Only when we have meta + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + + - dwi: + type: file + description: Nifti DWI volume to perform BET + crop. + pattern: "*.{nii,nii.gz}" + + - bval: + type: file + description: B-values in FSL format. + pattern: "*.bval" + + - bvec: + type: file + description: B-vectors in FSL format. + pattern: "*.bvec" + +output: + #Only when we have meta + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + + - versions: + type: file + description: File containing software versions + pattern: "versions.yml" + + - b0: + type: file + description: b0 image(s) extracted from the DWI + pattern: "*_b0*.{nii,nii.gz}" + +authors: + - "@AlexVCaron" diff --git a/nextflow.config b/nextflow.config index b52ed35..ec399fb 100644 --- a/nextflow.config +++ b/nextflow.config @@ -1,270 +1,82 @@ -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - nf-core/fibercup Nextflow config file -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Default config options for all compute environments ----------------------------------------------------------------------------------------- -*/ +process { + publishDir = {"./Results_Fiber_Cup/${task.process}"} + scratch = true + errorStrategy = { task.attempt <= 3 ? 'retry' : 'ignore' } + maxRetries = 3 + maxErrors = -1 + stageInMode = 'copy' + stageOutMode = 'rsync' + tag = { "$sid" } + afterScript = 'sleep 1' +} -// Global default params, used in configs params { - // TODO nf-core: Specify your pipeline's command line flags - // Input options - input = null - // References - genome = null - igenomes_base = 's3://ngi-igenomes/igenomes' - igenomes_ignore = false - - - // MultiQC options - multiqc_config = null - multiqc_title = null - multiqc_logo = null - max_multiqc_email_size = '25.MB' - multiqc_methods_description = null - - // Boilerplate options - outdir = null - publish_dir_mode = 'copy' - email = null - email_on_fail = null - plaintext_email = false - monochrome_logs = false - hook_url = null - help = false - version = false - - // Config options - config_profile_name = null - config_profile_description = null - custom_config_version = 'master' - custom_config_base = "https://raw.githubusercontent.com/nf-core/configs/${params.custom_config_version}" - config_profile_contact = null - config_profile_url = null - - - // Max resource options - // Defaults only, expecting to be overwritten - max_memory = '128.GB' - max_cpus = 16 - max_time = '240.h' - - // Schema validation default options - validationFailUnrecognisedParams = false - validationLenientMode = false - validationSchemaIgnoreParams = 'genomes,igenomes_base' - validationShowHiddenParams = false - validate_params = true + //** DENOISING DWI Options **// + extent = 3 + + //** DTI Options **// + ad = false + evecs = false + evals = false + fa = true + ga = false + rgb = false + md = true + mode = false + norm = false + rd = false + tensor = false + nonphysical = false + pulsation = false + residual = false + + //** FRF Options **// + fa=0.7 + min_fa=0.5 + min_nvox=300 + roi_radius=20 + set_frf=true + manual_frf="15,4,4" + + //** FODF Options **// + b0_thr_extract_b0 = 10 + dwi_shell_tolerance = 20 + min_fodf_shell_value = 700 + sh_order = 8 + sh_basis = "descoteaux07" + fa_threshold = 0.1 + md_threshold = 0.003 + relative_threshold = 0.1 + processes = 4 + peaks = false + peak_indices = false + afd_max = false + afd_total = false + afd_sum = false + nufo = false + + //** LOCALTRACKING Options **// + local_compress_streamlines=true + local_algo="prob" + local_seeding="npv" + local_nbr_seeds=10 + local_step=0.5 + local_theta=20 + local_sfthres=0.1 + local_sfthres_init=0.5 + local_min_len=20 + local_max_len=200 + local_compress_value=0.2 + local_random_seed=0 } -// Load base.config by default for all pipelines -includeConfig 'conf/base.config' - -// Load nf-core custom profiles from different Institutions -try { - includeConfig "${params.custom_config_base}/nfcore_custom.config" -} catch (Exception e) { - System.err.println("WARNING: Could not load nf-core/config profiles: ${params.custom_config_base}/nfcore_custom.config") -} -// Load nf-core/fibercup custom profiles from different institutions. -// Warning: Uncomment only if a pipeline-specific instititutional config already exists on nf-core/configs! -// try { -// includeConfig "${params.custom_config_base}/pipeline/fibercup.config" -// } catch (Exception e) { -// System.err.println("WARNING: Could not load nf-core/config/fibercup profiles: ${params.custom_config_base}/pipeline/fibercup.config") -// } profiles { - debug { - dumpHashes = true - process.beforeScript = 'echo $HOSTNAME' - cleanup = false - } - conda { - conda.enabled = true - docker.enabled = false - singularity.enabled = false - podman.enabled = false - shifter.enabled = false - charliecloud.enabled = false - apptainer.enabled = false - } - mamba { - conda.enabled = true - conda.useMamba = true - docker.enabled = false - singularity.enabled = false - podman.enabled = false - shifter.enabled = false - charliecloud.enabled = false - apptainer.enabled = false - } - docker { - docker.enabled = true - docker.userEmulation = true - conda.enabled = false - singularity.enabled = false - podman.enabled = false - shifter.enabled = false - charliecloud.enabled = false - apptainer.enabled = false - } - arm { - docker.runOptions = '-u $(id -u):$(id -g) --platform=linux/amd64' - } - singularity { - singularity.enabled = true - singularity.autoMounts = true - conda.enabled = false - docker.enabled = false - podman.enabled = false - shifter.enabled = false - charliecloud.enabled = false - apptainer.enabled = false - } - podman { - podman.enabled = true - conda.enabled = false - docker.enabled = false - singularity.enabled = false - shifter.enabled = false - charliecloud.enabled = false - apptainer.enabled = false - } - shifter { - shifter.enabled = true - conda.enabled = false - docker.enabled = false - singularity.enabled = false - podman.enabled = false - charliecloud.enabled = false - apptainer.enabled = false - } - charliecloud { - charliecloud.enabled = true - conda.enabled = false - docker.enabled = false - singularity.enabled = false - podman.enabled = false - shifter.enabled = false - apptainer.enabled = false - } - apptainer { - apptainer.enabled = true - apptainer.autoMounts = true - conda.enabled = false - docker.enabled = false - singularity.enabled = false - podman.enabled = false - shifter.enabled = false - charliecloud.enabled = false - } - gitpod { - executor.name = 'local' - executor.cpus = 4 - executor.memory = 8.GB - } - test { includeConfig 'conf/test.config' } - test_full { includeConfig 'conf/test_full.config' } -} - -// Set default registry for Apptainer, Docker, Podman and Singularity independent of -profile -// Will not be used unless Apptainer / Docker / Podman / Singularity are enabled -// Set to your registry if you have a mirror of containers -apptainer.registry = 'quay.io' -docker.registry = 'quay.io' -podman.registry = 'quay.io' -singularity.registry = 'quay.io' - -// Nextflow plugins -plugins { - id 'nf-validation' // Validation of pipeline parameters and creation of an input channel from a sample sheet -} - -// Load igenomes.config if required -if (!params.igenomes_ignore) { - includeConfig 'conf/igenomes.config' -} else { - params.genomes = [:] -} -// Export these variables to prevent local Python/R libraries from conflicting with those in the container -// The JULIA depot path has been adjusted to a fixed path `/usr/local/share/julia` that needs to be used for packages in the container. -// See https://apeltzer.github.io/post/03-julia-lang-nextflow/ for details on that. Once we have a common agreement on where to keep Julia packages, this is adjustable. - -env { - PYTHONNOUSERSITE = 1 - R_PROFILE_USER = "/.Rprofile" - R_ENVIRON_USER = "/.Renviron" - JULIA_DEPOT_PATH = "/usr/local/share/julia" -} - -// Capture exit codes from upstream processes when piping -process.shell = ['/bin/bash', '-euo', 'pipefail'] - -def trace_timestamp = new java.util.Date().format( 'yyyy-MM-dd_HH-mm-ss') -timeline { - enabled = true - file = "${params.outdir}/pipeline_info/execution_timeline_${trace_timestamp}.html" -} -report { - enabled = true - file = "${params.outdir}/pipeline_info/execution_report_${trace_timestamp}.html" -} -trace { - enabled = true - file = "${params.outdir}/pipeline_info/execution_trace_${trace_timestamp}.txt" -} -dag { - enabled = true - file = "${params.outdir}/pipeline_info/pipeline_dag_${trace_timestamp}.html" -} - -manifest { - name = 'nf-core/fibercup' - author = """stan""" - homePage = 'https://github.com/nf-core/fibercup' - description = """test for the new nf-scil pipeline""" - mainScript = 'main.nf' - nextflowVersion = '!>=23.04.0' - version = '1.0dev' - doi = '' -} - -// Load modules.config for DSL2 module specific options -includeConfig 'conf/modules.config' - -// Function to ensure that resource requirements don't go beyond -// a maximum limit -def check_max(obj, type) { - if (type == 'memory') { - try { - if (obj.compareTo(params.max_memory as nextflow.util.MemoryUnit) == 1) - return params.max_memory as nextflow.util.MemoryUnit - else - return obj - } catch (all) { - println " ### ERROR ### Max memory '${params.max_memory}' is not valid! Using default value: $obj" - return obj - } - } else if (type == 'time') { - try { - if (obj.compareTo(params.max_time as nextflow.util.Duration) == 1) - return params.max_time as nextflow.util.Duration - else - return obj - } catch (all) { - println " ### ERROR ### Max time '${params.max_time}' is not valid! Using default value: $obj" - return obj - } - } else if (type == 'cpus') { - try { - return Math.min( obj, params.max_cpus as int ) - } catch (all) { - println " ### ERROR ### Max cpus '${params.max_cpus}' is not valid! Using default value: $obj" - return obj + no_symlink { + process{ + publishDir = [path: {"./Results_Fiber_Cup/${task.process}"}, mode: 'copy'] } } -} +} \ No newline at end of file