diff --git a/conf/modules.config b/conf/modules.config index 9da06635..5cbc5b31 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -249,10 +249,23 @@ process { } } + withName: '.*FGUMI_SORT' { + ext.prefix = { "${meta.id}.fgumi.unmapped.sorted" } + ext.args = { + [ + "--order queryname", + "--compression-level 0", + "--max-memory ${task.memory.toGiga() / task.cpus}G", + "--tmp-dir .", + "--key-types mi" + ].join(" ").trim() + } + } + //// FGUMI fastq | SNAP | zipper (step 3a) withName: RAW_FGUMI_SNAPZIPSORT { ext.prefix = { "${meta.id}.fgumi" } - ext.args2 = { + ext.args = { [ "-b-", "-sm 20", @@ -264,10 +277,21 @@ process { meta.readgroup ? "-R \"@RG\\t" + meta.readgroup.findResults { rg -> rg.value?.trim() ? "${rg.key}:${rg.value}" : null }.join("\\t") + "\"" : "", ].join(" ").trim() } + ext.args2 = { + [ + "--order queryname", + "--compression-level 0", + "--max-memory ${task.memory.toGiga() / task.cpus}G", + "--tmp-dir .", + "--key-types mi" + ].join(" ").trim() + } ext.args4 = { [ "--order template-coordinate", - "--max-memory ${task.memory.toGiga()}G", + "--max-memory ${task.memory.toGiga() / task.cpus}G", + "--key-types mi", + "--tmp-dir .", ].join(" ").trim() } } @@ -275,24 +299,30 @@ process { //// FGUMI group (step 4) withName: '.*FGUMI_GROUP' { ext.prefix = { "${meta.id}.fgumi.group" } - ext.args = { "--edits ${meta.fgumi_group_edits != null ? meta.fgumi_group_edits : 1}" } + ext.args = { + [ + "--edits ${meta.fgumi_group_edits != null ? meta.fgumi_group_edits : 1}" , + "--max-memory ${task.memory.toGiga() / task.cpus}G" + ].join(" ").trim() + } } //// FGUMI simplex (step 5) withName: '.*FGUMI_SIMPLEX' { ext.prefix = { "${meta.id}.fgumi.simplex" } - ext.args = { "--max-memory ${task.memory.toGiga()}G" } + ext.args = { "--max-memory ${task.memory.toGiga() / task.cpus}G" } } //// FGUMI filter + coordinate sort/index (step 7) withName: '.*FGUMI_FILTER' { ext.prefix = { "${meta.id}.fgumi.filter" } + ext.args = { "--max-memory ${task.memory.toGiga() / task.cpus}G" } } //// FGUMI consensus alignment (step 8) withName: UMI_FGUMI_SNAPZIPSORT { ext.prefix = { "${meta.id}.fgumi" } - ext.args2 = { + ext.args = { [ "-b-", "-sm 20", @@ -304,10 +334,19 @@ process { meta.readgroup ? "-R \"@RG\\t" + meta.readgroup.findResults { rg -> rg.value?.trim() ? "${rg.key}:${rg.value}" : null }.join("\\t") + "\"" : "", ].join(" ").trim() } + ext.args2 = { + [ + "--order queryname", + "--compression-level 0", + "--max-memory ${task.memory.toGiga() / task.cpus}G", + "--tmp-dir .", + "--key-types mi" + ].join(" ").trim() + } ext.args4 = { [ "--order coordinate", - "--max-memory ${task.memory.toGiga()}G", + "--max-memory ${task.memory.toGiga() / task.cpus}G", "--write-index", ].join(" ").trim() } diff --git a/modules.json b/modules.json index ccf88121..5bbcd04d 100644 --- a/modules.json +++ b/modules.json @@ -77,6 +77,11 @@ "installed_by": ["modules"], "patch": "modules/nf-core/fgumi/simplex/fgumi-simplex.diff" }, + "fgumi/sort": { + "branch": "master", + "git_sha": "c3e04783b2ac99ffc011745ec092614a7bef86ca", + "installed_by": ["modules"] + }, "gnu/sort": { "branch": "master", "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", diff --git a/modules/local/fgumi/snapzipsort/main.nf b/modules/local/fgumi/snapzipsort/main.nf index 88c34893..9139df7a 100644 --- a/modules/local/fgumi/snapzipsort/main.nf +++ b/modules/local/fgumi/snapzipsort/main.nf @@ -32,16 +32,20 @@ process FGUMI_SNAPZIPSORT { [ -z "\$INDEX_FILE" ] && echo "Snap index files not found" 1>&2 && exit 1 INDEX=\$(dirname "\$INDEX_FILE") - fgumi fastq \\ - --input ${unmapped_bam} \\ - ${args} \\ - | snap-aligner paired \\ + snap-aligner paired \\ \$INDEX \\ - -pairedInterleavedFastq - \\ + ${unmapped_bam} \\ -t ${task.cpus} \\ -o -bam - \\ - ${args2} \\ - | fgumi zipper \\ + ${args} \\ + | fgumi sort \\ + --input - \\ + --output ${prefix}.intermediate.bam \\ + --threads ${task.cpus} \\ + ${args2} + + fgumi zipper \\ + --input ${prefix}.intermediate.bam \\ --unmapped ${unmapped_bam} \\ --reference ${fasta} \\ --threads ${task.cpus} \\ diff --git a/modules/nf-core/fgumi/sort/environment.yml b/modules/nf-core/fgumi/sort/environment.yml new file mode 100644 index 00000000..68aafa2b --- /dev/null +++ b/modules/nf-core/fgumi/sort/environment.yml @@ -0,0 +1,7 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - "bioconda::fgumi=0.4.0" diff --git a/modules/nf-core/fgumi/sort/main.nf b/modules/nf-core/fgumi/sort/main.nf new file mode 100644 index 00000000..a3262e5d --- /dev/null +++ b/modules/nf-core/fgumi/sort/main.nf @@ -0,0 +1,45 @@ +process FGUMI_SORT { + tag "${meta.id}" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/4a/4a62b457c53300603da026225f95b4db04d1c9f8ba7f734787818fc105d51323/data' + : 'community.wave.seqera.io/library/fgumi:0.4.0--1fb5dc6de05ce63b'}" + + input: + tuple val(meta), path(bam) + + output: + tuple val(meta), path("*.bam"), emit: bam + tuple val(meta), path("*.{csi,bai}"), emit: index, optional: true + tuple val("${task.process}"), val('fgumi'), eval('fgumi --version | sed "s/^fgumi //"'), topic: versions, emit: versions_fgumi + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}_sorted" + + if ("${bam}" == "${prefix}.bam") { + error("Input and output names are the same, use \"task.ext.prefix\" to disambiguate!") + } + + """ + fgumi sort \\ + --input ${bam} \\ + --output ${prefix}.bam \\ + --threads ${task.cpus} \\ + ${args} + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}_sorted" + if ("${bam}" == "${prefix}.bam") { + error("Input and output names are the same, use \"task.ext.prefix\" to disambiguate!") + } + """ + touch ${prefix}.bam + """ +} diff --git a/modules/nf-core/fgumi/sort/meta.yml b/modules/nf-core/fgumi/sort/meta.yml new file mode 100644 index 00000000..1dc25228 --- /dev/null +++ b/modules/nf-core/fgumi/sort/meta.yml @@ -0,0 +1,82 @@ +name: "fgumi_sort" +description: | + Sorts a SAM or BAM file. Several sort orders are available, including coordinate, + queryname, and template-coordinate. This is a high-performance replacement for fgbio SortBam. +keywords: + - sort + - bam + - sam +tools: + - "fgumi": + description: "High-performance tools for working with UMI-tagged sequencing data." + homepage: "https://github.com/fulcrumgenomics/fgumi" + documentation: "https://docs.rs/fgumi" + tool_dev_url: "https://github.com/fulcrumgenomics/fgumi" + licence: + - "MIT" + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - bam: + type: file + description: | + The input SAM or BAM file to be sorted. + pattern: "*.{bam,sam}" + ontologies: + - edam: "http://edamontology.org/format_2572" +output: + bam: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.bam": + type: file + description: | + Sorted output BAM file. + pattern: "*.bam" + ontologies: + - edam: "http://edamontology.org/format_2572" + index: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.{csi,bai}": + type: file + description: | + Index file if the bam file is coordinate sorted. + pattern: "*.{csi,bai}" + ontologies: + - edam: "http://edamontology.org/format_3327" + versions_fgumi: + - - ${task.process}: + type: string + description: The process the versions were collected from + - fgumi: + type: string + description: The tool name + - 'fgumi --version | sed "s/^fgumi //"': + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - fgumi: + type: string + description: The tool name + - 'fgumi --version | sed "s/^fgumi //"': + type: eval + description: The expression to obtain the version of the tool +authors: + - "@sppearce" +maintainers: + - "@sppearce" diff --git a/modules/nf-core/fgumi/sort/tests/main.nf.test b/modules/nf-core/fgumi/sort/tests/main.nf.test new file mode 100644 index 00000000..fc549bf5 --- /dev/null +++ b/modules/nf-core/fgumi/sort/tests/main.nf.test @@ -0,0 +1,89 @@ +nextflow_process { + + name "Test Process FGUMI_SORT" + script "../main.nf" + process "FGUMI_SORT" + + tag "modules" + tag "modules_nfcore" + tag "fgumi" + tag "fgumi/sort" + + test("sarscov2 - bam") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot( + bam(process.out.bam.get(0).get(1)).getReadsMD5(), + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + + } + + test("sarscov2 - bam - template-coordinate") { + + when { + params { + module_args = '--order template-coordinate' + } + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot( + bam(process.out.bam.get(0).get(1)).getReadsMD5(), + 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), + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/nf-core/fgumi/sort/tests/main.nf.test.snap b/modules/nf-core/fgumi/sort/tests/main.nf.test.snap new file mode 100644 index 00000000..ad6dfc3b --- /dev/null +++ b/modules/nf-core/fgumi/sort/tests/main.nf.test.snap @@ -0,0 +1,87 @@ +{ + "sarscov2 - bam - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_sorted.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + + ], + "2": [ + [ + "FGUMI_SORT", + "fgumi", + "0.4.0" + ] + ], + "bam": [ + [ + { + "id": "test" + }, + "test_sorted.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "index": [ + + ], + "versions_fgumi": [ + [ + "FGUMI_SORT", + "fgumi", + "0.4.0" + ] + ] + } + ], + "timestamp": "2026-06-26T13:03:56.414869", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.4" + } + }, + "sarscov2 - bam": { + "content": [ + "461d8083b03a321eb1902ad544fd7d2f", + { + "versions_fgumi": [ + [ + "FGUMI_SORT", + "fgumi", + "0.4.0" + ] + ] + } + ], + "timestamp": "2026-06-26T13:03:43.67993", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.4" + } + }, + "sarscov2 - bam - template-coordinate": { + "content": [ + "461d8083b03a321eb1902ad544fd7d2f", + { + "versions_fgumi": [ + [ + "FGUMI_SORT", + "fgumi", + "0.4.0" + ] + ] + } + ], + "timestamp": "2026-06-26T13:03:47.645547", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.4" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/fgumi/sort/tests/nextflow.config b/modules/nf-core/fgumi/sort/tests/nextflow.config new file mode 100644 index 00000000..4ad67e9b --- /dev/null +++ b/modules/nf-core/fgumi/sort/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: FGUMI_SORT { + ext.args = { params.module_args } + } +} diff --git a/subworkflows/local/fastq_umiconsensus_fgumi/main.nf b/subworkflows/local/fastq_umiconsensus_fgumi/main.nf index e2416f9b..2353e259 100644 --- a/subworkflows/local/fastq_umiconsensus_fgumi/main.nf +++ b/subworkflows/local/fastq_umiconsensus_fgumi/main.nf @@ -2,6 +2,7 @@ // MODULES include { FGUMI_EXTRACT } from "../../../modules/nf-core/fgumi/extract/main.nf" +include { FGUMI_SORT } from "../../../modules/nf-core/fgumi/sort/main.nf" include { FGUMI_FILTER } from "../../../modules/nf-core/fgumi/filter/main.nf" include { FGUMI_GROUP } from "../../../modules/nf-core/fgumi/group/main.nf" include { FGUMI_MERGE } from "../../../modules/nf-core/fgumi/merge/main.nf" @@ -25,9 +26,13 @@ workflow FASTQ_UMICONSENSUS_FGUMI { .map { meta, fastqs -> [meta, fastqs, (meta.readgroup?.LB ?: meta.library ?: meta.id)] } ) + FGUMI_SORT( + FGUMI_EXTRACT.out.bam + ) + // Step 3: align with SNAP, zipper tags back, then template-coordinate sort. RAW_FGUMI_SNAPZIPSORT( - FGUMI_EXTRACT.out.bam + FGUMI_SORT.out.bam .join(ch_meta_fastqs) .map { meta, ubams, _fastqs -> [ @@ -84,7 +89,7 @@ workflow FASTQ_UMICONSENSUS_FGUMI { // Step 7: filter consensus reads, then coordinate-sort/index for downstream CRAM conversion. FGUMI_FILTER( - FGUMI_SIMPLEX.out.bam.join(ch_meta_fastqs).map { meta, simplex_bams, _fastqs -> + FGUMI_SIMPLEX.out.bam.map { meta, simplex_bams -> [meta, simplex_bams, getGenomeAttribute(meta.genome_data, 'fasta')] }, '1,1,1', @@ -93,8 +98,7 @@ workflow FASTQ_UMICONSENSUS_FGUMI { UMI_FGUMI_SNAPZIPSORT( FGUMI_FILTER.out.bam - .join(ch_meta_fastqs) - .map { meta, filtered_bams, _fastqs -> + .map { meta, filtered_bams -> [ meta, filtered_bams, diff --git a/tests/modules/local/fgumi/snapzipsort/main.nf.test b/tests/modules/local/fgumi/snapzipsort/main.nf.test index 5c2c404b..f9aa8784 100644 --- a/tests/modules/local/fgumi/snapzipsort/main.nf.test +++ b/tests/modules/local/fgumi/snapzipsort/main.nf.test @@ -3,6 +3,7 @@ nextflow_process { name "Test Process FGUMI_SNAPZIPSORT" script "modules/local/fgumi/snapzipsort/main.nf" process "FGUMI_SNAPZIPSORT" + config "./nextflow.config" tag "modules" tag "modules/local" diff --git a/tests/modules/local/fgumi/snapzipsort/main.nf.test.snap b/tests/modules/local/fgumi/snapzipsort/main.nf.test.snap index bea6ad48..f3187dd5 100644 --- a/tests/modules/local/fgumi/snapzipsort/main.nf.test.snap +++ b/tests/modules/local/fgumi/snapzipsort/main.nf.test.snap @@ -11,7 +11,7 @@ "id": "test", "single_end": false }, - "test.fgumi.bam:md5,6bab0c75514fda4b4e7efbbae5e33cc8" + "test.fgumi.bam:md5,9bfbd21ebea8ca63427cec8dd13e6063" ] ] }, @@ -30,7 +30,7 @@ ] } ], - "timestamp": "2026-07-06T13:16:43.167620236", + "timestamp": "2026-07-15T13:19:00.257043127", "meta": { "nf-test": "0.9.5", "nextflow": "26.04.4" diff --git a/tests/modules/local/fgumi/snapzipsort/nextflow.config b/tests/modules/local/fgumi/snapzipsort/nextflow.config new file mode 100644 index 00000000..74ecf260 --- /dev/null +++ b/tests/modules/local/fgumi/snapzipsort/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: 'FGUMI_SNAPZIPSORT' { + ext.args3 = "--order template-coordinate --key-types mi" + } +} diff --git a/tests/subworkflows/local/fastq_to_aligned_cram/main.nf.test.snap b/tests/subworkflows/local/fastq_to_aligned_cram/main.nf.test.snap index fa0f4ff7..844972d2 100644 --- a/tests/subworkflows/local/fastq_to_aligned_cram/main.nf.test.snap +++ b/tests/subworkflows/local/fastq_to_aligned_cram/main.nf.test.snap @@ -3,7 +3,7 @@ "content": [ { "align_reports": [ - + ], "cram_crai": [ [ @@ -26,16 +26,16 @@ ] ], "family_size_histogram": [ - + ], "rna_junctions": [ - + ], "rna_splice_junctions": [ - + ], "sormadup_metrics": [ - + ] } ], @@ -49,7 +49,7 @@ "content": [ { "align_reports": [ - + ], "cram_crai": [ [ @@ -72,13 +72,13 @@ ] ], "family_size_histogram": [ - + ], "rna_junctions": [ - + ], "rna_splice_junctions": [ - + ], "sormadup_metrics": [ [ @@ -111,7 +111,7 @@ "content": [ { "align_reports": [ - + ], "cram_crai": [ [ @@ -134,13 +134,13 @@ ] ], "family_size_histogram": [ - + ], "rna_junctions": [ - + ], "rna_splice_junctions": [ - + ], "sormadup_metrics": [ [ @@ -173,7 +173,7 @@ "content": [ { "align_reports": [ - + ], "cram_crai": [ [ @@ -197,7 +197,7 @@ ] ], "family_size_histogram": [ - + ], "rna_junctions": [ [ @@ -271,7 +271,7 @@ "content": [ { "align_reports": [ - + ], "cram_crai": [ [ @@ -294,16 +294,16 @@ ] ], "family_size_histogram": [ - + ], "rna_junctions": [ - + ], "rna_splice_junctions": [ - + ], "sormadup_metrics": [ - + ] } ], @@ -317,7 +317,7 @@ "content": [ { "align_reports": [ - + ], "cram_crai": [ [ @@ -365,24 +365,24 @@ } } }, - "test.fgumi.group.family_size_histogram.txt:md5,57742826c7e781f773840f1c977f5f0b" + "test.fgumi.group.family_size_histogram.txt:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], "rna_junctions": [ - + ], "rna_splice_junctions": [ - + ], "sormadup_metrics": [ - + ] } ], - "timestamp": "2026-07-06T13:26:52.23745498", + "timestamp": "2026-07-15T16:14:27.2930013", "meta": { "nf-test": "0.9.5", "nextflow": "26.04.4" } } -} +} \ No newline at end of file diff --git a/tests/subworkflows/local/fastq_umiconsensus_fgumi/main.nf.test b/tests/subworkflows/local/fastq_umiconsensus_fgumi/main.nf.test index b1b4d573..63dbe8b3 100644 --- a/tests/subworkflows/local/fastq_umiconsensus_fgumi/main.nf.test +++ b/tests/subworkflows/local/fastq_umiconsensus_fgumi/main.nf.test @@ -3,6 +3,7 @@ nextflow_workflow { name "Test Workflow FASTQ_UMICONSENSUS_FGUMI" script "subworkflows/local/fastq_umiconsensus_fgumi/main.nf" workflow "FASTQ_UMICONSENSUS_FGUMI" + config "./nextflow.config" tag "subworkflows" tag "subworkflows/local" diff --git a/tests/subworkflows/local/fastq_umiconsensus_fgumi/main.nf.test.snap b/tests/subworkflows/local/fastq_umiconsensus_fgumi/main.nf.test.snap index 68e5d648..27975a12 100644 --- a/tests/subworkflows/local/fastq_umiconsensus_fgumi/main.nf.test.snap +++ b/tests/subworkflows/local/fastq_umiconsensus_fgumi/main.nf.test.snap @@ -22,8 +22,8 @@ } } }, - "test.fgumi.bam:md5,d8847959ea89503e93669d3d5526813c", - "test.fgumi.bam.bai:md5,6b054c31ca1e0c5cdf99d6fdc6229654" + "test.fgumi.bam:md5,c4a33382a1fb8a6361d23928359d672f", + "test.fgumi.bam.bai:md5,0d76977b2e36046cc176112776c5fa4e" ] ], "consensus_metrics": [ @@ -46,7 +46,7 @@ } } }, - "test.fgumi.simplex.stats.txt:md5,c259ad6db7c02de9e0f1b9f95a3d6ab0" + "test.fgumi.simplex.stats.txt:md5,8729c0587ff37d0e71b466c694bc62e4" ] ], "family_size_histogram": [ @@ -69,7 +69,7 @@ } } }, - "test.fgumi.group.family_size_histogram.txt:md5,57742826c7e781f773840f1c977f5f0b" + "test.fgumi.group.family_size_histogram.txt:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], "filtering_metrics": [ @@ -92,7 +92,7 @@ } } }, - "test.fgumi.filter.stats.txt:md5,01b1b32c9151552a623eef933b8d1f96" + "test.fgumi.filter.stats.txt:md5,88b8f839f5e05e7944504f526949763c" ] ], "grouping_metrics": [ @@ -115,12 +115,12 @@ } } }, - "test.fgumi.group.grouping_metrics.txt:md5,deb6be58b11022b67145fe4e2c7ca299" + "test.fgumi.group.grouping_metrics.txt:md5,b177c7de45cf25495c61f7bced9d8416" ] ] } ], - "timestamp": "2026-07-06T13:29:57.554096841", + "timestamp": "2026-07-15T15:59:40.968881197", "meta": { "nf-test": "0.9.5", "nextflow": "26.04.4" diff --git a/tests/subworkflows/local/fastq_umiconsensus_fgumi/nextflow.config b/tests/subworkflows/local/fastq_umiconsensus_fgumi/nextflow.config new file mode 100644 index 00000000..74ecf260 --- /dev/null +++ b/tests/subworkflows/local/fastq_umiconsensus_fgumi/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: 'FGUMI_SNAPZIPSORT' { + ext.args3 = "--order template-coordinate --key-types mi" + } +}