diff --git a/modules/nf-core/viralconsensus/environment.yml b/modules/nf-core/viralconsensus/environment.yml new file mode 100644 index 000000000000..390707db5d97 --- /dev/null +++ b/modules/nf-core/viralconsensus/environment.yml @@ -0,0 +1,8 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + # renovate: datasource=conda depName=bioconda/viral_consensus + - bioconda::viral_consensus=1.0.0 diff --git a/modules/nf-core/viralconsensus/main.nf b/modules/nf-core/viralconsensus/main.nf new file mode 100644 index 000000000000..e43ade1ba205 --- /dev/null +++ b/modules/nf-core/viralconsensus/main.nf @@ -0,0 +1,52 @@ +process VIRALCONSENSUS { + tag "$meta.id" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/viral_consensus:1.0.0--hcf1f8c1_0': + 'biocontainers/viral_consensus:1.0.0--hcf1f8c1_0' }" + + input: + tuple val(meta), path(bam) + path fasta + path primer_bed + val save_pos_counts + val save_ins_counts + + output: + tuple val(meta), path("*.consensus.fa"), emit: fasta + tuple val(meta), path("*.pos_counts.tsv"), optional: true, emit: pos_counts + tuple val(meta), path("*.ins_counts.json"), optional: true, emit: ins_counts + tuple val("${task.process}"), val('viralconsensus'), eval("viral_consensus --version 2>&1 | head -n1 | sed 's/ViralConsensus //'"), topic: versions, emit: versions_viralconsensus + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def primer_arg = primer_bed ? "-p ${primer_bed}" : '' + def pos_counts_arg = save_pos_counts ? "-op ${prefix}.pos_counts.tsv" : '' + def ins_counts_arg = save_ins_counts ? "-oi ${prefix}.ins_counts.json" : '' + """ + viral_consensus \\ + -i $bam \\ + -r $fasta \\ + -o ${prefix}.consensus.fa \\ + $primer_arg \\ + $pos_counts_arg \\ + $ins_counts_arg \\ + $args + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + def touch_pos_counts = save_pos_counts ? "touch ${prefix}.pos_counts.tsv" : '' + def touch_ins_counts = save_ins_counts ? "touch ${prefix}.ins_counts.json" : '' + """ + touch ${prefix}.consensus.fa + $touch_pos_counts + $touch_ins_counts + """ +} diff --git a/modules/nf-core/viralconsensus/meta.yml b/modules/nf-core/viralconsensus/meta.yml new file mode 100644 index 000000000000..092740eb7971 --- /dev/null +++ b/modules/nf-core/viralconsensus/meta.yml @@ -0,0 +1,121 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "viralconsensus" +description: Fast and memory-efficient viral consensus genome sequence + generation from read alignments +keywords: + - virus + - genomics + - consensus + - bam + - fasta +tools: + - "viralconsensus": + description: "ViralConsensus is a fast and memory-efficient tool for calling viral + consensus genome sequences directly from read alignment data." + homepage: "https://github.com/niemasd/ViralConsensus" + documentation: "https://github.com/niemasd/ViralConsensus#usage" + tool_dev_url: "https://github.com/niemasd/ViralConsensus" + doi: "10.1093/bioinformatics/btad317" + licence: ["GPL-3.0-or-later"] + identifier: biotools:viralconsensus + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - bam: + type: file + description: BAM/SAM/CRAM file containing aligned reads + pattern: "*.{bam,sam,cram}" + ontologies: + - edam: http://edamontology.org/format_2572 + - fasta: + type: file + description: Reference genome sequence in FASTA format + pattern: "*.{fa,fasta,fna}" + ontologies: + - edam: http://edamontology.org/format_1929 + - primer_bed: + type: file + description: Optional BED file with primer coordinates for trimming + pattern: "*.bed" + ontologies: + - edam: http://edamontology.org/format_3003 + - save_pos_counts: + type: boolean + description: | + Save per-position counts to a TSV file + - save_ins_counts: + type: boolean + description: | + Save insertion counts to a JSON file + +output: + fasta: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.consensus.fa": + type: file + description: Consensus genome sequence in FASTA format + pattern: "*.consensus.fa" + ontologies: + - edam: http://edamontology.org/format_1929 + pos_counts: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.pos_counts.tsv": + type: file + description: Per-position base counts (optional, enabled via + save_pos_counts input) + pattern: "*.pos_counts.tsv" + ontologies: + - edam: http://edamontology.org/format_3475 + ins_counts: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.ins_counts.json": + type: file + description: Insertion counts (optional, enabled via + save_ins_counts input) + pattern: "*.ins_counts.json" + ontologies: + - edam: http://edamontology.org/format_3464 + versions_viralconsensus: + - - ${task.process}: + type: string + description: The name of the process + - viralconsensus: + type: string + description: The name of the tool + - "viral_consensus --version 2>&1 | head -n1 | sed 's/ViralConsensus //'": + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - viralconsensus: + type: string + description: The name of the tool + - "viral_consensus --version 2>&1 | head -n1 | sed 's/ViralConsensus //'": + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@niemasd" +maintainers: + - "@niemasd" + - "@lucaspatel" diff --git a/modules/nf-core/viralconsensus/tests/main.nf.test b/modules/nf-core/viralconsensus/tests/main.nf.test new file mode 100644 index 000000000000..ea4a46743607 --- /dev/null +++ b/modules/nf-core/viralconsensus/tests/main.nf.test @@ -0,0 +1,127 @@ +nextflow_process { + + name "Test Process VIRALCONSENSUS" + script "../main.nf" + process "VIRALCONSENSUS" + + tag "modules" + tag "modules_nfcore" + tag "viralconsensus" + + test("sarscov2 - bam - basic") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) + ] + input[1] = file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + input[2] = [] + input[3] = false + input[4] = false + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.fasta, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() } + ) + } + } + + test("sarscov2 - bam - with primer bed") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) + ] + input[1] = file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + input[2] = file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/bed/test.bed', checkIfExists: true) + input[3] = false + input[4] = false + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.fasta, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() } + ) + } + } + + test("sarscov2 - bam - with optional outputs") { + + config "./nextflow.config" + + when { + params { + module_args = '-q 30 -d 5 -f 0.6' + } + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) + ] + input[1] = file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + input[2] = [] + input[3] = true + input[4] = true + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.fasta, + process.out.pos_counts, + process.out.ins_counts, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() } + ) + } + } + + test("sarscov2 - bam - stub") { + options '-stub' + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) + ] + input[1] = file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + input[2] = [] + input[3] = false + input[4] = false + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } +} diff --git a/modules/nf-core/viralconsensus/tests/main.nf.test.snap b/modules/nf-core/viralconsensus/tests/main.nf.test.snap new file mode 100644 index 000000000000..a114903ecd92 --- /dev/null +++ b/modules/nf-core/viralconsensus/tests/main.nf.test.snap @@ -0,0 +1,149 @@ +{ + "sarscov2 - bam - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.consensus.fa:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + + ], + "2": [ + + ], + "3": [ + [ + "VIRALCONSENSUS", + "viralconsensus", + "viral_consensus v1.0.0" + ] + ], + "fasta": [ + [ + { + "id": "test" + }, + "test.consensus.fa:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "ins_counts": [ + + ], + "pos_counts": [ + + ], + "versions_viralconsensus": [ + [ + "VIRALCONSENSUS", + "viralconsensus", + "viral_consensus v1.0.0" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-24T19:54:20.412415" + }, + "sarscov2 - bam - with primer bed": { + "content": [ + [ + [ + { + "id": "test" + }, + "test.consensus.fa:md5,67220b967a5ebe4827f1ff418fd73654" + ] + ], + { + "versions_viralconsensus": [ + [ + "VIRALCONSENSUS", + "viralconsensus", + "viral_consensus v1.0.0" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-24T19:56:36.892682" + }, + "sarscov2 - bam - basic": { + "content": [ + [ + [ + { + "id": "test" + }, + "test.consensus.fa:md5,c1a43a011177eff31a4afa9fb8612c7b" + ] + ], + { + "versions_viralconsensus": [ + [ + "VIRALCONSENSUS", + "viralconsensus", + "viral_consensus v1.0.0" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-24T19:56:33.226459" + }, + "sarscov2 - bam - with optional outputs": { + "content": [ + [ + [ + { + "id": "test" + }, + "test.consensus.fa:md5,8002717bf92a11fb4cf1bc97089063e3" + ] + ], + [ + [ + { + "id": "test" + }, + "test.pos_counts.tsv:md5,94284ee424c75e13761452b4a18e4ad3" + ] + ], + [ + [ + { + "id": "test" + }, + "test.ins_counts.json:md5,b144d1a3bbb6c56dfc6376a198645d57" + ] + ], + { + "versions_viralconsensus": [ + [ + "VIRALCONSENSUS", + "viralconsensus", + "viral_consensus v1.0.0" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-24T19:56:42.830597" + } +} \ No newline at end of file diff --git a/modules/nf-core/viralconsensus/tests/nextflow.config b/modules/nf-core/viralconsensus/tests/nextflow.config new file mode 100644 index 000000000000..50898f37a1cf --- /dev/null +++ b/modules/nf-core/viralconsensus/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: VIRALCONSENSUS { + ext.args = params.module_args + } +}