diff --git a/subworkflows/nf-core/fastq_align_minimap2/main.nf b/subworkflows/nf-core/fastq_align_minimap2/main.nf new file mode 100644 index 000000000000..66173c55d56a --- /dev/null +++ b/subworkflows/nf-core/fastq_align_minimap2/main.nf @@ -0,0 +1,66 @@ +// +// Optionally concatenate FASTQ files, align with minimap2, index BAM, and run BAM stats. +// + +include { CAT_FASTQ } from '../../../modules/nf-core/cat/fastq/main' +include { MINIMAP2_ALIGN } from '../../../modules/nf-core/minimap2/align/main' +include { BAM_STATS_SAMTOOLS } from '../bam_stats_samtools/main' + +workflow FASTQ_ALIGN_MINIMAP2 { + + take: + ch_reads + // [ val(meta), path(reads) ] + // reads may be one FASTQ or a list of FASTQs. Use meta.single_end = true for ONT. + + ch_fasta_fai + // [ val(meta2), path(fasta), path(fai) ] + // reference can be FASTA or prebuilt minimap2 .mmi + + val_bam_index_extension + // "bai" or "csi" + + val_cigar_bam + // true/false; passed to minimap2/align as long-CIGAR BAM handling + + val_skip_cat + // true: pass input FASTQ(s) directly to minimap2 + // false: concatenate with CAT_FASTQ before minimap2 + + main: + + ch_fasta = ch_fasta_fai.map { meta, fasta, _fai -> [ meta, fasta ] } + + ch_merged_fastq = channel.empty() + + if (val_skip_cat) { + ch_reads_for_alignment = ch_reads + } else { + CAT_FASTQ(ch_reads) + ch_reads_for_alignment = CAT_FASTQ.out.reads + ch_merged_fastq = CAT_FASTQ.out.reads + } + + MINIMAP2_ALIGN( + ch_reads_for_alignment, + ch_fasta, + true, + val_bam_index_extension, + false, + val_cigar_bam + ) + + MINIMAP2_ALIGN.out.bam + .join(MINIMAP2_ALIGN.out.index, by: 0, failOnDuplicate: true, failOnMismatch: true) + .set { ch_bam_bai } + + BAM_STATS_SAMTOOLS(ch_bam_bai, ch_fasta_fai) + + emit: + merged_fastq = ch_merged_fastq + bam = MINIMAP2_ALIGN.out.bam + index = MINIMAP2_ALIGN.out.index + stats = BAM_STATS_SAMTOOLS.out.stats + flagstat = BAM_STATS_SAMTOOLS.out.flagstat + idxstats = BAM_STATS_SAMTOOLS.out.idxstats +} diff --git a/subworkflows/nf-core/fastq_align_minimap2/meta.yml b/subworkflows/nf-core/fastq_align_minimap2/meta.yml new file mode 100644 index 000000000000..b8cf146e5b0b --- /dev/null +++ b/subworkflows/nf-core/fastq_align_minimap2/meta.yml @@ -0,0 +1,66 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: fastq_align_minimap2 +description: Optionally concatenate FASTQ files, align reads with minimap2, and run BAM statistics +keywords: + - fastq + - align + - minimap2 + - bam + - long-read +components: + - cat/fastq + - minimap2/align + - bam_stats_samtools + - samtools/stats + - samtools/flagstat + - samtools/idxstats +input: + - ch_reads: + description: | + FASTQ input channel. + Structure: [ val(meta), path(reads) ] + reads may be one FASTQ file or a list of FASTQ files. + - ch_fasta_fai: + description: | + Reference FASTA and FASTA index. + Structure: [ val(meta2), path(fasta), path(fai) ] + - val_bam_index_extension: + description: | + BAM index extension created by minimap2/align. + Typical values are "bai" or "csi". + - val_cigar_bam: + description: | + Whether to use minimap2 long-CIGAR BAM handling. + - val_skip_cat: + description: | + If true, pass input FASTQ files directly to minimap2. + If false, concatenate FASTQ files with cat/fastq before alignment. +output: + - merged_fastq: + description: | + Concatenated FASTQ file, emitted only when val_skip_cat is false. + Structure: [ val(meta), path(fastq) ] + - bam: + description: | + Coordinate-sorted BAM file from minimap2/align. + Structure: [ val(meta), path(bam) ] + - index: + description: | + BAM index file from minimap2/align. + Structure: [ val(meta), path(index) ] + - stats: + description: | + Samtools stats output from bam_stats_samtools. + Structure: [ val(meta), path(stats) ] + - flagstat: + description: | + Samtools flagstat output from bam_stats_samtools. + Structure: [ val(meta), path(flagstat) ] + - idxstats: + description: | + Samtools idxstats output from bam_stats_samtools. + Structure: [ val(meta), path(idxstats) ] +authors: + - "@manascripts" +maintainers: + - "@manascripts" diff --git a/subworkflows/nf-core/fastq_align_minimap2/tests/main.nf.test b/subworkflows/nf-core/fastq_align_minimap2/tests/main.nf.test new file mode 100644 index 000000000000..67fd782f8c76 --- /dev/null +++ b/subworkflows/nf-core/fastq_align_minimap2/tests/main.nf.test @@ -0,0 +1,122 @@ +nextflow_workflow { + + name "Test Workflow FASTQ_ALIGN_MINIMAP2" + script "../main.nf" + workflow "FASTQ_ALIGN_MINIMAP2" + config "./nextflow.config" + + tag "subworkflows" + tag "subworkflows_nfcore" + tag "subworkflows/fastq_align_minimap2" + tag "cat/fastq" + tag "minimap2/align" + tag "subworkflows/bam_stats_samtools" + + test("nanopore fastq with concatenation") { + when { + workflow { + """ + input[0] = [ + [ id:'test', single_end:true ], + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/nanopore/fastq/test.fastq.gz', checkIfExists:true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/nanopore/fastq/test_2.fastq.gz', checkIfExists:true) + ] + ] + + input[1] = Channel.value([ + [ id:'sarscov2' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists:true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.fai', checkIfExists:true) + ]) + + input[2] = 'bai' + input[3] = true + input[4] = false + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert file(workflow.out.merged_fastq[0][1]).exists() }, + { assert file(workflow.out.bam[0][1]).exists() }, + { assert file(workflow.out.index[0][1]).exists() }, + { assert snapshot( + workflow.out.stats, + workflow.out.flagstat, + workflow.out.idxstats + ).match() } + ) + } + } + + test("nanopore fastq without concatenation") { + when { + workflow { + """ + input[0] = [ + [ id:'test', single_end:true ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/nanopore/fastq/test.fastq.gz', checkIfExists:true) + ] + + input[1] = Channel.value([ + [ id:'sarscov2' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists:true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.fai', checkIfExists:true) + ]) + + input[2] = 'bai' + input[3] = true + input[4] = true + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert file(workflow.out.bam[0][1]).exists() }, + { assert file(workflow.out.index[0][1]).exists() }, + { assert snapshot( + workflow.out.stats, + workflow.out.flagstat, + workflow.out.idxstats + ).match() } + ) + } + } + + test("nanopore fastq without concatenation - stub") { + options "-stub" + + when { + workflow { + """ + input[0] = [ + [ id:'test', single_end:true ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/nanopore/fastq/test.fastq.gz', checkIfExists:true) + ] + + input[1] = Channel.value([ + [ id:'sarscov2' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists:true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.fai', checkIfExists:true) + ]) + + input[2] = 'bai' + input[3] = true + input[4] = true + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot(workflow.out).match() } + ) + } + } +} diff --git a/subworkflows/nf-core/fastq_align_minimap2/tests/main.nf.test.snap b/subworkflows/nf-core/fastq_align_minimap2/tests/main.nf.test.snap new file mode 100644 index 000000000000..56d6a14fd9fa --- /dev/null +++ b/subworkflows/nf-core/fastq_align_minimap2/tests/main.nf.test.snap @@ -0,0 +1,181 @@ +{ + "nanopore fastq without concatenation": { + "content": [ + [ + [ + { + "id": "test", + "single_end": true + }, + "test.stats:md5,508d91023f4d64f86d3274398ba6b719" + ] + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.flagstat:md5,ba966c6410eccf2375c3bcc081ef4787" + ] + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.idxstats:md5,7c8e67206e2a55b23091c47122a64218" + ] + ] + ], + "timestamp": "2026-07-03T14:15:53.754010425", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.4" + } + }, + "nanopore fastq with concatenation": { + "content": [ + [ + [ + { + "id": "test", + "single_end": true + }, + "test.stats:md5,8216af0618d438f67aa5c82ab7c2fb8a" + ] + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.flagstat:md5,dc0c491264218cd55f9778ea833010c8" + ] + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.idxstats:md5,3d8ebd6404f1ace85bcf2ee371be1fd7" + ] + ] + ], + "timestamp": "2026-07-03T14:15:45.966918942", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.4" + } + }, + "nanopore fastq without concatenation - stub": { + "content": [ + { + "0": [ + + ], + "1": [ + [ + { + "id": "test", + "single_end": true + }, + "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test", + "single_end": true + }, + "test.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + [ + { + "id": "test", + "single_end": true + }, + "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + [ + { + "id": "test", + "single_end": true + }, + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" + ] + ], + "5": [ + [ + { + "id": "test", + "single_end": true + }, + "test.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "bam": [ + [ + { + "id": "test", + "single_end": true + }, + "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "flagstat": [ + [ + { + "id": "test", + "single_end": true + }, + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" + ] + ], + "idxstats": [ + [ + { + "id": "test", + "single_end": true + }, + "test.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "index": [ + [ + { + "id": "test", + "single_end": true + }, + "test.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "merged_fastq": [ + + ], + "stats": [ + [ + { + "id": "test", + "single_end": true + }, + "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "timestamp": "2026-07-03T14:16:02.051124138", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.4" + } + } +} \ No newline at end of file diff --git a/subworkflows/nf-core/fastq_align_minimap2/tests/nextflow.config b/subworkflows/nf-core/fastq_align_minimap2/tests/nextflow.config new file mode 100644 index 000000000000..ee870a222d8b --- /dev/null +++ b/subworkflows/nf-core/fastq_align_minimap2/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: MINIMAP2_ALIGN { + ext.args = '-x map-ont -Y --MD' + } +}