diff --git a/CHANGELOG.md b/CHANGELOG.md index 2107ec86..20c0f1a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## v1.2.0dev +### `Added` + +- [#190](https://github.com/IntGenomicsLab/lrsomatic/pull/190) - Added mutational signature analysis: SigProfilerMatrixGenerator matrices (SBS/DBS/ID) and per-sample COSMIC signature fitting with SigProfilerAssignment on the phased somatic VCF, with CHM13-T2T support via a purpose-built image (`ghcr.io/ljwharbers/sigprofiler`) and CHM13-renormalised COSMIC signatures; new `--sigprofiler_*` / `--download_sigprofiler_genome` / `--skip_signatures` parameters (@ljwharbers). + ### `Changed` - [#184](https://github.com/IntGenomicsLab/lrsomatic/pull/184) - Replaced the CHM13 Severus panel of normals with the merged 1000 Genomes + ASAP panel (@AmberVerhasselt). diff --git a/CITATIONS.md b/CITATIONS.md index d92c057f..a88736e7 100644 --- a/CITATIONS.md +++ b/CITATIONS.md @@ -74,6 +74,14 @@ > Keskus, A.G., Bryant, A., Ahmad, T. et al. Severus detects somatic structural variation and complex rearrangements in cancer genomes using long-read sequencing. Nat Biotechnol (2025). https://doi.org/10.1038/s41587-025-02618-8 +- [SigProfilerAssignment](https://pubmed.ncbi.nlm.nih.gov/38096571/) + + > Díaz-Gay M, Vangara R, Barnes M, Wang X, Islam SMA, Vermes I, Duke S, Narasimman NB, Yang T, Jiang Z, Moody S, Senkin S, Brennan P, Stratton MR, Alexandrov LB. Assigning mutational signatures to individual samples and individual somatic mutations with SigProfilerAssignment. Bioinformatics. 2023 Dec 1;39(12):btad756. doi: 10.1093/bioinformatics/btad756. PubMed PMID: 38096571; PubMed Central PMCID: PMC10733897. + +- [SigProfilerMatrixGenerator](https://pubmed.ncbi.nlm.nih.gov/31470794/) + + > Bergstrom EN, Huang MN, Mahto U, Barnes M, Stratton MR, Rozen SG, Alexandrov LB. SigProfilerMatrixGenerator: a tool for visualizing and exploring patterns of small mutational events. BMC Genomics. 2019 Aug 30;20(1):685. doi: 10.1186/s12864-019-6041-2. PubMed PMID: 31470794; PubMed Central PMCID: PMC6717374. + - [Wakhan](https://github.com/KolmogorovLab/Wakhan) > A tool to analyze haplotype-specific chromosome-scale somatic copy number aberrations and aneuploidy using long reads (Oxford Nanopore, PacBio). https://github.com/KolmogorovLab/Wakhan diff --git a/conf/igenomes.config b/conf/igenomes.config index 0b259244..4d9605b2 100644 --- a/conf/igenomes.config +++ b/conf/igenomes.config @@ -23,6 +23,7 @@ params { bed_file = "https://raw.githubusercontent.com/KolmogorovLab/Severus/refs/heads/main/vntrs/human_GRCh38_no_alt_analysis_set.trf.bed" vep_genome = "GRCh38" vep_species = "homo_sapiens" + sigprofiler_genome = "GRCh38" gnomad = "${params.igenomes_base}/Homo_sapiens/ClairSTO/GRCh38/Annotation/gnomad.r2.1.af-ge-0.001.sites.vcf.gz" dbsnp = "${params.igenomes_base}/Homo_sapiens/ClairSTO/GRCh38/Annotation/dbsnp.b138.non-somatic.sites.vcf.gz" onekgenomes = "${params.igenomes_base}/Homo_sapiens/ClairSTO/GRCh38/Annotation/1000g-pon.sites.vcf.gz" @@ -39,6 +40,10 @@ params { bed_file = "https://raw.githubusercontent.com/KolmogorovLab/Severus/refs/heads/main/vntrs/chm13.bed" vep_genome = "T2T-CHM13v2.0" vep_species = "homo_sapiens_gca009914755v4" + // SigProfilerMatrixGenerator CHM13-T2T payload (SigProfilerSuite/SigProfilerMatrixGenerator#250); + // not yet hosted on the AlexandrovLab FTP, so it is fetched from the IntGenomicsLab Globus collection + sigprofiler_genome = "CHM13-T2T" + sigprofiler_genome_url = "https://g-608c0c.273595.03c0.data.globus.org/chm13_release_2026-08/CHM13-T2T.tar.gz" gnomad = "${params.igenomes_base}/Homo_sapiens/ClairSTO/CHM13/Annotation/ClairSTO-pon/final_gnomad.vcf.gz" dbsnp = "${params.igenomes_base}/Homo_sapiens/ClairSTO/CHM13/Annotation/ClairSTO-pon/final_dbsnp.vcf.gz" onekgenomes = "${params.igenomes_base}/Homo_sapiens/ClairSTO/CHM13/Annotation/ClairSTO-pon/final_1kgenomes.vcf.gz" diff --git a/conf/modules.config b/conf/modules.config index 9f7d3c34..81f5ed9d 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -575,6 +575,50 @@ process { ] } + /////////////////////////// + // Mutational signatures // + /////////////////////////// + withName: 'SIGPROFILER_INSTALL' { + // ~3 GB genome payload; published so later runs can reuse it via --sigprofiler_genome_dir + publishDir = [ + mode: params.publish_dir_mode, + path: { params.outdir_cache ? "${params.outdir_cache}/sigprofiler" : "${params.outdir}/cache/sigprofiler" } + ] + } + + withName : '.*:SIGNATURES_BCFTOOLS_VIEW' { + // SigProfilerMatrixGenerator ignores FILTER and reads plain-text VCF only + ext.args = { "--apply-filters PASS --types snps,mnps,indels --exclude 'ALT=\"*\"' --output-type v" } + ext.prefix = { "${meta.id}_somatic_pass" } + publishDir = [ + enabled: false + ] + } + + withName : '.*:SIGPROFILER_MATRIXGENERATOR' { + ext.args = { params.sigprofiler_matrix_args ?: '' } + publishDir = [ + path: { "${params.outdir}/${meta.id}/signatures/matrices" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } + + withName : '.*:SIGPROFILER_ASSIGNMENT' { + ext.args = { + [ + params.sigprofiler_assignment_args ?: '', + params.sigprofiler_exclude_signature_subgroups ? "--exclude_signature_subgroups ${params.sigprofiler_exclude_signature_subgroups}" : '' + ].join(' ').trim() + } + ext.prefix = { "COSMIC_v${params.sigprofiler_cosmic_version}" } + publishDir = [ + path: { "${params.outdir}/${meta.id}/signatures/assignment" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } + ///////// // VEP // ///////// diff --git a/conf/test.config b/conf/test.config index 1938506d..b79f8461 100644 --- a/conf/test.config +++ b/conf/test.config @@ -58,5 +58,7 @@ params { skip_wakhan = true skip_ascat = true skip_modkit = true + // needs a ~3 GB SigProfilerMatrixGenerator genome payload; validated manually, not in CI + skip_signatures = true } diff --git a/docs/output.md b/docs/output.md index 61d82b28..bd70831a 100644 --- a/docs/output.md +++ b/docs/output.md @@ -29,6 +29,9 @@ The pipeline produces per-sample output directories. Two modes exist depending o │ │ │ ├── nanoplot_ubam_rep1 │ │ │ └── samtools │ │ └── whatshap_stats +│ ├── signatures +│ │ ├── assignment +│ │ └── matrices │ ├── variants │ │ ├── clairsto │ │ ├── deepsomatic @@ -70,6 +73,9 @@ The pipeline produces per-sample output directories. Two modes exist depending o │ │ │ ├── nanoplot_ubam_rep1 │ │ │ └── samtools │ │ └── whatshap_stats +│ ├── signatures +│ │ ├── assignment +│ │ └── matrices │ ├── variants │ │ ├── clair3 │ │ ├── clairs @@ -433,6 +439,72 @@ Phased variant calls produced by Longphase. Present in all samples. +### `signatures` + +Mutational signature analysis of the PASS SNVs and indels in the phased somatic VCF: [SigProfilerMatrixGenerator](https://github.com/SigProfilerSuite/SigProfilerMatrixGenerator) builds the mutational matrices and [SigProfilerAssignment](https://github.com/SigProfilerSuite/SigProfilerAssignment) fits COSMIC reference signatures to them. For `--genome CHM13` the matrices use the `CHM13-T2T` genome and the SBS/DBS fits use COSMIC signatures renormalised to CHM13; ID83 signatures are not genome-normalised by COSMIC and always use the GRCh37 set. The `DBS78` and `ID83` directories are absent when a sample has no doublet substitutions or indels. + +
+Output files + +``` +├── signatures +│ ├── matrices +│ │ ├── output +│ │ │ ├── SBS +│ │ │ │ ├── sample.SBS96.all +│ │ │ │ ├── sample.SBS288.all +│ │ │ │ ├── sample.SBS1536.all +│ │ │ │ └── ... +│ │ │ ├── DBS +│ │ │ │ ├── sample.DBS78.all +│ │ │ │ └── ... +│ │ │ ├── ID +│ │ │ │ ├── sample.ID83.all +│ │ │ │ └── ... +│ │ │ ├── plots +│ │ │ │ └── *.pdf +│ │ │ ├── vcf_files +│ │ │ └── logs +│ │ │ ├── SigProfilerMatrixGenerator_sample_.out +│ │ │ └── SigProfilerMatrixGenerator_sample_.err +│ └── assignment +│ └── COSMIC_v3.6 +│ ├── SBS96 +│ │ ├── Assignment_Solution +│ │ │ ├── Activities +│ │ │ │ ├── Assignment_Solution_Activities.txt +│ │ │ │ ├── Assignment_Solution_Activity_Plots.pdf +│ │ │ │ ├── Assignment_Solution_TMB_plot.pdf +│ │ │ │ └── Decomposed_MutationType_Probabilities.txt +│ │ │ ├── Signatures +│ │ │ │ ├── Assignment_Solution_Signatures.txt +│ │ │ │ └── SBS_96_plots_Assignment_Solution.pdf +│ │ │ └── Solution_Stats +│ │ │ ├── Assignment_Solution_Samples_Stats.txt +│ │ │ └── Assignment_Solution_Signature_Assignment_log.txt +│ │ └── JOB_METADATA_SPA.txt +│ ├── DBS78 +│ │ └── ... +│ └── ID83 +│ └── ... +``` + +| File | Description | +| ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| `matrices/output/SBS/sample.SBS96.all` | SBS96 mutational matrix (mutation counts per trinucleotide class); further context sizes (SBS6, SBS24, SBS288, SBS384, SBS1536, SBS6144) alongside | +| `matrices/output/DBS/sample.DBS78.all` | DBS78 doublet-substitution matrix (and DBS186/DBS1248/DBS2976 variants) | +| `matrices/output/ID/sample.ID83.all` | ID83 indel matrix (and ID28/ID96/ID415 variants) | +| `matrices/output/plots/*.pdf` | SigProfilerPlotting spectra of the matrices (with the default `--sigprofiler_matrix_args "--plot"`) | +| `matrices/output/vcf_files/` | Sorted input mutations with their SigProfilerMatrixGenerator classification (`seqInfo`) | +| `matrices/output/logs/*` | SigProfilerMatrixGenerator log and error files; the summary reports the number of analysed mutations and reference-base mismatches | +| `assignment/COSMIC_v//Assignment_Solution/Activities/*_Activities.txt` | Number of mutations attributed to every COSMIC signature | +| `assignment/COSMIC_v//Assignment_Solution/Activities/*.pdf` | Activity bar plots and tumour mutational burden plot | +| `assignment/COSMIC_v//Assignment_Solution/Signatures/` | The reference signatures used for the fit and their spectra | +| `assignment/COSMIC_v//Assignment_Solution/Solution_Stats/` | Per-sample reconstruction statistics (cosine similarity, L2 error) and the step-wise assignment log | +| `assignment/COSMIC_v//JOB_METADATA_SPA.txt` | SigProfilerAssignment run metadata, including the genome build the reference signatures were normalised to | + +
+ ### `wakhan`
diff --git a/docs/usage.md b/docs/usage.md index d0546eb0..a063bf60 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -125,6 +125,8 @@ nextflow run IntGenomicsLab/lrsomatic \ If you want to run with a CHM13 reference without using `--genome CHM13` (for example, via a custom FASTA or configuration), you must also specify `--vep_genome T2T-CHM13v2.0` and `--vep_species homo_sapiens_gca009914755v4`. +For mutational signatures, `--genome CHM13` selects the `CHM13-T2T` SigProfilerMatrixGenerator genome. Its payload is not on the AlexandrovLab FTP yet, so `--download_sigprofiler_genome` fetches it from the IntGenomicsLab Globus collection (`--sigprofiler_genome_url`); see [Mutational Signature Options](#mutational-signature-options). + For structural variants, the CHM13 panel of normals is a merged panel combining the 1000 Genomes CHM13 panel shipped with SEVERUS and the ASAP cohort, with median confidence intervals per breakpoint. The pipeline exposes it as `--pon_file` and hands it to SEVERUS via that tool's own `--PON` flag; it is downloaded automatically with `--genome CHM13`. GRCh38 continues to use the 1000 Genomes panel shipped with SEVERUS. ### Pipeline options @@ -153,6 +155,7 @@ For structural variants, the CHM13 panel of normals is a merged panel combining | `--skip_modcall` | A boolean to skip modkit methylation calling. Default = `false` | | `--skip_modkit` | A boolean to skip the modkit pileup step. Default = `false` | | `--skip_whatshapstats` | A boolean to skip WhatsHap phasing statistics. Default = `false` | +| `--skip_signatures` | A boolean to skip mutational signature analysis (SigProfilerMatrixGenerator + SigProfilerAssignment). Default = `false` | #### LONGPHASE options: @@ -216,6 +219,26 @@ For structural variants, the CHM13 panel of normals is a merged panel combining | ----------------- | ------------------------------------------------------------------------------------------------------- | | `--wakhan_chroms` | A string specifying a subset of chromosomes for WAKHAN to process, e.g. `"chr1,chr2"`. Default = `null` | +#### Mutational Signature Options + +Mutational signature analysis runs [SigProfilerMatrixGenerator](https://github.com/SigProfilerSuite/SigProfilerMatrixGenerator) on the PASS SNVs and indels of the phased somatic VCF of every sample (SBS, DBS and ID matrices at all context sizes, with plots) and then fits COSMIC reference signatures per sample with [SigProfilerAssignment](https://github.com/SigProfilerSuite/SigProfilerAssignment) (SBS96, DBS78 and ID83). It needs SigProfilerMatrixGenerator's per-genome payload (~3 GB, the transcriptional-strand-annotated chromosomes), which is not shipped with the pipeline. Either: + +- run once with `--download_sigprofiler_genome`; the payload is installed, checksum-verified and published to `/cache/sigprofiler/volume`, or +- point `--sigprofiler_genome_dir` at an existing SigProfilerMatrixGenerator volume (a directory containing `tsb//`, e.g. one created with `SigProfilerMatrixGenerator install GRCh38 --volume ` or the published cache from a previous run). + +Running with neither (and without `--skip_signatures`) stops the pipeline at start-up with an explanatory error. + +| Parameter | Description | +| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--sigprofiler_genome_dir` | Full path to a SigProfilerMatrixGenerator volume containing `tsb//`. Default = `null` | +| `--download_sigprofiler_genome` | A boolean to download and install the genome payload during the run (published to `/cache/sigprofiler/volume`). Default = `false` | +| `--sigprofiler_cosmic_version` | COSMIC reference signature version fitted by SigProfilerAssignment. Default = `3.6` | +| `--sigprofiler_exclude_signature_subgroups` | Comma-separated SigProfilerAssignment signature subgroups to exclude from the fit, e.g. `"Artifact_signatures,Lymphoid_signatures"` (see the SigProfilerAssignment documentation). Default = `null` | +| `--sigprofiler_matrix_args` | Extra arguments for `SigProfilerMatrixGenerator matrix_generator`. Default = `"--plot"` | +| `--sigprofiler_assignment_args` | Extra arguments for `SigProfilerAssignment cosmic_fit`, e.g. `"--make_plots False"`. Default = `null` | + +The tools run from a purpose-built image (`ghcr.io/ljwharbers/sigprofiler`) because CHM13 support is not in a SigProfiler release yet: SigProfilerMatrixGenerator comes from the branch behind [SigProfilerSuite/SigProfilerMatrixGenerator#250](https://github.com/SigProfilerSuite/SigProfilerMatrixGenerator/pull/250) (adds the `CHM13-T2T` genome) and SigProfilerAssignment from [ljwharbers/SigProfilerAssignment](https://github.com/ljwharbers/SigProfilerAssignment/tree/chm13-t2t-support), which adds COSMIC SBS/DBS signatures renormalised to the CHM13 trinucleotide/dinucleotide composition (stock SigProfilerAssignment silently falls back to GRCh37 signatures for CHM13). Indel (ID83) signatures are not genome-normalised by COSMIC and always use the GRCh37 set. Conda is not supported for this step. + #### Variant Filtering and Combining Options These options control how variants from multiple callers are filtered and merged. @@ -248,15 +271,17 @@ These options control how variants from multiple callers are filtered and merged The following parameters are automatically populated from the `--genome` iGenomes configuration and do not normally need to be set manually. They can be overridden when using a custom genome or reference build not present in the iGenomes configuration. -| Parameter | Description | -| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | -| `--fasta` | Full path to the reference FASTA file. Auto-populated from `--genome`. Override for custom genomes. | -| `--bed_file` | BED file of callable/target regions passed to SEVERUS for SV calling. Auto-populated from `--genome`. | -| `--pon_file` | Panel of Normals breakpoint table (bgzipped CSV) for SEVERUS somatic SV filtering in tumor-only mode. Auto-populated from `--genome`. | -| `--centromere_bed` | BED file of centromere coordinates passed to WAKHAN. Auto-populated from `--genome`. | -| `--genome_name` | Assembly name string passed to ASCAT for genome-specific reference file selection. Auto-populated from `--genome`. | -| `--vep_genome` | VEP genome identifier (e.g. `GRCh38`, `T2T-CHM13v2.0`). Auto-populated from `--genome`. Override for CHM13 or custom assemblies. | -| `--vep_species` | VEP species identifier. Auto-populated from `--genome`. Override for non-standard assemblies (e.g. `homo_sapiens_gca009914755v4` for CHM13). | +| Parameter | Description | +| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--fasta` | Full path to the reference FASTA file. Auto-populated from `--genome`. Override for custom genomes. | +| `--bed_file` | BED file of callable/target regions passed to SEVERUS for SV calling. Auto-populated from `--genome`. | +| `--pon_file` | Panel of Normals breakpoint table (bgzipped CSV) for SEVERUS somatic SV filtering in tumor-only mode. Auto-populated from `--genome`. | +| `--centromere_bed` | BED file of centromere coordinates passed to WAKHAN. Auto-populated from `--genome`. | +| `--genome_name` | Assembly name string passed to ASCAT for genome-specific reference file selection. Auto-populated from `--genome`. | +| `--vep_genome` | VEP genome identifier (e.g. `GRCh38`, `T2T-CHM13v2.0`). Auto-populated from `--genome`. Override for CHM13 or custom assemblies. | +| `--vep_species` | VEP species identifier. Auto-populated from `--genome`. Override for non-standard assemblies (e.g. `homo_sapiens_gca009914755v4` for CHM13). | +| `--sigprofiler_genome` | SigProfilerMatrixGenerator / SigProfilerAssignment genome name (`GRCh38` or `CHM13-T2T`). Auto-populated from `--genome`. | +| `--sigprofiler_genome_url` | URL of the `.tar.gz` payload for `--download_sigprofiler_genome`; unset means the AlexandrovLab FTP. Auto-populated from `--genome` (set for CHM13 only). | ### Updating the pipeline diff --git a/modules.json b/modules.json index a3e19fa2..bd895810 100644 --- a/modules.json +++ b/modules.json @@ -50,6 +50,11 @@ "installed_by": ["modules", "vcf_gather_bcftools"], "patch": "modules/nf-core/bcftools/sort/bcftools-sort.diff" }, + "bcftools/view": { + "branch": "master", + "git_sha": "feef37435aea56816adf4b3bde1fc76aac327a8d", + "installed_by": ["modules"] + }, "deepvariant/callvariants": { "branch": "master", "git_sha": "f2b138ee1d91f67d31c187317d7e83e429bf0309", diff --git a/modules/local/sigprofiler/assignment/main.nf b/modules/local/sigprofiler/assignment/main.nf new file mode 100644 index 00000000..cc849a61 --- /dev/null +++ b/modules/local/sigprofiler/assignment/main.nf @@ -0,0 +1,76 @@ +process SIGPROFILER_ASSIGNMENT { + tag "$meta.id" + label 'process_low' + + // Conda is not supported: the image installs SigProfilerMatrixGenerator from the fork + // that adds the CHM13-T2T genome (SigProfilerSuite/SigProfilerMatrixGenerator#250) and + // SigProfilerAssignment from the fork that adds CHM13-T2T COSMIC signatures. Return to + // the bioconda/biocontainers releases once both are merged and released upstream. + container "${workflow.containerEngine == 'singularity' || workflow.containerEngine == 'apptainer' + ? 'oras://ghcr.io/ljwharbers/sigprofiler-sif:1.3.6-chm13-28a9ce8' + : 'ghcr.io/ljwharbers/sigprofiler:1.3.6-chm13-28a9ce8'}" + + input: + tuple val(meta), path(sbs96), path(dbs78), path(id83) // SigProfilerMatrixGenerator matrices; dbs78/id83 may be [] + val(genome) // genome build for the COSMIC reference signatures, e.g. GRCh38 or CHM13-T2T + val(cosmic_version) // COSMIC version, e.g. 3.6 + + output: + tuple val(meta), path("*/SBS96") , emit: sbs96 , optional: true + tuple val(meta), path("*/DBS78") , emit: dbs78 , optional: true + tuple val(meta), path("*/ID83") , emit: id83 , optional: true + tuple val(meta), path("*/*/Assignment_Solution/Activities/*_Activities.txt") , emit: activities, optional: true + tuple val("${task.process}"), val('sigprofilerassignment'), eval("python -c 'import importlib.metadata as m; print(m.version(\"SigProfilerAssignment\"))'"), topic: versions, emit: versions_sigprofilerassignment + + when: + task.ext.when == null || task.ext.when + + script: + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "SIGPROFILER_ASSIGNMENT does not support Conda. Please use Docker / Singularity / Apptainer instead." + } + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + // SBS matrices are collapsed to SBS96 for the fit; DBS78 and ID83 must not be + // (SigProfilerAssignment refuses collapse_to_SBS96 for non-SBS contexts). + def fits = [[sbs96, 'SBS96', 'True'], [dbs78, 'DBS78', 'False'], [id83, 'ID83', 'False']] + .findAll { matrix, _ctx, _collapse -> matrix } + .collect { matrix, ctx, collapse -> + """ + if [ "\$(matrix_total ${matrix})" -gt 0 ]; then + SigProfilerAssignment cosmic_fit \\ + ${matrix} \\ + ${prefix}/${ctx} \\ + --input_type matrix \\ + --genome_build ${genome} \\ + --cosmic_version ${cosmic_version} \\ + --collapse_to_SBS96 ${collapse} \\ + --volume spa_volume \\ + --cpu ${task.cpus} \\ + ${args} + else + echo "${ctx}: no mutations in ${matrix}, skipping COSMIC fit" >&2 + fi + """ + } + .join('\n') + """ + # total mutation count of a SigProfilerMatrixGenerator matrix (all samples, all classes) + matrix_total() { + awk -F'\\t' 'NR > 1 { for (i = 2; i <= NF; i++) s += \$i } END { printf "%d\\n", s }' "\$1" + } + + # sigProfilerPlotting caches plot templates in its (read-only) package directory unless + # this variable points elsewhere; SigProfilerAssignment does not forward --volume to it. + mkdir -p ${prefix} spa_volume + export SIGPROFILERPLOTTING_VOLUME="\$PWD/spa_volume" + ${fits} + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + mkdir -p ${prefix}/SBS96/Assignment_Solution/Activities + touch ${prefix}/SBS96/Assignment_Solution/Activities/Assignment_Solution_Activities.txt + """ +} diff --git a/modules/local/sigprofiler/assignment/meta.yml b/modules/local/sigprofiler/assignment/meta.yml new file mode 100644 index 00000000..df86e6ea --- /dev/null +++ b/modules/local/sigprofiler/assignment/meta.yml @@ -0,0 +1,106 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "sigprofiler_assignment" +description: Fit COSMIC reference mutational signatures to SBS96, DBS78 and ID83 matrices with SigProfilerAssignment +keywords: + - mutational signatures + - cosmic + - signature fitting + - sigprofiler + - somatic +tools: + - "sigprofilerassignment": + description: "SigProfilerAssignment assigns known mutational signatures (COSMIC or custom) to mutational matrices or mutation calls" + homepage: "https://github.com/SigProfilerSuite/SigProfilerAssignment" + documentation: "https://sigprofilersuite.github.io/SigProfilerAssignment/" + tool_dev_url: "https://github.com/SigProfilerSuite/SigProfilerAssignment" + doi: "10.1093/bioinformatics/btad756" + licence: ["BSD-2-Clause"] + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - sbs96: + type: file + description: SBS96 matrix from SigProfilerMatrixGenerator + pattern: "*.SBS96.all" + - dbs78: + type: file + description: DBS78 matrix from SigProfilerMatrixGenerator (optional, may be []) + pattern: "*.DBS78.all" + - id83: + type: file + description: ID83 matrix from SigProfilerMatrixGenerator (optional, may be []) + pattern: "*.ID83.all" + - genome: + type: string + description: | + Genome build selecting the COSMIC reference signatures (GRCh37, GRCh38, CHM13-T2T, ...). + Only SBS and DBS signatures are build-specific; ID signatures always use the GRCh37 set. + - cosmic_version: + type: float + description: COSMIC signature version, e.g. 3.6 + +output: + sbs96: + - meta: + type: map + description: Groovy Map containing sample information + - "*/SBS96": + type: directory + description: SigProfilerAssignment output for the SBS96 fit (Assignment_Solution/ with activities, signatures, statistics and plots; JOB_METADATA_SPA.txt) + pattern: "*/SBS96" + dbs78: + - meta: + type: map + description: Groovy Map containing sample information + - "*/DBS78": + type: directory + description: SigProfilerAssignment output for the DBS78 fit + pattern: "*/DBS78" + id83: + - meta: + type: map + description: Groovy Map containing sample information + - "*/ID83": + type: directory + description: SigProfilerAssignment output for the ID83 fit + pattern: "*/ID83" + activities: + - meta: + type: map + description: Groovy Map containing sample information + - "*/*/Assignment_Solution/Activities/*_Activities.txt": + type: file + description: Signature activity (mutation count per signature) tables, one per fitted matrix type + pattern: "*/*/Assignment_Solution/Activities/*_Activities.txt" + versions_sigprofilerassignment: + - - ${task.process}: + type: string + description: The process the versions were collected from + - sigprofilerassignment: + type: string + description: The tool name + - 'python -c ''import importlib.metadata as m; print(m.version("SigProfilerAssignment"))''': + type: string + description: The command used to generate the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - sigprofilerassignment: + type: string + description: The tool name + - 'python -c ''import importlib.metadata as m; print(m.version("SigProfilerAssignment"))''': + type: string + description: The command used to generate the version of the tool + +authors: + - "@ljwharbers" +maintainers: + - "@ljwharbers" diff --git a/modules/local/sigprofiler/install/main.nf b/modules/local/sigprofiler/install/main.nf new file mode 100644 index 00000000..503d9a16 --- /dev/null +++ b/modules/local/sigprofiler/install/main.nf @@ -0,0 +1,52 @@ +process SIGPROFILER_INSTALL { + tag "$genome" + label 'process_single' + label 'process_long' + + // Conda is not supported: the image installs SigProfilerMatrixGenerator from the fork + // that adds the CHM13-T2T genome (SigProfilerSuite/SigProfilerMatrixGenerator#250) and + // SigProfilerAssignment from the fork that adds CHM13-T2T COSMIC signatures. Return to + // the bioconda/biocontainers releases once both are merged and released upstream. + container "${workflow.containerEngine == 'singularity' || workflow.containerEngine == 'apptainer' + ? 'oras://ghcr.io/ljwharbers/sigprofiler-sif:1.3.6-chm13-28a9ce8' + : 'ghcr.io/ljwharbers/sigprofiler:1.3.6-chm13-28a9ce8'}" + + input: + val(genome) // SigProfilerMatrixGenerator genome name, e.g. GRCh38 or CHM13-T2T + val(url) // URL of .tar.gz to install from; empty/null = AlexandrovLab FTP + + output: + path("volume") , emit: volume + tuple val("${task.process}"), val('sigprofilermatrixgenerator'), eval("python -c 'import importlib.metadata as m; print(m.version(\"SigProfilerMatrixGenerator\"))'"), topic: versions, emit: versions_sigprofilermatrixgenerator + + when: + task.ext.when == null || task.ext.when + + script: + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "SIGPROFILER_INSTALL does not support Conda. Please use Docker / Singularity / Apptainer instead." + } + def args = task.ext.args ?: '' + def install = url + ? """ + python -c 'import urllib.request; urllib.request.urlretrieve("${url}", "${genome}.tar.gz")' + SigProfilerMatrixGenerator install ${genome} --local_genome . --volume volume ${args} + rm -f ${genome}.tar.gz + """ + : """ + SigProfilerMatrixGenerator install ${genome} --volume volume ${args} + """ + """ + mkdir -p volume + ${install} + + # install only extracts; make sure every chromosome file matches the registered checksums + python -c 'from SigProfilerMatrixGenerator.scripts import reference_genome_manager as rgm; assert rgm.ReferenceGenomeManager("volume").is_genome_installed("${genome}"), "${genome} failed the SigProfilerMatrixGenerator checksum verification"' + """ + + stub: + """ + mkdir -p volume/tsb/${genome} + touch volume/tsb/${genome}/1.txt + """ +} diff --git a/modules/local/sigprofiler/install/meta.yml b/modules/local/sigprofiler/install/meta.yml new file mode 100644 index 00000000..69d8c7e5 --- /dev/null +++ b/modules/local/sigprofiler/install/meta.yml @@ -0,0 +1,61 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "sigprofiler_install" +description: Install a SigProfilerMatrixGenerator reference genome payload (transcriptional strand bias files) into a volume directory +keywords: + - mutational signatures + - reference genome + - sigprofiler + - download +tools: + - "sigprofilermatrixgenerator": + description: "SigProfilerMatrixGenerator creates mutational matrices for all types of somatic mutations" + homepage: "https://github.com/SigProfilerSuite/SigProfilerMatrixGenerator" + documentation: "https://sigprofilersuite.github.io/SigProfilerMatrixGenerator/" + tool_dev_url: "https://github.com/SigProfilerSuite/SigProfilerMatrixGenerator" + doi: "10.1186/s12864-019-6041-2" + licence: ["BSD-2-Clause"] + +input: + - genome: + type: string + description: SigProfilerMatrixGenerator genome name, e.g. GRCh38 or CHM13-T2T + - url: + type: string + description: | + URL of the .tar.gz archive to install from. When empty the archive is + fetched from the AlexandrovLab FTP server by SigProfilerMatrixGenerator itself. + +output: + volume: + - "volume": + type: directory + description: SigProfilerMatrixGenerator volume containing tsb// with the verified chromosome files + pattern: "volume" + versions_sigprofilermatrixgenerator: + - - ${task.process}: + type: string + description: The process the versions were collected from + - sigprofilermatrixgenerator: + type: string + description: The tool name + - 'python -c ''import importlib.metadata as m; print(m.version("SigProfilerMatrixGenerator"))''': + type: string + description: The command used to generate the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - sigprofilermatrixgenerator: + type: string + description: The tool name + - 'python -c ''import importlib.metadata as m; print(m.version("SigProfilerMatrixGenerator"))''': + type: string + description: The command used to generate the version of the tool + +authors: + - "@ljwharbers" +maintainers: + - "@ljwharbers" diff --git a/modules/local/sigprofiler/matrixgenerator/main.nf b/modules/local/sigprofiler/matrixgenerator/main.nf new file mode 100644 index 00000000..cbfb0853 --- /dev/null +++ b/modules/local/sigprofiler/matrixgenerator/main.nf @@ -0,0 +1,66 @@ +process SIGPROFILER_MATRIXGENERATOR { + tag "$meta.id" + label 'process_medium' + + // Conda is not supported: the image installs SigProfilerMatrixGenerator from the fork + // that adds the CHM13-T2T genome (SigProfilerSuite/SigProfilerMatrixGenerator#250) and + // SigProfilerAssignment from the fork that adds CHM13-T2T COSMIC signatures. Return to + // the bioconda/biocontainers releases once both are merged and released upstream. + container "${workflow.containerEngine == 'singularity' || workflow.containerEngine == 'apptainer' + ? 'oras://ghcr.io/ljwharbers/sigprofiler-sif:1.3.6-chm13-28a9ce8' + : 'ghcr.io/ljwharbers/sigprofiler:1.3.6-chm13-28a9ce8'}" + + input: + tuple val(meta), path(vcf) // somatic small-variant VCF (plain or bgzipped) + tuple val(meta2), path(volume) // SigProfilerMatrixGenerator volume containing tsb// + val(genome) // SigProfilerMatrixGenerator genome name, e.g. GRCh38 or CHM13-T2T + + output: + tuple val(meta), path("output/SBS/*.SBS96.all") , emit: sbs96 + tuple val(meta), path("output/DBS/*.DBS78.all") , emit: dbs78 , optional: true + tuple val(meta), path("output/ID/*.ID83.all") , emit: id83 , optional: true + tuple val(meta), path("output") , emit: output_dir + tuple val(meta), path("output/logs/*") , emit: logs , optional: true + tuple val("${task.process}"), val('sigprofilermatrixgenerator'), eval("python -c 'import importlib.metadata as m; print(m.version(\"SigProfilerMatrixGenerator\"))'"), topic: versions, emit: versions_sigprofilermatrixgenerator + + when: + task.ext.when == null || task.ext.when + + script: + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + error "SIGPROFILER_MATRIXGENERATOR does not support Conda. Please use Docker / Singularity / Apptainer instead." + } + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + // SigProfilerMatrixGenerator names the sample after the input file name up to the first '.', + // and dispatches on the extension of the (single) file in the input directory. + def sample = prefix.replaceAll(/[^A-Za-z0-9_-]/, '_') + def decompress = vcf.name.endsWith('.gz') ? "gzip -cd ${vcf} > input/${sample}.vcf" : "cp ${vcf} input/${sample}.vcf" + """ + # Stage only a symlink to the genome payload in a local volume so the tool never writes + # into the shared reference directory. + mkdir -p input volume/tsb plot_templates + ln -s "\$(readlink -f ${volume})/tsb/${genome}" volume/tsb/${genome} + # sigProfilerPlotting caches plot templates in its (read-only) package directory unless told otherwise + export SIGPROFILERPLOTTING_VOLUME="\$PWD/plot_templates" + + ${decompress} + + SigProfilerMatrixGenerator matrix_generator \\ + ${prefix} \\ + ${genome} \\ + input/ \\ + --volume volume \\ + ${args} + + # SigProfilerMatrixGenerator writes everything (matrices, plots, logs, sorted VCF) under input/output + mv input/output output + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + mkdir -p output/SBS output/DBS output/ID output/logs + touch output/SBS/${prefix}.SBS96.all output/DBS/${prefix}.DBS78.all output/ID/${prefix}.ID83.all output/logs/${prefix}.out + """ +} diff --git a/modules/local/sigprofiler/matrixgenerator/meta.yml b/modules/local/sigprofiler/matrixgenerator/meta.yml new file mode 100644 index 00000000..40cc7d55 --- /dev/null +++ b/modules/local/sigprofiler/matrixgenerator/meta.yml @@ -0,0 +1,109 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "sigprofiler_matrixgenerator" +description: Generate SBS, DBS and ID mutational matrices from a somatic small-variant VCF with SigProfilerMatrixGenerator +keywords: + - mutational signatures + - mutational matrix + - sbs96 + - sigprofiler + - somatic +tools: + - "sigprofilermatrixgenerator": + description: "SigProfilerMatrixGenerator creates mutational matrices for all types of somatic mutations" + homepage: "https://github.com/SigProfilerSuite/SigProfilerMatrixGenerator" + documentation: "https://sigprofilersuite.github.io/SigProfilerMatrixGenerator/" + tool_dev_url: "https://github.com/SigProfilerSuite/SigProfilerMatrixGenerator" + doi: "10.1186/s12864-019-6041-2" + licence: ["BSD-2-Clause"] + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - vcf: + type: file + description: | + Somatic SNV/indel VCF for one sample (plain or bgzipped). Only CHROM, POS, REF and + ALT are read; FILTER is ignored, so filter to PASS beforehand. + pattern: "*.{vcf,vcf.gz}" + - - meta2: + type: map + description: Groovy Map (unused) + - volume: + type: directory + description: SigProfilerMatrixGenerator volume containing tsb// + - genome: + type: string + description: SigProfilerMatrixGenerator genome name, e.g. GRCh38 or CHM13-T2T + +output: + sbs96: + - meta: + type: map + description: Groovy Map containing sample information + - "output/SBS/*.SBS96.all": + type: file + description: SBS96 mutational matrix (rows are the 96 trinucleotide classes, one column per sample) + pattern: "output/SBS/*.SBS96.all" + dbs78: + - meta: + type: map + description: Groovy Map containing sample information + - "output/DBS/*.DBS78.all": + type: file + description: DBS78 mutational matrix (absent when the sample has no doublet substitutions) + pattern: "output/DBS/*.DBS78.all" + id83: + - meta: + type: map + description: Groovy Map containing sample information + - "output/ID/*.ID83.all": + type: file + description: ID83 mutational matrix (absent when the sample has no indels) + pattern: "output/ID/*.ID83.all" + output_dir: + - meta: + type: map + description: Groovy Map containing sample information + - "output": + type: directory + description: All SigProfilerMatrixGenerator output (SBS/DBS/ID matrices at every context size, plots, TSB statistics, sorted VCF) + pattern: "output" + logs: + - meta: + type: map + description: Groovy Map containing sample information + - "output/logs/*": + type: file + description: SigProfilerMatrixGenerator log files + pattern: "output/logs/*" + versions_sigprofilermatrixgenerator: + - - ${task.process}: + type: string + description: The process the versions were collected from + - sigprofilermatrixgenerator: + type: string + description: The tool name + - 'python -c ''import importlib.metadata as m; print(m.version("SigProfilerMatrixGenerator"))''': + type: string + description: The command used to generate the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - sigprofilermatrixgenerator: + type: string + description: The tool name + - 'python -c ''import importlib.metadata as m; print(m.version("SigProfilerMatrixGenerator"))''': + type: string + description: The command used to generate the version of the tool + +authors: + - "@ljwharbers" +maintainers: + - "@ljwharbers" diff --git a/modules/nf-core/bcftools/view/environment.yml b/modules/nf-core/bcftools/view/environment.yml new file mode 100644 index 00000000..13b63a62 --- /dev/null +++ b/modules/nf-core/bcftools/view/environment.yml @@ -0,0 +1,10 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + # renovate: datasource=conda depName=bioconda/bcftools + - bioconda::bcftools=1.23.1 + # renovate: datasource=conda depName=bioconda/htslib + - bioconda::htslib=1.23.1 diff --git a/modules/nf-core/bcftools/view/main.nf b/modules/nf-core/bcftools/view/main.nf new file mode 100644 index 00000000..46e8ef5d --- /dev/null +++ b/modules/nf-core/bcftools/view/main.nf @@ -0,0 +1,75 @@ +process BCFTOOLS_VIEW { + tag "${meta.id}" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/0b/0b4d52ca9a56d07be3f78a12af654e5116f5112908dba277e6796fd9dfb83fe5/data' + : 'community.wave.seqera.io/library/bcftools_htslib:1.23.1--9f08ec665533d64a'}" + + input: + tuple val(meta), path(vcf), path(index) + path regions + path targets + path samples + + output: + tuple val(meta), path("*.{vcf,vcf.gz,bcf,bcf.gz}"), emit: vcf + tuple val(meta), path("*.{tbi,csi}"), emit: index, optional: true + tuple val("${task.process}"), val('bcftools'), eval("bcftools --version | sed '1!d; s/^.*bcftools //'"), topic: versions, emit: versions_bcftools + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def regions_file = regions ? "--regions-file ${regions}" : "" + def targets_file = targets ? "--targets-file ${targets}" : "" + def samples_file = samples ? "--samples-file ${samples}" : "" + def extension = args.contains("--output-type b") || args.contains("-Ob") + ? "bcf.gz" + : args.contains("--output-type u") || args.contains("-Ou") + ? "bcf" + : args.contains("--output-type z") || args.contains("-Oz") + ? "vcf.gz" + : args.contains("--output-type v") || args.contains("-Ov") + ? "vcf" + : "vcf" + """ + bcftools view \\ + --output ${prefix}.${extension} \\ + ${regions_file} \\ + ${targets_file} \\ + ${samples_file} \\ + ${args} \\ + --threads ${task.cpus} \\ + ${vcf} + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def extension = args.contains("--output-type b") || args.contains("-Ob") + ? "bcf.gz" + : args.contains("--output-type u") || args.contains("-Ou") + ? "bcf" + : args.contains("--output-type z") || args.contains("-Oz") + ? "vcf.gz" + : args.contains("--output-type v") || args.contains("-Ov") + ? "vcf" + : "vcf" + def stub_index = args.contains("--write-index=tbi") || args.contains("-W=tbi") + ? "tbi" + : args.contains("--write-index=csi") || args.contains("-W=csi") + ? "csi" + : args.contains("--write-index") || args.contains("-W") + ? "csi" + : "" + def create_cmd = extension.endsWith(".gz") ? "echo '' | gzip >" : "touch" + def create_index = extension.endsWith(".gz") && stub_index.matches("csi|tbi") ? "touch ${prefix}.${extension}.${stub_index}" : "" + """ + ${create_cmd} ${prefix}.${extension} + ${create_index} + """ +} diff --git a/modules/nf-core/bcftools/view/meta.yml b/modules/nf-core/bcftools/view/meta.yml new file mode 100644 index 00000000..96351894 --- /dev/null +++ b/modules/nf-core/bcftools/view/meta.yml @@ -0,0 +1,101 @@ +name: bcftools_view +description: View, subset and filter VCF or BCF files by position and filtering + expression. Convert between VCF and BCF +keywords: + - variant calling + - view + - bcftools + - VCF +tools: + - view: + description: | + View, subset and filter VCF or BCF files by position and filtering expression. Convert between VCF and BCF + homepage: http://samtools.github.io/bcftools/bcftools.html + documentation: http://www.htslib.org/doc/bcftools.html + doi: 10.1093/bioinformatics/btp352 + licence: + - "MIT" + identifier: biotools:bcftools +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - vcf: + type: file + description: | + The vcf file to be inspected. + e.g. 'file.vcf' + ontologies: [] + - index: + type: file + description: | + The tab index for the VCF file to be inspected. + e.g. 'file.tbi' + ontologies: [] + - regions: + type: file + description: | + Optionally, restrict the operation to regions listed in this file. + e.g. 'file.vcf' + ontologies: [] + - targets: + type: file + description: | + Optionally, restrict the operation to regions listed in this file (doesn't rely upon index files) + e.g. 'file.vcf' + ontologies: [] + - samples: + type: file + description: | + Optional, file of sample names to be included or excluded. + e.g. 'file.tsv' + ontologies: [] +output: + vcf: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.{vcf,vcf.gz,bcf,bcf.gz}": + type: file + description: VCF normalized output file + pattern: "*.{vcf,vcf.gz,bcf,bcf.gz}" + ontologies: [] + index: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.{tbi,csi}": + type: file + description: VCF file index + pattern: "*.{tbi,csi}" + versions_bcftools: + - - ${task.process}: + type: string + description: The process the versions were collected from + - bcftools: + type: string + description: The tool name + - bcftools --version | sed '1!d; s/^.*bcftools //': + type: eval + description: The command used to generate the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - bcftools: + type: string + description: The tool name + - bcftools --version | sed '1!d; s/^.*bcftools //': + type: eval + description: The command used to generate the version of the tool +authors: + - "@abhi18av" +maintainers: + - "@abhi18av" diff --git a/modules/nf-core/bcftools/view/tests/main.nf.test b/modules/nf-core/bcftools/view/tests/main.nf.test new file mode 100644 index 00000000..842e4833 --- /dev/null +++ b/modules/nf-core/bcftools/view/tests/main.nf.test @@ -0,0 +1,285 @@ +nextflow_process { + + name "Test Process BCFTOOLS_VIEW" + script "../main.nf" + process "BCFTOOLS_VIEW" + + tag "modules" + tag "modules_nfcore" + tag "bcftools" + tag "bcftools/view" + + test("sarscov2 - [vcf, tbi], [], [], []") { + + config "./nextflow.config" + + when { + process { + """ + input[0] = [ + [ id:'out', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/vcf/test.vcf.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/vcf/test.vcf.gz.tbi', checkIfExists: true) + ] + input[1] = [] + input[2] = [] + input[3] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.vcf, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() } + ) + } + } + + test("sarscov2 - [vcf, tbi], [], [], [] - vcf_gz_index") { + + config "./vcf_gz_index.config" + + when { + process { + """ + input[0] = [ + [ id:'out', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/vcf/test.vcf.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/vcf/test.vcf.gz.tbi', checkIfExists: true) + ] + input[1] = [] + input[2] = [] + input[3] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.vcf, + process.out.index.collect { it.collect { it instanceof Map ? it : file(it).name } }, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() }, + { assert process.out.index[0][1].endsWith(".csi") } + ) + } + } + + test("sarscov2 - [vcf, tbi], [], [], [] - vcf_gz_index_csi") { + + config "./vcf_gz_index_csi.config" + + when { + process { + """ + input[0] = [ + [ id:'out', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/vcf/test.vcf.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/vcf/test.vcf.gz.tbi', checkIfExists: true) + ] + input[1] = [] + input[2] = [] + input[3] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.vcf, + process.out.index.collect { it.collect { it instanceof Map ? it : file(it).name } }, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() }, + { assert process.out.index[0][1].endsWith(".csi") } + ) + } + } + + test("sarscov2 - [vcf, tbi], [], [], [] - vcf_gz_index_tbi") { + + config "./vcf_gz_index_tbi.config" + + when { + process { + """ + input[0] = [ + [ id:'out', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/vcf/test.vcf.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/vcf/test.vcf.gz.tbi', checkIfExists: true) + ] + input[1] = [] + input[2] = [] + input[3] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.vcf, + process.out.index.collect { it.collect { it instanceof Map ? it : file(it).name } }, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() }, + { assert process.out.index[0][1].endsWith(".tbi") } + ) + } + } + + test("sarscov2 - [vcf, tbi], vcf, tsv, []") { + + config "./nextflow.config" + + when { + process { + """ + input[0] = [ + [ id:'out', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/vcf/test.vcf.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/vcf/test.vcf.gz.tbi', checkIfExists: true) + ] + input[1] = file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/vcf/test3.vcf.gz', checkIfExists: true) + input[2] = file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/vcf/test2.targets.tsv.gz', checkIfExists: true) + input[3] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.vcf, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() } + ) + } + } + + test("sarscov2 - [vcf, tbi], [], [], [] - stub") { + + config "./nextflow.config" + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'out', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/vcf/test.vcf.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/vcf/test.vcf.gz.tbi', checkIfExists: true) + ] + input[1] = [] + input[2] = [] + input[3] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + file(process.out.vcf[0][1]).name, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() } + ) + } + } + + test("sarscov2 - [vcf, tbi], [], [], [] - vcf_gz_index - stub") { + + config "./vcf_gz_index.config" + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'out', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/vcf/test.vcf.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/vcf/test.vcf.gz.tbi', checkIfExists: true) + ] + input[1] = [] + input[2] = [] + input[3] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() }, + { assert process.out.index[0][1].endsWith(".csi") } + ) + } + } + + test("sarscov2 - [vcf, tbi], [], [], [] - vcf_gz_index_csi - stub") { + + config "./vcf_gz_index_csi.config" + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'out', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/vcf/test.vcf.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/vcf/test.vcf.gz.tbi', checkIfExists: true) + ] + input[1] = [] + input[2] = [] + input[3] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() }, + { assert process.out.index[0][1].endsWith(".csi") } + ) + } + } + + test("sarscov2 - [vcf, tbi], [], [], [] - vcf_gz_index_tbi - stub") { + + config "./vcf_gz_index_tbi.config" + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'out', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/vcf/test.vcf.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/vcf/test.vcf.gz.tbi', checkIfExists: true) + ] + input[1] = [] + input[2] = [] + input[3] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() }, + { assert process.out.index[0][1].endsWith(".tbi") } + ) + } + } +} diff --git a/modules/nf-core/bcftools/view/tests/main.nf.test.snap b/modules/nf-core/bcftools/view/tests/main.nf.test.snap new file mode 100644 index 00000000..4e4bf997 --- /dev/null +++ b/modules/nf-core/bcftools/view/tests/main.nf.test.snap @@ -0,0 +1,366 @@ +{ + "sarscov2 - [vcf, tbi], [], [], [] - vcf_gz_index_csi - stub": { + "content": [ + { + "0": [ + [ + { + "id": "out", + "single_end": false + }, + "out_vcf.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "1": [ + [ + { + "id": "out", + "single_end": false + }, + "out_vcf.vcf.gz.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + "BCFTOOLS_VIEW", + "bcftools", + "1.23.1" + ] + ], + "index": [ + [ + { + "id": "out", + "single_end": false + }, + "out_vcf.vcf.gz.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "vcf": [ + [ + { + "id": "out", + "single_end": false + }, + "out_vcf.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "versions_bcftools": [ + [ + "BCFTOOLS_VIEW", + "bcftools", + "1.23.1" + ] + ] + } + ], + "timestamp": "2026-05-28T14:45:16.225112", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.2" + } + }, + "sarscov2 - [vcf, tbi], [], [], [] - vcf_gz_index_tbi": { + "content": [ + [ + [ + { + "id": "out", + "single_end": false + }, + "out_vcf.vcf.gz:md5,8e722884ffb75155212a3fc053918766" + ] + ], + [ + [ + { + "id": "out", + "single_end": false + }, + "out_vcf.vcf.gz.tbi" + ] + ], + { + "versions_bcftools": [ + [ + "BCFTOOLS_VIEW", + "bcftools", + "1.23.1" + ] + ] + } + ], + "timestamp": "2026-05-28T14:44:45.565621", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.2" + } + }, + "sarscov2 - [vcf, tbi], [], [], [] - vcf_gz_index - stub": { + "content": [ + { + "0": [ + [ + { + "id": "out", + "single_end": false + }, + "out_vcf.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "1": [ + [ + { + "id": "out", + "single_end": false + }, + "out_vcf.vcf.gz.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + "BCFTOOLS_VIEW", + "bcftools", + "1.23.1" + ] + ], + "index": [ + [ + { + "id": "out", + "single_end": false + }, + "out_vcf.vcf.gz.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "vcf": [ + [ + { + "id": "out", + "single_end": false + }, + "out_vcf.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "versions_bcftools": [ + [ + "BCFTOOLS_VIEW", + "bcftools", + "1.23.1" + ] + ] + } + ], + "timestamp": "2026-05-28T14:45:09.894603", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.2" + } + }, + "sarscov2 - [vcf, tbi], [], [], [] - vcf_gz_index_tbi - stub": { + "content": [ + { + "0": [ + [ + { + "id": "out", + "single_end": false + }, + "out_vcf.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "1": [ + [ + { + "id": "out", + "single_end": false + }, + "out_vcf.vcf.gz.tbi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + "BCFTOOLS_VIEW", + "bcftools", + "1.23.1" + ] + ], + "index": [ + [ + { + "id": "out", + "single_end": false + }, + "out_vcf.vcf.gz.tbi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "vcf": [ + [ + { + "id": "out", + "single_end": false + }, + "out_vcf.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "versions_bcftools": [ + [ + "BCFTOOLS_VIEW", + "bcftools", + "1.23.1" + ] + ] + } + ], + "timestamp": "2026-05-28T14:45:22.083172", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.2" + } + }, + "sarscov2 - [vcf, tbi], vcf, tsv, []": { + "content": [ + [ + [ + { + "id": "out", + "single_end": false + }, + "out.vcf:md5,1bcbd0eff25d316ba915d06463aab17b" + ] + ], + { + "versions_bcftools": [ + [ + "BCFTOOLS_VIEW", + "bcftools", + "1.23.1" + ] + ] + } + ], + "timestamp": "2026-03-20T18:23:19.908213867", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.2" + } + }, + "sarscov2 - [vcf, tbi], [], [], [] - stub": { + "content": [ + "out.vcf", + { + "versions_bcftools": [ + [ + "BCFTOOLS_VIEW", + "bcftools", + "1.23.1" + ] + ] + } + ], + "timestamp": "2026-03-20T18:23:28.40103319", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.2" + } + }, + "sarscov2 - [vcf, tbi], [], [], [] - vcf_gz_index": { + "content": [ + [ + [ + { + "id": "out", + "single_end": false + }, + "out_vcf.vcf.gz:md5,8e722884ffb75155212a3fc053918766" + ] + ], + [ + [ + { + "id": "out", + "single_end": false + }, + "out_vcf.vcf.gz.csi" + ] + ], + { + "versions_bcftools": [ + [ + "BCFTOOLS_VIEW", + "bcftools", + "1.23.1" + ] + ] + } + ], + "timestamp": "2026-05-28T14:44:32.872424", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.2" + } + }, + "sarscov2 - [vcf, tbi], [], [], []": { + "content": [ + [ + [ + { + "id": "out", + "single_end": false + }, + "out.vcf:md5,8e722884ffb75155212a3fc053918766" + ] + ], + { + "versions_bcftools": [ + [ + "BCFTOOLS_VIEW", + "bcftools", + "1.23.1" + ] + ] + } + ], + "timestamp": "2026-03-20T18:22:45.088231326", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.2" + } + }, + "sarscov2 - [vcf, tbi], [], [], [] - vcf_gz_index_csi": { + "content": [ + [ + [ + { + "id": "out", + "single_end": false + }, + "out_vcf.vcf.gz:md5,8e722884ffb75155212a3fc053918766" + ] + ], + [ + [ + { + "id": "out", + "single_end": false + }, + "out_vcf.vcf.gz.csi" + ] + ], + { + "versions_bcftools": [ + [ + "BCFTOOLS_VIEW", + "bcftools", + "1.23.1" + ] + ] + } + ], + "timestamp": "2026-05-28T14:44:39.144211", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.2" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/bcftools/view/tests/nextflow.config b/modules/nf-core/bcftools/view/tests/nextflow.config new file mode 100644 index 00000000..932e3ba6 --- /dev/null +++ b/modules/nf-core/bcftools/view/tests/nextflow.config @@ -0,0 +1,3 @@ +process { + ext.args = '--no-version --output-type v' +} diff --git a/modules/nf-core/bcftools/view/tests/vcf_gz_index.config b/modules/nf-core/bcftools/view/tests/vcf_gz_index.config new file mode 100644 index 00000000..7dd696ee --- /dev/null +++ b/modules/nf-core/bcftools/view/tests/vcf_gz_index.config @@ -0,0 +1,4 @@ +process { + ext.prefix = { "${meta.id}_vcf" } + ext.args = "--output-type z --write-index --no-version" +} diff --git a/modules/nf-core/bcftools/view/tests/vcf_gz_index_csi.config b/modules/nf-core/bcftools/view/tests/vcf_gz_index_csi.config new file mode 100644 index 00000000..aebffb6f --- /dev/null +++ b/modules/nf-core/bcftools/view/tests/vcf_gz_index_csi.config @@ -0,0 +1,4 @@ +process { + ext.prefix = { "${meta.id}_vcf" } + ext.args = "--output-type z --write-index=csi --no-version" +} diff --git a/modules/nf-core/bcftools/view/tests/vcf_gz_index_tbi.config b/modules/nf-core/bcftools/view/tests/vcf_gz_index_tbi.config new file mode 100644 index 00000000..b192ae7d --- /dev/null +++ b/modules/nf-core/bcftools/view/tests/vcf_gz_index_tbi.config @@ -0,0 +1,4 @@ +process { + ext.prefix = { "${meta.id}_vcf" } + ext.args = "--output-type z --write-index=tbi --no-version" +} diff --git a/nextflow.config b/nextflow.config index cb53306c..50e1735f 100644 --- a/nextflow.config +++ b/nextflow.config @@ -43,6 +43,14 @@ params { vep_custom = null vep_custom_tbi = null + // Mutational signatures (SigProfiler) + sigprofiler_genome_dir = null + download_sigprofiler_genome = false + sigprofiler_cosmic_version = 3.6 + sigprofiler_exclude_signature_subgroups = null + sigprofiler_matrix_args = '--plot' + sigprofiler_assignment_args = null + // Skip options skip_qc = false skip_cramino = false @@ -59,6 +67,7 @@ params { skip_modkit = false use_gpu = false skip_whatshapstats = false + skip_signatures = false // minimap2 options minimap2_ont_model = null diff --git a/nextflow_schema.json b/nextflow_schema.json index bdc64a04..60f04a0d 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -303,6 +303,49 @@ } } }, + "sigprofiler_options": { + "title": "Mutational signature options", + "type": "object", + "description": "Options for SigProfilerMatrixGenerator / SigProfilerAssignment", + "default": "", + "fa_icon": "fas fa-fingerprint", + "properties": { + "sigprofiler_genome_dir": { + "type": "string", + "format": "directory-path", + "description": "SigProfilerMatrixGenerator volume directory containing tsb// (the installed genome payload).", + "help_text": "Create it once with `--download_sigprofiler_genome` (published to `/cache/sigprofiler/volume`) or with `SigProfilerMatrixGenerator install --volume `, then pass it here on later runs.", + "fa_icon": "fas fa-folder-open" + }, + "download_sigprofiler_genome": { + "type": "boolean", + "description": "Download and install the ~3 GB SigProfilerMatrixGenerator genome payload during the run instead of using --sigprofiler_genome_dir.", + "fa_icon": "fas fa-download" + }, + "sigprofiler_cosmic_version": { + "type": "number", + "default": 3.6, + "description": "COSMIC reference signature version fitted by SigProfilerAssignment.", + "fa_icon": "fas fa-hashtag" + }, + "sigprofiler_exclude_signature_subgroups": { + "type": "string", + "description": "Comma-separated SigProfilerAssignment signature subgroups to exclude from the fit (e.g. `Artifact_signatures,Lymphoid_signatures`).", + "fa_icon": "fas fa-filter" + }, + "sigprofiler_matrix_args": { + "type": "string", + "default": "--plot", + "description": "Extra arguments for `SigProfilerMatrixGenerator matrix_generator` (e.g. `--plot --tsb_stat`).", + "fa_icon": "fas fa-terminal" + }, + "sigprofiler_assignment_args": { + "type": "string", + "description": "Extra arguments for `SigProfilerAssignment cosmic_fit` (e.g. `--make_plots False`).", + "fa_icon": "fas fa-terminal" + } + } + }, "skip_options": { "title": "Skip options", "type": "object", @@ -317,6 +360,10 @@ "type": "boolean", "description": "Skips Cramino" }, + "skip_signatures": { + "type": "boolean", + "description": "Skips mutational signature analysis (SigProfilerMatrixGenerator + SigProfilerAssignment)" + }, "skip_mosdepth": { "type": "boolean", "description": "Skips Mosdepth" @@ -552,6 +599,9 @@ { "$ref": "#/$defs/wakhan_options" }, + { + "$ref": "#/$defs/sigprofiler_options" + }, { "$ref": "#/$defs/skip_options" }, diff --git a/subworkflows/local/prepare_signatures.nf b/subworkflows/local/prepare_signatures.nf new file mode 100644 index 00000000..01136245 --- /dev/null +++ b/subworkflows/local/prepare_signatures.nf @@ -0,0 +1,54 @@ +include { SIGPROFILER_INSTALL } from '../../modules/local/sigprofiler/install/main' + +workflow PREPARE_SIGNATURES { + + take: + genome // str: SigProfilerMatrixGenerator genome name (e.g. "GRCh38", "CHM13-T2T") + genome_url // str: URL of .tar.gz, or null to let SigProfilerMatrixGenerator download from the AlexandrovLab FTP + genome_dir // path: existing SigProfilerMatrixGenerator volume (contains tsb//), or null + download_genome // bool: if true, install the genome payload with SIGPROFILER_INSTALL instead of using genome_dir + + main: + + ch_versions = channel.empty() + sigprofiler_volume = channel.empty() + + if (!genome) { + error("No SigProfilerMatrixGenerator genome is defined for --genome ${params.genome}. Set --sigprofiler_genome (e.g. GRCh38 or CHM13-T2T) or use --skip_signatures.") + } + + if (download_genome) { + // + // MODULE: SIGPROFILER_INSTALL (label: process_single, process_long) + // Downloads and verifies the per-genome TSB payload (~3 GB) into a volume directory. + // Published to ${params.outdir}/cache/ so later runs can pass it as --sigprofiler_genome_dir. + // + SIGPROFILER_INSTALL ( + genome, + genome_url ?: '' + ) + sigprofiler_volume = SIGPROFILER_INSTALL.out.volume + } + else { + if (!genome_dir) { + error("Mutational signature analysis needs the SigProfilerMatrixGenerator payload for ${genome}.\n" + + "Either pass --sigprofiler_genome_dir (a directory containing tsb/${genome}/) " + + "or add --download_sigprofiler_genome to install it into ${params.outdir}/cache/ on this run, " + + "or disable the step with --skip_signatures.") + } + def tsb_dir = file("${genome_dir}/tsb/${genome}", type: 'dir') + if (!tsb_dir.exists() || !tsb_dir.isDirectory()) { + error("Path provided with --sigprofiler_genome_dir is invalid.\nMake sure there is a directory named tsb/${genome} in ${genome_dir}.") + } + def n_chrom = tsb_dir.listFiles().count { f -> f.name.endsWith('.txt') } + if (n_chrom < 24) { + error("${tsb_dir} holds ${n_chrom} chromosome files; a complete SigProfilerMatrixGenerator install of ${genome} has 24 (1-22, X, Y).") + } + sigprofiler_volume = channel.fromPath(genome_dir, type: 'dir', checkIfExists: true).collect() + } + // sigprofiler_volume: path -- SigProfilerMatrixGenerator volume root (downloaded or validated local) + + emit: + volume = sigprofiler_volume // path -- volume directory containing tsb// + versions = ch_versions +} diff --git a/tests/.nftignore b/tests/.nftignore index a1de7635..7d776918 100644 --- a/tests/.nftignore +++ b/tests/.nftignore @@ -27,3 +27,5 @@ pipeline_info/*.{html,json,txt,yml} */qc/{tumor,normal}/mosdepth/*.txt */variants/deepsomatic/*.{vcf.gz,vcf.gz.tbi} */variants/deepvariant/*.{vcf.gz,vcf.gz.tbi} +*/signatures/matrices/** +*/signatures/assignment/** diff --git a/workflows/lrsomatic.nf b/workflows/lrsomatic.nf index 211552df..aeb8027f 100644 --- a/workflows/lrsomatic.nf +++ b/workflows/lrsomatic.nf @@ -35,12 +35,16 @@ include { ENSEMBLVEP_VEP as GERMLINE_VEP } from '../modules/nf-core/ensemblve include { ENSEMBLVEP_VEP as SV_VEP } from '../modules/nf-core/ensemblvep/vep/main.nf' include { WHATSHAP_STATS } from '../modules/nf-core/whatshap/stats/main' include { MODKIT_PILEUP } from '../modules/nf-core/modkit/pileup/main' +include { BCFTOOLS_VIEW as SIGNATURES_BCFTOOLS_VIEW } from '../modules/nf-core/bcftools/view/main' +include { SIGPROFILER_MATRIXGENERATOR } from '../modules/local/sigprofiler/matrixgenerator/main' +include { SIGPROFILER_ASSIGNMENT } from '../modules/local/sigprofiler/assignment/main' // // IMPORT SUBWORKFLOWS // include { PREPARE_REFERENCE_FILES } from '../subworkflows/local/prepare_reference_files' include { PREPARE_ANNOTATION } from '../subworkflows/local/prepare_annotation' +include { PREPARE_SIGNATURES } from '../subworkflows/local/prepare_signatures' include { BAM_STATS_SAMTOOLS } from '../subworkflows/nf-core/bam_stats_samtools/main' include { TUMORONLY_SMALLVAR } from '../subworkflows/local/tumor_only/tumoronly_smallvar' include { PAIRED_SMALLVAR_SOMATIC } from '../subworkflows/local/paired/paired_smallvar_somatic' @@ -97,6 +101,8 @@ workflow LRSOMATIC { params.bed_file = getGenomeAttribute('bed_file') params.vep_genome = getGenomeAttribute('vep_genome') params.vep_species = getGenomeAttribute('vep_species') + params.sigprofiler_genome = getGenomeAttribute('sigprofiler_genome') + params.sigprofiler_genome_url = getGenomeAttribute('sigprofiler_genome_url') // Convert comma-separated caller strings to lists for internal use params.germline_var_keep = params.germline_var_keep instanceof List @@ -758,6 +764,68 @@ workflow LRSOMATIC { ) } + if (!params.skip_signatures) { + + // SUBWORKFLOW: PREPARE_SIGNATURES + // Validates (--sigprofiler_genome_dir) or installs (--download_sigprofiler_genome) the + // SigProfilerMatrixGenerator genome payload, tsb// + // Output: .volume -- path to the SigProfilerMatrixGenerator volume directory + PREPARE_SIGNATURES ( + params.sigprofiler_genome, + params.sigprofiler_genome_url, + params.sigprofiler_genome_dir, + params.download_sigprofiler_genome + ) + ch_versions = ch_versions.mix(PREPARE_SIGNATURES.out.versions) + sigprofiler_volume = PREPARE_SIGNATURES.out.volume.map { volume -> [[:], volume] } + // sigprofiler_volume: [[:], volume_dir] -- empty meta + SigProfilerMatrixGenerator volume + + // + // MODULE: SIGNATURES_BCFTOOLS_VIEW (BCFTOOLS_VIEW alias; label: process_medium) + // SigProfilerMatrixGenerator ignores FILTER and needs a plain-text VCF, so keep only the + // PASS SNVs / MNVs / indels of the phased somatic VCF as uncompressed VCF + // Input: PHASING_HAPLOTYPING.out.phased_somatic_vcf -- [meta, vcf, tbi] + // Output: .vcf -- [meta, vcf] + // + SIGNATURES_BCFTOOLS_VIEW ( + PHASING_HAPLOTYPING.out.phased_somatic_vcf, + [], + [], + [] + ) + + // + // MODULE: SIGPROFILER_MATRIXGENERATOR (label: process_medium) + // Input: [meta, vcf], [[:], volume], genome name + // Output: .sbs96 / .dbs78 / .id83 -- [meta, matrix]; .output_dir -- all matrices and plots + // + SIGPROFILER_MATRIXGENERATOR ( + SIGNATURES_BCFTOOLS_VIEW.out.vcf, + sigprofiler_volume, + params.sigprofiler_genome + ) + + // DBS78 / ID83 matrices are only written when the sample carries such variants + SIGPROFILER_MATRIXGENERATOR.out.sbs96 + .join(SIGPROFILER_MATRIXGENERATOR.out.dbs78, remainder: true) + .join(SIGPROFILER_MATRIXGENERATOR.out.id83, remainder: true) + .map { meta, sbs96, dbs78, id83 -> [meta, sbs96, dbs78 ?: [], id83 ?: []] } + .set { sigprofiler_matrices } + // sigprofiler_matrices: [meta, sbs96, dbs78 | [], id83 | []] + + // + // MODULE: SIGPROFILER_ASSIGNMENT (label: process_low) + // Fits COSMIC reference signatures to each matrix; SBS/DBS signatures are specific to + // the genome build (GRCh38 or CHM13-T2T), ID signatures use the GRCh37 set upstream + // Output: per-sample Assignment_Solution directories with activities, statistics and plots + // + SIGPROFILER_ASSIGNMENT ( + sigprofiler_matrices, + params.sigprofiler_genome, + params.sigprofiler_cosmic_version + ) + } + // Build SEVERUS input by combining tumor-only and T/N paired samples with phased germline VCFs // Tumor-only samples get empty lists for normal BAM/BAI (SEVERUS runs in tumor-only mode) branched_minimap.tumor_only