From d909266df591596584629b899b7a5ce011d0362a Mon Sep 17 00:00:00 2001 From: egreenberg7 Date: Thu, 23 Oct 2025 20:22:24 -0400 Subject: [PATCH 01/12] Add Sylph modules --- modules.json | 10 ++ modules/nf-core/sylph/profile/environment.yml | 7 ++ modules/nf-core/sylph/profile/main.nf | 51 +++++++++++ modules/nf-core/sylph/profile/meta.yml | 59 ++++++++++++ .../nf-core/sylph/profile/tests/main.nf.test | 80 ++++++++++++++++ .../sylph/profile/tests/main.nf.test.snap | 61 +++++++++++++ .../nf-core/sylphtax/taxprof/environment.yml | 7 ++ modules/nf-core/sylphtax/taxprof/main.nf | 53 +++++++++++ modules/nf-core/sylphtax/taxprof/meta.yml | 57 ++++++++++++ .../sylphtax/taxprof/tests/main.nf.test | 91 +++++++++++++++++++ .../sylphtax/taxprof/tests/main.nf.test.snap | 43 +++++++++ workflows/rnaseq/main.nf | 2 + 12 files changed, 521 insertions(+) create mode 100644 modules/nf-core/sylph/profile/environment.yml create mode 100644 modules/nf-core/sylph/profile/main.nf create mode 100644 modules/nf-core/sylph/profile/meta.yml create mode 100644 modules/nf-core/sylph/profile/tests/main.nf.test create mode 100644 modules/nf-core/sylph/profile/tests/main.nf.test.snap create mode 100644 modules/nf-core/sylphtax/taxprof/environment.yml create mode 100644 modules/nf-core/sylphtax/taxprof/main.nf create mode 100644 modules/nf-core/sylphtax/taxprof/meta.yml create mode 100644 modules/nf-core/sylphtax/taxprof/tests/main.nf.test create mode 100644 modules/nf-core/sylphtax/taxprof/tests/main.nf.test.snap diff --git a/modules.json b/modules.json index aa20e9320..07c7b56e2 100644 --- a/modules.json +++ b/modules.json @@ -262,6 +262,16 @@ "git_sha": "1f008221e451e7a4738226c49e69aaa2eb731369", "installed_by": ["modules", "quantify_pseudo_alignment"] }, + "sylph/profile": { + "branch": "master", + "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", + "installed_by": ["modules"] + }, + "sylphtax/taxprof": { + "branch": "master", + "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", + "installed_by": ["modules"] + }, "trimgalore": { "branch": "master", "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", diff --git a/modules/nf-core/sylph/profile/environment.yml b/modules/nf-core/sylph/profile/environment.yml new file mode 100644 index 000000000..ae8337cc8 --- /dev/null +++ b/modules/nf-core/sylph/profile/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::sylph=0.7.0 diff --git a/modules/nf-core/sylph/profile/main.nf b/modules/nf-core/sylph/profile/main.nf new file mode 100644 index 000000000..1231bab39 --- /dev/null +++ b/modules/nf-core/sylph/profile/main.nf @@ -0,0 +1,51 @@ +process SYLPH_PROFILE { + tag "$meta.id" + label 'process_high' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/sylph:0.7.0--h919a2d8_0' : + 'biocontainers/sylph:0.7.0--h919a2d8_0' }" + + input: + tuple val(meta), path(reads) + path(database) + + output: + tuple val(meta), path('*.tsv'), emit: profile_out + 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 input = meta.single_end ? "${reads}" : "-1 ${reads[0]} -2 ${reads[1]}" + """ + sylph profile \\ + -t $task.cpus \\ + $args \\ + $database\\ + $input \\ + -o ${prefix}.tsv + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + sylph: \$(sylph -V | awk '{print \$2}') + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def input = meta.single_end ? "${reads}" : "-1 ${reads[0]} -2 ${reads[1]}" + """ + touch ${prefix}.tsv + cat <<-END_VERSIONS > versions.yml + "${task.process}": + sylph: \$(sylph -V | awk '{print \$2}') + END_VERSIONS + """ + +} diff --git a/modules/nf-core/sylph/profile/meta.yml b/modules/nf-core/sylph/profile/meta.yml new file mode 100644 index 000000000..c78b0f33c --- /dev/null +++ b/modules/nf-core/sylph/profile/meta.yml @@ -0,0 +1,59 @@ +name: "sylph_profile" +description: Sylph profile command for taxonoming profiling +keywords: + - profile + - metagenomics + - sylph + - classification +tools: + - sylph: + description: Sylph quickly enables querying of genomes against even low-coverage + shotgun metagenomes to find nearest neighbour ANI. + homepage: https://github.com/bluenote-1577/sylph + documentation: https://github.com/bluenote-1577/sylph + tool_dev_url: https://github.com/bluenote-1577/sylph + doi: 10.1038/s41587-024-02412-y + licence: ["MIT"] + identifier: biotools:sylph +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + - reads: + type: file + description: | + List of input FastQ/FASTA files of size 1 and 2 for single-end and paired-end data, + respectively. They are automatically sketched to .sylsp/.syldb + ontologies: [] + - database: + type: file + description: Pre-sketched *.syldb/*.sylsp files. Raw single-end fastq/fasta are + allowed and will be automatically sketched to .sylsp/.syldb. + pattern: "*.{syldb,sylsp,fasta,fastq}" + ontologies: + - edam: http://edamontology.org/format_1930 # FASTQ +output: + profile_out: + - - meta: + type: map + description: Groovy Map containing sample information + - "*.tsv": + type: file + description: Output file of species-level taxonomic profiling with abundances + and ANIs. + pattern: "*tsv" + ontologies: [] + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML +authors: + - "@jiahang1234" + - "@sofstam" +maintainers: + - "@sofstam" diff --git a/modules/nf-core/sylph/profile/tests/main.nf.test b/modules/nf-core/sylph/profile/tests/main.nf.test new file mode 100644 index 000000000..cfdddf685 --- /dev/null +++ b/modules/nf-core/sylph/profile/tests/main.nf.test @@ -0,0 +1,80 @@ +nextflow_process { + + name "Test Process SYLPH_PROFILE" + script "../main.nf" + process "SYLPH_PROFILE" + tag "modules" + tag "modules_nfcore" + tag "sylph" + tag "sylph/profile" + + test("sarscov2 illumina single-end [fastq_gz]") { + when { + process { + """ + input[0] = [ [ id:'test',single_end:true ], // meta map + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ] + ] + input[1] = file(params.modules_testdata_base_path +'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + """ + } + } + + then { + assert process.success + assert snapshot( + process.out.versions, + file(process.out.profile_out[0][1]).readLines()[0] + ).match() + } + } + + test("sarscov2 illumina paired-end [fastq_gz]") { + when { + process { + """ + input[0] = [ [ id:'test' ], // meta map + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) + ] + ] + input[1] = file(params.modules_testdata_base_path +'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + """ + } + } + + then { + assert process.success + assert snapshot( + process.out.versions, + file(process.out.profile_out[0][1]).readLines()[0] + ).match() + } + } + + test("sarscov2 illumina paired-end [fastq_gz]-stub") { + options "-stub" + + when { + process { + """ + input[0] = [ [ id:'test' ], // meta map + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) + ] + ] + input[1] = file(params.modules_testdata_base_path +'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + """ + } + } + + then { + assert process.success + assert snapshot(process.out).match() + } + } +} diff --git a/modules/nf-core/sylph/profile/tests/main.nf.test.snap b/modules/nf-core/sylph/profile/tests/main.nf.test.snap new file mode 100644 index 000000000..5541ce615 --- /dev/null +++ b/modules/nf-core/sylph/profile/tests/main.nf.test.snap @@ -0,0 +1,61 @@ +{ + "sarscov2 illumina paired-end [fastq_gz]": { + "content": [ + [ + "versions.yml:md5,7b5a545483277cc0ff9189f8891e737f" + ], + "Sample_file\tGenome_file\tTaxonomic_abundance\tSequence_abundance\tAdjusted_ANI\tEff_cov\tANI_5-95_percentile\tEff_lambda\tLambda_5-95_percentile\tMedian_cov\tMean_cov_geq1\tContainment_ind\tNaive_ANI\tkmers_reassigned\tContig_name" + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.4" + }, + "timestamp": "2025-03-05T11:07:00.061876287" + }, + "sarscov2 illumina single-end [fastq_gz]": { + "content": [ + [ + "versions.yml:md5,7b5a545483277cc0ff9189f8891e737f" + ], + "Sample_file\tGenome_file\tTaxonomic_abundance\tSequence_abundance\tAdjusted_ANI\tEff_cov\tANI_5-95_percentile\tEff_lambda\tLambda_5-95_percentile\tMedian_cov\tMean_cov_geq1\tContainment_ind\tNaive_ANI\tkmers_reassigned\tContig_name" + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.4" + }, + "timestamp": "2025-03-05T11:05:21.230604092" + }, + "sarscov2 illumina paired-end [fastq_gz]-stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,7b5a545483277cc0ff9189f8891e737f" + ], + "profile_out": [ + [ + { + "id": "test" + }, + "test.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,7b5a545483277cc0ff9189f8891e737f" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.4" + }, + "timestamp": "2025-03-05T11:08:35.882851964" + } +} \ No newline at end of file diff --git a/modules/nf-core/sylphtax/taxprof/environment.yml b/modules/nf-core/sylphtax/taxprof/environment.yml new file mode 100644 index 000000000..517edcad5 --- /dev/null +++ b/modules/nf-core/sylphtax/taxprof/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::sylph-tax=1.2.0" diff --git a/modules/nf-core/sylphtax/taxprof/main.nf b/modules/nf-core/sylphtax/taxprof/main.nf new file mode 100644 index 000000000..d7508b3a5 --- /dev/null +++ b/modules/nf-core/sylphtax/taxprof/main.nf @@ -0,0 +1,53 @@ + +process SYLPHTAX_TAXPROF { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/sylph-tax:1.2.0--pyhdfd78af_0': + 'biocontainers/sylph-tax:1.2.0--pyhdfd78af_0' }" + + input: + tuple val(meta), path(sylph_results) + path taxonomy + + output: + tuple val(meta), path("*.sylphmpa"), emit: taxprof_output + 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 SYLPH_TAXONOMY_CONFIG="/tmp/config.json" + sylph-tax \\ + taxprof \\ + $sylph_results \\ + $args \\ + -t $taxonomy + + mv *.sylphmpa ${prefix}.sylphmpa + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + sylph-tax: \$(sylph-tax --version) + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + export SYLPH_TAXONOMY_CONFIG="/tmp/config.json" + touch ${prefix}.sylphmpa + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + sylph-tax: \$(sylph-tax --version) + END_VERSIONS + """ +} diff --git a/modules/nf-core/sylphtax/taxprof/meta.yml b/modules/nf-core/sylphtax/taxprof/meta.yml new file mode 100644 index 000000000..c254b608b --- /dev/null +++ b/modules/nf-core/sylphtax/taxprof/meta.yml @@ -0,0 +1,57 @@ +name: sylphtax_taxprof +description: Incorporates taxonomy into sylph metagenomic classifier +keywords: + - taxonomy + - sylph + - metagenomics +tools: + - sylphtax: + description: Integrating taxonomic information into the sylph metagenome profiler. + homepage: https://github.com/bluenote-1577/sylph-tax?tab=readme-ov-file + documentation: https://sylph-docs.github.io/sylph-tax/ + licence: ["MIT"] + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + - sylph_results: + type: file + description: Output results from sylph classifier. The database file(s) used + to create this file with sylph must be the same as those of the taxonomy input + channel of this module. + pattern: "*.{tsv}" + ontologies: + - edam: http://edamontology.org/format_3475 # TSV + - taxonomy: + type: file + description: A list of sylph-tax identifiers (e.g. GTDB_r220 or IMGVR_4.1). Multiple + taxonomy metadata files can be input. Custom taxonomy files are also possible. + ontologies: [] +output: + taxprof_output: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + pattern: "*{.sylphmpa}" + - "*.sylphmpa": + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + pattern: "*{.sylphmpa}" + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML +authors: + - "@sofstam" +maintainers: + - "@sofstam" diff --git a/modules/nf-core/sylphtax/taxprof/tests/main.nf.test b/modules/nf-core/sylphtax/taxprof/tests/main.nf.test new file mode 100644 index 000000000..0f0f9724d --- /dev/null +++ b/modules/nf-core/sylphtax/taxprof/tests/main.nf.test @@ -0,0 +1,91 @@ +nextflow_process { + + name "Test Process SYLPHTAX_TAXPROF" + script "../main.nf" + process "SYLPHTAX_TAXPROF" + + tag "modules" + tag "modules_nfcore" + tag "sylph" + tag "sylph/profile" + tag "sylphtax" + tag "sylphtax/taxprof" + + + test("sarscov2 illumina single-end [fastq_gz]") { + setup { + run("SYLPH_PROFILE") { + script "../../../sylph/profile/main.nf" + process { + """ + input[0] = [ [ id:'test', single_end:true ], // meta map + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ] + ] + input[1] = file(params.modules_testdata_base_path +'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + """ + } + } + } + when { + process { + """ + input[0] = SYLPH_PROFILE.out.profile_out + input[1] = file('https://github.com/nf-core/test-datasets/raw/taxprofiler/data/database/sylph/test_taxonomy.tsv.gz', checkIfExists: true) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.versions, + process.out.taxprof_output + ).match() } + ) + } + + } + + test("stub sarscov2 illumina single-end [fastq_gz]") { + + options '-stub' + + setup { + run("SYLPH_PROFILE") { + script "../../../sylph/profile/main.nf" + process { + """ + input[0] = [ [ id:'test' ], // meta map + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ] + ] + input[1] = file(params.modules_testdata_base_path +'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + """ + } + } + } + when { + process { + """ + input[0] = SYLPH_PROFILE.out.profile_out + input[1] = file('https://github.com/nf-core/test-datasets/raw/taxprofiler/data/database/sylph/test_taxonomy.tsv.gz', checkIfExists: true) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.versions, + process.out.taxprof_output + ).match() } + ) + } + } + +} diff --git a/modules/nf-core/sylphtax/taxprof/tests/main.nf.test.snap b/modules/nf-core/sylphtax/taxprof/tests/main.nf.test.snap new file mode 100644 index 000000000..3c26e75ec --- /dev/null +++ b/modules/nf-core/sylphtax/taxprof/tests/main.nf.test.snap @@ -0,0 +1,43 @@ +{ + "stub sarscov2 illumina single-end [fastq_gz]": { + "content": [ + [ + "versions.yml:md5,bdbbd22b3e721ba2027d3e6cb1dc4bb4" + ], + [ + [ + { + "id": "test" + }, + "test.sylphmpa:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.5" + }, + "timestamp": "2025-04-07T15:28:04.026470884" + }, + "sarscov2 illumina single-end [fastq_gz]": { + "content": [ + [ + "versions.yml:md5,bdbbd22b3e721ba2027d3e6cb1dc4bb4" + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.sylphmpa:md5,a9743c21a53ba766226e57d2a25f6167" + ] + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.5" + }, + "timestamp": "2025-04-07T15:27:55.45776116" + } +} \ No newline at end of file diff --git a/workflows/rnaseq/main.nf b/workflows/rnaseq/main.nf index d3a3ded0c..75dcbf585 100755 --- a/workflows/rnaseq/main.nf +++ b/workflows/rnaseq/main.nf @@ -43,6 +43,8 @@ include { STRINGTIE_STRINGTIE } from '../../modules/nf-core/stringtie/str include { SUBREAD_FEATURECOUNTS } from '../../modules/nf-core/subread/featurecounts' include { KRAKEN2_KRAKEN2 as KRAKEN2 } from '../../modules/nf-core/kraken2/kraken2/main' include { BRACKEN_BRACKEN as BRACKEN } from '../../modules/nf-core/bracken/bracken/main' +include { SYLPH_PROFILE } from '../../modules/nf-core/sylph/profile/main' +include { SYLPHTAX_TAXPROF } from '../../modules/nf-core/sylphtax/taxprof/main' include { MULTIQC } from '../../modules/nf-core/multiqc' include { BEDTOOLS_GENOMECOV as BEDTOOLS_GENOMECOV_FW } from '../../modules/nf-core/bedtools/genomecov' include { BEDTOOLS_GENOMECOV as BEDTOOLS_GENOMECOV_REV } from '../../modules/nf-core/bedtools/genomecov' From de640381c1161387dd4af07df3a13e4bcdf64cc7 Mon Sep 17 00:00:00 2001 From: egreenberg7 Date: Thu, 23 Oct 2025 20:57:11 -0400 Subject: [PATCH 02/12] Add sylph parameters --- nextflow.config | 2 ++ nextflow_schema.json | 14 +++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/nextflow.config b/nextflow.config index 5aa7a7524..b48a0d780 100644 --- a/nextflow.config +++ b/nextflow.config @@ -100,6 +100,8 @@ params { save_kraken_assignments = false save_kraken_unassigned = false bracken_precision = "S" + sylph_db = null + sylph_taxonomy = null skip_rseqc = false skip_biotype_qc = false skip_deseq2_qc = false diff --git a/nextflow_schema.json b/nextflow_schema.json index d2bc59295..3245d2200 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -601,7 +601,7 @@ "type": "string", "description": "Tool to use for detecting contaminants in unaligned reads - available options are 'kraken2' and 'kraken2_bracken'", "fa_icon": "fas fa-virus-slash", - "enum": ["kraken2", "kraken2_bracken"] + "enum": ["kraken2", "kraken2_bracken", "sylph"] }, "kraken_db": { "type": "string", @@ -617,6 +617,18 @@ "description": "Taxonomic level for Bracken abundance estimations.", "help_text": "Use the first letter of taxonomic levels: Domain, Phylum, Class, Order, Family, Genus, or Species.", "enum": ["D", "P", "C", "O", "F", "G", "S"] + }, + "sylph_db": { + "type": "string", + "description": "Database when using Sylph for contamination detection", + "help_text": "See the usage documentation for more information on setting up and using Sylph databases.", + "fa_icon": "fas fa-database" + }, + "sylph_taxonomy": { + "type": "string", + "description": "Taxonomy when using Sylph for contamination detection/", + "help_text": "See the usage documentation for more information on Sylph taxonomies.", + "fa_icon": "fas fa-tree" } } }, From 151a27488ea033ea512a1aae9e6b64bdacdb0e82 Mon Sep 17 00:00:00 2001 From: egreenberg7 Date: Thu, 23 Oct 2025 21:52:54 -0400 Subject: [PATCH 03/12] Input validation --- .../utils_nfcore_rnaseq_pipeline/main.nf | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/subworkflows/local/utils_nfcore_rnaseq_pipeline/main.nf b/subworkflows/local/utils_nfcore_rnaseq_pipeline/main.nf index e5dd439ea..229572bca 100644 --- a/subworkflows/local/utils_nfcore_rnaseq_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_rnaseq_pipeline/main.nf @@ -303,7 +303,7 @@ def validateInputParameters() { // Check that Kraken/Bracken database provided if using kraken2/bracken if (params.contaminant_screening in ['kraken2', 'kraken2_bracken']) { if (!params.kraken_db) { - error("Contaminant screening set to kraken2 but not database is provided. Please provide a database with the --kraken_db option.") + error("Contaminant screening set to kraken2 but no database was provided. Please provide a database with the --kraken_db option.") } // Check that Kraken/Bracken parameters are not provided when Kraken2 is not being used } else { @@ -316,6 +316,20 @@ def validateInputParameters() { } } + // Check that Sylph database and taxonomy is provided if using Sylph + if (params.contaminant_screening == 'sylph') { + if (!params.sylph_db) { + error("Contaminant screening is set to Sylph but no database was provided. Please provide a database with the --sylph_db option.") + } + if (!params.sylph_taxonomy) { + error("Contaminant screening is set to Sylph but no taxonomy was provided. Please provide a taxonomy with the --sylph_taxonomy option.") + } + } else { + if (params.sylph_db || params.sylph_taxonomy) { + sylphArgumentsWithoutSylphUsageWarn() + } + } + // Check which RSeQC modules we are running def valid_rseqc_modules = ['bam_stat', 'inner_distance', 'infer_experiment', 'junction_annotation', 'junction_saturation', 'read_distribution', 'read_duplication', 'tin'] def rseqc_modules = params.rseqc_modules ? params.rseqc_modules.split(',').collect{ it.trim().toLowerCase() } : [] @@ -533,17 +547,18 @@ def additionaFastaIndexWarn(index) { } // -// Print a warning if --save_kraken_assignments or --save_kraken_unassigned is provided without --kraken_db +// Print a warning if --save_kraken_assignments, --save_kraken_unassigned, or --kraken-db, +// is provided without setting --contaminant-screening to 'kraken2' or 'kraken2_bracken' // def krakenArgumentsWithoutKrakenDBWarn() { log.warn "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + - " 'Kraken2 related arguments have been provided without setting contaminant\n" + + " Kraken2 related arguments have been provided without setting contaminant\n" + " screening to Kraken2. Kraken2 is not being run so these will not be used.\n" + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" } /// -/// Print a warning if --bracken-precision is provided without --kraken_db +/// Print a warning if --bracken-precision is provided without contaminant screening using kraken2 /// def brackenPrecisionWithoutKrakenDBWarn() { log.warn "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + @@ -552,6 +567,16 @@ def brackenPrecisionWithoutKrakenDBWarn() { "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" } +// +// Print a warning if --sylph_db or --sylph_taxonomy is provided without contaminant screening set to 'sylph' +// +def sylphArgumentsWithoutSylphUsageWarn() { + log.warn "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + + " Sylph related arguments have been provided without setting contaminant\n" + + " screening to Sylph. Sylph is not being run so these will not be used.\n" + + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" +} + // // Function to generate an error if contigs in genome fasta file > 512 Mbp // From 51a71adc8e34f0a2bd4caa1387777facc102835d Mon Sep 17 00:00:00 2001 From: egreenberg7 Date: Thu, 23 Oct 2025 21:53:12 -0400 Subject: [PATCH 04/12] Add sylph to pipeline --- workflows/rnaseq/main.nf | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/workflows/rnaseq/main.nf b/workflows/rnaseq/main.nf index 75dcbf585..416957939 100755 --- a/workflows/rnaseq/main.nf +++ b/workflows/rnaseq/main.nf @@ -663,7 +663,21 @@ workflow RNASEQ { ch_versions = ch_versions.mix(BRACKEN.out.versions) ch_multiqc_files = ch_multiqc_files.mix(BRACKEN.out.txt.collect{it[1]}) } - } + } else if (params.contaminant_screening == 'sylph') { + SYLPH_PROFILE ( + ch_unaligned_sequences, + params.sylph_db + ) + ch_sylph_profile = SYLPH_PROFILE.out.profile_out + ch_versions = ch_versions.mix(SYLPH_PROFILE.out.versions) + + SYLPHTAX_TAXPROF ( + ch_sylph_profile, + params.sylph_taxonomy + ) + ch_versions = ch_versions.mix(SYLPHTAX_TAXPROF.out.versions) + ch_multiqc_files = ch_multiqc_files.mix(SYLPHTAX_TAXPROF.out.taxprof_output.collect{it[1]}) + } } // From 1d3f56ee04281a27ea65132f27f30a5f3d578c2e Mon Sep 17 00:00:00 2001 From: egreenberg7 Date: Thu, 23 Oct 2025 21:53:30 -0400 Subject: [PATCH 05/12] Change publish directories for sylph --- modules/nf-core/sylph/profile/nextflow.config | 12 ++++++++++++ modules/nf-core/sylphtax/taxprof/nextflow.config | 12 ++++++++++++ workflows/rnaseq/nextflow.config | 2 ++ 3 files changed, 26 insertions(+) create mode 100644 modules/nf-core/sylph/profile/nextflow.config create mode 100644 modules/nf-core/sylphtax/taxprof/nextflow.config diff --git a/modules/nf-core/sylph/profile/nextflow.config b/modules/nf-core/sylph/profile/nextflow.config new file mode 100644 index 000000000..f54f711c0 --- /dev/null +++ b/modules/nf-core/sylph/profile/nextflow.config @@ -0,0 +1,12 @@ +if (!params.skip_qc) { + if (params.contaminant_screening in ['sylph']) { + process { + withName: 'SYLPH_PROFILE' { + publishDir = [ + path: { "${params.outdir}/${params.aligner}/contaminants/sylph" }, + mode: params.publish_dir_mode + ] + } + } + } +} \ No newline at end of file diff --git a/modules/nf-core/sylphtax/taxprof/nextflow.config b/modules/nf-core/sylphtax/taxprof/nextflow.config new file mode 100644 index 000000000..505f70dc2 --- /dev/null +++ b/modules/nf-core/sylphtax/taxprof/nextflow.config @@ -0,0 +1,12 @@ +if (!params.skip_qc) { + if (params.contaminant_screening in ['sylph']) { + process { + withName: 'SYLPHTAX_TAXPROF' { + publishDir = [ + path: { "${params.outdir}/${params.aligner}/contaminants/sylph" }, + mode: params.publish_dir_mode + ] + } + } + } +} \ No newline at end of file diff --git a/workflows/rnaseq/nextflow.config b/workflows/rnaseq/nextflow.config index 7bd96fc31..10cf9f6bb 100644 --- a/workflows/rnaseq/nextflow.config +++ b/workflows/rnaseq/nextflow.config @@ -10,6 +10,8 @@ includeConfig "../../modules/nf-core/stringtie/stringtie/nextflow.config" includeConfig "../../modules/nf-core/subread/featurecounts/nextflow.config" includeConfig "../../modules/nf-core/kraken2/kraken2/nextflow.config" includeConfig "../../modules/nf-core/bracken/bracken/nextflow.config" +includeConfig "../../modules/nf-core/sylph/profile/nextflow.config" +includeConfig "../../modules/nf-core/sylphtax/taxprof/nextflow.config" includeConfig "../../subworkflows/local/align_star/nextflow.config" includeConfig "../../subworkflows/local/quantify_rsem/nextflow.config" includeConfig "../../subworkflows/nf-core/quantify_pseudo_alignment/nextflow.config" From 086b60004661b2bbbf235b40f23cf68466e5cd7f Mon Sep 17 00:00:00 2001 From: egreenberg7 Date: Thu, 23 Oct 2025 22:42:21 -0400 Subject: [PATCH 06/12] Initial documentation changes --- CHANGELOG.md | 12 ++++++++++++ CITATIONS.md | 4 ++++ README.md | 4 +++- docs/output.md | 16 ++++++++++++++++ docs/usage.md | 10 +++++++--- 5 files changed, 42 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d42fcdde3..d7b2888ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [PR #1608](https://github.com/nf-core/rnaseq/pull/1608) - Bump version after release 3.21.0 +| Old parameter | New parameter | +| ------------- | -------------------| +| | `--sylph_db` | +| | `--sylph_taxonomy` | + +### Software dependencies + +| Dependency | Old version | New version | +| -----------| ----------- | ----------- | +| `sylph` | | 0.7.0 | +| `sylph-tax`| | 1.2.0 | + ## [[3.21.0](https://github.com/nf-core/rnaseq/releases/tag/3.21.0)] - 2025-09-18 ### Credits diff --git a/CITATIONS.md b/CITATIONS.md index 5eaeea7f8..79a31ca13 100644 --- a/CITATIONS.md +++ b/CITATIONS.md @@ -88,6 +88,10 @@ > Kovaka S, Zimin AV, Pertea GM, Razaghi R, Salzberg SL, Pertea M. Transcriptome assembly from long-read RNA-seq alignments with StringTie2 Genome Biol. 2019 Dec 16;20(1):278. doi: 10.1186/s13059-019-1910-1. PubMed PMID: 31842956; PubMed Central PMCID: PMC6912988. +- [Sylph](https://pubmed.ncbi.nlm.nih.gov/39379646/) + + > Shaw J, Yu YW. Rapid species-level metagenome profiling and containment estimation with sylph. Nat Biotechnol. 2025 Aug;43(8):1348-1359. doi: 10.1038/s41587-024-02412-y. Epub 2024 Oct 8. PMID: 39379646; PMCID: PMC12339375. + - [Trim Galore!](https://www.bioinformatics.babraham.ac.uk/projects/trim_galore/) - [UMI-tools](https://pubmed.ncbi.nlm.nih.gov/28100584/) diff --git a/README.md b/README.md index 8b309334f..8c122d248 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,9 @@ 3. [`dupRadar`](https://bioconductor.org/packages/release/bioc/html/dupRadar.html) 4. [`Preseq`](http://smithlabresearch.org/software/preseq/) 5. [`DESeq2`](https://bioconductor.org/packages/release/bioc/html/DESeq2.html) - 6. [`Kraken2`](https://ccb.jhu.edu/software/kraken2/) -> [`Bracken`](https://ccb.jhu.edu/software/bracken/) on unaligned sequences; _optional_ + 6. Contamination detection on unaligned sequences; _optional_ + 1. [`Kraken2`](https://ccb.jhu.edu/software/kraken2/) -> [`Bracken`](https://ccb.jhu.edu/software/bracken/) + 2. [`Sylph`](https://sylph-docs.github.io/) 15. Pseudoalignment and quantification ([`Salmon`](https://combine-lab.github.io/salmon/) or ['Kallisto'](https://pachterlab.github.io/kallisto/); _optional_) 16. Present QC for raw read, alignment, gene biotype, sample similarity, and strand-specificity checks ([`MultiQC`](http://multiqc.info/), [`R`](https://www.r-project.org/)) diff --git a/docs/output.md b/docs/output.md index b47593a05..e985eae94 100644 --- a/docs/output.md +++ b/docs/output.md @@ -57,6 +57,7 @@ The pipeline is built using [Nextflow](https://www.nextflow.io/) and processes d - [featureCounts](#featurecounts) - [DESeq2](#deseq2) - [Kraken2/Bracken](#kraken2bracken) + - [Sylph](#Sylph) - [MultiQC](#multiqc) - [Pseudoalignment and quantification](#pseudoalignment-and-quantification) - [Pseudoalignment](#pseudoalignment) @@ -737,6 +738,21 @@ The plot on the left hand side shows the standard PC plot - notice the variable ![MultiQC - Bracken top species plot](images/bracken-top-n-plot.png) +### Sylph + +
+Output files + +- `/contaminants/sylph` + - `*.tsv` Summary of containment ANI and abundances of detected species in the sample. See the [Sylph documentation](https://sylph-docs.github.io/Output-format/) for full details on the output format. + - `*.sylphmpa` Taxonomic report of unaligned reads from `sylph-tax`. See the [Sylph documentation](https://sylph-docs.github.io/sylph-tax-output-format/) for full details on the output format. + +
+ +[Sylph](https://sylph-docs.github.io/) is a metagenomic profiler that determines the species present in reads by statistically estimating containment ANI. These algorithms are run on unaligned sequences to detect potential contamination of samples. MultiQC TBD. + +#TODO add MultiQC info + ### MultiQC
diff --git a/docs/usage.md b/docs/usage.md index 00cac88b2..eb8300ab9 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -401,11 +401,15 @@ By default, the input GTF file will be filtered to ensure that sequence names co The `--contaminant_screening` option is not currently available using ARM architecture ('-profile arm') ::: -The pipeline provides the option to scan unaligned reads for contamination from other species using [Kraken2](https://ccb.jhu.edu/software/kraken2/), with the possibility of applying corrections from [Bracken](https://ccb.jhu.edu/software/bracken/). Since running Bracken is not computationally expensive, we recommend always using it to refine the abundance estimates generated by Kraken2. +The pipeline provides the option to scan unaligned reads for contamination from other species using either [Sylph](https://sylph-docs.github.io/) or the [Kraken2](https://ccb.jhu.edu/software/kraken2/)/[Bracken](https://ccb.jhu.edu/software/bracken/) suite. -It is important to note that the accuracy of Kraken2 is [highly dependent on the database](https://doi.org/10.1099/mgen.0.000949) used. Specifically, it is [crucial](https://doi.org/10.1128/mbio.01607-23) to ensure that the host genome is included in the database. If you are particularly concerned about certain contaminants, it may be beneficial to use a smaller, more focused database containing primarily those contaminants instead of the full standard database. Various pre-built databases [are available for download](https://benlangmead.github.io/aws-indexes/k2), and instructions for building a custom database can be found in the [Kraken2 documentation](https://github.com/DerrickWood/kraken2/blob/master/docs/MANUAL.markdown). Additionally, genomes of contaminants detected in previous sequencing experiments are available on the [OpenContami website](https://openlooper.hgc.jp/opencontami/help/help_oct.php). +Sylph is a [faster and much more memory-efficient tool](https://doi.org/10.1038/s41587-024-02412-y) with about equal precision in species detection to Kraken2/Bracken. Sylph also has lower rates of false positives. However, Sylph does not assign specific reads to species; it only provides overall abundance estimates. Sylph abundance estimates also [cannot assign a certain percentage of reads as unclassified](https://github.com/bluenote-1577/sylph/issues/49). -While Kraken2 is capable of detecting low-abundance contaminants in a sample, false positives can occur. Therefore, if only a very small number of reads from a contaminating species are detected, these results should be interpreted with caution. +Pre-constructed sylph databases can be found [here](https://sylph-docs.github.io/pre%E2%80%90built-databases/) and taxonomies [here](https://sylph-docs.github.io/sylph-tax/). The [documentation](https://sylph-docs.github.io/sylph-tax/) also has instructions on creating custom databases/taxonomies. As a newer tool, the effect of database choice on Sylph's performance has not been explored as thoroughly as for Kraken2 or Bracken. However, the following comments on choosing databases for Kraken2 are very likely still applicable to an extent for Sylph. + +The accuracy of Kraken2 is [highly dependent on the database](https://doi.org/10.1099/mgen.0.000949) used. Specifically, it is [crucial](https://doi.org/10.1128/mbio.01607-23) to ensure that the host genome/transcriptome is included in the database. (Note that the pre-built sylph databases do _not_ appear to contain the human genome/transcriptome). If you are particularly concerned about certain contaminants, it may be beneficial to use a smaller, more focused database containing primarily those contaminants instead of the full standard database. Various pre-built databases [are available for download](https://benlangmead.github.io/aws-indexes/k2), and instructions for building a custom database can be found in the [Kraken2 documentation](https://github.com/DerrickWood/kraken2/blob/master/docs/MANUAL.markdown). Additionally, genomes of contaminants detected in previous sequencing experiments are available on the [OpenContami website](https://openlooper.hgc.jp/opencontami/help/help_oct.php). + +While Kraken2 is capable of detecting low-abundance contaminants in a sample, false positives can occur. Therefore, if only a very small number of reads from a contaminating species are detected, these results should be interpreted with caution. Lastly, while Kraken2 can be used without Bracken, since running Bracken is not computationally expensive, we recommend always using it to refine the abundance estimates generated by Kraken2. ## Running the pipeline From 8dc4a1e25dbaa7115d364896d6f739087d44c419 Mon Sep 17 00:00:00 2001 From: egreenberg7 Date: Wed, 29 Oct 2025 18:52:27 -0400 Subject: [PATCH 07/12] Slight schema change --- nextflow_schema.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nextflow_schema.json b/nextflow_schema.json index 3245d2200..761ddd8d7 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -620,12 +620,14 @@ }, "sylph_db": { "type": "string", + "format": "file-path", "description": "Database when using Sylph for contamination detection", "help_text": "See the usage documentation for more information on setting up and using Sylph databases.", "fa_icon": "fas fa-database" }, "sylph_taxonomy": { "type": "string", + "format": "file-path", "description": "Taxonomy when using Sylph for contamination detection/", "help_text": "See the usage documentation for more information on Sylph taxonomies.", "fa_icon": "fas fa-tree" From 184e0d43dfd0b5aba273d08a4f0fcbf94f3c4ebd Mon Sep 17 00:00:00 2001 From: egreenberg7 Date: Thu, 30 Oct 2025 06:22:34 -0400 Subject: [PATCH 08/12] Fix empty sylph output issue --- workflows/rnaseq/main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workflows/rnaseq/main.nf b/workflows/rnaseq/main.nf index 416957939..753c5c493 100755 --- a/workflows/rnaseq/main.nf +++ b/workflows/rnaseq/main.nf @@ -668,7 +668,7 @@ workflow RNASEQ { ch_unaligned_sequences, params.sylph_db ) - ch_sylph_profile = SYLPH_PROFILE.out.profile_out + ch_sylph_profile = SYLPH_PROFILE.out.profile_out.filter{!it[1].isEmpty()} ch_versions = ch_versions.mix(SYLPH_PROFILE.out.versions) SYLPHTAX_TAXPROF ( From 9dfae3d51e1519ee48035ec8da9c66c1140e42b0 Mon Sep 17 00:00:00 2001 From: egreenberg7 Date: Thu, 30 Oct 2025 07:03:35 -0400 Subject: [PATCH 09/12] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7b2888ed..47f3c00bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Enhancements and fixes - [PR #1608](https://github.com/nf-core/rnaseq/pull/1608) - Bump version after release 3.21.0 +- [PR #1616](https://github.com/nf-core/rnaseq/pull/1616) - Add Sylph for contamination detection. | Old parameter | New parameter | | ------------- | -------------------| From a92a83453979651f77a37bdf98c311c21fac89fe Mon Sep 17 00:00:00 2001 From: egreenberg7 Date: Thu, 4 Dec 2025 19:02:34 -0500 Subject: [PATCH 10/12] Reduce warning messages for contamination detection --- nextflow_schema.json | 10 ++-- .../utils_nfcore_rnaseq_pipeline/main.nf | 52 ++----------------- 2 files changed, 8 insertions(+), 54 deletions(-) diff --git a/nextflow_schema.json b/nextflow_schema.json index 761ddd8d7..1d12625a0 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -599,7 +599,7 @@ }, "contaminant_screening": { "type": "string", - "description": "Tool to use for detecting contaminants in unaligned reads - available options are 'kraken2' and 'kraken2_bracken'", + "description": "Tool to use for detecting contaminants in unaligned reads - available options are 'sylph', 'kraken2', or 'kraken2_bracken'", "fa_icon": "fas fa-virus-slash", "enum": ["kraken2", "kraken2_bracken", "sylph"] }, @@ -607,7 +607,7 @@ "type": "string", "format": "directory-path", "description": "Database when using Kraken2/Bracken for contaminant screening.", - "help_text": "See the usage documentation for more information on setting up and using Kraken2 databases.", + "help_text": "See the usage documentation for more information on setting up and using Kraken2 databases. Requires the --contaminant-screening option to be set to 'kraken2' or 'kraken2_bracken' to have an effect", "fa_icon": "fas fa-fish" }, "bracken_precision": { @@ -615,21 +615,21 @@ "default": "S", "fa_icon": "fas fa-tree", "description": "Taxonomic level for Bracken abundance estimations.", - "help_text": "Use the first letter of taxonomic levels: Domain, Phylum, Class, Order, Family, Genus, or Species.", + "help_text": "Use the first letter of taxonomic levels: Domain, Phylum, Class, Order, Family, Genus, or Species. Requires --contaminant-screening option to be set to 'kraken2_bracken' to have an effect.", "enum": ["D", "P", "C", "O", "F", "G", "S"] }, "sylph_db": { "type": "string", "format": "file-path", "description": "Database when using Sylph for contamination detection", - "help_text": "See the usage documentation for more information on setting up and using Sylph databases.", + "help_text": "See the usage documentation for more information on setting up and using Sylph databases. Requires --contaminant-screening option to be set to 'sylph' to have an effect.", "fa_icon": "fas fa-database" }, "sylph_taxonomy": { "type": "string", "format": "file-path", "description": "Taxonomy when using Sylph for contamination detection/", - "help_text": "See the usage documentation for more information on Sylph taxonomies.", + "help_text": "See the usage documentation for more information on Sylph taxonomies. Requires --contaminant-screening option to be set to 'sylph' to have an effect.", "fa_icon": "fas fa-tree" } } diff --git a/subworkflows/local/utils_nfcore_rnaseq_pipeline/main.nf b/subworkflows/local/utils_nfcore_rnaseq_pipeline/main.nf index 229572bca..64c8bb44e 100644 --- a/subworkflows/local/utils_nfcore_rnaseq_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_rnaseq_pipeline/main.nf @@ -301,19 +301,8 @@ def validateInputParameters() { } // Check that Kraken/Bracken database provided if using kraken2/bracken - if (params.contaminant_screening in ['kraken2', 'kraken2_bracken']) { - if (!params.kraken_db) { - error("Contaminant screening set to kraken2 but no database was provided. Please provide a database with the --kraken_db option.") - } - // Check that Kraken/Bracken parameters are not provided when Kraken2 is not being used - } else { - if (!params.bracken_precision.equals('S')) { - brackenPrecisionWithoutKrakenDBWarn() - } - - if (params.save_kraken_assignments || params.save_kraken_unassigned || params.kraken_db) { - krakenArgumentsWithoutKrakenDBWarn() - } + if (params.contaminant_screening in ['kraken2', 'kraken2_bracken'] && !params.kraken_db) { + error("Contaminant screening set to kraken2 but no database was provided. Please provide a database with the --kraken_db option.") } // Check that Sylph database and taxonomy is provided if using Sylph @@ -324,11 +313,7 @@ def validateInputParameters() { if (!params.sylph_taxonomy) { error("Contaminant screening is set to Sylph but no taxonomy was provided. Please provide a taxonomy with the --sylph_taxonomy option.") } - } else { - if (params.sylph_db || params.sylph_taxonomy) { - sylphArgumentsWithoutSylphUsageWarn() - } - } + } // Check which RSeQC modules we are running def valid_rseqc_modules = ['bam_stat', 'inner_distance', 'infer_experiment', 'junction_annotation', 'junction_saturation', 'read_distribution', 'read_duplication', 'tin'] @@ -546,37 +531,6 @@ def additionaFastaIndexWarn(index) { "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" } -// -// Print a warning if --save_kraken_assignments, --save_kraken_unassigned, or --kraken-db, -// is provided without setting --contaminant-screening to 'kraken2' or 'kraken2_bracken' -// -def krakenArgumentsWithoutKrakenDBWarn() { - log.warn "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + - " Kraken2 related arguments have been provided without setting contaminant\n" + - " screening to Kraken2. Kraken2 is not being run so these will not be used.\n" + - "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" -} - -/// -/// Print a warning if --bracken-precision is provided without contaminant screening using kraken2 -/// -def brackenPrecisionWithoutKrakenDBWarn() { - log.warn "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + - " '--bracken-precision' parameter has been provided without Kraken2 contaminant screening.\n" + - " Bracken will not run so precision will not be set.\n" + - "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" -} - -// -// Print a warning if --sylph_db or --sylph_taxonomy is provided without contaminant screening set to 'sylph' -// -def sylphArgumentsWithoutSylphUsageWarn() { - log.warn "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + - " Sylph related arguments have been provided without setting contaminant\n" + - " screening to Sylph. Sylph is not being run so these will not be used.\n" + - "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" -} - // // Function to generate an error if contigs in genome fasta file > 512 Mbp // From 6f056aa2842ffce3b7ebbf27ee25d552e24599e6 Mon Sep 17 00:00:00 2001 From: egreenberg7 Date: Thu, 4 Dec 2025 19:11:25 -0500 Subject: [PATCH 11/12] Allow for multiple sylph databases --- nextflow_schema.json | 8 ++++---- workflows/rnaseq/main.nf | 8 ++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/nextflow_schema.json b/nextflow_schema.json index 1d12625a0..16df4f386 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -621,16 +621,16 @@ "sylph_db": { "type": "string", "format": "file-path", - "description": "Database when using Sylph for contamination detection", + "description": "Comma separated list of databases to profile against when using Sylph for contamination detection", "help_text": "See the usage documentation for more information on setting up and using Sylph databases. Requires --contaminant-screening option to be set to 'sylph' to have an effect.", "fa_icon": "fas fa-database" }, "sylph_taxonomy": { "type": "string", - "format": "file-path", - "description": "Taxonomy when using Sylph for contamination detection/", + "description": "Comma separated list of taxonomies when using Sylph for contamination detection", "help_text": "See the usage documentation for more information on Sylph taxonomies. Requires --contaminant-screening option to be set to 'sylph' to have an effect.", - "fa_icon": "fas fa-tree" + "fa_icon": "fas fa-tree", + "format": "file-path" } } }, diff --git a/workflows/rnaseq/main.nf b/workflows/rnaseq/main.nf index 753c5c493..482c1aae8 100755 --- a/workflows/rnaseq/main.nf +++ b/workflows/rnaseq/main.nf @@ -664,16 +664,20 @@ workflow RNASEQ { ch_multiqc_files = ch_multiqc_files.mix(BRACKEN.out.txt.collect{it[1]}) } } else if (params.contaminant_screening == 'sylph') { + def sylph_databases = params.sylph_db ? params.sylph_db.split(',').collect{ file(it.trim()) } : [] + ch_sylph_databases = channel.value(sylph_databases) SYLPH_PROFILE ( ch_unaligned_sequences, - params.sylph_db + ch_sylph_databases ) ch_sylph_profile = SYLPH_PROFILE.out.profile_out.filter{!it[1].isEmpty()} ch_versions = ch_versions.mix(SYLPH_PROFILE.out.versions) + def sylph_taxonomies = params.sylph_taxonomy ? params.sylph_taxonomy.split(',').collect{ file(it.trim()) } : [] + ch_sylph_taxonomies = channel.value(sylph_taxonomies) SYLPHTAX_TAXPROF ( ch_sylph_profile, - params.sylph_taxonomy + ch_sylph_taxonomies ) ch_versions = ch_versions.mix(SYLPHTAX_TAXPROF.out.versions) ch_multiqc_files = ch_multiqc_files.mix(SYLPHTAX_TAXPROF.out.taxprof_output.collect{it[1]}) From 700a8aec865958ed31eb7285be84fbcaba9a6dc8 Mon Sep 17 00:00:00 2001 From: egreenberg7 Date: Thu, 11 Dec 2025 12:43:00 -0500 Subject: [PATCH 12/12] Update multiqc to support sylph --- CHANGELOG.md | 1 + docs/images/sylphtax-top-n-plot.png | Bin 0 -> 75002 bytes docs/output.md | 4 +- modules.json | 2 +- modules/nf-core/multiqc/environment.yml | 2 +- modules/nf-core/multiqc/main.nf | 44 ++++++------- modules/nf-core/multiqc/meta.yml | 20 +++--- .../multiqc/tests/custom_prefix.config | 5 ++ modules/nf-core/multiqc/tests/main.nf.test | 32 +++++++++- .../nf-core/multiqc/tests/main.nf.test.snap | 58 ++++++++++++------ .../rnaseq/assets/multiqc/multiqc_config.yml | 3 + 11 files changed, 111 insertions(+), 60 deletions(-) create mode 100644 docs/images/sylphtax-top-n-plot.png create mode 100644 modules/nf-core/multiqc/tests/custom_prefix.config diff --git a/CHANGELOG.md b/CHANGELOG.md index 47f3c00bf..c2279b314 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | Dependency | Old version | New version | | -----------| ----------- | ----------- | +| `MultiQC` | 1.31 | 1.33 | | `sylph` | | 0.7.0 | | `sylph-tax`| | 1.2.0 | diff --git a/docs/images/sylphtax-top-n-plot.png b/docs/images/sylphtax-top-n-plot.png new file mode 100644 index 0000000000000000000000000000000000000000..1a0c6460e80cef38bcc7674cc6b4e69cf035f598 GIT binary patch literal 75002 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 -[Sylph](https://sylph-docs.github.io/) is a metagenomic profiler that determines the species present in reads by statistically estimating containment ANI. These algorithms are run on unaligned sequences to detect potential contamination of samples. MultiQC TBD. +[Sylph](https://sylph-docs.github.io/) is a metagenomic profiler that determines the species present in reads by statistically estimating containment ANI. Its companion script, [sylph-tax](https://sylph-docs.github.io/sylph-tax/), converts these ANI estimates into estimated taxonomic abundances in the sample. These algorithms are run on unaligned sequences to detect potential contamination of samples. MultiQC shows the Top 10 strains in the Sylph-tax abundance estimates, with toggles available for higher taxonomic levels. -#TODO add MultiQC info +![MultiQC - Sylphtax top species plot](images/sylphtax-top-n-plot.png) ### MultiQC diff --git a/modules.json b/modules.json index 07c7b56e2..16fe286de 100644 --- a/modules.json +++ b/modules.json @@ -108,7 +108,7 @@ }, "multiqc": { "branch": "master", - "git_sha": "e10b76ca0c66213581bec2833e30d31f239dec0b", + "git_sha": "9656d955b700a8707c4a67821ab056f8c1095675", "installed_by": ["modules"] }, "picard/markduplicates": { diff --git a/modules/nf-core/multiqc/environment.yml b/modules/nf-core/multiqc/environment.yml index dd513cbd1..009874d4c 100644 --- a/modules/nf-core/multiqc/environment.yml +++ b/modules/nf-core/multiqc/environment.yml @@ -4,4 +4,4 @@ channels: - conda-forge - bioconda dependencies: - - bioconda::multiqc=1.31 + - bioconda::multiqc=1.33 diff --git a/modules/nf-core/multiqc/main.nf b/modules/nf-core/multiqc/main.nf index 5288f5ccf..3b0e975be 100644 --- a/modules/nf-core/multiqc/main.nf +++ b/modules/nf-core/multiqc/main.nf @@ -3,11 +3,11 @@ process MULTIQC { conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/ef/eff0eafe78d5f3b65a6639265a16b89fdca88d06d18894f90fcdb50142004329/data' : - 'community.wave.seqera.io/library/multiqc:1.31--1efbafd542a23882' }" + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/34/34e733a9ae16a27e80fe00f863ea1479c96416017f24a907996126283e7ecd4d/data' : + 'community.wave.seqera.io/library/multiqc:1.33--ee7739d47738383b' }" input: - path multiqc_files, stageAs: "?/*" + path multiqc_files, stageAs: "?/*" path(multiqc_config) path(extra_multiqc_config) path(multiqc_logo) @@ -15,10 +15,11 @@ process MULTIQC { path(sample_names) output: - path "*multiqc_report.html", emit: report - path "*_data" , emit: data - path "*_plots" , optional:true, emit: plots - path "versions.yml" , emit: versions + path "*.html" , emit: report + path "*_data" , emit: data + path "*_plots" , optional:true, emit: plots + tuple val("${task.process}"), val('multiqc'), eval('multiqc --version | sed "s/.* //g"'), emit: versions + // MultiQC should not push its versions to the `versions` topic. Its input depends on the versions topic to be resolved thus outputting to the topic will let the pipeline hang forever when: task.ext.when == null || task.ext.when @@ -26,38 +27,29 @@ process MULTIQC { script: def args = task.ext.args ?: '' def prefix = task.ext.prefix ? "--filename ${task.ext.prefix}.html" : '' - def config = multiqc_config ? "--config $multiqc_config" : '' - def extra_config = extra_multiqc_config ? "--config $extra_multiqc_config" : '' + def config = multiqc_config ? "--config ${multiqc_config}" : '' + def extra_config = extra_multiqc_config ? "--config ${extra_multiqc_config}" : '' def logo = multiqc_logo ? "--cl-config 'custom_logo: \"${multiqc_logo}\"'" : '' def replace = replace_names ? "--replace-names ${replace_names}" : '' def samples = sample_names ? "--sample-names ${sample_names}" : '' """ multiqc \\ --force \\ - $args \\ - $config \\ - $prefix \\ - $extra_config \\ - $logo \\ - $replace \\ - $samples \\ + ${args} \\ + ${config} \\ + ${prefix} \\ + ${extra_config} \\ + ${logo} \\ + ${replace} \\ + ${samples} \\ . - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - multiqc: \$( multiqc --version | sed -e "s/multiqc, version //g" ) - END_VERSIONS """ stub: """ mkdir multiqc_data + touch multiqc_data/.stub mkdir multiqc_plots touch multiqc_report.html - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - multiqc: \$( multiqc --version | sed -e "s/multiqc, version //g" ) - END_VERSIONS """ } diff --git a/modules/nf-core/multiqc/meta.yml b/modules/nf-core/multiqc/meta.yml index ce30eb732..e4b8f94dd 100644 --- a/modules/nf-core/multiqc/meta.yml +++ b/modules/nf-core/multiqc/meta.yml @@ -57,10 +57,10 @@ input: - edam: http://edamontology.org/format_3475 # TSV output: report: - - "*multiqc_report.html": + - "*.html": type: file description: MultiQC report file - pattern: "multiqc_report.html" + pattern: ".html" ontologies: [] data: - "*_data": @@ -74,12 +74,16 @@ output: pattern: "*_data" ontologies: [] versions: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" - ontologies: - - edam: http://edamontology.org/format_3750 # YAML + - - ${task.process}: + type: string + description: The process the versions were collected from + - multiqc: + type: string + description: The tool name + - multiqc --version | sed "s/.* //g": + type: eval + description: The expression to obtain the version of the tool + authors: - "@abhi18av" - "@bunop" diff --git a/modules/nf-core/multiqc/tests/custom_prefix.config b/modules/nf-core/multiqc/tests/custom_prefix.config new file mode 100644 index 000000000..b30b1358b --- /dev/null +++ b/modules/nf-core/multiqc/tests/custom_prefix.config @@ -0,0 +1,5 @@ +process { + withName: 'MULTIQC' { + ext.prefix = "custom_prefix" + } +} diff --git a/modules/nf-core/multiqc/tests/main.nf.test b/modules/nf-core/multiqc/tests/main.nf.test index 33316a7dd..d1ae8b06c 100644 --- a/modules/nf-core/multiqc/tests/main.nf.test +++ b/modules/nf-core/multiqc/tests/main.nf.test @@ -30,7 +30,33 @@ nextflow_process { { assert process.success }, { assert process.out.report[0] ==~ ".*/multiqc_report.html" }, { assert process.out.data[0] ==~ ".*/multiqc_data" }, - { assert snapshot(process.out.versions).match("multiqc_versions_single") } + { assert snapshot(process.out.findAll { key, val -> key.startsWith("versions")}).match() } + ) + } + + } + + test("sarscov2 single-end [fastqc] - custom prefix") { + config "./custom_prefix.config" + + when { + process { + """ + input[0] = Channel.of(file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true)) + input[1] = [] + input[2] = [] + input[3] = [] + input[4] = [] + input[5] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert process.out.report[0] ==~ ".*/custom_prefix.html" }, + { assert process.out.data[0] ==~ ".*/custom_prefix_data" } ) } @@ -56,7 +82,7 @@ nextflow_process { { assert process.success }, { assert process.out.report[0] ==~ ".*/multiqc_report.html" }, { assert process.out.data[0] ==~ ".*/multiqc_data" }, - { assert snapshot(process.out.versions).match("multiqc_versions_config") } + { assert snapshot(process.out.findAll { key, val -> key.startsWith("versions")}).match() } ) } } @@ -84,7 +110,7 @@ nextflow_process { { assert snapshot(process.out.report.collect { file(it).getName() } + process.out.data.collect { file(it).getName() } + process.out.plots.collect { file(it).getName() } + - process.out.versions ).match("multiqc_stub") } + process.out.findAll { key, val -> key.startsWith("versions")} ).match() } ) } diff --git a/modules/nf-core/multiqc/tests/main.nf.test.snap b/modules/nf-core/multiqc/tests/main.nf.test.snap index 17881d15c..d72d35b74 100644 --- a/modules/nf-core/multiqc/tests/main.nf.test.snap +++ b/modules/nf-core/multiqc/tests/main.nf.test.snap @@ -1,41 +1,61 @@ { - "multiqc_versions_single": { + "sarscov2 single-end [fastqc]": { "content": [ - [ - "versions.yml:md5,8968b114a3e20756d8af2b80713bcc4f" - ] + { + "versions": [ + [ + "MULTIQC", + "multiqc", + "1.33" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-08T20:57:36.139055243" + "timestamp": "2025-12-09T10:10:43.020315838" }, - "multiqc_stub": { + "sarscov2 single-end [fastqc] - stub": { "content": [ [ "multiqc_report.html", "multiqc_data", "multiqc_plots", - "versions.yml:md5,8968b114a3e20756d8af2b80713bcc4f" + { + "versions": [ + [ + "MULTIQC", + "multiqc", + "1.33" + ] + ] + } ] ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-08T20:59:15.142230631" + "timestamp": "2025-12-09T10:11:14.131950776" }, - "multiqc_versions_config": { + "sarscov2 single-end [fastqc] [config]": { "content": [ - [ - "versions.yml:md5,8968b114a3e20756d8af2b80713bcc4f" - ] + { + "versions": [ + [ + "MULTIQC", + "multiqc", + "1.33" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-08T20:58:29.629087066" + "timestamp": "2025-12-09T10:11:07.15692209" } } \ No newline at end of file diff --git a/workflows/rnaseq/assets/multiqc/multiqc_config.yml b/workflows/rnaseq/assets/multiqc/multiqc_config.yml index bd24bc156..7038a1be2 100644 --- a/workflows/rnaseq/assets/multiqc/multiqc_config.yml +++ b/workflows/rnaseq/assets/multiqc/multiqc_config.yml @@ -97,6 +97,7 @@ run_modules: - rseqc - qualimap - kraken + - sylphtax # Order of modules @@ -162,6 +163,8 @@ sp: kraken: - fn: "*.kraken2.report.txt" - fn: "*.kraken2.report_bracken.txt" + sylphtax: + - fn: "*.sylphmpa" rseqc/bam_stat: fn: "*.bam_stat.txt" rseqc/gene_body_coverage: