From 1511db33dcdb079e3f9ce465b12af243fdfa2858 Mon Sep 17 00:00:00 2001 From: Amber Verhasselt Date: Fri, 2 Oct 2026 10:20:30 +0200 Subject: [PATCH 1/5] Add optional whitelist Severus calling (--severus_whitelist) SEVERUS_WHITELIST runs a patched Severus 1.7 image (docker.io/amberverhasselt/severus:1.7-whitelist-6813dee) whose --whitelist reports every SV inside the given regions, past the read-quality, minimum-support and VNTR filters. Setting --severus_whitelist replaces SEVERUS with it; its VCFs feed SV VEP, Wakhan and the report. Extra flags go through --severus_whitelist_args. Conda is not supported for this step. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 1 + conf/modules.config | 11 + docs/output.md | 2 + docs/usage.md | 10 +- modules/local/severus/whitelist/main.nf | 90 +++++++ modules/local/severus/whitelist/meta.yml | 287 +++++++++++++++++++++++ nextflow.config | 2 + nextflow_schema.json | 13 + workflows/lrsomatic.nf | 33 ++- 9 files changed, 437 insertions(+), 12 deletions(-) create mode 100644 modules/local/severus/whitelist/main.nf create mode 100644 modules/local/severus/whitelist/meta.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index db7ca789..9bccfd84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### `Added` +- Added optional whitelist SV calling: `--severus_whitelist ` replaces `SEVERUS` with `SEVERUS_WHITELIST`, which runs a patched Severus 1.7 image (`docker.io/amberverhasselt/severus:1.7-whitelist-6813dee`) that reports every SV inside the whitelisted regions, past the read-quality, minimum-support and VNTR filters, with corroboration required for single-read junctions. Its VCFs feed SV VEP, Wakhan and the report in place of the standard Severus output. Extra flags go through `--severus_whitelist_args`. Conda is not supported for this step (@AmberVerhasselt). - [#197](https://github.com/IntGenomicsLab/lrsomatic/pull/197) - Added CHM13 support for ClairS-TO's Verdict module, which tags tumour-only calls as germline, somatic or subclonal somatic; its resources were GRCh38-only, so on CHM13 germline variants leaked into `somatic.vcf.gz`. With `--genome CHM13 --skip_ascat` the pipeline builds a CHM13 resource set from the ASCAT files it already downloads and passes it as `--cna_resource_dir`; a prepared directory can be given with `--clairsto_cna_resources` (validated at launch). Without `--skip_ascat` tagging comes from ASCAT's own tables instead (next entry) (@ljwharbers). - [#197](https://github.com/IntGenomicsLab/lrsomatic/pull/197) - Added `CLAIRSTO_VERDICT_TAG`: when ASCAT is in the run, Verdict's germline tagging is computed from ASCAT's purity, ploidy and segments instead of Verdict's own estimate, so `CLAIRSTO` runs with `--disable_verdict` and ASCAT runs before small variant calling. Output names are unchanged. The tables the tags were computed from are published as `_Tumor_Purity_Ploidy.txt` and `_Tumor_CNA.txt`, also on `--skip_ascat` runs (@ljwharbers). - [#197](https://github.com/IntGenomicsLab/lrsomatic/pull/197) - Added a stub nf-test for `TUMORONLY_SMALLVAR` covering both germline tagging paths (tag `small`) (@ljwharbers). diff --git a/conf/modules.config b/conf/modules.config index ef812d4c..9155b147 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -433,6 +433,17 @@ process { ] } + // Replaces SEVERUS when --severus_whitelist is set, so it publishes to the same place + withName: '.*:SEVERUS_WHITELIST' { + ext.prefix = "." + ext.args = { "--min-support ${params.severus_minsupport} --output-read-ids ${params.severus_whitelist_args ?: ''}" } + publishDir = [ + path: { "${params.outdir}/${meta.id}/variants/severus" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } + withName: '.*:SAVANA_.*' { publishDir = [ path: { "${params.outdir}/${meta.id}/variants/savana" }, diff --git a/docs/output.md b/docs/output.md index ccc2188c..1ca5ce7e 100644 --- a/docs/output.md +++ b/docs/output.md @@ -358,6 +358,8 @@ The germline/somatic split comes from a panel of normals and from ClairS-TO's Ve | `read_qual.txt` | file containing quality statistics about identified segements | | `severus.log` | log file | +With `--severus_whitelist` the same files are published here by `SEVERUS_WHITELIST`. Passing `--write-alignments` through `--severus_whitelist_args` adds a `read_alignments` file. + #### `savana` SAVANA structural variant and copy-number calling. Runs alongside Severus/ASCAT rather than replacing diff --git a/docs/usage.md b/docs/usage.md index 520817aa..5cd3750c 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -270,9 +270,13 @@ opt-in. See [VEP plugins](#vep-plugins) for sizes, licence terms and per-assembl #### SEVERUS Options -| Parameter | Description | -| ---------------------- | ------------------------------------------------------------------------------------ | -| `--severus_minsupport` | Minimum number of supporting reads required for SEVERUS to call an SV. Default = `3` | +| Parameter | Description | +| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--severus_minsupport` | Minimum number of supporting reads required for SEVERUS to call an SV. Default = `3` | +| `--severus_whitelist` | BED file of regions in which every SV is reported (Severus `--whitelist`). Setting it replaces the standard Severus run with `SEVERUS_WHITELIST`, whose VCFs feed SV VEP, Wakhan and the report. Default = off | +| `--severus_whitelist_args` | Extra arguments for `SEVERUS_WHITELIST`, e.g. `--whitelist-single-read any`, `--whitelist-allow-intra-region` or `--write-alignments`. Default = none | + +`SEVERUS_WHITELIST` runs a patched Severus build ([AmberVerhasselt/Severus](https://github.com/AmberVerhasselt/Severus/tree/whitelist-reciprocal-corroboration)) from `docker.io/amberverhasselt/severus` (`oras://docker.io/amberverhasselt/severus-sif` under Singularity/Apptainer). Inside the whitelisted regions it skips the read-quality, minimum-support and VNTR filters, and keeps a junction supported by a single read only when a reciprocal or independently supported junction corroborates it. This is meant for loci such as the IG and TCR genes, where translocations are often carried by few, divergent reads. **Conda is not supported** for this step. #### SAVANA Options diff --git a/modules/local/severus/whitelist/main.nf b/modules/local/severus/whitelist/main.nf new file mode 100644 index 00000000..e397426f --- /dev/null +++ b/modules/local/severus/whitelist/main.nf @@ -0,0 +1,90 @@ +process SEVERUS_WHITELIST { + tag "$meta.id" + label 'process_medium' + + // No conda: the --whitelist fixes exist only in the patched image, and bioconda's severus would silently drop them + // Severus 1.7 + github.com/AmberVerhasselt/Severus/tree/whitelist-reciprocal-corroboration (6813dee); revert to the biocontainer once KolmogorovLab/Severus carries it + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container + ? 'oras://docker.io/amberverhasselt/severus-sif:1.7-whitelist-6813dee' + : 'docker.io/amberverhasselt/severus:1.7-whitelist-6813dee'}" + + input: + tuple val(meta), path(target_input), path(target_index), path(control_input), path(control_index), path(vcf), path(tbi) + tuple val(meta2), path(bed), path(pon_path) + tuple val(meta3), path(whitelist) + + output: + tuple val(meta), path("${prefix}/severus.log") , emit: log + tuple val(meta), path("${prefix}/read_qual.txt") , emit: read_qual + tuple val(meta), path("${prefix}/breakpoints_double.csv") , emit: breakpoints_double + tuple val(meta), path("${prefix}/read_alignments") , emit: read_alignments , optional: true + tuple val(meta), path("${prefix}/read_ids.csv") , emit: read_ids , optional: true + tuple val(meta), path("${prefix}/severus_collaped_dup.bed") , emit: collapsed_dup , optional: true + tuple val(meta), path("${prefix}/severus_LOH.bed") , emit: loh , optional: true + tuple val(meta), path("${prefix}/all_SVs/severus_all.vcf.gz") , emit: all_vcf , optional: true + tuple val(meta), path("${prefix}/all_SVs/breakpoint_clusters_list.tsv") , emit: all_breakpoints_clusters_list , optional: true + tuple val(meta), path("${prefix}/all_SVs/breakpoint_clusters.tsv") , emit: all_breakpoints_clusters , optional: true + tuple val(meta), path("${prefix}/all_SVs/plots/severus*.html") , emit: all_plots , optional: true + tuple val(meta), path("${prefix}/somatic_SVs/severus_somatic.vcf.gz") , emit: somatic_vcf , optional: true + tuple val(meta), path("${prefix}/somatic_SVs/breakpoint_clusters_list.tsv"), emit: somatic_breakpoints_clusters_list, optional: true + tuple val(meta), path("${prefix}/somatic_SVs/breakpoint_clusters.tsv") , emit: somatic_breakpoints_clusters , optional: true + tuple val(meta), path("${prefix}/somatic_SVs/plots/severus*.html") , emit: somatic_plots , optional: true + // The -whitelist suffix keeps the patched build distinguishable from stock severus in the versions report + tuple val("${task.process}"), val('severus'), eval('echo "$(severus --version)-whitelist-6813dee"'), emit: versions_severus, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "SEVERUS_WHITELIST does not support Conda. Please use Docker / Singularity / Apptainer instead, or run without --severus_whitelist." + } + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + + def control = control_input ? "--control-bam ${control_input}" : "" + def vntr_bed = bed ? "--vntr-bed ${bed}" : "" + def phasing_vcf = vcf ? "--phasing-vcf ${vcf}" : "" + def pon = pon_path && (!control_input) ? "--PON ${pon_path}" : "" + + """ + severus \\ + $args \\ + --threads $task.cpus \\ + --target-bam $target_input \\ + $vntr_bed \\ + $pon \\ + $control \\ + $phasing_vcf \\ + --whitelist $whitelist \\ + --out-dir ${prefix} + + bgzip ${prefix}/somatic_SVs/severus_somatic.vcf + tabix -p vcf ${prefix}/somatic_SVs/severus_somatic.vcf.gz + bgzip ${prefix}/all_SVs/severus_all.vcf + tabix -p vcf ${prefix}/all_SVs/severus_all.vcf.gz + """ + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + + """ + mkdir -p ${prefix}/all_SVs/plots + mkdir -p ${prefix}/somatic_SVs/plots + + touch ${prefix}/severus_collaped_dup.bed + touch ${prefix}/severus.log + touch ${prefix}/severus_LOH.bed + touch ${prefix}/read_ids.csv + touch ${prefix}/read_qual.txt + touch ${prefix}/breakpoints_double.csv + echo "" | gzip > ${prefix}/all_SVs/severus_all.vcf.gz + touch ${prefix}/all_SVs/breakpoint_clusters_list.tsv + touch ${prefix}/all_SVs/breakpoint_clusters.tsv + touch ${prefix}/all_SVs/plots/severus_0.html + echo "" | gzip > ${prefix}/somatic_SVs/severus_somatic.vcf.gz + touch ${prefix}/somatic_SVs/breakpoint_clusters_list.tsv + touch ${prefix}/somatic_SVs/breakpoint_clusters.tsv + touch ${prefix}/somatic_SVs/plots/severus_0.html + """ +} diff --git a/modules/local/severus/whitelist/meta.yml b/modules/local/severus/whitelist/meta.yml new file mode 100644 index 00000000..2c79fa8d --- /dev/null +++ b/modules/local/severus/whitelist/meta.yml @@ -0,0 +1,287 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "severus_whitelist" +description: Severus somatic SV calling with --whitelist, from a patched Severus 1.7 image + that reports every SV inside the whitelisted regions (container only, no conda) +keywords: + - structural + - variation + - somatic + - germline + - long-read +tools: + - "severus": + description: "A tool for somatic structural variant calling using long reads" + homepage: "https://github.com/KolmogorovLab/Severus" + documentation: "https://github.com/KolmogorovLab/Severus" + tool_dev_url: "https://github.com/KolmogorovLab/Severus" + doi: "10.1101/2024.03.22.24304756" + licence: ["BSD-3-clause"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + - target_input: + type: file + description: path to one or multiple target BAM/CRAM files (e.g. tumor, must + be indexed) + pattern: "*.{bam,cram}" + ontologies: [] + - target_index: + type: file + description: path to one or multiple target BAM/CRAM index files + pattern: "*.{bai,crai,csi}" + ontologies: [] + - control_input: + type: file + description: path to the control BAM/CRAM file (e.g. normal, must be indexed) + pattern: "*.{bam,cram}" + ontologies: [] + - control_index: + type: file + description: path to the control BAM/CRAM file index + pattern: "*.{bai,crai,csi}" + ontologies: [] + - vcf: + type: file + description: path to vcf file used for phasing (if using haplotype specific + SV calling + pattern: "*.{vcf,vcf.gz}" + ontologies: [] + - tbi: + type: file + description: index of the phasing vcf + pattern: "*.tbi" + ontologies: [] + - - meta2: + type: map + description: | + Groovy Map containing tandem repeat regions information + e.g. `[ id:'hg38']` + - bed: + type: file + description: path to bed file for tandem repeat regions (must be ordered) + pattern: "*.bed" + ontologies: [] + - pon_path: + type: file + description: panel of normals, passed as --PON for tumour-only samples only + pattern: "*.tsv.gz" + ontologies: [] + - - meta3: + type: map + description: | + Groovy Map containing whitelist information + e.g. `[:]` + - whitelist: + type: file + description: bed file of regions in which every SV is reported (--whitelist) + pattern: "*.bed" + ontologies: [] +output: + log: + - - meta: + type: map + description: | + Groovy Map containing sample information + pattern: "${prefix}/severus.log" + - ${prefix}/severus.log: + type: file + description: | + log file + pattern: "${prefix}/severus.log" + ontologies: [] + read_qual: + - - meta: + type: map + description: | + Groovy Map containing sample information + pattern: "${prefix}/severus.log" + - ${prefix}/read_qual.txt: + type: file + description: | + txt file containing read quality information + pattern: "${prefix}/read_qual.txt" + ontologies: [] + breakpoints_double: + - - meta: + type: map + description: | + Groovy Map containing sample information + pattern: "${prefix}/severus.log" + - ${prefix}/breakpoints_double.csv: + type: file + description: | + Detailed info about the detected breakpoints for all samples in text format, intended for an advanced user. + pattern: "${prefix}/breakpoints_double.csv" + ontologies: [] + read_alignments: + - - meta: + type: map + description: | + Groovy Map containing sample information + pattern: "${prefix}/severus.log" + - ${prefix}/read_alignments: + type: file + description: | + pattern: "${prefix}/read_alignments" + ontologies: [] + read_ids: + - - meta: + type: map + description: | + Groovy Map containing sample information + pattern: "${prefix}/severus.log" + - ${prefix}/read_ids.csv: + type: file + description: | + Contains supporting read IDs for each SV + pattern: "${prefix}/read_ids" + ontologies: [] + collapsed_dup: + - - meta: + type: map + description: | + Groovy Map containing sample information + pattern: "${prefix}/severus.log" + - ${prefix}/severus_collaped_dup.bed: + type: file + description: | + pattern: "${prefix}/severus_collaped_dup" + ontologies: [] + loh: + - - meta: + type: map + description: | + Groovy Map containing sample information + pattern: "${prefix}/severus.log" + - ${prefix}/severus_LOH.bed: + type: file + description: | + BED file containing loss of heterozygosity information + pattern: "${prefix}/severus_LOH.bed" + ontologies: [] + all_vcf: + - - meta: + type: map + description: | + Groovy Map containing sample information + pattern: "${prefix}/severus.log" + - ${prefix}/all_SVs/severus_all.vcf: + type: map + description: | + VCF file containing somatic and germline structural variants + pattern: "${prefix}/all_SVs/severus_all.vcf" + all_breakpoints_clusters_list: + - - meta: + type: map + description: | + Groovy Map containing sample information + pattern: "${prefix}/severus.log" + - ${prefix}/all_SVs/breakpoints_clusters_list.tsv: + type: file + description: | + a TSV containing a list of all breakpoint clusters + pattern: "${prefix}/all_SVs/breakpoints_clusters_list.tsv" + ontologies: [] + all_breakpoints_clusters: + - - meta: + type: map + description: | + Groovy Map containing sample information + pattern: "${prefix}/severus.log" + - ${prefix}/all_SVs/breakpoints_clusters.tsv: + type: file + description: | + TSV file listing meta information in breakpoint clusters + pattern: "${prefix}/all_SVs/breakpoints_clusters.tsv" + ontologies: [] + all_plots: + - - meta: + type: map + description: | + Groovy Map containing sample information + pattern: "${prefix}/severus.log" + - ${prefix}/all_SVs/plots/severus*.html: + type: file + description: | + Plotly graph containing of somatic and germline breakpoint clusters + pattern: "${prefix}/all_SVs/plots/*.html" + ontologies: [] + somatic_vcf: + - - meta: + type: map + description: | + Groovy Map containing sample information + pattern: "${prefix}/severus.log" + - ${prefix}/somatic_SVs/severus_somatic.vcf: + type: file + description: | + VCF file containing somatic structural variants (SV) + pattern: "${prefix}/somatic_SVs/severus_all.vcf" + ontologies: [] + somatic_breakpoints_clusters_list: + - - meta: + type: map + description: | + Groovy Map containing sample information + pattern: "${prefix}/severus.log" + - ${prefix}/somatic_SVs/breakpoints_clusters_list.tsv: + type: file + description: | + TSV file containing full list of somatic breakpoint clusters + pattern: "${prefix}/somatic_SVs/breakpoints_clusters_list.tsv" + ontologies: [] + somatic_breakpoints_clusters: + - - meta: + type: map + description: | + Groovy Map containing sample information + pattern: "${prefix}/severus.log" + - ${prefix}/somatic_SVs/breakpoints_clusters.tsv: + type: file + description: | + TSV file containing meta information of somatic breakpoint clusters + pattern: "${prefix}/somatic_SVs/breakpoints_clusters.tsv" + ontologies: [] + somatic_plots: + - - meta: + type: map + description: | + Groovy Map containing sample information + pattern: "${prefix}/severus.log" + - ${prefix}/somatic_SVs/plots/severus*.html: + type: file + description: | + Plotly graph of somatic breakpoint clusters + pattern: "${prefix}/somatic_SVs/plots/*.html" + ontologies: [] + versions_severus: + - - ${task.process}: + type: string + description: The name of the process + - severus: + type: string + description: The name of the tool + - echo "$(severus --version)-whitelist-6813dee": + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - severus: + type: string + description: The name of the tool + - echo "$(severus --version)-whitelist-6813dee": + type: eval + description: The expression to obtain the version of the tool +authors: + - "@fellen31" + - "@AmberVerhasselt" +maintainers: + - "@AmberVerhasselt" diff --git a/nextflow.config b/nextflow.config index 628b5ca3..29dd706f 100644 --- a/nextflow.config +++ b/nextflow.config @@ -108,6 +108,8 @@ params { // Severus options severus_minsupport = 3 + severus_whitelist = null // BED of regions where every SV is reported; replaces SEVERUS with SEVERUS_WHITELIST + severus_whitelist_args = null // Savana options savana_pb_minsupport = 10 diff --git a/nextflow_schema.json b/nextflow_schema.json index 47356879..c5dbd40a 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -362,6 +362,19 @@ "severus_minsupport": { "type": "integer", "default": 3 + }, + "severus_whitelist": { + "type": "string", + "format": "file-path", + "exists": true, + "pattern": "^\\S+\\.bed$", + "description": "BED file of regions in which Severus reports every SV (Severus' --whitelist). Setting it replaces the standard Severus run with a patched Severus 1.7 image; its VCFs feed SV VEP, Wakhan and the report. Not available under Conda.", + "help_text": "Inside the whitelisted regions the patched build skips the read-quality, minimum-support and VNTR filters, and keeps single-read junctions only when a reciprocal or independently supported junction corroborates them. Use it for loci such as the IG and TCR genes, where translocations are often carried by few, divergent reads.", + "fa_icon": "fas fa-file-alt" + }, + "severus_whitelist_args": { + "type": "string", + "description": "Extra arguments for the whitelist Severus run, e.g. `--whitelist-single-read any`, `--whitelist-allow-intra-region` or `--write-alignments`. Only used with `--severus_whitelist`." } } }, diff --git a/workflows/lrsomatic.nf b/workflows/lrsomatic.nf index 7f15bcb0..ab629e1d 100644 --- a/workflows/lrsomatic.nf +++ b/workflows/lrsomatic.nf @@ -29,6 +29,7 @@ include { NANOPLOT as NANOPLOT_POST } from '../modules/nf-core/nanoplot/ include { MOSDEPTH } from '../modules/nf-core/mosdepth/main' include { ASCAT } from '../modules/nf-core/ascat/main' include { SEVERUS } from '../modules/nf-core/severus/main.nf' +include { SEVERUS_WHITELIST } from '../modules/local/severus/whitelist/main' include { METAEXTRACT } from '../modules/local/metaextract/main' include { CLAIRSTO_CNA_RESOURCES } from '../modules/local/clairsto/cna_resources/main' include { WAKHAN } from '../modules/local/wakhan/main' @@ -972,18 +973,32 @@ workflow LRSOMATIC { // normal_bam/bai are empty lists [] for tumor-only samples // - // MODULE: SEVERUS (label: process_high) + // MODULE: SEVERUS or SEVERUS_WHITELIST (label: process_medium) // Input: severus_input -- [meta, tumor_bam, tumor_bai, normal_bam, normal_bai, vcf, tbi] // [[:], bed_file, pon_file] -- optional target BED and panel-of-normals for SV filtering - // Output: .all_vcf -- [meta, vcf] -- all somatic SVs (sniffles2 format) + // [[:], whitelist] -- SEVERUS_WHITELIST only: regions in which every SV is reported + // Output: .all_vcf / .somatic_vcf -- [meta, vcf.gz] -- all and somatic SVs + // --severus_whitelist swaps in the patched build; everything downstream reads ch_severus_*_vcf // - SEVERUS ( - severus_input, - [[:], params.bed_file, params.pon_file] - ) + if (params.severus_whitelist) { + SEVERUS_WHITELIST ( + severus_input, + [[:], params.bed_file, params.pon_file], + [[:], file(params.severus_whitelist, checkIfExists: true)] + ) + ch_severus_all_vcf = SEVERUS_WHITELIST.out.all_vcf + ch_severus_somatic_vcf = SEVERUS_WHITELIST.out.somatic_vcf + } else { + SEVERUS ( + severus_input, + [[:], params.bed_file, params.pon_file] + ) + ch_severus_all_vcf = SEVERUS.out.all_vcf + ch_severus_somatic_vcf = SEVERUS.out.somatic_vcf + } - SEVERUS.out.all_vcf + ch_severus_all_vcf .map { meta, vcf -> def extra = [] return [meta, vcf, extra] @@ -1244,7 +1259,7 @@ workflow LRSOMATIC { // Attach SEVERUS SV VCF to the severus_input channel (dropping the phased TBI) severus_input - .join(SEVERUS.out.all_vcf) + .join(ch_severus_all_vcf) .map { meta, tumor_bam, tumor_bai, normal_bam, normal_bai, phased_vcf, _phased_tbi, all_vcf -> return [meta, tumor_bam, tumor_bai, normal_bam, normal_bai, phased_vcf, all_vcf] } @@ -1288,7 +1303,7 @@ workflow LRSOMATIC { .map { meta, vcf -> [meta.id, vcf] } .set { report_sv_vep_ch } - SEVERUS.out.somatic_vcf + ch_severus_somatic_vcf .map { meta, vcf -> [meta.id, vcf] } .set { report_severus_ch } From 393e7cea5a4c4c158b02143408c7949f5a25456a Mon Sep 17 00:00:00 2001 From: Amber Verhasselt Date: Fri, 2 Oct 2026 12:20:59 +0200 Subject: [PATCH 2/5] Address review on #215: module stub test, meta.yml outputs, CHANGELOG, help_text - Add a small-tagged stub nf-test for SEVERUS_WHITELIST (tumour-only and paired), so PR CI pulls the patched image and checks the output declarations and the 1.7-whitelist-6813dee version - meta.yml now lists the outputs main.nf emits (.vcf.gz, breakpoint_clusters*) - Stub writes its gzipped VCFs with gzip -n so the snapshot is reproducible - CHANGELOG entry carries the PR number; severus_whitelist_args gets a help_text Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 2 +- modules/local/severus/whitelist/main.nf | 4 +- modules/local/severus/whitelist/meta.yml | 39 +- .../severus/whitelist/tests/main.nf.test | 85 +++ .../severus/whitelist/tests/main.nf.test.snap | 540 ++++++++++++++++++ nextflow_schema.json | 4 +- 6 files changed, 651 insertions(+), 23 deletions(-) create mode 100644 modules/local/severus/whitelist/tests/main.nf.test create mode 100644 modules/local/severus/whitelist/tests/main.nf.test.snap diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bccfd84..f55a9c27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### `Added` -- Added optional whitelist SV calling: `--severus_whitelist ` replaces `SEVERUS` with `SEVERUS_WHITELIST`, which runs a patched Severus 1.7 image (`docker.io/amberverhasselt/severus:1.7-whitelist-6813dee`) that reports every SV inside the whitelisted regions, past the read-quality, minimum-support and VNTR filters, with corroboration required for single-read junctions. Its VCFs feed SV VEP, Wakhan and the report in place of the standard Severus output. Extra flags go through `--severus_whitelist_args`. Conda is not supported for this step (@AmberVerhasselt). +- [#215](https://github.com/IntGenomicsLab/lrsomatic/pull/215) - Added optional whitelist SV calling: `--severus_whitelist ` replaces `SEVERUS` with `SEVERUS_WHITELIST`, which runs a patched Severus 1.7 image (`docker.io/amberverhasselt/severus:1.7-whitelist-6813dee`) that reports every SV inside the whitelisted regions, past the read-quality, minimum-support and VNTR filters, with corroboration required for single-read junctions. Its VCFs feed SV VEP, Wakhan and the report in place of the standard Severus output. Extra flags go through `--severus_whitelist_args`. Conda is not supported for this step (@AmberVerhasselt). - [#197](https://github.com/IntGenomicsLab/lrsomatic/pull/197) - Added CHM13 support for ClairS-TO's Verdict module, which tags tumour-only calls as germline, somatic or subclonal somatic; its resources were GRCh38-only, so on CHM13 germline variants leaked into `somatic.vcf.gz`. With `--genome CHM13 --skip_ascat` the pipeline builds a CHM13 resource set from the ASCAT files it already downloads and passes it as `--cna_resource_dir`; a prepared directory can be given with `--clairsto_cna_resources` (validated at launch). Without `--skip_ascat` tagging comes from ASCAT's own tables instead (next entry) (@ljwharbers). - [#197](https://github.com/IntGenomicsLab/lrsomatic/pull/197) - Added `CLAIRSTO_VERDICT_TAG`: when ASCAT is in the run, Verdict's germline tagging is computed from ASCAT's purity, ploidy and segments instead of Verdict's own estimate, so `CLAIRSTO` runs with `--disable_verdict` and ASCAT runs before small variant calling. Output names are unchanged. The tables the tags were computed from are published as `_Tumor_Purity_Ploidy.txt` and `_Tumor_CNA.txt`, also on `--skip_ascat` runs (@ljwharbers). - [#197](https://github.com/IntGenomicsLab/lrsomatic/pull/197) - Added a stub nf-test for `TUMORONLY_SMALLVAR` covering both germline tagging paths (tag `small`) (@ljwharbers). diff --git a/modules/local/severus/whitelist/main.nf b/modules/local/severus/whitelist/main.nf index e397426f..7bd26718 100644 --- a/modules/local/severus/whitelist/main.nf +++ b/modules/local/severus/whitelist/main.nf @@ -78,11 +78,11 @@ process SEVERUS_WHITELIST { touch ${prefix}/read_ids.csv touch ${prefix}/read_qual.txt touch ${prefix}/breakpoints_double.csv - echo "" | gzip > ${prefix}/all_SVs/severus_all.vcf.gz + echo "" | gzip -n > ${prefix}/all_SVs/severus_all.vcf.gz touch ${prefix}/all_SVs/breakpoint_clusters_list.tsv touch ${prefix}/all_SVs/breakpoint_clusters.tsv touch ${prefix}/all_SVs/plots/severus_0.html - echo "" | gzip > ${prefix}/somatic_SVs/severus_somatic.vcf.gz + echo "" | gzip -n > ${prefix}/somatic_SVs/severus_somatic.vcf.gz touch ${prefix}/somatic_SVs/breakpoint_clusters_list.tsv touch ${prefix}/somatic_SVs/breakpoint_clusters.tsv touch ${prefix}/somatic_SVs/plots/severus_0.html diff --git a/modules/local/severus/whitelist/meta.yml b/modules/local/severus/whitelist/meta.yml index 2c79fa8d..fa7e177c 100644 --- a/modules/local/severus/whitelist/meta.yml +++ b/modules/local/severus/whitelist/meta.yml @@ -170,34 +170,35 @@ output: description: | Groovy Map containing sample information pattern: "${prefix}/severus.log" - - ${prefix}/all_SVs/severus_all.vcf: - type: map + - ${prefix}/all_SVs/severus_all.vcf.gz: + type: file description: | - VCF file containing somatic and germline structural variants - pattern: "${prefix}/all_SVs/severus_all.vcf" - all_breakpoints_clusters_list: + bgzipped VCF file containing somatic and germline structural variants + pattern: "${prefix}/all_SVs/severus_all.vcf.gz" + ontologies: [] + all_breakpoint_clusters_list: - - meta: type: map description: | Groovy Map containing sample information pattern: "${prefix}/severus.log" - - ${prefix}/all_SVs/breakpoints_clusters_list.tsv: + - ${prefix}/all_SVs/breakpoint_clusters_list.tsv: type: file description: | a TSV containing a list of all breakpoint clusters - pattern: "${prefix}/all_SVs/breakpoints_clusters_list.tsv" + pattern: "${prefix}/all_SVs/breakpoint_clusters_list.tsv" ontologies: [] - all_breakpoints_clusters: + all_breakpoint_clusters: - - meta: type: map description: | Groovy Map containing sample information pattern: "${prefix}/severus.log" - - ${prefix}/all_SVs/breakpoints_clusters.tsv: + - ${prefix}/all_SVs/breakpoint_clusters.tsv: type: file description: | TSV file listing meta information in breakpoint clusters - pattern: "${prefix}/all_SVs/breakpoints_clusters.tsv" + pattern: "${prefix}/all_SVs/breakpoint_clusters.tsv" ontologies: [] all_plots: - - meta: @@ -217,35 +218,35 @@ output: description: | Groovy Map containing sample information pattern: "${prefix}/severus.log" - - ${prefix}/somatic_SVs/severus_somatic.vcf: + - ${prefix}/somatic_SVs/severus_somatic.vcf.gz: type: file description: | - VCF file containing somatic structural variants (SV) - pattern: "${prefix}/somatic_SVs/severus_all.vcf" + bgzipped VCF file containing somatic structural variants (SV) + pattern: "${prefix}/somatic_SVs/severus_somatic.vcf.gz" ontologies: [] - somatic_breakpoints_clusters_list: + somatic_breakpoint_clusters_list: - - meta: type: map description: | Groovy Map containing sample information pattern: "${prefix}/severus.log" - - ${prefix}/somatic_SVs/breakpoints_clusters_list.tsv: + - ${prefix}/somatic_SVs/breakpoint_clusters_list.tsv: type: file description: | TSV file containing full list of somatic breakpoint clusters - pattern: "${prefix}/somatic_SVs/breakpoints_clusters_list.tsv" + pattern: "${prefix}/somatic_SVs/breakpoint_clusters_list.tsv" ontologies: [] - somatic_breakpoints_clusters: + somatic_breakpoint_clusters: - - meta: type: map description: | Groovy Map containing sample information pattern: "${prefix}/severus.log" - - ${prefix}/somatic_SVs/breakpoints_clusters.tsv: + - ${prefix}/somatic_SVs/breakpoint_clusters.tsv: type: file description: | TSV file containing meta information of somatic breakpoint clusters - pattern: "${prefix}/somatic_SVs/breakpoints_clusters.tsv" + pattern: "${prefix}/somatic_SVs/breakpoint_clusters.tsv" ontologies: [] somatic_plots: - - meta: diff --git a/modules/local/severus/whitelist/tests/main.nf.test b/modules/local/severus/whitelist/tests/main.nf.test new file mode 100644 index 00000000..2d7aaeca --- /dev/null +++ b/modules/local/severus/whitelist/tests/main.nf.test @@ -0,0 +1,85 @@ +nextflow_process { + + name "Test Process SEVERUS_WHITELIST" + script "../main.nf" + process "SEVERUS_WHITELIST" + + tag "modules" + tag "modules_local" + tag "severus_whitelist" + tag "small" + + // Severus needs SV-bearing long reads, so only the stub runs in CI. It checks the output declarations + // and pulls the patched image, whose `severus --version` feeds the versions topic. The inputs are + // stand-ins: the stub never reads them. + test("tumour-only - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.sorted.bam.bai', checkIfExists: true), + [], + [], + file("${projectDir}/modules/local/clairsto/verdict_tag/tests/fixtures/snv.vcf.gz", checkIfExists: true), + [] + ] + input[1] = [ [:], [], [] ] + input[2] = [ + [:], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.bed', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert process.out.all_vcf.size() == 1 }, + { assert process.out.somatic_vcf.size() == 1 }, + { assert process.out.versions_severus[0][2] == '1.7-whitelist-6813dee' }, + { assert snapshot(process.out).match() } + ) + } + } + + test("paired - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'test', paired_data:true ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.sorted.bam.bai', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.sorted.bam.bai', checkIfExists: true), + file("${projectDir}/modules/local/clairsto/verdict_tag/tests/fixtures/snv.vcf.gz", checkIfExists: true), + [] + ] + input[1] = [ [:], [], [] ] + input[2] = [ + [:], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.bed', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert process.out.all_vcf.size() == 1 }, + { assert process.out.somatic_vcf.size() == 1 }, + { assert snapshot(process.out).match() } + ) + } + } +} diff --git a/modules/local/severus/whitelist/tests/main.nf.test.snap b/modules/local/severus/whitelist/tests/main.nf.test.snap new file mode 100644 index 00000000..67abe3a2 --- /dev/null +++ b/modules/local/severus/whitelist/tests/main.nf.test.snap @@ -0,0 +1,540 @@ +{ + "tumour-only - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "severus.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "read_qual.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "10": [ + [ + { + "id": "test" + }, + "severus_0.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "11": [ + [ + { + "id": "test" + }, + "severus_somatic.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "12": [ + [ + { + "id": "test" + }, + "breakpoint_clusters_list.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "13": [ + [ + { + "id": "test" + }, + "breakpoint_clusters.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "14": [ + [ + { + "id": "test" + }, + "severus_0.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "15": [ + [ + "SEVERUS_WHITELIST", + "severus", + "1.7-whitelist-6813dee" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "breakpoints_double.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + + ], + "4": [ + [ + { + "id": "test" + }, + "read_ids.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "5": [ + [ + { + "id": "test" + }, + "severus_collaped_dup.bed:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "6": [ + [ + { + "id": "test" + }, + "severus_LOH.bed:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "7": [ + [ + { + "id": "test" + }, + "severus_all.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "8": [ + [ + { + "id": "test" + }, + "breakpoint_clusters_list.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "9": [ + [ + { + "id": "test" + }, + "breakpoint_clusters.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "all_breakpoints_clusters": [ + [ + { + "id": "test" + }, + "breakpoint_clusters.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "all_breakpoints_clusters_list": [ + [ + { + "id": "test" + }, + "breakpoint_clusters_list.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "all_plots": [ + [ + { + "id": "test" + }, + "severus_0.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "all_vcf": [ + [ + { + "id": "test" + }, + "severus_all.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "breakpoints_double": [ + [ + { + "id": "test" + }, + "breakpoints_double.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "collapsed_dup": [ + [ + { + "id": "test" + }, + "severus_collaped_dup.bed:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "log": [ + [ + { + "id": "test" + }, + "severus.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "loh": [ + [ + { + "id": "test" + }, + "severus_LOH.bed:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "read_alignments": [ + + ], + "read_ids": [ + [ + { + "id": "test" + }, + "read_ids.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "read_qual": [ + [ + { + "id": "test" + }, + "read_qual.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "somatic_breakpoints_clusters": [ + [ + { + "id": "test" + }, + "breakpoint_clusters.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "somatic_breakpoints_clusters_list": [ + [ + { + "id": "test" + }, + "breakpoint_clusters_list.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "somatic_plots": [ + [ + { + "id": "test" + }, + "severus_0.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "somatic_vcf": [ + [ + { + "id": "test" + }, + "severus_somatic.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "versions_severus": [ + [ + "SEVERUS_WHITELIST", + "severus", + "1.7-whitelist-6813dee" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.4" + }, + "timestamp": "2026-10-02T12:20:15.380291756" + }, + "paired - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "paired_data": true + }, + "severus.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test", + "paired_data": true + }, + "read_qual.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "10": [ + [ + { + "id": "test", + "paired_data": true + }, + "severus_0.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "11": [ + [ + { + "id": "test", + "paired_data": true + }, + "severus_somatic.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "12": [ + [ + { + "id": "test", + "paired_data": true + }, + "breakpoint_clusters_list.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "13": [ + [ + { + "id": "test", + "paired_data": true + }, + "breakpoint_clusters.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "14": [ + [ + { + "id": "test", + "paired_data": true + }, + "severus_0.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "15": [ + [ + "SEVERUS_WHITELIST", + "severus", + "1.7-whitelist-6813dee" + ] + ], + "2": [ + [ + { + "id": "test", + "paired_data": true + }, + "breakpoints_double.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + + ], + "4": [ + [ + { + "id": "test", + "paired_data": true + }, + "read_ids.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "5": [ + [ + { + "id": "test", + "paired_data": true + }, + "severus_collaped_dup.bed:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "6": [ + [ + { + "id": "test", + "paired_data": true + }, + "severus_LOH.bed:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "7": [ + [ + { + "id": "test", + "paired_data": true + }, + "severus_all.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "8": [ + [ + { + "id": "test", + "paired_data": true + }, + "breakpoint_clusters_list.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "9": [ + [ + { + "id": "test", + "paired_data": true + }, + "breakpoint_clusters.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "all_breakpoints_clusters": [ + [ + { + "id": "test", + "paired_data": true + }, + "breakpoint_clusters.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "all_breakpoints_clusters_list": [ + [ + { + "id": "test", + "paired_data": true + }, + "breakpoint_clusters_list.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "all_plots": [ + [ + { + "id": "test", + "paired_data": true + }, + "severus_0.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "all_vcf": [ + [ + { + "id": "test", + "paired_data": true + }, + "severus_all.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "breakpoints_double": [ + [ + { + "id": "test", + "paired_data": true + }, + "breakpoints_double.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "collapsed_dup": [ + [ + { + "id": "test", + "paired_data": true + }, + "severus_collaped_dup.bed:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "log": [ + [ + { + "id": "test", + "paired_data": true + }, + "severus.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "loh": [ + [ + { + "id": "test", + "paired_data": true + }, + "severus_LOH.bed:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "read_alignments": [ + + ], + "read_ids": [ + [ + { + "id": "test", + "paired_data": true + }, + "read_ids.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "read_qual": [ + [ + { + "id": "test", + "paired_data": true + }, + "read_qual.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "somatic_breakpoints_clusters": [ + [ + { + "id": "test", + "paired_data": true + }, + "breakpoint_clusters.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "somatic_breakpoints_clusters_list": [ + [ + { + "id": "test", + "paired_data": true + }, + "breakpoint_clusters_list.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "somatic_plots": [ + [ + { + "id": "test", + "paired_data": true + }, + "severus_0.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "somatic_vcf": [ + [ + { + "id": "test", + "paired_data": true + }, + "severus_somatic.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "versions_severus": [ + [ + "SEVERUS_WHITELIST", + "severus", + "1.7-whitelist-6813dee" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.4" + }, + "timestamp": "2026-10-02T12:20:22.108817792" + } +} \ No newline at end of file diff --git a/nextflow_schema.json b/nextflow_schema.json index c5dbd40a..8b9d06e1 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -374,7 +374,9 @@ }, "severus_whitelist_args": { "type": "string", - "description": "Extra arguments for the whitelist Severus run, e.g. `--whitelist-single-read any`, `--whitelist-allow-intra-region` or `--write-alignments`. Only used with `--severus_whitelist`." + "description": "Extra arguments for the whitelist Severus run, e.g. `--whitelist-single-read any`, `--whitelist-allow-intra-region` or `--write-alignments`. Only used with `--severus_whitelist`.", + "help_text": "Appended after `--min-support --output-read-ids`, so a later flag wins where Severus allows it. Ignored unless `--severus_whitelist` is set. `--whitelist-single-read` takes `reciprocal` (default), `any` or `off`; `--write-alignments` additionally publishes a `read_alignments` file.", + "fa_icon": "fas fa-terminal" } } }, From 38e5191aad07b54d9cd8aa09f091012aac55a516 Mon Sep 17 00:00:00 2001 From: Amber Verhasselt Date: Fri, 2 Oct 2026 21:28:51 +0200 Subject: [PATCH 3/5] Fold SEVERUS_WHITELIST into SEVERUS (optional whitelist input) SEVERUS_WHITELIST duplicated the patched nf-core SEVERUS module except for its container, a whitelist input, a conda guard and a version suffix. SEVERUS now always runs the patched Severus 1.7 image (oras://docker.io/amberverhasselt/severus-sif:1.7-whitelist-6813dee) and takes an optional third input [meta, whitelist] that adds --whitelist when set. - modules/nf-core/severus: whitelist input, patched container; severus.diff regenerated - workflow: single SEVERUS call; whitelist passed as [] when --severus_whitelist is unset - conf/modules.config: SEVERUS_WHITELIST block dropped; severus_whitelist_args appended to SEVERUS ext.args only with --severus_whitelist - --severus_whitelist under conda/mamba now errors at startup (validateSeverusWhitelist) - modules/local/severus/whitelist removed; docs, schema text and CHANGELOG updated Checked on FL3 (T2T, tumour-only): without a whitelist the patched image gives identical VCF records to stock 1.7 (header gains one WL_RESCUE INFO line); with TIER1_NHL_genes_T2T_v2.bed it calls the reciprocal IGH-BCL2 t(14;18) as PASS. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 2 +- conf/modules.config | 13 +- docs/output.md | 2 +- docs/usage.md | 12 +- modules/local/severus/whitelist/main.nf | 90 --- modules/local/severus/whitelist/meta.yml | 288 ---------- .../severus/whitelist/tests/main.nf.test | 85 --- .../severus/whitelist/tests/main.nf.test.snap | 540 ------------------ modules/nf-core/severus/main.nf | 10 +- modules/nf-core/severus/severus.diff | 21 +- nextflow.config | 2 +- nextflow_schema.json | 2 +- .../utils_nfcore_lrsomatic_pipeline/main.nf | 10 + workflows/lrsomatic.nf | 33 +- 14 files changed, 56 insertions(+), 1054 deletions(-) delete mode 100644 modules/local/severus/whitelist/main.nf delete mode 100644 modules/local/severus/whitelist/meta.yml delete mode 100644 modules/local/severus/whitelist/tests/main.nf.test delete mode 100644 modules/local/severus/whitelist/tests/main.nf.test.snap diff --git a/CHANGELOG.md b/CHANGELOG.md index f55a9c27..82fabb82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### `Added` -- [#215](https://github.com/IntGenomicsLab/lrsomatic/pull/215) - Added optional whitelist SV calling: `--severus_whitelist ` replaces `SEVERUS` with `SEVERUS_WHITELIST`, which runs a patched Severus 1.7 image (`docker.io/amberverhasselt/severus:1.7-whitelist-6813dee`) that reports every SV inside the whitelisted regions, past the read-quality, minimum-support and VNTR filters, with corroboration required for single-read junctions. Its VCFs feed SV VEP, Wakhan and the report in place of the standard Severus output. Extra flags go through `--severus_whitelist_args`. Conda is not supported for this step (@AmberVerhasselt). +- [#215](https://github.com/IntGenomicsLab/lrsomatic/pull/215) - Added optional whitelist SV calling: `SEVERUS` now runs a patched Severus 1.7 image (`oras://docker.io/amberverhasselt/severus-sif:1.7-whitelist-6813dee`), whose output without a whitelist is identical to stock 1.7, and takes an optional whitelist BED: `--severus_whitelist ` reports every SV inside the whitelisted regions, past the read-quality, minimum-support and VNTR filters, with corroboration required for single-read junctions. Extra flags go through `--severus_whitelist_args`. `--severus_whitelist` is not supported under Conda (@AmberVerhasselt). - [#197](https://github.com/IntGenomicsLab/lrsomatic/pull/197) - Added CHM13 support for ClairS-TO's Verdict module, which tags tumour-only calls as germline, somatic or subclonal somatic; its resources were GRCh38-only, so on CHM13 germline variants leaked into `somatic.vcf.gz`. With `--genome CHM13 --skip_ascat` the pipeline builds a CHM13 resource set from the ASCAT files it already downloads and passes it as `--cna_resource_dir`; a prepared directory can be given with `--clairsto_cna_resources` (validated at launch). Without `--skip_ascat` tagging comes from ASCAT's own tables instead (next entry) (@ljwharbers). - [#197](https://github.com/IntGenomicsLab/lrsomatic/pull/197) - Added `CLAIRSTO_VERDICT_TAG`: when ASCAT is in the run, Verdict's germline tagging is computed from ASCAT's purity, ploidy and segments instead of Verdict's own estimate, so `CLAIRSTO` runs with `--disable_verdict` and ASCAT runs before small variant calling. Output names are unchanged. The tables the tags were computed from are published as `_Tumor_Purity_Ploidy.txt` and `_Tumor_CNA.txt`, also on `--skip_ascat` runs (@ljwharbers). - [#197](https://github.com/IntGenomicsLab/lrsomatic/pull/197) - Added a stub nf-test for `TUMORONLY_SMALLVAR` covering both germline tagging paths (tag `small`) (@ljwharbers). diff --git a/conf/modules.config b/conf/modules.config index 9155b147..b1b62f65 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -425,18 +425,7 @@ process { withName: '.*:SEVERUS' { ext.prefix = "." - ext.args = { "--min-support ${params.severus_minsupport} --output-read-ids " } - publishDir = [ - path: { "${params.outdir}/${meta.id}/variants/severus" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - - // Replaces SEVERUS when --severus_whitelist is set, so it publishes to the same place - withName: '.*:SEVERUS_WHITELIST' { - ext.prefix = "." - ext.args = { "--min-support ${params.severus_minsupport} --output-read-ids ${params.severus_whitelist_args ?: ''}" } + ext.args = { "--min-support ${params.severus_minsupport} --output-read-ids ${params.severus_whitelist ? (params.severus_whitelist_args ?: '') : ''}" } publishDir = [ path: { "${params.outdir}/${meta.id}/variants/severus" }, mode: params.publish_dir_mode, diff --git a/docs/output.md b/docs/output.md index 1ca5ce7e..344b7924 100644 --- a/docs/output.md +++ b/docs/output.md @@ -358,7 +358,7 @@ The germline/somatic split comes from a panel of normals and from ClairS-TO's Ve | `read_qual.txt` | file containing quality statistics about identified segements | | `severus.log` | log file | -With `--severus_whitelist` the same files are published here by `SEVERUS_WHITELIST`. Passing `--write-alignments` through `--severus_whitelist_args` adds a `read_alignments` file. +With `--severus_whitelist` the same files are published here. Passing `--write-alignments` through `--severus_whitelist_args` adds a `read_alignments` file. #### `savana` diff --git a/docs/usage.md b/docs/usage.md index 5cd3750c..0424a445 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -270,13 +270,13 @@ opt-in. See [VEP plugins](#vep-plugins) for sizes, licence terms and per-assembl #### SEVERUS Options -| Parameter | Description | -| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `--severus_minsupport` | Minimum number of supporting reads required for SEVERUS to call an SV. Default = `3` | -| `--severus_whitelist` | BED file of regions in which every SV is reported (Severus `--whitelist`). Setting it replaces the standard Severus run with `SEVERUS_WHITELIST`, whose VCFs feed SV VEP, Wakhan and the report. Default = off | -| `--severus_whitelist_args` | Extra arguments for `SEVERUS_WHITELIST`, e.g. `--whitelist-single-read any`, `--whitelist-allow-intra-region` or `--write-alignments`. Default = none | +| Parameter | Description | +| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `--severus_minsupport` | Minimum number of supporting reads required for SEVERUS to call an SV. Default = `3` | +| `--severus_whitelist` | BED file of regions in which every SV is reported (Severus `--whitelist`). Passed to `SEVERUS` as `--whitelist`; its VCFs feed SV VEP, Wakhan and the report as usual. Default = off | +| `--severus_whitelist_args` | Extra arguments for `SEVERUS`, only used with `--severus_whitelist`, e.g. `--whitelist-single-read any`, `--whitelist-allow-intra-region` or `--write-alignments`. Default = none | -`SEVERUS_WHITELIST` runs a patched Severus build ([AmberVerhasselt/Severus](https://github.com/AmberVerhasselt/Severus/tree/whitelist-reciprocal-corroboration)) from `docker.io/amberverhasselt/severus` (`oras://docker.io/amberverhasselt/severus-sif` under Singularity/Apptainer). Inside the whitelisted regions it skips the read-quality, minimum-support and VNTR filters, and keeps a junction supported by a single read only when a reciprocal or independently supported junction corroborates it. This is meant for loci such as the IG and TCR genes, where translocations are often carried by few, divergent reads. **Conda is not supported** for this step. +`SEVERUS` runs a patched Severus 1.7 build ([AmberVerhasselt/Severus](https://github.com/AmberVerhasselt/Severus/tree/whitelist-reciprocal-corroboration)) from `oras://docker.io/amberverhasselt/severus-sif` (`docker.io/amberverhasselt/severus` under Docker); without `--severus_whitelist` its output is identical to stock Severus 1.7. Inside the whitelisted regions it skips the read-quality, minimum-support and VNTR filters, and keeps a junction supported by a single read only when a reciprocal or independently supported junction corroborates it. This is meant for loci such as the IG and TCR genes, where translocations are often carried by few, divergent reads. **`--severus_whitelist` is not supported under Conda**, which installs stock Severus. #### SAVANA Options diff --git a/modules/local/severus/whitelist/main.nf b/modules/local/severus/whitelist/main.nf deleted file mode 100644 index 7bd26718..00000000 --- a/modules/local/severus/whitelist/main.nf +++ /dev/null @@ -1,90 +0,0 @@ -process SEVERUS_WHITELIST { - tag "$meta.id" - label 'process_medium' - - // No conda: the --whitelist fixes exist only in the patched image, and bioconda's severus would silently drop them - // Severus 1.7 + github.com/AmberVerhasselt/Severus/tree/whitelist-reciprocal-corroboration (6813dee); revert to the biocontainer once KolmogorovLab/Severus carries it - container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container - ? 'oras://docker.io/amberverhasselt/severus-sif:1.7-whitelist-6813dee' - : 'docker.io/amberverhasselt/severus:1.7-whitelist-6813dee'}" - - input: - tuple val(meta), path(target_input), path(target_index), path(control_input), path(control_index), path(vcf), path(tbi) - tuple val(meta2), path(bed), path(pon_path) - tuple val(meta3), path(whitelist) - - output: - tuple val(meta), path("${prefix}/severus.log") , emit: log - tuple val(meta), path("${prefix}/read_qual.txt") , emit: read_qual - tuple val(meta), path("${prefix}/breakpoints_double.csv") , emit: breakpoints_double - tuple val(meta), path("${prefix}/read_alignments") , emit: read_alignments , optional: true - tuple val(meta), path("${prefix}/read_ids.csv") , emit: read_ids , optional: true - tuple val(meta), path("${prefix}/severus_collaped_dup.bed") , emit: collapsed_dup , optional: true - tuple val(meta), path("${prefix}/severus_LOH.bed") , emit: loh , optional: true - tuple val(meta), path("${prefix}/all_SVs/severus_all.vcf.gz") , emit: all_vcf , optional: true - tuple val(meta), path("${prefix}/all_SVs/breakpoint_clusters_list.tsv") , emit: all_breakpoints_clusters_list , optional: true - tuple val(meta), path("${prefix}/all_SVs/breakpoint_clusters.tsv") , emit: all_breakpoints_clusters , optional: true - tuple val(meta), path("${prefix}/all_SVs/plots/severus*.html") , emit: all_plots , optional: true - tuple val(meta), path("${prefix}/somatic_SVs/severus_somatic.vcf.gz") , emit: somatic_vcf , optional: true - tuple val(meta), path("${prefix}/somatic_SVs/breakpoint_clusters_list.tsv"), emit: somatic_breakpoints_clusters_list, optional: true - tuple val(meta), path("${prefix}/somatic_SVs/breakpoint_clusters.tsv") , emit: somatic_breakpoints_clusters , optional: true - tuple val(meta), path("${prefix}/somatic_SVs/plots/severus*.html") , emit: somatic_plots , optional: true - // The -whitelist suffix keeps the patched build distinguishable from stock severus in the versions report - tuple val("${task.process}"), val('severus'), eval('echo "$(severus --version)-whitelist-6813dee"'), emit: versions_severus, topic: versions - - when: - task.ext.when == null || task.ext.when - - script: - if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { - error "SEVERUS_WHITELIST does not support Conda. Please use Docker / Singularity / Apptainer instead, or run without --severus_whitelist." - } - def args = task.ext.args ?: '' - prefix = task.ext.prefix ?: "${meta.id}" - - def control = control_input ? "--control-bam ${control_input}" : "" - def vntr_bed = bed ? "--vntr-bed ${bed}" : "" - def phasing_vcf = vcf ? "--phasing-vcf ${vcf}" : "" - def pon = pon_path && (!control_input) ? "--PON ${pon_path}" : "" - - """ - severus \\ - $args \\ - --threads $task.cpus \\ - --target-bam $target_input \\ - $vntr_bed \\ - $pon \\ - $control \\ - $phasing_vcf \\ - --whitelist $whitelist \\ - --out-dir ${prefix} - - bgzip ${prefix}/somatic_SVs/severus_somatic.vcf - tabix -p vcf ${prefix}/somatic_SVs/severus_somatic.vcf.gz - bgzip ${prefix}/all_SVs/severus_all.vcf - tabix -p vcf ${prefix}/all_SVs/severus_all.vcf.gz - """ - - stub: - prefix = task.ext.prefix ?: "${meta.id}" - - """ - mkdir -p ${prefix}/all_SVs/plots - mkdir -p ${prefix}/somatic_SVs/plots - - touch ${prefix}/severus_collaped_dup.bed - touch ${prefix}/severus.log - touch ${prefix}/severus_LOH.bed - touch ${prefix}/read_ids.csv - touch ${prefix}/read_qual.txt - touch ${prefix}/breakpoints_double.csv - echo "" | gzip -n > ${prefix}/all_SVs/severus_all.vcf.gz - touch ${prefix}/all_SVs/breakpoint_clusters_list.tsv - touch ${prefix}/all_SVs/breakpoint_clusters.tsv - touch ${prefix}/all_SVs/plots/severus_0.html - echo "" | gzip -n > ${prefix}/somatic_SVs/severus_somatic.vcf.gz - touch ${prefix}/somatic_SVs/breakpoint_clusters_list.tsv - touch ${prefix}/somatic_SVs/breakpoint_clusters.tsv - touch ${prefix}/somatic_SVs/plots/severus_0.html - """ -} diff --git a/modules/local/severus/whitelist/meta.yml b/modules/local/severus/whitelist/meta.yml deleted file mode 100644 index fa7e177c..00000000 --- a/modules/local/severus/whitelist/meta.yml +++ /dev/null @@ -1,288 +0,0 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json -name: "severus_whitelist" -description: Severus somatic SV calling with --whitelist, from a patched Severus 1.7 image - that reports every SV inside the whitelisted regions (container only, no conda) -keywords: - - structural - - variation - - somatic - - germline - - long-read -tools: - - "severus": - description: "A tool for somatic structural variant calling using long reads" - homepage: "https://github.com/KolmogorovLab/Severus" - documentation: "https://github.com/KolmogorovLab/Severus" - tool_dev_url: "https://github.com/KolmogorovLab/Severus" - doi: "10.1101/2024.03.22.24304756" - licence: ["BSD-3-clause"] - identifier: "" - -input: - - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. `[ id:'sample1', single_end:false ]` - - target_input: - type: file - description: path to one or multiple target BAM/CRAM files (e.g. tumor, must - be indexed) - pattern: "*.{bam,cram}" - ontologies: [] - - target_index: - type: file - description: path to one or multiple target BAM/CRAM index files - pattern: "*.{bai,crai,csi}" - ontologies: [] - - control_input: - type: file - description: path to the control BAM/CRAM file (e.g. normal, must be indexed) - pattern: "*.{bam,cram}" - ontologies: [] - - control_index: - type: file - description: path to the control BAM/CRAM file index - pattern: "*.{bai,crai,csi}" - ontologies: [] - - vcf: - type: file - description: path to vcf file used for phasing (if using haplotype specific - SV calling - pattern: "*.{vcf,vcf.gz}" - ontologies: [] - - tbi: - type: file - description: index of the phasing vcf - pattern: "*.tbi" - ontologies: [] - - - meta2: - type: map - description: | - Groovy Map containing tandem repeat regions information - e.g. `[ id:'hg38']` - - bed: - type: file - description: path to bed file for tandem repeat regions (must be ordered) - pattern: "*.bed" - ontologies: [] - - pon_path: - type: file - description: panel of normals, passed as --PON for tumour-only samples only - pattern: "*.tsv.gz" - ontologies: [] - - - meta3: - type: map - description: | - Groovy Map containing whitelist information - e.g. `[:]` - - whitelist: - type: file - description: bed file of regions in which every SV is reported (--whitelist) - pattern: "*.bed" - ontologies: [] -output: - log: - - - meta: - type: map - description: | - Groovy Map containing sample information - pattern: "${prefix}/severus.log" - - ${prefix}/severus.log: - type: file - description: | - log file - pattern: "${prefix}/severus.log" - ontologies: [] - read_qual: - - - meta: - type: map - description: | - Groovy Map containing sample information - pattern: "${prefix}/severus.log" - - ${prefix}/read_qual.txt: - type: file - description: | - txt file containing read quality information - pattern: "${prefix}/read_qual.txt" - ontologies: [] - breakpoints_double: - - - meta: - type: map - description: | - Groovy Map containing sample information - pattern: "${prefix}/severus.log" - - ${prefix}/breakpoints_double.csv: - type: file - description: | - Detailed info about the detected breakpoints for all samples in text format, intended for an advanced user. - pattern: "${prefix}/breakpoints_double.csv" - ontologies: [] - read_alignments: - - - meta: - type: map - description: | - Groovy Map containing sample information - pattern: "${prefix}/severus.log" - - ${prefix}/read_alignments: - type: file - description: | - pattern: "${prefix}/read_alignments" - ontologies: [] - read_ids: - - - meta: - type: map - description: | - Groovy Map containing sample information - pattern: "${prefix}/severus.log" - - ${prefix}/read_ids.csv: - type: file - description: | - Contains supporting read IDs for each SV - pattern: "${prefix}/read_ids" - ontologies: [] - collapsed_dup: - - - meta: - type: map - description: | - Groovy Map containing sample information - pattern: "${prefix}/severus.log" - - ${prefix}/severus_collaped_dup.bed: - type: file - description: | - pattern: "${prefix}/severus_collaped_dup" - ontologies: [] - loh: - - - meta: - type: map - description: | - Groovy Map containing sample information - pattern: "${prefix}/severus.log" - - ${prefix}/severus_LOH.bed: - type: file - description: | - BED file containing loss of heterozygosity information - pattern: "${prefix}/severus_LOH.bed" - ontologies: [] - all_vcf: - - - meta: - type: map - description: | - Groovy Map containing sample information - pattern: "${prefix}/severus.log" - - ${prefix}/all_SVs/severus_all.vcf.gz: - type: file - description: | - bgzipped VCF file containing somatic and germline structural variants - pattern: "${prefix}/all_SVs/severus_all.vcf.gz" - ontologies: [] - all_breakpoint_clusters_list: - - - meta: - type: map - description: | - Groovy Map containing sample information - pattern: "${prefix}/severus.log" - - ${prefix}/all_SVs/breakpoint_clusters_list.tsv: - type: file - description: | - a TSV containing a list of all breakpoint clusters - pattern: "${prefix}/all_SVs/breakpoint_clusters_list.tsv" - ontologies: [] - all_breakpoint_clusters: - - - meta: - type: map - description: | - Groovy Map containing sample information - pattern: "${prefix}/severus.log" - - ${prefix}/all_SVs/breakpoint_clusters.tsv: - type: file - description: | - TSV file listing meta information in breakpoint clusters - pattern: "${prefix}/all_SVs/breakpoint_clusters.tsv" - ontologies: [] - all_plots: - - - meta: - type: map - description: | - Groovy Map containing sample information - pattern: "${prefix}/severus.log" - - ${prefix}/all_SVs/plots/severus*.html: - type: file - description: | - Plotly graph containing of somatic and germline breakpoint clusters - pattern: "${prefix}/all_SVs/plots/*.html" - ontologies: [] - somatic_vcf: - - - meta: - type: map - description: | - Groovy Map containing sample information - pattern: "${prefix}/severus.log" - - ${prefix}/somatic_SVs/severus_somatic.vcf.gz: - type: file - description: | - bgzipped VCF file containing somatic structural variants (SV) - pattern: "${prefix}/somatic_SVs/severus_somatic.vcf.gz" - ontologies: [] - somatic_breakpoint_clusters_list: - - - meta: - type: map - description: | - Groovy Map containing sample information - pattern: "${prefix}/severus.log" - - ${prefix}/somatic_SVs/breakpoint_clusters_list.tsv: - type: file - description: | - TSV file containing full list of somatic breakpoint clusters - pattern: "${prefix}/somatic_SVs/breakpoint_clusters_list.tsv" - ontologies: [] - somatic_breakpoint_clusters: - - - meta: - type: map - description: | - Groovy Map containing sample information - pattern: "${prefix}/severus.log" - - ${prefix}/somatic_SVs/breakpoint_clusters.tsv: - type: file - description: | - TSV file containing meta information of somatic breakpoint clusters - pattern: "${prefix}/somatic_SVs/breakpoint_clusters.tsv" - ontologies: [] - somatic_plots: - - - meta: - type: map - description: | - Groovy Map containing sample information - pattern: "${prefix}/severus.log" - - ${prefix}/somatic_SVs/plots/severus*.html: - type: file - description: | - Plotly graph of somatic breakpoint clusters - pattern: "${prefix}/somatic_SVs/plots/*.html" - ontologies: [] - versions_severus: - - - ${task.process}: - type: string - description: The name of the process - - severus: - type: string - description: The name of the tool - - echo "$(severus --version)-whitelist-6813dee": - type: eval - description: The expression to obtain the version of the tool -topics: - versions: - - - ${task.process}: - type: string - description: The name of the process - - severus: - type: string - description: The name of the tool - - echo "$(severus --version)-whitelist-6813dee": - type: eval - description: The expression to obtain the version of the tool -authors: - - "@fellen31" - - "@AmberVerhasselt" -maintainers: - - "@AmberVerhasselt" diff --git a/modules/local/severus/whitelist/tests/main.nf.test b/modules/local/severus/whitelist/tests/main.nf.test deleted file mode 100644 index 2d7aaeca..00000000 --- a/modules/local/severus/whitelist/tests/main.nf.test +++ /dev/null @@ -1,85 +0,0 @@ -nextflow_process { - - name "Test Process SEVERUS_WHITELIST" - script "../main.nf" - process "SEVERUS_WHITELIST" - - tag "modules" - tag "modules_local" - tag "severus_whitelist" - tag "small" - - // Severus needs SV-bearing long reads, so only the stub runs in CI. It checks the output declarations - // and pulls the patched image, whose `severus --version` feeds the versions topic. The inputs are - // stand-ins: the stub never reads them. - test("tumour-only - stub") { - - options "-stub" - - when { - process { - """ - input[0] = [ - [ id:'test' ], - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.sorted.bam.bai', checkIfExists: true), - [], - [], - file("${projectDir}/modules/local/clairsto/verdict_tag/tests/fixtures/snv.vcf.gz", checkIfExists: true), - [] - ] - input[1] = [ [:], [], [] ] - input[2] = [ - [:], - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.bed', checkIfExists: true) - ] - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert process.out.all_vcf.size() == 1 }, - { assert process.out.somatic_vcf.size() == 1 }, - { assert process.out.versions_severus[0][2] == '1.7-whitelist-6813dee' }, - { assert snapshot(process.out).match() } - ) - } - } - - test("paired - stub") { - - options "-stub" - - when { - process { - """ - input[0] = [ - [ id:'test', paired_data:true ], - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.sorted.bam.bai', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.sorted.bam', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.sorted.bam.bai', checkIfExists: true), - file("${projectDir}/modules/local/clairsto/verdict_tag/tests/fixtures/snv.vcf.gz", checkIfExists: true), - [] - ] - input[1] = [ [:], [], [] ] - input[2] = [ - [:], - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.bed', checkIfExists: true) - ] - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert process.out.all_vcf.size() == 1 }, - { assert process.out.somatic_vcf.size() == 1 }, - { assert snapshot(process.out).match() } - ) - } - } -} diff --git a/modules/local/severus/whitelist/tests/main.nf.test.snap b/modules/local/severus/whitelist/tests/main.nf.test.snap deleted file mode 100644 index 67abe3a2..00000000 --- a/modules/local/severus/whitelist/tests/main.nf.test.snap +++ /dev/null @@ -1,540 +0,0 @@ -{ - "tumour-only - stub": { - "content": [ - { - "0": [ - [ - { - "id": "test" - }, - "severus.log:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "test" - }, - "read_qual.txt:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "10": [ - [ - { - "id": "test" - }, - "severus_0.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "11": [ - [ - { - "id": "test" - }, - "severus_somatic.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" - ] - ], - "12": [ - [ - { - "id": "test" - }, - "breakpoint_clusters_list.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "13": [ - [ - { - "id": "test" - }, - "breakpoint_clusters.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "14": [ - [ - { - "id": "test" - }, - "severus_0.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "15": [ - [ - "SEVERUS_WHITELIST", - "severus", - "1.7-whitelist-6813dee" - ] - ], - "2": [ - [ - { - "id": "test" - }, - "breakpoints_double.csv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "3": [ - - ], - "4": [ - [ - { - "id": "test" - }, - "read_ids.csv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "5": [ - [ - { - "id": "test" - }, - "severus_collaped_dup.bed:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "6": [ - [ - { - "id": "test" - }, - "severus_LOH.bed:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "7": [ - [ - { - "id": "test" - }, - "severus_all.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" - ] - ], - "8": [ - [ - { - "id": "test" - }, - "breakpoint_clusters_list.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "9": [ - [ - { - "id": "test" - }, - "breakpoint_clusters.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "all_breakpoints_clusters": [ - [ - { - "id": "test" - }, - "breakpoint_clusters.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "all_breakpoints_clusters_list": [ - [ - { - "id": "test" - }, - "breakpoint_clusters_list.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "all_plots": [ - [ - { - "id": "test" - }, - "severus_0.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "all_vcf": [ - [ - { - "id": "test" - }, - "severus_all.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" - ] - ], - "breakpoints_double": [ - [ - { - "id": "test" - }, - "breakpoints_double.csv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "collapsed_dup": [ - [ - { - "id": "test" - }, - "severus_collaped_dup.bed:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "log": [ - [ - { - "id": "test" - }, - "severus.log:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "loh": [ - [ - { - "id": "test" - }, - "severus_LOH.bed:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "read_alignments": [ - - ], - "read_ids": [ - [ - { - "id": "test" - }, - "read_ids.csv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "read_qual": [ - [ - { - "id": "test" - }, - "read_qual.txt:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "somatic_breakpoints_clusters": [ - [ - { - "id": "test" - }, - "breakpoint_clusters.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "somatic_breakpoints_clusters_list": [ - [ - { - "id": "test" - }, - "breakpoint_clusters_list.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "somatic_plots": [ - [ - { - "id": "test" - }, - "severus_0.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "somatic_vcf": [ - [ - { - "id": "test" - }, - "severus_somatic.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" - ] - ], - "versions_severus": [ - [ - "SEVERUS_WHITELIST", - "severus", - "1.7-whitelist-6813dee" - ] - ] - } - ], - "meta": { - "nf-test": "0.9.3", - "nextflow": "25.10.4" - }, - "timestamp": "2026-10-02T12:20:15.380291756" - }, - "paired - stub": { - "content": [ - { - "0": [ - [ - { - "id": "test", - "paired_data": true - }, - "severus.log:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "test", - "paired_data": true - }, - "read_qual.txt:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "10": [ - [ - { - "id": "test", - "paired_data": true - }, - "severus_0.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "11": [ - [ - { - "id": "test", - "paired_data": true - }, - "severus_somatic.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" - ] - ], - "12": [ - [ - { - "id": "test", - "paired_data": true - }, - "breakpoint_clusters_list.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "13": [ - [ - { - "id": "test", - "paired_data": true - }, - "breakpoint_clusters.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "14": [ - [ - { - "id": "test", - "paired_data": true - }, - "severus_0.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "15": [ - [ - "SEVERUS_WHITELIST", - "severus", - "1.7-whitelist-6813dee" - ] - ], - "2": [ - [ - { - "id": "test", - "paired_data": true - }, - "breakpoints_double.csv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "3": [ - - ], - "4": [ - [ - { - "id": "test", - "paired_data": true - }, - "read_ids.csv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "5": [ - [ - { - "id": "test", - "paired_data": true - }, - "severus_collaped_dup.bed:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "6": [ - [ - { - "id": "test", - "paired_data": true - }, - "severus_LOH.bed:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "7": [ - [ - { - "id": "test", - "paired_data": true - }, - "severus_all.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" - ] - ], - "8": [ - [ - { - "id": "test", - "paired_data": true - }, - "breakpoint_clusters_list.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "9": [ - [ - { - "id": "test", - "paired_data": true - }, - "breakpoint_clusters.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "all_breakpoints_clusters": [ - [ - { - "id": "test", - "paired_data": true - }, - "breakpoint_clusters.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "all_breakpoints_clusters_list": [ - [ - { - "id": "test", - "paired_data": true - }, - "breakpoint_clusters_list.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "all_plots": [ - [ - { - "id": "test", - "paired_data": true - }, - "severus_0.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "all_vcf": [ - [ - { - "id": "test", - "paired_data": true - }, - "severus_all.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" - ] - ], - "breakpoints_double": [ - [ - { - "id": "test", - "paired_data": true - }, - "breakpoints_double.csv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "collapsed_dup": [ - [ - { - "id": "test", - "paired_data": true - }, - "severus_collaped_dup.bed:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "log": [ - [ - { - "id": "test", - "paired_data": true - }, - "severus.log:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "loh": [ - [ - { - "id": "test", - "paired_data": true - }, - "severus_LOH.bed:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "read_alignments": [ - - ], - "read_ids": [ - [ - { - "id": "test", - "paired_data": true - }, - "read_ids.csv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "read_qual": [ - [ - { - "id": "test", - "paired_data": true - }, - "read_qual.txt:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "somatic_breakpoints_clusters": [ - [ - { - "id": "test", - "paired_data": true - }, - "breakpoint_clusters.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "somatic_breakpoints_clusters_list": [ - [ - { - "id": "test", - "paired_data": true - }, - "breakpoint_clusters_list.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "somatic_plots": [ - [ - { - "id": "test", - "paired_data": true - }, - "severus_0.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "somatic_vcf": [ - [ - { - "id": "test", - "paired_data": true - }, - "severus_somatic.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" - ] - ], - "versions_severus": [ - [ - "SEVERUS_WHITELIST", - "severus", - "1.7-whitelist-6813dee" - ] - ] - } - ], - "meta": { - "nf-test": "0.9.3", - "nextflow": "25.10.4" - }, - "timestamp": "2026-10-02T12:20:22.108817792" - } -} \ No newline at end of file diff --git a/modules/nf-core/severus/main.nf b/modules/nf-core/severus/main.nf index f37891c7..47a0863f 100644 --- a/modules/nf-core/severus/main.nf +++ b/modules/nf-core/severus/main.nf @@ -3,13 +3,17 @@ process SEVERUS { label 'process_medium' conda "${moduleDir}/environment.yml" + // Patched Severus 1.7 (github.com/AmberVerhasselt/Severus/tree/whitelist-reciprocal-corroboration, 6813dee) carries the --whitelist fixes; + // without --whitelist its output is identical to stock 1.7. Revert to the biocontainer once KolmogorovLab/Severus carries the fixes. + // Conda installs stock bioconda Severus, so --severus_whitelist is refused under conda at startup (utils_nfcore_lrsomatic_pipeline). container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? - 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/8f/8fd0858ee067f8b95246e57683a7431bc126929b4203fbda8e315cd94d2570ad/data': - 'community.wave.seqera.io/library/severus:1.7--d16d59609ef4ee7d' }" + 'oras://docker.io/amberverhasselt/severus-sif:1.7-whitelist-6813dee': + 'docker.io/amberverhasselt/severus:1.7-whitelist-6813dee' }" input: tuple val(meta), path(target_input), path(target_index), path(control_input), path(control_index), path(vcf), path(tbi) tuple val(meta2), path(bed), path(pon_path) + tuple val(meta3), path(whitelist) output: tuple val(meta), path("${prefix}/severus.log") , emit: log @@ -39,6 +43,7 @@ process SEVERUS { def vntr_bed = bed ? "--vntr-bed ${bed}" : "" def phasing_vcf = vcf ? "--phasing-vcf ${vcf}" : "" def pon = pon_path && (!control_input) ? "--PON ${pon_path}" : "" + def whitelist_bed = whitelist ? "--whitelist ${whitelist}" : "" """ severus \\ @@ -49,6 +54,7 @@ process SEVERUS { $pon \\ $control \\ $phasing_vcf \\ + $whitelist_bed \\ --out-dir ${prefix} bgzip ${prefix}/somatic_SVs/severus_somatic.vcf diff --git a/modules/nf-core/severus/severus.diff b/modules/nf-core/severus/severus.diff index 133f591c..ca04e159 100644 --- a/modules/nf-core/severus/severus.diff +++ b/modules/nf-core/severus/severus.diff @@ -4,18 +4,29 @@ Changes in component 'nf-core/severus' Changes in 'severus/main.nf': --- modules/nf-core/severus/main.nf +++ modules/nf-core/severus/main.nf -@@ -8,8 +8,8 @@ - 'community.wave.seqera.io/library/severus:1.7--d16d59609ef4ee7d' }" +@@ -3,13 +3,17 @@ + label 'process_medium' + + conda "${moduleDir}/environment.yml" ++ // Patched Severus 1.7 (github.com/AmberVerhasselt/Severus/tree/whitelist-reciprocal-corroboration, 6813dee) carries the --whitelist fixes; ++ // without --whitelist its output is identical to stock 1.7. Revert to the biocontainer once KolmogorovLab/Severus carries the fixes. ++ // Conda installs stock bioconda Severus, so --severus_whitelist is refused under conda at startup (utils_nfcore_lrsomatic_pipeline). + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? +- 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/8f/8fd0858ee067f8b95246e57683a7431bc126929b4203fbda8e315cd94d2570ad/data': +- 'community.wave.seqera.io/library/severus:1.7--d16d59609ef4ee7d' }" ++ 'oras://docker.io/amberverhasselt/severus-sif:1.7-whitelist-6813dee': ++ 'docker.io/amberverhasselt/severus:1.7-whitelist-6813dee' }" input: - tuple val(meta), path(target_input), path(target_index), path(control_input), path(control_index), path(vcf) - tuple val(meta2), path(bed) + tuple val(meta), path(target_input), path(target_index), path(control_input), path(control_index), path(vcf), path(tbi) + tuple val(meta2), path(bed), path(pon_path) ++ tuple val(meta3), path(whitelist) output: tuple val(meta), path("${prefix}/severus.log") , emit: log -@@ -19,13 +19,13 @@ +@@ -19,13 +23,13 @@ tuple val(meta), path("${prefix}/read_ids.csv") , emit: read_ids , optional: true tuple val(meta), path("${prefix}/severus_collaped_dup.bed") , emit: collapsed_dup , optional: true tuple val(meta), path("${prefix}/severus_LOH.bed") , emit: loh , optional: true @@ -35,11 +46,12 @@ Changes in 'severus/main.nf': tuple val(meta), path("${prefix}/somatic_SVs/plots/severus*.html") , emit: somatic_plots , optional: true tuple val("${task.process}"), val('severus'), eval("severus --version"), emit: versions_severus, topic: versions when: -@@ -38,15 +38,23 @@ +@@ -38,15 +42,25 @@ def control = control_input ? "--control-bam ${control_input}" : "" def vntr_bed = bed ? "--vntr-bed ${bed}" : "" def phasing_vcf = vcf ? "--phasing-vcf ${vcf}" : "" + def pon = pon_path && (!control_input) ? "--PON ${pon_path}" : "" ++ def whitelist_bed = whitelist ? "--whitelist ${whitelist}" : "" + """ severus \\ @@ -50,6 +62,7 @@ Changes in 'severus/main.nf': + $pon \\ $control \\ $phasing_vcf \\ ++ $whitelist_bed \\ --out-dir ${prefix} + + bgzip ${prefix}/somatic_SVs/severus_somatic.vcf diff --git a/nextflow.config b/nextflow.config index 29dd706f..14c282e2 100644 --- a/nextflow.config +++ b/nextflow.config @@ -108,7 +108,7 @@ params { // Severus options severus_minsupport = 3 - severus_whitelist = null // BED of regions where every SV is reported; replaces SEVERUS with SEVERUS_WHITELIST + severus_whitelist = null // BED of regions where every SV is reported (Severus --whitelist) severus_whitelist_args = null // Savana options diff --git a/nextflow_schema.json b/nextflow_schema.json index 8b9d06e1..19f53448 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -368,7 +368,7 @@ "format": "file-path", "exists": true, "pattern": "^\\S+\\.bed$", - "description": "BED file of regions in which Severus reports every SV (Severus' --whitelist). Setting it replaces the standard Severus run with a patched Severus 1.7 image; its VCFs feed SV VEP, Wakhan and the report. Not available under Conda.", + "description": "BED file of regions in which Severus reports every SV (Severus' --whitelist). Passed to Severus as --whitelist; its VCFs feed SV VEP, Wakhan and the report as usual. Not available under Conda.", "help_text": "Inside the whitelisted regions the patched build skips the read-quality, minimum-support and VNTR filters, and keeps single-read junctions only when a reciprocal or independently supported junction corroborates them. Use it for loci such as the IG and TCR genes, where translocations are often carried by few, divergent reads.", "fa_icon": "fas fa-file-alt" }, diff --git a/subworkflows/local/utils_nfcore_lrsomatic_pipeline/main.nf b/subworkflows/local/utils_nfcore_lrsomatic_pipeline/main.nf index bc8c7da8..f913a622 100644 --- a/subworkflows/local/utils_nfcore_lrsomatic_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_lrsomatic_pipeline/main.nf @@ -239,6 +239,16 @@ workflow PIPELINE_COMPLETION { def validateInputParameters() { genomeExistsError() validateReportGenePanels() + validateSeverusWhitelist() +} + +// +// The --whitelist fixes exist only in the patched Severus image; bioconda's severus would run the unfixed whitelist logic +// +def validateSeverusWhitelist() { + if (params.severus_whitelist && workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error("--severus_whitelist: not supported under Conda/Mamba, which installs stock Severus without the whitelist fixes. Use Docker / Singularity / Apptainer, or run without --severus_whitelist.") + } } // diff --git a/workflows/lrsomatic.nf b/workflows/lrsomatic.nf index ab629e1d..74bf4235 100644 --- a/workflows/lrsomatic.nf +++ b/workflows/lrsomatic.nf @@ -29,7 +29,6 @@ include { NANOPLOT as NANOPLOT_POST } from '../modules/nf-core/nanoplot/ include { MOSDEPTH } from '../modules/nf-core/mosdepth/main' include { ASCAT } from '../modules/nf-core/ascat/main' include { SEVERUS } from '../modules/nf-core/severus/main.nf' -include { SEVERUS_WHITELIST } from '../modules/local/severus/whitelist/main' include { METAEXTRACT } from '../modules/local/metaextract/main' include { CLAIRSTO_CNA_RESOURCES } from '../modules/local/clairsto/cna_resources/main' include { WAKHAN } from '../modules/local/wakhan/main' @@ -973,32 +972,20 @@ workflow LRSOMATIC { // normal_bam/bai are empty lists [] for tumor-only samples // - // MODULE: SEVERUS or SEVERUS_WHITELIST (label: process_medium) + // MODULE: SEVERUS (label: process_medium) // Input: severus_input -- [meta, tumor_bam, tumor_bai, normal_bam, normal_bai, vcf, tbi] // [[:], bed_file, pon_file] -- optional target BED and panel-of-normals for SV filtering - // [[:], whitelist] -- SEVERUS_WHITELIST only: regions in which every SV is reported + // [[:], whitelist] -- optional (--severus_whitelist): regions in which every SV is reported // Output: .all_vcf / .somatic_vcf -- [meta, vcf.gz] -- all and somatic SVs - // --severus_whitelist swaps in the patched build; everything downstream reads ch_severus_*_vcf // - if (params.severus_whitelist) { - SEVERUS_WHITELIST ( - severus_input, - [[:], params.bed_file, params.pon_file], - [[:], file(params.severus_whitelist, checkIfExists: true)] - ) - ch_severus_all_vcf = SEVERUS_WHITELIST.out.all_vcf - ch_severus_somatic_vcf = SEVERUS_WHITELIST.out.somatic_vcf - } else { - SEVERUS ( - severus_input, - [[:], params.bed_file, params.pon_file] - ) - ch_severus_all_vcf = SEVERUS.out.all_vcf - ch_severus_somatic_vcf = SEVERUS.out.somatic_vcf - } + SEVERUS ( + severus_input, + [[:], params.bed_file, params.pon_file], + [[:], params.severus_whitelist ? file(params.severus_whitelist, checkIfExists: true) : []] + ) - ch_severus_all_vcf + SEVERUS.out.all_vcf .map { meta, vcf -> def extra = [] return [meta, vcf, extra] @@ -1259,7 +1246,7 @@ workflow LRSOMATIC { // Attach SEVERUS SV VCF to the severus_input channel (dropping the phased TBI) severus_input - .join(ch_severus_all_vcf) + .join(SEVERUS.out.all_vcf) .map { meta, tumor_bam, tumor_bai, normal_bam, normal_bai, phased_vcf, _phased_tbi, all_vcf -> return [meta, tumor_bam, tumor_bai, normal_bam, normal_bai, phased_vcf, all_vcf] } @@ -1303,7 +1290,7 @@ workflow LRSOMATIC { .map { meta, vcf -> [meta.id, vcf] } .set { report_sv_vep_ch } - ch_severus_somatic_vcf + SEVERUS.out.somatic_vcf .map { meta, vcf -> [meta.id, vcf] } .set { report_severus_ch } From 9016b747914fe99b7a40781eea4acc13a1b68e99 Mon Sep 17 00:00:00 2001 From: Amber Verhasselt Date: Fri, 2 Oct 2026 21:56:43 +0200 Subject: [PATCH 4/5] Address review on #215: meta.yml inputs, module tests, small whitelist test - modules/nf-core/severus/meta.yml: document the tbi, pon_path and optional whitelist inputs; output paths now match main.nf (.vcf.gz, breakpoint_clusters*) - modules/nf-core/severus/tests: add the tbi, PON and whitelist slots so every case matches the module's inputs (these tests stay ignored by nf-test.config) - tests/severus_whitelist.nf.test: small-tagged, real Severus on the nf-core nanopore BAMs, paired, with and without --whitelist; asserts --whitelist reaches the command only when given, snapshots the VCF records and version (1.7). Default filters give 0 records; the whitelist rescues 21. - severus.diff regenerated to cover meta.yml and the module tests Co-Authored-By: Claude Opus 5.5 (1M context) --- modules/nf-core/severus/main.nf | 3 +- modules/nf-core/severus/meta.yml | 51 +++-- modules/nf-core/severus/severus.diff | 206 ++++++++++++++++++++- modules/nf-core/severus/tests/main.nf.test | 25 ++- tests/severus_whitelist.nf.test | 91 +++++++++ tests/severus_whitelist.nf.test.snap | 74 ++++++++ 6 files changed, 421 insertions(+), 29 deletions(-) create mode 100644 tests/severus_whitelist.nf.test create mode 100644 tests/severus_whitelist.nf.test.snap diff --git a/modules/nf-core/severus/main.nf b/modules/nf-core/severus/main.nf index 47a0863f..430d7cde 100644 --- a/modules/nf-core/severus/main.nf +++ b/modules/nf-core/severus/main.nf @@ -4,7 +4,8 @@ process SEVERUS { conda "${moduleDir}/environment.yml" // Patched Severus 1.7 (github.com/AmberVerhasselt/Severus/tree/whitelist-reciprocal-corroboration, 6813dee) carries the --whitelist fixes; - // without --whitelist its output is identical to stock 1.7. Revert to the biocontainer once KolmogorovLab/Severus carries the fixes. + // without --whitelist its VCF records are identical to stock 1.7 (the header adds a WL_RESCUE INFO line). Revert to the biocontainer once + // KolmogorovLab/Severus carries the fixes. // Conda installs stock bioconda Severus, so --severus_whitelist is refused under conda at startup (utils_nfcore_lrsomatic_pipeline). container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? 'oras://docker.io/amberverhasselt/severus-sif:1.7-whitelist-6813dee': diff --git a/modules/nf-core/severus/meta.yml b/modules/nf-core/severus/meta.yml index f03b19cd..80c8c559 100644 --- a/modules/nf-core/severus/meta.yml +++ b/modules/nf-core/severus/meta.yml @@ -51,6 +51,11 @@ input: specific SV calling pattern: "*.{vcf,vcf.gz}" ontologies: [] + - tbi: + type: file + description: index of the phasing vcf + pattern: "*.tbi" + ontologies: [] - - meta2: type: map description: | @@ -61,6 +66,22 @@ input: description: path to bed file for tandem repeat regions (must be ordered) pattern: "*.bed" ontologies: [] + - pon_path: + type: file + description: panel of normals, passed as --PON for tumour-only samples only + pattern: "*.tsv.gz" + ontologies: [] + - - meta3: + type: map + description: | + Groovy Map containing whitelist information + e.g. `[:]` + - whitelist: + type: file + description: optional bed file of regions in which every SV is reported (--whitelist); + needs the patched Severus image + pattern: "*.bed" + ontologies: [] output: log: - - meta: @@ -144,33 +165,33 @@ output: type: map description: | Groovy Map containing sample information - - ${prefix}/all_SVs/severus_all.vcf: - type: map + - ${prefix}/all_SVs/severus_all.vcf.gz: + type: file description: | - VCF file containing somatic and germline structural variants - pattern: "${prefix}/all_SVs/severus_all.vcf" + bgzipped VCF file containing somatic and germline structural variants + pattern: "${prefix}/all_SVs/severus_all.vcf.gz" ontologies: [] all_breakpoints_clusters_list: - - meta: type: map description: | Groovy Map containing sample information - - ${prefix}/all_SVs/breakpoints_clusters_list.tsv: + - ${prefix}/all_SVs/breakpoint_clusters_list.tsv: type: file description: | a TSV containing a list of all breakpoint clusters - pattern: "${prefix}/all_SVs/breakpoints_clusters_list.tsv" + pattern: "${prefix}/all_SVs/breakpoint_clusters_list.tsv" ontologies: [] all_breakpoints_clusters: - - meta: type: map description: | Groovy Map containing sample information - - ${prefix}/all_SVs/breakpoints_clusters.tsv: + - ${prefix}/all_SVs/breakpoint_clusters.tsv: type: file description: | TSV file listing meta information in breakpoint clusters - pattern: "${prefix}/all_SVs/breakpoints_clusters.tsv" + pattern: "${prefix}/all_SVs/breakpoint_clusters.tsv" ontologies: [] all_plots: - - meta: @@ -188,33 +209,33 @@ output: type: map description: | Groovy Map containing sample information - - ${prefix}/somatic_SVs/severus_somatic.vcf: + - ${prefix}/somatic_SVs/severus_somatic.vcf.gz: type: file description: | - VCF file containing somatic structural variants (SV) - pattern: "${prefix}/somatic_SVs/severus_all.vcf" + bgzipped VCF file containing somatic structural variants (SV) + pattern: "${prefix}/somatic_SVs/severus_somatic.vcf.gz" ontologies: [] somatic_breakpoints_clusters_list: - - meta: type: map description: | Groovy Map containing sample information - - ${prefix}/somatic_SVs/breakpoints_clusters_list.tsv: + - ${prefix}/somatic_SVs/breakpoint_clusters_list.tsv: type: file description: | TSV file containing full list of somatic breakpoint clusters - pattern: "${prefix}/somatic_SVs/breakpoints_clusters_list.tsv" + pattern: "${prefix}/somatic_SVs/breakpoint_clusters_list.tsv" ontologies: [] somatic_breakpoints_clusters: - - meta: type: map description: | Groovy Map containing sample information - - ${prefix}/somatic_SVs/breakpoints_clusters.tsv: + - ${prefix}/somatic_SVs/breakpoint_clusters.tsv: type: file description: | TSV file containing meta information of somatic breakpoint clusters - pattern: "${prefix}/somatic_SVs/breakpoints_clusters.tsv" + pattern: "${prefix}/somatic_SVs/breakpoint_clusters.tsv" ontologies: [] somatic_plots: - - meta: diff --git a/modules/nf-core/severus/severus.diff b/modules/nf-core/severus/severus.diff index ca04e159..3c405689 100644 --- a/modules/nf-core/severus/severus.diff +++ b/modules/nf-core/severus/severus.diff @@ -1,15 +1,137 @@ Changes in component 'nf-core/severus' 'modules/nf-core/severus/environment.yml' is unchanged -'modules/nf-core/severus/meta.yml' is unchanged +Changes in 'severus/meta.yml': +--- modules/nf-core/severus/meta.yml ++++ modules/nf-core/severus/meta.yml +@@ -51,6 +51,11 @@ + specific SV calling + pattern: "*.{vcf,vcf.gz}" + ontologies: [] ++ - tbi: ++ type: file ++ description: index of the phasing vcf ++ pattern: "*.tbi" ++ ontologies: [] + - - meta2: + type: map + description: | +@@ -61,6 +66,22 @@ + description: path to bed file for tandem repeat regions (must be ordered) + pattern: "*.bed" + ontologies: [] ++ - pon_path: ++ type: file ++ description: panel of normals, passed as --PON for tumour-only samples only ++ pattern: "*.tsv.gz" ++ ontologies: [] ++ - - meta3: ++ type: map ++ description: | ++ Groovy Map containing whitelist information ++ e.g. `[:]` ++ - whitelist: ++ type: file ++ description: optional bed file of regions in which every SV is reported (--whitelist); ++ needs the patched Severus image ++ pattern: "*.bed" ++ ontologies: [] + output: + log: + - - meta: +@@ -144,33 +165,33 @@ + type: map + description: | + Groovy Map containing sample information +- - ${prefix}/all_SVs/severus_all.vcf: +- type: map ++ - ${prefix}/all_SVs/severus_all.vcf.gz: ++ type: file + description: | +- VCF file containing somatic and germline structural variants +- pattern: "${prefix}/all_SVs/severus_all.vcf" ++ bgzipped VCF file containing somatic and germline structural variants ++ pattern: "${prefix}/all_SVs/severus_all.vcf.gz" + ontologies: [] + all_breakpoints_clusters_list: + - - meta: + type: map + description: | + Groovy Map containing sample information +- - ${prefix}/all_SVs/breakpoints_clusters_list.tsv: ++ - ${prefix}/all_SVs/breakpoint_clusters_list.tsv: + type: file + description: | + a TSV containing a list of all breakpoint clusters +- pattern: "${prefix}/all_SVs/breakpoints_clusters_list.tsv" ++ pattern: "${prefix}/all_SVs/breakpoint_clusters_list.tsv" + ontologies: [] + all_breakpoints_clusters: + - - meta: + type: map + description: | + Groovy Map containing sample information +- - ${prefix}/all_SVs/breakpoints_clusters.tsv: ++ - ${prefix}/all_SVs/breakpoint_clusters.tsv: + type: file + description: | + TSV file listing meta information in breakpoint clusters +- pattern: "${prefix}/all_SVs/breakpoints_clusters.tsv" ++ pattern: "${prefix}/all_SVs/breakpoint_clusters.tsv" + ontologies: [] + all_plots: + - - meta: +@@ -188,33 +209,33 @@ + type: map + description: | + Groovy Map containing sample information +- - ${prefix}/somatic_SVs/severus_somatic.vcf: ++ - ${prefix}/somatic_SVs/severus_somatic.vcf.gz: + type: file + description: | +- VCF file containing somatic structural variants (SV) +- pattern: "${prefix}/somatic_SVs/severus_all.vcf" ++ bgzipped VCF file containing somatic structural variants (SV) ++ pattern: "${prefix}/somatic_SVs/severus_somatic.vcf.gz" + ontologies: [] + somatic_breakpoints_clusters_list: + - - meta: + type: map + description: | + Groovy Map containing sample information +- - ${prefix}/somatic_SVs/breakpoints_clusters_list.tsv: ++ - ${prefix}/somatic_SVs/breakpoint_clusters_list.tsv: + type: file + description: | + TSV file containing full list of somatic breakpoint clusters +- pattern: "${prefix}/somatic_SVs/breakpoints_clusters_list.tsv" ++ pattern: "${prefix}/somatic_SVs/breakpoint_clusters_list.tsv" + ontologies: [] + somatic_breakpoints_clusters: + - - meta: + type: map + description: | + Groovy Map containing sample information +- - ${prefix}/somatic_SVs/breakpoints_clusters.tsv: ++ - ${prefix}/somatic_SVs/breakpoint_clusters.tsv: + type: file + description: | + TSV file containing meta information of somatic breakpoint clusters +- pattern: "${prefix}/somatic_SVs/breakpoints_clusters.tsv" ++ pattern: "${prefix}/somatic_SVs/breakpoint_clusters.tsv" + ontologies: [] + somatic_plots: + - - meta: + Changes in 'severus/main.nf': --- modules/nf-core/severus/main.nf +++ modules/nf-core/severus/main.nf -@@ -3,13 +3,17 @@ +@@ -3,13 +3,18 @@ label 'process_medium' conda "${moduleDir}/environment.yml" + // Patched Severus 1.7 (github.com/AmberVerhasselt/Severus/tree/whitelist-reciprocal-corroboration, 6813dee) carries the --whitelist fixes; -+ // without --whitelist its output is identical to stock 1.7. Revert to the biocontainer once KolmogorovLab/Severus carries the fixes. ++ // without --whitelist its VCF records are identical to stock 1.7 (the header adds a WL_RESCUE INFO line). Revert to the biocontainer once ++ // KolmogorovLab/Severus carries the fixes. + // Conda installs stock bioconda Severus, so --severus_whitelist is refused under conda at startup (utils_nfcore_lrsomatic_pipeline). container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? - 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/8f/8fd0858ee067f8b95246e57683a7431bc126929b4203fbda8e315cd94d2570ad/data': @@ -26,7 +148,7 @@ Changes in 'severus/main.nf': output: tuple val(meta), path("${prefix}/severus.log") , emit: log -@@ -19,13 +23,13 @@ +@@ -19,13 +24,13 @@ tuple val(meta), path("${prefix}/read_ids.csv") , emit: read_ids , optional: true tuple val(meta), path("${prefix}/severus_collaped_dup.bed") , emit: collapsed_dup , optional: true tuple val(meta), path("${prefix}/severus_LOH.bed") , emit: loh , optional: true @@ -46,7 +168,7 @@ Changes in 'severus/main.nf': tuple val(meta), path("${prefix}/somatic_SVs/plots/severus*.html") , emit: somatic_plots , optional: true tuple val("${task.process}"), val('severus'), eval("severus --version"), emit: versions_severus, topic: versions when: -@@ -38,15 +42,25 @@ +@@ -38,15 +43,25 @@ def control = control_input ? "--control-bam ${control_input}" : "" def vntr_bed = bed ? "--vntr-bed ${bed}" : "" def phasing_vcf = vcf ? "--phasing-vcf ${vcf}" : "" @@ -74,6 +196,78 @@ Changes in 'severus/main.nf': stub: 'modules/nf-core/severus/tests/nextflow.config' is unchanged -'modules/nf-core/severus/tests/main.nf.test' is unchanged +Changes in 'severus/tests/main.nf.test': +--- modules/nf-core/severus/tests/main.nf.test ++++ modules/nf-core/severus/tests/main.nf.test +@@ -21,9 +21,11 @@ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test2.sorted.bam.bai', checkIfExists: true), + [], + [], ++ [], + [] +- ] +- input[1] = [[],[]] ++ ] ++ input[1] = [[],[],[]] ++ input[2] = [[:],[]] + """ + } + } +@@ -60,9 +62,11 @@ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test2.sorted.bam.bai', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test.sorted.phased.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test.sorted.phased.bam.bai', checkIfExists: true), ++ [], + [] + ] +- input[1] = [[],[]] ++ input[1] = [[],[],[]] ++ input[2] = [[:],[]] + """ + } + } +@@ -103,8 +107,10 @@ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test.sorted.phased.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test.sorted.phased.bam.bai', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/gvcf/test.genome.vcf', checkIfExists: true), ++ [] + ] +- input[1] = [[],[]] ++ input[1] = [[],[],[]] ++ input[2] = [[:],[]] + """ + } + } +@@ -145,11 +151,14 @@ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test.sorted.phased.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test.sorted.phased.bam.bai', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/gvcf/test.genome.vcf', checkIfExists: true), ++ [] + ] + input[1] = [ + [ id:'bed'], // meta map +- file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.blacklist_intervals.bed', checkIfExists: true) ++ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.blacklist_intervals.bed', checkIfExists: true), ++ [] + ] ++ input[2] = [[:],[]] + """ + } + } +@@ -189,9 +198,11 @@ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test.sorted.phased.bam.bai', checkIfExists: true), + [], + [], ++ [], + [] +- ] +- input[1] = [[],[]] ++ ] ++ input[1] = [[],[],[]] ++ input[2] = [[:],[]] + """ + } + } + 'modules/nf-core/severus/tests/main.nf.test.snap' is unchanged ************************************************************ diff --git a/modules/nf-core/severus/tests/main.nf.test b/modules/nf-core/severus/tests/main.nf.test index a6ab42ed..43211dc4 100644 --- a/modules/nf-core/severus/tests/main.nf.test +++ b/modules/nf-core/severus/tests/main.nf.test @@ -21,9 +21,11 @@ nextflow_process { file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test2.sorted.bam.bai', checkIfExists: true), [], [], + [], [] - ] - input[1] = [[],[]] + ] + input[1] = [[],[],[]] + input[2] = [[:],[]] """ } } @@ -60,9 +62,11 @@ nextflow_process { file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test2.sorted.bam.bai', checkIfExists: true), file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test.sorted.phased.bam', checkIfExists: true), file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test.sorted.phased.bam.bai', checkIfExists: true), + [], [] ] - input[1] = [[],[]] + input[1] = [[],[],[]] + input[2] = [[:],[]] """ } } @@ -103,8 +107,10 @@ nextflow_process { file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test.sorted.phased.bam', checkIfExists: true), file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test.sorted.phased.bam.bai', checkIfExists: true), file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/gvcf/test.genome.vcf', checkIfExists: true), + [] ] - input[1] = [[],[]] + input[1] = [[],[],[]] + input[2] = [[:],[]] """ } } @@ -145,11 +151,14 @@ nextflow_process { file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test.sorted.phased.bam', checkIfExists: true), file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test.sorted.phased.bam.bai', checkIfExists: true), file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/gvcf/test.genome.vcf', checkIfExists: true), + [] ] input[1] = [ [ id:'bed'], // meta map - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.blacklist_intervals.bed', checkIfExists: true) + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.blacklist_intervals.bed', checkIfExists: true), + [] ] + input[2] = [[:],[]] """ } } @@ -189,9 +198,11 @@ nextflow_process { file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test.sorted.phased.bam.bai', checkIfExists: true), [], [], + [], [] - ] - input[1] = [[],[]] + ] + input[1] = [[],[],[]] + input[2] = [[:],[]] """ } } diff --git a/tests/severus_whitelist.nf.test b/tests/severus_whitelist.nf.test new file mode 100644 index 00000000..a0686ab6 --- /dev/null +++ b/tests/severus_whitelist.nf.test @@ -0,0 +1,91 @@ +nextflow_process { + + name "Test Process SEVERUS with and without --whitelist" + script "../modules/nf-core/severus/main.nf" + process "SEVERUS" + + tag "modules" + tag "severus" + tag "small" + + // Lives here rather than in modules/nf-core/severus/tests, which nf-test.config ignores, so PR CI runs it. + // Paired, because without a control or PON Severus writes no somatic_SVs and the module's bgzip step fails. + // Real Severus on the nf-core nanopore test BAMs: checks that the patched image runs, that --whitelist reaches + // the command only when a whitelist is given, and that the bgzipped VCFs are emitted either way. + test("paired - no whitelist") { + + when { + process { + """ + input[0] = [ + [ id:'test', paired_data:true ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test2.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test2.sorted.bam.bai', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test.sorted.phased.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test.sorted.phased.bam.bai', checkIfExists: true), + [], + [] + ] + input[1] = [ [:], [], [] ] + input[2] = [ [:], [] ] + """ + } + } + + then { + def cmd = file(process.out.log[0][1]).readLines().find { it.contains('Cmd:') } + assertAll( + { assert process.success }, + { assert !cmd.contains('--whitelist') }, + { assert file(process.out.log[0][1]).readLines().last().contains('Writing vcf') }, + { assert snapshot( + path(process.out.all_vcf[0][1]).linesGzip.findAll { !it.startsWith('#') }, + path(process.out.somatic_vcf[0][1]).linesGzip.findAll { !it.startsWith('#') }, + file(process.out.all_vcf[0][1]).name, + file(process.out.somatic_vcf[0][1]).name, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + } + + test("paired - whitelist") { + + when { + process { + """ + input[0] = [ + [ id:'test', paired_data:true ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test2.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test2.sorted.bam.bai', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test.sorted.phased.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test.sorted.phased.bam.bai', checkIfExists: true), + [], + [] + ] + input[1] = [ [:], [], [] ] + input[2] = [ + [:], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.bed', checkIfExists: true) + ] + """ + } + } + + then { + def cmd = file(process.out.log[0][1]).readLines().find { it.contains('Cmd:') } + assertAll( + { assert process.success }, + { assert cmd.contains('--whitelist genome.bed') }, + { assert file(process.out.log[0][1]).readLines().last().contains('Writing vcf') }, + { assert snapshot( + path(process.out.all_vcf[0][1]).linesGzip.findAll { !it.startsWith('#') }, + path(process.out.somatic_vcf[0][1]).linesGzip.findAll { !it.startsWith('#') }, + file(process.out.all_vcf[0][1]).name, + file(process.out.somatic_vcf[0][1]).name, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + } +} diff --git a/tests/severus_whitelist.nf.test.snap b/tests/severus_whitelist.nf.test.snap new file mode 100644 index 00000000..0fa7f2a7 --- /dev/null +++ b/tests/severus_whitelist.nf.test.snap @@ -0,0 +1,74 @@ +{ + "paired - whitelist": { + "content": [ + [ + "chr22\t10108\tseverus_BND0_1\tN\t]chr22:18016]N\t13.0\tFAIL_MAP_CONS\tPRECISE;SVTYPE=BND;SVLEN=7908;MATE_ID=severus_BND0_2;BND_TYPE=DUP_LIKE;STRANDS=-+;MAPQ=13.0;PHASESETID=0|0;HP=0|0;SUPP_READS=2:0:0:2:0:0;REF_READS=10:5:3:14:6:4;CLUSTERID=severus_0;INSIDE_WHITELIST=TRUE;WL_RESCUE=WHITELIST\tGT:VAF:hVAF:DR:DV\t./.:0:0,0,0:38:0\t0/1:0.09:0.14,0.00,0.00:21:2", + "chr22\t10116\tseverus_BND1_1\tN\t]chr22:15265]N\t9.5\tFAIL_MAP_CONS\tPRECISE;SVTYPE=BND;SVLEN=5149;MATE_ID=severus_BND1_2;BND_TYPE=DUP_LIKE;STRANDS=-+;MAPQ=9.5;PHASESETID=0|0;HP=0|0;SUPP_READS=2:0:0:2:0:0;REF_READS=10:5:3:2:6:4;CLUSTERID=severus_0;INSIDE_WHITELIST=TRUE;WL_RESCUE=WHITELIST\tGT:VAF:hVAF:DR:DV\t./.:0:0,0,0:38:0\t0/1:0.12:0.25,0.00,0.00:15:2", + "chr22\t10126\tseverus_BND2_1\tN\t[chr22:13460[N\t12.5\tFAIL_MAP_CONS\tPRECISE;SVTYPE=BND;SVLEN=3334;MATE_ID=severus_BND2_2;BND_TYPE=INV_LIKE;STRANDS=--;MAPQ=12.5;PHASESETID=0|0;HP=0|0;SUPP_READS=2:0:0:2:0:0;REF_READS=10:5:3:29:6:3;CLUSTERID=severus_0;INSIDE_WHITELIST=TRUE;WL_RESCUE=WHITELIST\tGT:VAF:hVAF:DR:DV\t./.:0:0,0,0:38:0\t0/1:0.07:0.10,0.00,0.00:28:2", + "chr22\t13433\tseverus_INS10\tN\tTGGAAGATGCTGTGGTGAGTCAGTGAGTGTGAAGGCATAGGACCTTACTGTACACTACTGTAGACTTTATAAACACCATATGCTTAGGCTACACCAAAAATTTTT\t36.0\tPASS\tPRECISE;SVTYPE=INS;SVLEN=105;MAPQ=36.0;SUPP_READS=2:0:0:2:0:0;REF_READS=6:6:3:6:6:3;INSIDE_WHITELIST=TRUE;WL_RESCUE=WHITELIST\tGT:VAF:hVAF:DR:DV\t./.:0:0,0,0:24:0\t0/1:0.13:0.33,0.00,0.00:13:2", + "chr22\t13460\tseverus_BND2_2\tN\t[chr22:10126[N\t12.5\tFAIL_MAP_CONS\tPRECISE;SVTYPE=BND;SVLEN=3334;MATE_ID=severus_BND2_1;BND_TYPE=INV_LIKE;STRANDS=--;MAPQ=12.5;PHASESETID=0|0;HP=0|0;SUPP_READS=2:0:0:2:0:0;REF_READS=29:6:3:10:5:3;CLUSTERID=severus_0;INSIDE_WHITELIST=TRUE;WL_RESCUE=WHITELIST\tGT:VAF:hVAF:DR:DV\t./.:0:0,0,0:26:0\t0/1:0.07:0.10,0.00,0.00:28:2", + "chr22\t13466\tseverus_BND3_1\tN\t]chr22:14087]N\t12.5\tFAIL_MAP_CONS\tPRECISE;SVTYPE=BND;SVLEN=621;MATE_ID=severus_BND3_2;BND_TYPE=DUP_LIKE;STRANDS=-+;MAPQ=12.5;PHASESETID=0|0;HP=0|0;SUPP_READS=3:0:0:3:0:0;REF_READS=29:6:3:17:6:3;CLUSTERID=severus_0;INSIDE_WHITELIST=TRUE;WL_RESCUE=WHITELIST\tGT:VAF:hVAF:DR:DV\t./.:0:0,0,0:26:0\t0/1:0.09:0.12,0.00,0.00:32:3", + "chr22\t13466\tseverus_BND4_1\tN\t]chr22:20713]N\t7.5\tFAIL_MAP_CONS\tPRECISE;SVTYPE=BND;SVLEN=7247;MATE_ID=severus_BND4_2;BND_TYPE=DUP_LIKE;STRANDS=-+;MAPQ=7.5;PHASESETID=0|0;HP=0|0;SUPP_READS=3:0:0:3:0:0;REF_READS=29:6:3:4:6:4;CLUSTERID=severus_0;INSIDE_WHITELIST=TRUE;WL_RESCUE=WHITELIST\tGT:VAF:hVAF:DR:DV\t./.:0:0,0,0:26:0\t0/1:0.10:0.16,0.00,0.00:26:3", + "chr22\t14087\tseverus_BND3_2\tN\tN[chr22:13466[\t12.5\tFAIL_MAP_CONS\tPRECISE;SVTYPE=BND;SVLEN=621;MATE_ID=severus_BND3_1;BND_TYPE=DUP_LIKE;STRANDS=+-;MAPQ=12.5;PHASESETID=0|0;HP=0|0;SUPP_READS=3:0:0:3:0:0;REF_READS=17:6:3:29:6:3;CLUSTERID=severus_0;INSIDE_WHITELIST=TRUE;WL_RESCUE=WHITELIST\tGT:VAF:hVAF:DR:DV\t./.:0:0,0,0:24:0\t0/1:0.09:0.12,0.00,0.00:32:3", + "chr22\t15265\tseverus_BND1_2\tN\tN[chr22:10116[\t9.5\tFAIL_MAP_CONS\tPRECISE;SVTYPE=BND;SVLEN=5149;MATE_ID=severus_BND1_1;BND_TYPE=DUP_LIKE;STRANDS=+-;MAPQ=9.5;PHASESETID=0|0;HP=0|0;SUPP_READS=2:0:0:2:0:0;REF_READS=2:6:4:10:5:3;CLUSTERID=severus_0;INSIDE_WHITELIST=TRUE;WL_RESCUE=WHITELIST\tGT:VAF:hVAF:DR:DV\t./.:0:0,0,0:27:0\t0/1:0.12:0.25,0.00,0.00:15:2", + "chr22\t17458\tseverus_BND5_1\tN\t[chr22:23304[N\t15.0\tFAIL_MAP_CONS\tPRECISE;SVTYPE=BND;SVLEN=5846;MATE_ID=severus_BND5_2;BND_TYPE=INV_LIKE;STRANDS=--;MAPQ=15.0;PHASESETID=0|0;HP=0|0;SUPP_READS=2:0:0:2:0:0;REF_READS=5:6:4:10:5:4;CLUSTERID=severus_0;INSIDE_WHITELIST=TRUE;WL_RESCUE=WHITELIST\tGT:VAF:hVAF:DR:DV\t./.:0:0,0,0:43:0\t0/1:0.11:0.22,0.00,0.00:17:2", + "chr22\t18016\tseverus_BND0_2\tN\tN[chr22:10108[\t13.0\tFAIL_MAP_CONS\tPRECISE;SVTYPE=BND;SVLEN=7908;MATE_ID=severus_BND0_1;BND_TYPE=DUP_LIKE;STRANDS=+-;MAPQ=13.0;PHASESETID=0|0;HP=0|0;SUPP_READS=2:0:0:2:0:0;REF_READS=14:6:4:10:5:3;CLUSTERID=severus_0;INSIDE_WHITELIST=TRUE;WL_RESCUE=WHITELIST\tGT:VAF:hVAF:DR:DV\t./.:0:0,0,0:26:0\t0/1:0.09:0.14,0.00,0.00:21:2", + "chr22\t18120\tseverus_BND6_1\tN\t[chr22:23303[N\t24.0\tFAIL_MAP_CONS\tIMPRECISE;SVTYPE=BND;SVLEN=5183;MATE_ID=severus_BND6_2;BND_TYPE=INV_LIKE;STRANDS=--;MAPQ=24.0;PHASESETID=0|0;HP=0|0;SUPP_READS=3:0:0:3:0:0;REF_READS=23:6:4:10:5:4;CLUSTERID=severus_0;INSIDE_WHITELIST=TRUE;WL_RESCUE=WHITELIST\tGT:VAF:hVAF:DR:DV\t./.:0:0,0,0:73:0\t0/1:0.10:0.16,0.00,0.00:26:3", + "chr22\t18187\tseverus_BND7_1\tN\t[chr22:20411[N\t22.0\tFAIL_MAP_CONS\tIMPRECISE;SVTYPE=BND;SVLEN=2224;MATE_ID=severus_BND7_2;BND_TYPE=INV_LIKE;STRANDS=--;MAPQ=22.0;PHASESETID=0|0;HP=0|0;SUPP_READS=5:0:0:5:0:0;REF_READS=23:6:4:5:6:4;CLUSTERID=severus_0;INSIDE_WHITELIST=TRUE;WL_RESCUE=WHITELIST\tGT:VAF:hVAF:DR:DV\t./.:0:0,0,0:73:0\t0/1:0.17:0.26,0.00,0.00:24:5", + "chr22\t18260\tseverus_INS11\tN\tACTGCACCCCAGCCTGGTCAACAGGGCGAGACTGCATCTCAGAAAAAAAAAAAAAA\t15.0\tFAIL_MAP_CONS\tIMPRECISE;SVTYPE=INS;SVLEN=57;MAPQ=15.0;PHASESETID=0|0;HP=0|0;SUPP_READS=7:0:0:7:0:0;REF_READS=30:6:4:30:6:4;INSIDE_WHITELIST=TRUE;WL_RESCUE=WHITELIST\tGT:VAF:hVAF:DR:DV\t./.:0:0,0,0:51:0\t0/1:0.17:0.23,0.00,0.00:33:7", + "chr22\t20411\tseverus_BND7_2\tN\t[chr22:18187[N\t22.0\tFAIL_MAP_CONS\tIMPRECISE;SVTYPE=BND;SVLEN=2224;MATE_ID=severus_BND7_1;BND_TYPE=INV_LIKE;STRANDS=--;MAPQ=22.0;PHASESETID=0|0;HP=0|0;SUPP_READS=5:0:0:5:0:0;REF_READS=5:6:4:23:6:4;CLUSTERID=severus_0;INSIDE_WHITELIST=TRUE;WL_RESCUE=WHITELIST\tGT:VAF:hVAF:DR:DV\t./.:0:0,0,0:27:0\t0/1:0.17:0.26,0.00,0.00:24:5", + "chr22\t20713\tseverus_BND4_2\tN\tN[chr22:13466[\t7.5\tFAIL_MAP_CONS\tPRECISE;SVTYPE=BND;SVLEN=7247;MATE_ID=severus_BND4_1;BND_TYPE=DUP_LIKE;STRANDS=+-;MAPQ=7.5;PHASESETID=0|0;HP=0|0;SUPP_READS=3:0:0:3:0:0;REF_READS=4:6:4:29:6:3;CLUSTERID=severus_0;INSIDE_WHITELIST=TRUE;WL_RESCUE=WHITELIST\tGT:VAF:hVAF:DR:DV\t./.:0:0,0,0:26:0\t0/1:0.10:0.16,0.00,0.00:26:3", + "chr22\t23303\tseverus_BND6_2\tN\t[chr22:18120[N\t24.0\tFAIL_MAP_CONS\tIMPRECISE;SVTYPE=BND;SVLEN=5183;MATE_ID=severus_BND6_1;BND_TYPE=INV_LIKE;STRANDS=--;MAPQ=24.0;PHASESETID=0|0;HP=0|0;SUPP_READS=3:0:0:3:0:0;REF_READS=10:5:4:23:6:4;CLUSTERID=severus_0;INSIDE_WHITELIST=TRUE;WL_RESCUE=WHITELIST\tGT:VAF:hVAF:DR:DV\t./.:0:0,0,0:28:0\t0/1:0.10:0.16,0.00,0.00:26:3", + "chr22\t23304\tseverus_BND5_2\tN\t[chr22:17458[N\t15.0\tFAIL_MAP_CONS\tPRECISE;SVTYPE=BND;SVLEN=5846;MATE_ID=severus_BND5_1;BND_TYPE=INV_LIKE;STRANDS=--;MAPQ=15.0;PHASESETID=0|0;HP=0|0;SUPP_READS=2:0:0:2:0:0;REF_READS=10:5:4:5:6:4;CLUSTERID=severus_0;INSIDE_WHITELIST=TRUE;WL_RESCUE=WHITELIST\tGT:VAF:hVAF:DR:DV\t./.:0:0,0,0:28:0\t0/1:0.11:0.22,0.00,0.00:17:2", + "chr22\t23312\tseverus_DUP8\tN\t\t43.5\tPASS\tPRECISE;SVTYPE=DUP;SVLEN=5374;END=28686;STRANDS=-+;DETAILED_TYPE=tandem_duplication;MAPQ=43.5;SUPP_READS=2:0:0:2:0:0;REF_READS=10:5:4:7:5:4;INSIDE_WHITELIST=TRUE;WL_RESCUE=WHITELIST\tGT:VAF:hVAF:DR:DV\t./.:0:0,0,0:28:0\t0/1:0.11:0.20,0.00,0.00:17:2", + "chr22\t27693\tseverus_BND9_1\tN\tN[chr22:28338[\t24.0\tFAIL_MAP_CONS\tIMPRECISE;SVTYPE=BND;SVLEN=645;MATE_ID=severus_BND9_2;BND_TYPE=DEL_LIKE;STRANDS=+-;MAPQ=24.0;PHASESETID=0|0;HP=0|0;SUPP_READS=4:0:0:4:0:0;REF_READS=8:5:4:13:5:4;CLUSTERID=severus_0;INSIDE_WHITELIST=TRUE;WL_RESCUE=WHITELIST\tGT:VAF:hVAF:DR:DV\t./.:0:0,0,0:34:0\t0/1:0.17:0.29,0.00,0.00:19:4", + "chr22\t28338\tseverus_BND9_2\tN\t]chr22:27693]N\t24.0\tFAIL_MAP_CONS\tIMPRECISE;SVTYPE=BND;SVLEN=645;MATE_ID=severus_BND9_1;BND_TYPE=DEL_LIKE;STRANDS=-+;MAPQ=24.0;PHASESETID=0|0;HP=0|0;SUPP_READS=4:0:0:4:0:0;REF_READS=13:5:4:8:5:4;CLUSTERID=severus_0;INSIDE_WHITELIST=TRUE;WL_RESCUE=WHITELIST\tGT:VAF:hVAF:DR:DV\t./.:0:0,0,0:31:0\t0/1:0.17:0.29,0.00,0.00:19:4" + ], + [ + + ], + "severus_all.vcf.gz", + "severus_somatic.vcf.gz", + { + "versions_severus": [ + [ + "SEVERUS", + "severus", + "1.7" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.4" + }, + "timestamp": "2026-10-02T21:55:20.932321899" + }, + "paired - no whitelist": { + "content": [ + [ + + ], + [ + + ], + "severus_all.vcf.gz", + "severus_somatic.vcf.gz", + { + "versions_severus": [ + [ + "SEVERUS", + "severus", + "1.7" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.4" + }, + "timestamp": "2026-10-02T21:55:13.3288247" + } +} \ No newline at end of file From ce36b249ff79f4c3a892c8608158870f345d872a Mon Sep 17 00:00:00 2001 From: Amber Verhasselt Date: Fri, 2 Oct 2026 22:13:11 +0200 Subject: [PATCH 5/5] Move SEVERUS whitelist test to tests/modules to satisfy nf-core lint nf-core lint (nf_test_content) treats every tests/*.nf.test as a pipeline test and requires outdir and a versions.yml snapshot, which a process test does not have. It only globs tests/*.nf.test, while nf-test discovers tests recursively, so tests/modules/ keeps the test in PR CI (tag small) without a lint exemption. Co-Authored-By: Claude Opus 5.5 (1M context) --- tests/{ => modules}/severus_whitelist.nf.test | 5 +++-- tests/{ => modules}/severus_whitelist.nf.test.snap | 0 2 files changed, 3 insertions(+), 2 deletions(-) rename tests/{ => modules}/severus_whitelist.nf.test (95%) rename tests/{ => modules}/severus_whitelist.nf.test.snap (100%) diff --git a/tests/severus_whitelist.nf.test b/tests/modules/severus_whitelist.nf.test similarity index 95% rename from tests/severus_whitelist.nf.test rename to tests/modules/severus_whitelist.nf.test index a0686ab6..b270b99c 100644 --- a/tests/severus_whitelist.nf.test +++ b/tests/modules/severus_whitelist.nf.test @@ -1,14 +1,15 @@ nextflow_process { name "Test Process SEVERUS with and without --whitelist" - script "../modules/nf-core/severus/main.nf" + script "../../modules/nf-core/severus/main.nf" process "SEVERUS" tag "modules" tag "severus" tag "small" - // Lives here rather than in modules/nf-core/severus/tests, which nf-test.config ignores, so PR CI runs it. + // Lives here rather than in modules/nf-core/severus/tests, which nf-test.config ignores, so PR CI runs it; + // not directly in tests/, where nf-core lint (nf_test_content) treats every *.nf.test as a pipeline test. // Paired, because without a control or PON Severus writes no somatic_SVs and the module's bgzip step fails. // Real Severus on the nf-core nanopore test BAMs: checks that the patched image runs, that --whitelist reaches // the command only when a whitelist is given, and that the bgzipped VCFs are emitted either way. diff --git a/tests/severus_whitelist.nf.test.snap b/tests/modules/severus_whitelist.nf.test.snap similarity index 100% rename from tests/severus_whitelist.nf.test.snap rename to tests/modules/severus_whitelist.nf.test.snap