From 90a712fb67ff90f5bb52a1b971151ca152b53e6b Mon Sep 17 00:00:00 2001 From: Simon Pearce <24893913+SPPearce@users.noreply.github.com> Date: Wed, 26 Aug 2026 03:51:14 +0000 Subject: [PATCH] Add parabricks/deepsomatic --- .../nf-core/parabricks/deepsomatic/main.nf | 64 ++++++ .../nf-core/parabricks/deepsomatic/meta.yml | 119 +++++++++++ .../parabricks/deepsomatic/tests/main.nf.test | 184 ++++++++++++++++++ .../deepsomatic/tests/nextflow.config | 7 + 4 files changed, 374 insertions(+) create mode 100644 modules/nf-core/parabricks/deepsomatic/main.nf create mode 100644 modules/nf-core/parabricks/deepsomatic/meta.yml create mode 100644 modules/nf-core/parabricks/deepsomatic/tests/main.nf.test create mode 100644 modules/nf-core/parabricks/deepsomatic/tests/nextflow.config diff --git a/modules/nf-core/parabricks/deepsomatic/main.nf b/modules/nf-core/parabricks/deepsomatic/main.nf new file mode 100644 index 000000000000..a5cb0acd42eb --- /dev/null +++ b/modules/nf-core/parabricks/deepsomatic/main.nf @@ -0,0 +1,64 @@ +process PARABRICKS_DEEPSOMATIC { + tag "${meta.id}" + label 'process_high' + label 'process_gpu' + // needed by the module to work properly can be removed when fixed upstream - see: https://github.com/nf-core/modules/issues/7226 + stageInMode 'copy' + + container "nvcr.io/nvidia/clara/clara-parabricks:4.7.1-1" + + input: + tuple val(meta), path(input_tumor), path(index_tumor), path(input_normal), path(index_normal), path(intervals) + tuple val(ref_meta), path(fasta) + + output: + tuple val(meta), path("*.vcf.gz"), emit: vcf, optional: true + tuple val(meta), path("*.g.vcf.gz"), emit: gvcf, optional: true + tuple val(meta), path("*.vcf.gz.tbi"), emit: index, optional: true + path "compatible_versions.yml", emit: compatible_versions, optional: true + tuple val("${task.process}"), val('parabricks'), eval("pbrun version | grep -m1 '^pbrun:' | sed 's/^pbrun:[[:space:]]*//'"), topic: versions, emit: versions_parabricks + + when: + task.ext.when == null || task.ext.when + + script: + // Exit if running this module with -profile conda / -profile mamba + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + exit(1, "Parabricks module does not support Conda. Please use Docker / Singularity / Podman instead.") + } + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def output_file = args.contains("--gvcf") ? "${prefix}.g.vcf.gz" : "${prefix}.vcf.gz" + def interval_command = intervals ? intervals.collect { interval -> "--interval-file ${interval}" }.join(' ') : "" + def num_gpus = task.accelerator ? "--num-gpus ${task.accelerator.request}" : '' + """ + pbrun \\ + deepsomatic \\ + --ref ${fasta} \\ + --in-tumor-bam ${input_tumor} \\ + --in-normal-bam ${input_normal} \\ + --out-variants ${output_file} \\ + ${interval_command} \\ + ${num_gpus} \\ + ${args} + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def output_cmd = args.contains("--gvcf") ? "echo '' | gzip > ${prefix}.g.vcf.gz" : "echo '' | gzip > ${prefix}.vcf.gz" + """ + ${output_cmd} + + # Capture the full version output once and store it in a variable + pbrun_version_output=\$(pbrun deepsomatic --version 2>&1) + + # Generate compatible_versions.yml + cat < compatible_versions.yml + "${task.process}": + pbrun_version: \$(echo "\$pbrun_version_output" | grep "pbrun:" | awk '{print \$2}') + compatible_with: + \$(echo "\$pbrun_version_output" | awk '/Compatible With:/,/^---/{ if (\$1 ~ /^[A-Z]/ && \$1 != "Compatible" && \$1 != "---") { printf " %s: %s\\n", \$1, \$2 } }') + EOF + """ +} diff --git a/modules/nf-core/parabricks/deepsomatic/meta.yml b/modules/nf-core/parabricks/deepsomatic/meta.yml new file mode 100644 index 000000000000..8564b4563944 --- /dev/null +++ b/modules/nf-core/parabricks/deepsomatic/meta.yml @@ -0,0 +1,119 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "parabricks_deepsomatic" +description: NVIDIA Clara Parabricks GPU-accelerated somatic variant calling, replicating + deepsomatic. +keywords: + - variant + - somatic + - vcf + - deepsomatic + - tumor + - normal +tools: + - "parabricks": + description: "NVIDIA Clara Parabricks GPU-accelerated genomics tools" + homepage: https://www.nvidia.com/en-us/clara/genomics/ + documentation: https://docs.nvidia.com/clara/parabricks/latest/index.html + licence: ["custom"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information. + [ id:'tumor_vs_normal'] + - input_tumor: + type: file + description: BAM/CRAM file for tumor sample. + pattern: "*.{bam,cram}" + ontologies: [] + - index_tumor: + type: file + description: BAI/CRAI index corresponding to tumor BAM/CRAM file. + pattern: "*.{bai,crai}" + ontologies: [] + - input_normal: + type: file + description: BAM/CRAM file for normal sample. + pattern: "*.{bam,cram}" + ontologies: [] + - index_normal: + type: file + description: BAI/CRAI index corresponding to normal BAM/CRAM file. + pattern: "*.{bai,crai}" + ontologies: [] + - intervals: + type: file + description: File or files containing genomic intervals for variant calling. + pattern: "*.{bed,interval_list,picard,list,intervals}" + ontologies: [] + - - ref_meta: + type: map + description: | + Groovy Map containing reference information. + [ id:'genome'] + - fasta: + type: file + description: Reference fasta - must be unzipped. + pattern: "*.fasta" + ontologies: [] + +output: + vcf: + - - meta: + type: map + description: | + Groovy Map containing sample information. + e.g. [ id:'tumor_vs_normal' ] + - "*.vcf.gz": + type: file + description: VCF file created with deepsomatic, optional + pattern: "*.vcf.gz" + ontologies: [] + gvcf: + - - meta: + type: map + description: | + Groovy Map containing sample information. + e.g. [ id:'tumor_vs_normal' ] + - "*.g.vcf.gz": + type: file + description: bgzipped gVCF created with deepsomatic, optional + pattern: "*.g.vcf.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + compatible_versions: + - compatible_versions.yml: + type: file + description: File containing info on compatible CPU-based software versions. + pattern: "compatible_versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML + versions_parabricks: + - - ${task.process}: + type: string + description: The process the versions were collected from + - parabricks: + type: string + description: The tool name + - "pbrun version | grep -m1 '^pbrun:' | sed 's/^pbrun:[[:space:]]*//'": + type: string + description: The command used to generate the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - parabricks: + type: string + description: The tool name + - "pbrun version | grep -m1 '^pbrun:' | sed 's/^pbrun:[[:space:]]*//'": + type: string + description: The command used to generate the version of the tool + +authors: + - "@sppearce" +maintainers: + - "@sppearce" diff --git a/modules/nf-core/parabricks/deepsomatic/tests/main.nf.test b/modules/nf-core/parabricks/deepsomatic/tests/main.nf.test new file mode 100644 index 000000000000..e17f05bb8c04 --- /dev/null +++ b/modules/nf-core/parabricks/deepsomatic/tests/main.nf.test @@ -0,0 +1,184 @@ +nextflow_process { + + name "Test Process PARABRICKS_DEEPSOMATIC" + script "../main.nf" + process "PARABRICKS_DEEPSOMATIC" + + tag "modules" + tag "modules_nfcore" + tag "parabricks" + tag "parabricks/deepsomatic" + tag "gpu" + config './nextflow.config' + + test("human - tumor_normal_pair") { + + params { + module_args = '' + } + + when { + process { + """ + input[0] = [ + [ id:'tumor_vs_normal' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.recalibrated.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.recalibrated.sorted.bam.bai', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.recalibrated.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.recalibrated.sorted.bam.bai', checkIfExists: true), + [] + ] + input[1] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out, variantsMD5Keys:["vcf"])).match() } + ) + } + + } + + test("human - tumor_normal_pair - intervals") { + + when { + params { + module_args = '' + } + process { + """ + input[0] = [ + [ id:'tumor_vs_normal' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.recalibrated.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.recalibrated.sorted.bam.bai', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.recalibrated.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.recalibrated.sorted.bam.bai', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/multi_intervals.bed', checkIfExists: true) + ] + input[1] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out, variantsMD5Keys:["vcf"])).match() } + ) + } + + } + + test("human - tumor_normal_pair - gvcf") { + + when { + params { + module_args = '--gvcf' + } + process { + """ + input[0] = [ + [ id:'tumor_vs_normal' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.recalibrated.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.recalibrated.sorted.bam.bai', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.recalibrated.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.recalibrated.sorted.bam.bai', checkIfExists: true), + [] + ] + input[1] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out, variantsMD5Keys:["gvcf"])).match() } + ) + } + + } + + test("human - tumor_normal_pair - stub") { + + options "-stub" + + when { + params { + module_args = '' + } + process { + """ + input[0] = [ + [ id:'tumor_vs_normal' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.recalibrated.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.recalibrated.sorted.bam.bai', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.recalibrated.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.recalibrated.sorted.bam.bai', checkIfExists: true), + [] + ] + input[1] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + + } + + test("human - tumor_normal_pair - gvcf - stub") { + + options "-stub" + + when { + params { + module_args = '--gvcf' + } + process { + """ + input[0] = [ + [ id:'tumor_vs_normal' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.recalibrated.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.recalibrated.sorted.bam.bai', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.recalibrated.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.recalibrated.sorted.bam.bai', checkIfExists: true), + [] + ] + input[1] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + + } + +} diff --git a/modules/nf-core/parabricks/deepsomatic/tests/nextflow.config b/modules/nf-core/parabricks/deepsomatic/tests/nextflow.config new file mode 100644 index 000000000000..29d817b5504d --- /dev/null +++ b/modules/nf-core/parabricks/deepsomatic/tests/nextflow.config @@ -0,0 +1,7 @@ +process { + + withName: 'PARABRICKS_DEEPSOMATIC' { + ext.args = params.module_args + } + +}