From 47f5d8386bf76d486a6e99128680d30acfe87125 Mon Sep 17 00:00:00 2001 From: Nicolas Vannieuwkerke Date: Mon, 7 Sep 2026 13:55:01 +0200 Subject: [PATCH 01/12] bump pipeline to 1.14.0dev --- .nf-core.yml | 2 +- assets/multiqc_config.yml | 2 +- nextflow.config | 2 +- ro-crate-metadata.json | 18 +++++++++--------- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.nf-core.yml b/.nf-core.yml index b4b7d5a8..3ea7e386 100644 --- a/.nf-core.yml +++ b/.nf-core.yml @@ -41,4 +41,4 @@ template: skip_features: - fastqc - is_nfcore - version: 1.13.6 + version: 1.14.0dev diff --git a/assets/multiqc_config.yml b/assets/multiqc_config.yml index 0bd23247..2771c125 100644 --- a/assets/multiqc_config.yml +++ b/assets/multiqc_config.yml @@ -1,5 +1,5 @@ report_comment: > - This report has been generated by the nf-cmgg/smallvariants analysis pipeline. + This report has been generated by the nf-cmgg/smallvariants analysis pipeline. report_section_order: "nf-cmgg-smallvariants-methods-description": order: -1000 diff --git a/nextflow.config b/nextflow.config index ef1079a5..7cb2c502 100644 --- a/nextflow.config +++ b/nextflow.config @@ -223,7 +223,7 @@ manifest { mainScript = 'main.nf' defaultBranch = 'main' nextflowVersion = '!>=26.04.0' - version = '1.13.6' + version = '1.14.0dev' doi = '' } diff --git a/ro-crate-metadata.json b/ro-crate-metadata.json index c544f040..8b4dec59 100644 --- a/ro-crate-metadata.json +++ b/ro-crate-metadata.json @@ -21,8 +21,8 @@ { "@id": "./", "@type": "Dataset", - "creativeWorkStatus": "Stable", - "datePublished": "2026-09-03T14:44:50+00:00", + "creativeWorkStatus": "InProgress", + "datePublished": "2026-09-07T11:54:03+00:00", "description": "# nf-cmgg/smallvariants\n\n[![Open in GitHub Codespaces](https://img.shields.io/badge/Open_In_GitHub_Codespaces-black?labelColor=grey&logo=github)](https://github.com/codespaces/new/nf-cmgg/smallvariants)\n[![GitHub Actions CI Status](https://github.com/nf-cmgg/smallvariants/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-cmgg/smallvariants/actions/workflows/nf-test.yml)\n[![GitHub Actions Linting Status](https://github.com/nf-cmgg/smallvariants/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-cmgg/smallvariants/actions/workflows/linting.yml)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX)\n[![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com)\n\n[![Nextflow](https://img.shields.io/badge/version-%E2%89%A526.04.0-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/)\n[![nf-core template version](https://img.shields.io/badge/nf--core_template-4.0.2-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/4.0.2)\n[![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/)\n[![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/)\n[![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/)\n[![Launch on Seqera Platform](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Seqera%20Platform-%234256e7)](https://cloud.seqera.io/launch?pipeline=https://github.com/nf-cmgg/smallvariants)\n\n## Introduction\n\n**nf-cmgg/smallvariants** is a nextflow pipeline for calling and annotating small variants from short DNA reads for WES and WGS data.\n\nThe pipeline is built using [Nextflow](https://www.nextflow.io), a workflow tool to run tasks across multiple compute infrastructures in a very portable manner. It uses Docker/Singularity containers making installation trivial and results highly reproducible. The [Nextflow DSL2](https://www.nextflow.io/docs/latest/dsl2.html) implementation of this pipeline uses one container per process which makes it much easier to maintain and update software dependencies. Where possible, these processes have been submitted to and installed from [nf-core/modules](https://github.com/nf-core/modules) in order to make them available to all nf-core pipelines, and to everyone within the Nextflow community!\n\nPlease have a look at the [documentation](https://nf-cmgg.github.io/smallvariants/latest/) on how to run the pipeline.\n\n## Development environment\n\nA [pixi](https://pixi.prefix.dev/latest/) development environment is available for this pipeline. Run the following command to install the environment:\n\n```\npixi install\n```\n\nThen run `pixi shell` to enter the environment and start developing.\n", "hasPart": [ { @@ -99,7 +99,7 @@ }, "mentions": [ { - "@id": "#84577f03-f447-4b0b-b2fb-5422aec4656d" + "@id": "#c826b3b7-b8ef-4fcd-8257-778b050fcc73" } ], "name": "nf-cmgg/smallvariants" @@ -132,7 +132,7 @@ } ], "dateCreated": "", - "dateModified": "2026-09-03T16:44:50Z", + "dateModified": "2026-09-07T13:54:03Z", "dct:conformsTo": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE/", "keywords": [ "nf-core", @@ -160,10 +160,10 @@ }, "url": [ "https://github.com/nf-cmgg/smallvariants", - "https://nf-co.re/nf-cmgg/smallvariants/1.13.6/" + "https://nf-co.re/nf-cmgg/smallvariants/dev/" ], "version": [ - "1.13.6" + "1.14.0dev" ] }, { @@ -179,11 +179,11 @@ "version": "!>=26.04.0" }, { - "@id": "#84577f03-f447-4b0b-b2fb-5422aec4656d", + "@id": "#c826b3b7-b8ef-4fcd-8257-778b050fcc73", "@type": "TestSuite", "instance": [ { - "@id": "#43065218-569c-4a08-b9a4-bca918d8c09a" + "@id": "#bd82b942-d87b-49da-91b1-afd3de258b7b" } ], "mainEntity": { @@ -192,7 +192,7 @@ "name": "Test suite for nf-cmgg/smallvariants" }, { - "@id": "#43065218-569c-4a08-b9a4-bca918d8c09a", + "@id": "#bd82b942-d87b-49da-91b1-afd3de258b7b", "@type": "TestInstance", "name": "GitHub Actions workflow for testing nf-cmgg/smallvariants", "resource": "repos/nf-cmgg/smallvariants/actions/workflows/nf-test.yml", From 850d881ff56148b9dd4bac20c7db8d1648a67247 Mon Sep 17 00:00:00 2001 From: Nicolas Vannieuwkerke Date: Mon, 7 Sep 2026 14:04:00 +0200 Subject: [PATCH 02/12] rename cram_call_gatk4 to cram_call_haplotypecaller --- .../main.nf | 4 ++-- .../tests/main.nf.test | 10 +++++----- .../tests/main.nf.test.snap | 4 ++-- workflows/smallvariants.nf | 10 +++++----- 4 files changed, 14 insertions(+), 14 deletions(-) rename subworkflows/local/{cram_call_gatk4 => cram_call_haplotypecaller}/main.nf (97%) rename subworkflows/local/{cram_call_gatk4 => cram_call_haplotypecaller}/tests/main.nf.test (95%) rename subworkflows/local/{cram_call_gatk4 => cram_call_haplotypecaller}/tests/main.nf.test.snap (96%) diff --git a/subworkflows/local/cram_call_gatk4/main.nf b/subworkflows/local/cram_call_haplotypecaller/main.nf similarity index 97% rename from subworkflows/local/cram_call_gatk4/main.nf rename to subworkflows/local/cram_call_haplotypecaller/main.nf index 274c27d0..654ad3cc 100644 --- a/subworkflows/local/cram_call_gatk4/main.nf +++ b/subworkflows/local/cram_call_haplotypecaller/main.nf @@ -1,5 +1,5 @@ // -// Call the variants using GATK4 tooling +// Call the variants using HaplotypeCaller // include { GATK4_CALIBRATEDRAGSTRMODEL } from '../../../modules/nf-core/gatk4/calibratedragstrmodel/main' @@ -8,7 +8,7 @@ include { BCFTOOLS_STATS } from '../../../modules/nf- include { VCF_CONCAT_BCFTOOLS } from '../vcf_concat_bcftools/main' -workflow CRAM_CALL_GATK4 { +workflow CRAM_CALL_HAPLOTYPECALLER { take: ch_input // channel: [mandatory] [ val(meta), path(cram), path(crai), path(bed) ] => sample CRAM files and their indexes with the split bed files ch_fasta // channel: [mandatory] [ val(meta), path(fasta) ] => fasta reference diff --git a/subworkflows/local/cram_call_gatk4/tests/main.nf.test b/subworkflows/local/cram_call_haplotypecaller/tests/main.nf.test similarity index 95% rename from subworkflows/local/cram_call_gatk4/tests/main.nf.test rename to subworkflows/local/cram_call_haplotypecaller/tests/main.nf.test index 1807948a..8ebe782b 100644 --- a/subworkflows/local/cram_call_gatk4/tests/main.nf.test +++ b/subworkflows/local/cram_call_haplotypecaller/tests/main.nf.test @@ -1,15 +1,15 @@ nextflow_workflow { - name "Test Workflow CRAM_CALL_GATK4" + name "Test Workflow CRAM_CALL_HAPLOTYPECALLER" script "../main.nf" - workflow "CRAM_CALL_GATK4" + workflow "CRAM_CALL_HAPLOTYPECALLER" tag "subworkflows" tag "subworkflows_local" - tag "cram_call_gatk4" + tag "CRAM_CALL_HAPLOTYPECALLER" tag "vcf_concat_bcftools" - test("cram_call_gatk4 - default") { + test("CRAM_CALL_HAPLOTYPECALLER - default") { when { @@ -66,7 +66,7 @@ nextflow_workflow { } - test("cram_call_gatk4 - dragstr") { + test("CRAM_CALL_HAPLOTYPECALLER - dragstr") { when { diff --git a/subworkflows/local/cram_call_gatk4/tests/main.nf.test.snap b/subworkflows/local/cram_call_haplotypecaller/tests/main.nf.test.snap similarity index 96% rename from subworkflows/local/cram_call_gatk4/tests/main.nf.test.snap rename to subworkflows/local/cram_call_haplotypecaller/tests/main.nf.test.snap index 908fb0d9..11d4837a 100644 --- a/subworkflows/local/cram_call_gatk4/tests/main.nf.test.snap +++ b/subworkflows/local/cram_call_haplotypecaller/tests/main.nf.test.snap @@ -1,5 +1,5 @@ { - "cram_call_gatk4 - default": { + "CRAM_CALL_HAPLOTYPECALLER - default": { "content": [ [ [ @@ -41,7 +41,7 @@ "nextflow": "26.04.1" } }, - "cram_call_gatk4 - dragstr": { + "CRAM_CALL_HAPLOTYPECALLER - dragstr": { "content": [ [ [ diff --git a/workflows/smallvariants.nf b/workflows/smallvariants.nf index 636d120c..ed61a9ec 100644 --- a/workflows/smallvariants.nf +++ b/workflows/smallvariants.nf @@ -17,7 +17,7 @@ include { methodsDescriptionText } from '../subworkflows/local/utils_ include { CRAM_PREPARE_SAMTOOLS_BEDTOOLS } from '../subworkflows/local/cram_prepare_samtools_bedtools/main' include { INPUT_SPLIT_BEDTOOLS } from '../subworkflows/local/input_split_bedtools/main' -include { CRAM_CALL_GATK4 } from '../subworkflows/local/cram_call_gatk4/main' +include { CRAM_CALL_HAPLOTYPECALLER } from '../subworkflows/local/cram_call_haplotypecaller/main' include { GVCF_JOINT_GENOTYPE_GATK4 } from '../subworkflows/local/gvcf_joint_genotype_gatk4/main' include { BAM_CALL_ELPREP } from '../subworkflows/local/bam_call_elprep/main' include { BAM_CALL_VARDICTJAVA } from '../subworkflows/local/bam_call_vardictjava/main' @@ -538,7 +538,7 @@ workflow SMALLVARIANTS { // Call variants with GATK4 HaplotypeCaller // - CRAM_CALL_GATK4( + CRAM_CALL_HAPLOTYPECALLER( ch_caller_inputs.cram.filter { meta, _cram, _crai, _bed -> // Filter out the entries that already have a GVCF meta.type == "cram" @@ -551,9 +551,9 @@ workflow SMALLVARIANTS { ch_dbsnp_tbi_ready, dragstr ) - ch_gvcfs_ready = ch_gvcfs_ready.mix(CRAM_CALL_GATK4.out.gvcfs) - ch_reports = ch_reports.mix(CRAM_CALL_GATK4.out.reports.map { _meta, report -> report }) - ch_gvcf_reports = ch_gvcf_reports.mix(CRAM_CALL_GATK4.out.reports) + ch_gvcfs_ready = ch_gvcfs_ready.mix(CRAM_CALL_HAPLOTYPECALLER.out.gvcfs) + ch_reports = ch_reports.mix(CRAM_CALL_HAPLOTYPECALLER.out.reports.map { _meta, report -> report }) + ch_gvcf_reports = ch_gvcf_reports.mix(CRAM_CALL_HAPLOTYPECALLER.out.reports) } if("elprep" in callers) { From d7b00657c8fe37034870cd5a555419b53c7bc2cc Mon Sep 17 00:00:00 2001 From: Nicolas Vannieuwkerke Date: Mon, 7 Sep 2026 15:24:06 +0200 Subject: [PATCH 03/12] create mutect2 subwf --- conf/modules.config | 23 +- modules.json | 5 + modules/nf-core/gatk4/mutect2/environment.yml | 10 + modules/nf-core/gatk4/mutect2/main.nf | 70 +++ modules/nf-core/gatk4/mutect2/meta.yml | 175 ++++++ .../nf-core/gatk4/mutect2/tests/main.nf.test | 542 ++++++++++++++++++ .../gatk4/mutect2/tests/main.nf.test.snap | 363 ++++++++++++ .../gatk4/mutect2/tests/nextflow.config | 5 + subworkflows/local/cram_call_mutect2/main.nf | 58 ++ .../cram_call_mutect2/tests/main.nf.test | 68 +++ .../cram_call_mutect2/tests/main.nf.test.snap | 44 ++ 11 files changed, 1359 insertions(+), 4 deletions(-) create mode 100644 modules/nf-core/gatk4/mutect2/environment.yml create mode 100644 modules/nf-core/gatk4/mutect2/main.nf create mode 100644 modules/nf-core/gatk4/mutect2/meta.yml create mode 100644 modules/nf-core/gatk4/mutect2/tests/main.nf.test create mode 100644 modules/nf-core/gatk4/mutect2/tests/main.nf.test.snap create mode 100644 modules/nf-core/gatk4/mutect2/tests/nextflow.config create mode 100644 subworkflows/local/cram_call_mutect2/main.nf create mode 100644 subworkflows/local/cram_call_mutect2/tests/main.nf.test create mode 100644 subworkflows/local/cram_call_mutect2/tests/main.nf.test.snap diff --git a/conf/modules.config b/conf/modules.config index 1cc3aff5..fbaee064 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -72,11 +72,11 @@ process { ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ - withName: "^.*CRAM_CALL_GATK4:GATK4_CALIBRATEDRAGSTRMODEL\$" { + withName: "^.*CRAM_CALL_HAPLOTYPECALLER:GATK4_CALIBRATEDRAGSTRMODEL\$" { ext.args = "--parallel" } - withName: "^.*CRAM_CALL_GATK4:GATK4_HAPLOTYPECALLER\$" { + withName: "^.*CRAM_CALL_HAPLOTYPECALLER:GATK4_HAPLOTYPECALLER\$" { time = { 16.h * task.attempt } ext.prefix = {"${meta.id}.g"} ext.args = { @@ -91,12 +91,12 @@ process { } } - withName: "^.*CRAM_CALL_GATK4:VCF_CONCAT_BCFTOOLS:BCFTOOLS_CONCAT\$" { + withName: "^.*CRAM_CALL_HAPLOTYPECALLER:VCF_CONCAT_BCFTOOLS:BCFTOOLS_CONCAT\$" { ext.prefix = { "${meta.id}.${meta.caller}.g" } ext.args = '--allow-overlaps --output-type z --write-index=tbi' } - withName: "^.*CRAM_CALL_GATK4:BCFTOOLS_STATS\$" { + withName: "^.*CRAM_CALL_HAPLOTYPECALLER:BCFTOOLS_STATS\$" { ext.prefix = { "${meta.id}.${meta.caller}" } } @@ -204,6 +204,21 @@ process { ext.prefix = {"${meta.id}.concat"} } + /* + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + MUTECT2 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + withName: "^.*CRAM_CALL_MUTECT2:MUTECT2\$" { + time = { 16.h * task.attempt } + } + + withName: "^.*CRAM_CALL_MUTECT2:VCF_CONCAT_BCFTOOLS:BCFTOOLS_CONCAT\$" { + ext.args = '--allow-overlaps --output-type z --write-index=tbi' + ext.prefix = {"${meta.id}.concat"} + } + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ FILTER diff --git a/modules.json b/modules.json index b0708491..11baf70c 100644 --- a/modules.json +++ b/modules.json @@ -117,6 +117,11 @@ "git_sha": "b73338eca19d798eb0ef6ed112ef4d27a30d2871", "installed_by": ["modules"] }, + "gatk4/mutect2": { + "branch": "master", + "git_sha": "6ef220fd9252b42fb3c50348bbdbcb6246730686", + "installed_by": ["modules"] + }, "gawk": { "branch": "master", "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", diff --git a/modules/nf-core/gatk4/mutect2/environment.yml b/modules/nf-core/gatk4/mutect2/environment.yml new file mode 100644 index 00000000..84f68508 --- /dev/null +++ b/modules/nf-core/gatk4/mutect2/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/gatk4-main + - bioconda::gatk4-main=4.7.0.0 + # renovate: datasource=conda depName=bioconda/gcnvkernel + - bioconda::gcnvkernel=0.9 diff --git a/modules/nf-core/gatk4/mutect2/main.nf b/modules/nf-core/gatk4/mutect2/main.nf new file mode 100644 index 00000000..98cef567 --- /dev/null +++ b/modules/nf-core/gatk4/mutect2/main.nf @@ -0,0 +1,70 @@ +process GATK4_MUTECT2 { + tag "${meta.id}" + label 'process_low' + + 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/b9/b9822b92da68a3e7916072218082e3fa79bebc2f377947c363613adeecd56ec5/data' + : 'community.wave.seqera.io/library/gatk4-main_gcnvkernel:961440660027ec01'}" + + input: + tuple val(meta), path(input), path(input_index), path(intervals) + tuple val(meta2), path(fasta) + tuple val(meta3), path(fai), path(gzi) + tuple val(meta4), path(dict) + path alleles + path alleles_tbi + path germline_resource + path germline_resource_tbi + path panel_of_normals + path panel_of_normals_tbi + + output: + tuple val(meta), path("${prefix}.vcf.gz"), emit: vcf + tuple val(meta), path("${prefix}.vcf.gz.stats"), emit: stats + tuple val(meta), path("${prefix}.vcf.gz.tbi"), emit: tbi + tuple val(meta), path("${prefix}.f1r2.tar.gz"), emit: f1r2, optional: true + tuple val("${task.process}"), val('gatk4'), eval("gatk --version | sed -n '/GATK.*v/s/.*v//p'"), topic: versions, emit: versions_gatk4 + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + def inputs = input.collect { vcf_ -> "--input ${vcf_}" }.join(" ") + def interval_command = intervals ? "--intervals ${intervals}" : "" + def pon_command = panel_of_normals ? "--panel-of-normals ${panel_of_normals}" : "" + def gr_command = germline_resource ? "--germline-resource ${germline_resource}" : "" + def a_command = alleles ? "--alleles ${alleles}" : "" + + def avail_mem = 3072 + if (!task.memory) { + log.info('[GATK Mutect2] Available memory not known - defaulting to 3GB. Specify process memory requirements to change this.') + } + else { + avail_mem = (task.memory.mega * 0.8).intValue() + } + """ + gatk --java-options "-Xmx${avail_mem}M -XX:-UsePerfData" \\ + Mutect2 \\ + ${inputs} \\ + --output ${prefix}.vcf.gz \\ + --reference ${fasta} \\ + ${pon_command} \\ + ${gr_command} \\ + ${a_command} \\ + ${interval_command} \\ + --tmp-dir . \\ + ${args} + """ + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + """ + echo "" | gzip > ${prefix}.vcf.gz + touch ${prefix}.vcf.gz.tbi + touch ${prefix}.vcf.gz.stats + echo "" | gzip > ${prefix}.f1r2.tar.gz + """ +} diff --git a/modules/nf-core/gatk4/mutect2/meta.yml b/modules/nf-core/gatk4/mutect2/meta.yml new file mode 100644 index 00000000..adac5cde --- /dev/null +++ b/modules/nf-core/gatk4/mutect2/meta.yml @@ -0,0 +1,175 @@ +name: gatk4_mutect2 +description: Call somatic SNVs and indels via local assembly of haplotypes. +keywords: + - gatk4 + - haplotype + - indels + - mutect2 + - snvs + - somatic +tools: + - gatk4: + description: | + Developed in the Data Sciences Platform at the Broad Institute, the toolkit offers a wide variety of tools + with a primary focus on variant discovery and genotyping. Its powerful processing engine + and high-performance computing features make it capable of taking on projects of any size. + homepage: https://gatk.broadinstitute.org/hc/en-us + documentation: https://gatk.broadinstitute.org/hc/en-us/categories/360002369672s + doi: 10.1158/1538-7445.AM2017-3590 + licence: ["Apache-2.0"] + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test'] + - input: + type: list + description: list of BAM files, also able to take CRAM as an input + pattern: "*.{bam/cram}" + - input_index: + type: list + description: list of BAM file indexes, also able to take CRAM indexes as an + input + pattern: "*.{bam.bai/cram.crai}" + - intervals: + type: file + description: Specify region the tools is run on. + pattern: ".{bed,interval_list}" + ontologies: [] + - - meta2: + type: map + description: | + Groovy Map containing reference information + e.g. [ id:'genome' ] + - fasta: + type: file + description: The reference fasta file + pattern: "*.{fasta,fasta.gz}" + ontologies: [] + - - meta3: + type: map + description: | + Groovy Map containing reference information + e.g. [ id:'genome' ] + - fai: + type: file + description: Index of reference fasta file + pattern: "*.{fasta.fai,fasta.fai.gz}" + ontologies: [] + - gzi: + type: file + description: Index of bgzipped reference fasta file + pattern: "*.fasta.gz.gzi" + ontologies: [] + - - meta4: + type: map + description: | + Groovy Map containing reference information + e.g. [ id:'genome' ] + - dict: + type: file + description: GATK sequence dictionary + pattern: "*.dict" + ontologies: [] + - alleles: + type: file + description: vcf file to be used to force-call alleles. + pattern: "*.vcf.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + - alleles_tbi: + type: file + description: Index file for alleles to be force-called. + pattern: "*.vcf.gz.tbi" + ontologies: [] + - germline_resource: + type: file + description: Population vcf of germline sequencing, containing allele fractions. + pattern: "*.vcf.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + - germline_resource_tbi: + type: file + description: Index file for the germline resource. + pattern: "*.vcf.gz.tbi" + ontologies: [] + - panel_of_normals: + type: file + description: vcf file to be used as a panel of normals. + pattern: "*.vcf.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + - panel_of_normals_tbi: + type: file + description: Index for the panel of normals. + pattern: "*.vcf.gz.tbi" + ontologies: [] +output: + vcf: + - - meta: + type: map + description: A Groovy map containing sample information + - "${prefix}.vcf.gz": + type: file + description: compressed vcf file + pattern: "${prefix}.vcf.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + stats: + - - meta: + type: map + description: A Groovy map containing sample information + - "${prefix}.vcf.gz.stats": + type: file + description: Stats file that pairs with output vcf file + pattern: "${prefix}.vcf.gz.stats" + ontologies: [] + tbi: + - - meta: + type: map + description: A Groovy map containing sample information + - "${prefix}.vcf.gz.tbi": + type: file + description: Index of vcf file + pattern: "${prefix}.vcf.gz.tbi" + ontologies: [] + f1r2: + - - meta: + type: map + description: A Groovy map containing sample information + - "${prefix}.f1r2.tar.gz": + type: file + description: file containing information to be passed to LearnReadOrientationModel + (only outputted when tumor_normal_pair mode is run) + pattern: "${prefix}.f1r2.tar.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + versions_gatk4: + - - ${task.process}: + type: string + description: The name of the process + - gatk4: + type: string + description: The name of the tool + - gatk --version | sed -n '/GATK.*v/s/.*v//p': + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - gatk4: + type: string + description: The name of the tool + - gatk --version | sed -n '/GATK.*v/s/.*v//p': + type: eval + description: The expression to obtain the version of the tool +authors: + - "@GCJMackenzie" + - "@ramprasadn" +maintainers: + - "@GCJMackenzie" + - "@ramprasadn" diff --git a/modules/nf-core/gatk4/mutect2/tests/main.nf.test b/modules/nf-core/gatk4/mutect2/tests/main.nf.test new file mode 100644 index 00000000..9e7b0fe7 --- /dev/null +++ b/modules/nf-core/gatk4/mutect2/tests/main.nf.test @@ -0,0 +1,542 @@ +nextflow_process { + + name "Test Process GATK4_MUTECT2" + script "../main.nf" + process "GATK4_MUTECT2" + + tag "modules" + tag "modules_nfcore" + tag "gatk4" + tag "gatk4/mutect2" + config "./nextflow.config" + + test("human - bam - tumor_normal_pair") { + when { + params { + module_args = "--normal-sample normal" + } + process { + """ + input[0] = [ + [ + id:'test', + normal_id:'normal', + tumor_id:'tumour' + ], + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.recalibrated.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.recalibrated.sorted.bam', checkIfExists: true) + ], + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.recalibrated.sorted.bam.bai', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.recalibrated.sorted.bam.bai', checkIfExists: true) + ], + [] + ] + input[1] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta', checkIfExists: true) + ] + input[2] = [ + [ id:'genome' ], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta.fai', checkIfExists: true)], + [] + ] + input[3] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.dict', checkIfExists: true) + ] + input[4] = [] + input[5] = [] + input[6] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/gnomAD.r2.1.1.vcf.gz', checkIfExists: true) + input[7] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/gnomAD.r2.1.1.vcf.gz.tbi', checkIfExists: true) + input[8] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/mills_and_1000G.indels.hg38.vcf.gz', checkIfExists: true) + input[9] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/mills_and_1000G.indels.hg38.vcf.gz.tbi', checkIfExists: true) + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot( + path(process.out.vcf.get(0).get(1)).vcf.variantsMD5, + process.out.tbi.collect { file(it[1]).getName() }, + process.out.stats, + process.out.f1r2, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() } + ) + } + } + + test("human - bam - tumor_normal_pair_f1r2") { + + when { + params { + module_args = "--normal-sample normal --f1r2-tar-gz test.f1r2.tar.gz" + } + process { + """ + input[0] = [ + [ + id:'test', + normal_id:'normal', + tumor_id:'tumour' + ], + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.recalibrated.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.recalibrated.sorted.bam', checkIfExists: true) + ], + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.recalibrated.sorted.bam.bai', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.recalibrated.sorted.bam.bai', checkIfExists: true) + ], + [] + ] + input[1] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta', checkIfExists: true) + ] + input[2] = [ + [ id:'genome' ], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta.fai', checkIfExists: true)], + [] + ] + input[3] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.dict', checkIfExists: true) + ] + input[4] = [] + input[5] = [] + input[6] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/gnomAD.r2.1.1.vcf.gz', checkIfExists: true) + input[7] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/gnomAD.r2.1.1.vcf.gz.tbi', checkIfExists: true) + input[8] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/mills_and_1000G.indels.hg38.vcf.gz', checkIfExists: true) + input[9] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/mills_and_1000G.indels.hg38.vcf.gz.tbi', checkIfExists: true) + """ + } + } + then { + assert process.success + assertAll( + { assert snapshot( + path(process.out.vcf.get(0).get(1)).vcf.variantsMD5, + process.out.tbi.collect { file(it[1]).getName() }, + process.out.stats, + process.out.f1r2.collect { file(it[1]).getName() }, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() } + ) + } + } + + test("human - bam - tumor_only"){ + when { + params { + module_args = '' + } + process { + """ + input[0] = [ + [ id:'test'], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.recalibrated.sorted.bam', checkIfExists: true)], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.recalibrated.sorted.bam.bai', checkIfExists: true)], + [] + ] + input[1] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta', checkIfExists: true) + ] + input[2] = [ + [ id:'genome' ], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta.fai', checkIfExists: true)], + [] + ] + input[3] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.dict', checkIfExists: true) + ] + input[4] = [] + input[5] = [] + input[6] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/gnomAD.r2.1.1.vcf.gz', checkIfExists: true) + input[7] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/gnomAD.r2.1.1.vcf.gz.tbi', checkIfExists: true) + input[8] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/mills_and_1000G.indels.hg38.vcf.gz', checkIfExists: true) + input[9] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/mills_and_1000G.indels.hg38.vcf.gz.tbi', checkIfExists: true) + """ + } + } + then { + assert process.success + assertAll( + { assert snapshot( + path(process.out.vcf.get(0).get(1)).vcf.variantsMD5, + process.out.tbi.collect { file(it[1]).getName() }, + process.out.stats, + process.out.f1r2, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() } + ) + } + } + + test("human - cram"){ + when { + params { + module_args = '' + } + process{ + """ + input[0] = [ + [ id:'test'], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test2.paired_end.recalibrated.sorted.cram', checkIfExists: true)], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test2.paired_end.recalibrated.sorted.cram.crai', checkIfExists: true)], + [] + ] + input[1] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta', checkIfExists: true) + ] + input[2] = [ + [ id:'genome' ], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta.fai', checkIfExists: true)], + [] + ] + input[3] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.dict', checkIfExists: true) + ] + input[4] = [] + input[5] = [] + input[6] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/gnomAD.r2.1.1.vcf.gz', checkIfExists: true) + input[7] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/gnomAD.r2.1.1.vcf.gz.tbi', checkIfExists: true) + input[8] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/mills_and_1000G.indels.hg38.vcf.gz', checkIfExists: true) + input[9] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/mills_and_1000G.indels.hg38.vcf.gz.tbi', checkIfExists: true) + """ + } + } + then { + assert process.success + assertAll( + { assert snapshot( + path(process.out.vcf.get(0).get(1)).vcf.variantsMD5, + process.out.tbi.collect { file(it[1]).getName() }, + process.out.stats, + process.out.f1r2, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() } + ) + } + } + + test("human - bam - generate_pon") { + when { + params { + module_args = '' + } + process { + """ + input[0] = [ + [ id:'test'], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.recalibrated.sorted.bam', checkIfExists: true)], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.recalibrated.sorted.bam.bai', checkIfExists: true)], + [] + ] + input[1] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta', checkIfExists: true) + ] + input[2] = [ + [ id:'genome' ], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta.fai', checkIfExists: true)], + [] + ] + input[3] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.dict', checkIfExists: true) + ] + input[4] = [] + input[5] = [] + input[6] = [] + input[7] = [] + input[8] = [] + input[9] = [] + """ + } + } + then { + assert process.success + assertAll( + { assert snapshot( + path(process.out.vcf.get(0).get(1)).vcf.variantsMD5, + process.out.tbi.collect { file(it[1]).getName() }, + process.out.stats, + process.out.f1r2, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() } + ) + } + } + + test("mitochondria - bam"){ + when { + params { + module_args = "--mitochondria-mode" + } + process { + """ + input[0] = [ + [ id:'test'], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/mitochon_standin.recalibrated.sorted.bam', checkIfExists: true)], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/mitochon_standin.recalibrated.sorted.bam.bai', checkIfExists: true)], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.bed', checkIfExists: true)] + ] + input[1] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ] + input[2] = [ + [ id:'genome' ], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true)], + [] + ] + input[3] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.dict', checkIfExists: true) + ] + input[4] = [] + input[5] = [] + input[6] = [] + input[7] = [] + input[8] = [] + input[9] = [] + """ + } + } + then { + assert process.success + assertAll( + { assert snapshot( + path(process.out.vcf.get(0).get(1)).vcf.variantsMD5, + process.out.tbi.collect { file(it[1]).getName() }, + process.out.stats, + process.out.f1r2, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() } + ) + } + } + + test("mitochondria - bam - gz_ref"){ + when { + params { + module_args = "--mitochondria-mode" + } + process { + """ + input[0] = [ + [ id:'test'], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/mitochon_standin.recalibrated.sorted.bam', checkIfExists: true)], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/mitochon_standin.recalibrated.sorted.bam.bai', checkIfExists: true)], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.bed', checkIfExists: true)] + ] + input[1] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.gz', checkIfExists: true) + ] + input[2] = [ + [ id:'genome' ], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.gz.fai', checkIfExists: true)], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.gz.gzi', checkIfExists: true)] + ] + input[3] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.dict', checkIfExists: true) + ] + input[4] = [] + input[5] = [] + input[6] = [] + input[7] = [] + input[8] = [] + input[9] = [] + """ + } + } + then { + assert process.success + assertAll( + { assert snapshot( + path(process.out.vcf.get(0).get(1)).vcf.variantsMD5, + process.out.tbi.collect { file(it[1]).getName() }, + process.out.stats, + process.out.f1r2, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() } + ) + } + } + + test("human - bam - tumor_normal_pair_f1r2 - stub"){ + + options "-stub" + + when { + params { + module_args = '' + } + process { + """ + input[0] = [ + [ + id:'test', + normal_id:'normal', + tumor_id:'tumour' + ], + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.recalibrated.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.recalibrated.sorted.bam', checkIfExists: true) + ], + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.recalibrated.sorted.bam.bai', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.recalibrated.sorted.bam.bai', checkIfExists: true) + ], + [] + ] + input[1] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta', checkIfExists: true) + ] + input[2] = [ + [ id:'genome' ], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta.fai', checkIfExists: true)], + [] + ] + input[3] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.dict', checkIfExists: true) + ] + input[4] = [] + input[5] = [] + input[6] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/gnomAD.r2.1.1.vcf.gz', checkIfExists: true) + input[7] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/gnomAD.r2.1.1.vcf.gz.tbi', checkIfExists: true) + input[8] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/mills_and_1000G.indels.hg38.vcf.gz', checkIfExists: true) + input[9] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/mills_and_1000G.indels.hg38.vcf.gz.tbi', checkIfExists: true) + """ + } + } + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } + + test("human - bam - tumor_normal_pair - force_call") { + when { + params { + module_args = "" + } + process { + """ + input[0] = [ + [ + id:'test', + normal_id:'normal', + tumor_id:'tumour' + ], + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.recalibrated.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.recalibrated.sorted.bam', checkIfExists: true) + ], + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.recalibrated.sorted.bam.bai', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.recalibrated.sorted.bam.bai', checkIfExists: true) + ], + [] + ] + input[1] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta', checkIfExists: true) + ] + input[2] = [ + [ id:'genome' ], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta.fai', checkIfExists: true)], + [] + ] + input[3] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.dict', checkIfExists: true) + ] + input[4] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/axiom_exome_plus.genotypes.all_populations.poly.hg38.vcf.gz', checkIfExists: true) + input[5] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/axiom_exome_plus.genotypes.all_populations.poly.hg38.vcf.gz.tbi', checkIfExists: true) + input[6] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/gnomAD.r2.1.1.vcf.gz', checkIfExists: true) + input[7] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/gnomAD.r2.1.1.vcf.gz.tbi', checkIfExists: true) + input[8] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/mills_and_1000G.indels.hg38.vcf.gz', checkIfExists: true) + input[9] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/mills_and_1000G.indels.hg38.vcf.gz.tbi', checkIfExists: true) + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot( + path(process.out.vcf.get(0).get(1)).vcf.variantsMD5, + process.out.tbi.collect { file(it[1]).getName() }, + process.out.stats, + process.out.f1r2, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() } + ) + } + } + + test("human - bam - tumor_only - force_call"){ + when { + params { + module_args = '' + } + process { + """ + input[0] = [ + [ id:'test'], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.recalibrated.sorted.bam', checkIfExists: true)], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.recalibrated.sorted.bam.bai', checkIfExists: true)], + [] + ] + input[1] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta', checkIfExists: true) + ] + input[2] = [ + [ id:'genome' ], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta.fai', checkIfExists: true)], + [] + ] + input[3] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.dict', checkIfExists: true) + ] + input[4] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/axiom_exome_plus.genotypes.all_populations.poly.hg38.vcf.gz', checkIfExists: true) + input[5] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/axiom_exome_plus.genotypes.all_populations.poly.hg38.vcf.gz.tbi', checkIfExists: true) + input[6] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/gnomAD.r2.1.1.vcf.gz', checkIfExists: true) + input[7] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/gnomAD.r2.1.1.vcf.gz.tbi', checkIfExists: true) + input[8] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/mills_and_1000G.indels.hg38.vcf.gz', checkIfExists: true) + input[9] = file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/germlineresources/mills_and_1000G.indels.hg38.vcf.gz.tbi', checkIfExists: true) + """ + } + } + then { + assert process.success + assertAll( + { assert snapshot( + path(process.out.vcf.get(0).get(1)).vcf.variantsMD5, + process.out.tbi.collect { file(it[1]).getName() }, + process.out.stats, + process.out.f1r2, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() } + ) + } + } +} diff --git a/modules/nf-core/gatk4/mutect2/tests/main.nf.test.snap b/modules/nf-core/gatk4/mutect2/tests/main.nf.test.snap new file mode 100644 index 00000000..5f8c7fc8 --- /dev/null +++ b/modules/nf-core/gatk4/mutect2/tests/main.nf.test.snap @@ -0,0 +1,363 @@ +{ + "human - bam - generate_pon": { + "content": [ + "876aa6be01c0c8fc71ad8e99ed842240", + [ + "test.vcf.gz.tbi" + ], + [ + [ + { + "id": "test" + }, + "test.vcf.gz.stats:md5,b569ce66bbffe9588b3d221e821023ee" + ] + ], + [ + + ], + { + "versions_gatk4": [ + [ + "GATK4_MUTECT2", + "gatk4", + "4.7.0.0" + ] + ] + } + ], + "timestamp": "2026-08-18T13:04:08.972426", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "human - bam - tumor_only - force_call": { + "content": [ + "9c2928d0e751262c048ac162b9223e4e", + [ + "test.vcf.gz.tbi" + ], + [ + [ + { + "id": "test" + }, + "test.vcf.gz.stats:md5,2398263886ecb546c8055af4fbad6040" + ] + ], + [ + + ], + { + "versions_gatk4": [ + [ + "GATK4_MUTECT2", + "gatk4", + "4.7.0.0" + ] + ] + } + ], + "timestamp": "2026-08-18T13:19:05.012814", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "human - bam - tumor_normal_pair": { + "content": [ + "7418ed45a029394253817a5eb7149334", + [ + "test.vcf.gz.tbi" + ], + [ + [ + { + "id": "test", + "normal_id": "normal", + "tumor_id": "tumour" + }, + "test.vcf.gz.stats:md5,17d2091015d04cbd4a26b7a67dc659e6" + ] + ], + [ + + ], + { + "versions_gatk4": [ + [ + "GATK4_MUTECT2", + "gatk4", + "4.7.0.0" + ] + ] + } + ], + "timestamp": "2026-08-18T12:42:02.158155", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "human - cram": { + "content": [ + "1b65f1a163b517944bf2e4b74230e035", + [ + "test.vcf.gz.tbi" + ], + [ + [ + { + "id": "test" + }, + "test.vcf.gz.stats:md5,55ed641e16089afb33cdbc478e202d3d" + ] + ], + [ + + ], + { + "versions_gatk4": [ + [ + "GATK4_MUTECT2", + "gatk4", + "4.7.0.0" + ] + ] + } + ], + "timestamp": "2026-08-18T12:59:18.462801", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "mitochondria - bam": { + "content": [ + "ea70f79e33805a2c0b47b32a48a8d26f", + [ + "test.vcf.gz.tbi" + ], + [ + [ + { + "id": "test" + }, + "test.vcf.gz.stats:md5,fc6ea14ca2da346babe78161beea28c9" + ] + ], + [ + + ], + { + "versions_gatk4": [ + [ + "GATK4_MUTECT2", + "gatk4", + "4.7.0.0" + ] + ] + } + ], + "timestamp": "2026-08-18T13:04:37.912171", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "human - bam - tumor_only": { + "content": [ + "1b65f1a163b517944bf2e4b74230e035", + [ + "test.vcf.gz.tbi" + ], + [ + [ + { + "id": "test" + }, + "test.vcf.gz.stats:md5,55ed641e16089afb33cdbc478e202d3d" + ] + ], + [ + + ], + { + "versions_gatk4": [ + [ + "GATK4_MUTECT2", + "gatk4", + "4.7.0.0" + ] + ] + } + ], + "timestamp": "2026-08-18T12:54:23.03524", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "mitochondria - bam - gz_ref": { + "content": [ + "ea70f79e33805a2c0b47b32a48a8d26f", + [ + "test.vcf.gz.tbi" + ], + [ + [ + { + "id": "test" + }, + "test.vcf.gz.stats:md5,fc6ea14ca2da346babe78161beea28c9" + ] + ], + [ + + ], + { + "versions_gatk4": [ + [ + "GATK4_MUTECT2", + "gatk4", + "4.7.0.0" + ] + ] + } + ], + "timestamp": "2026-08-18T13:05:04.049877", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "human - bam - tumor_normal_pair - force_call": { + "content": [ + "63c4ff01a8316f4243b6b12b06ded74e", + [ + "test.vcf.gz.tbi" + ], + [ + [ + { + "id": "test", + "normal_id": "normal", + "tumor_id": "tumour" + }, + "test.vcf.gz.stats:md5,81390058f99b80db30c89020aa9fc546" + ] + ], + [ + + ], + { + "versions_gatk4": [ + [ + "GATK4_MUTECT2", + "gatk4", + "4.7.0.0" + ] + ] + } + ], + "timestamp": "2026-08-18T13:13:17.23664", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "human - bam - tumor_normal_pair_f1r2 - stub": { + "content": [ + { + "f1r2": [ + [ + { + "id": "test", + "normal_id": "normal", + "tumor_id": "tumour" + }, + "test.f1r2.tar.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "stats": [ + [ + { + "id": "test", + "normal_id": "normal", + "tumor_id": "tumour" + }, + "test.vcf.gz.stats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "tbi": [ + [ + { + "id": "test", + "normal_id": "normal", + "tumor_id": "tumour" + }, + "test.vcf.gz.tbi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "vcf": [ + [ + { + "id": "test", + "normal_id": "normal", + "tumor_id": "tumour" + }, + "test.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "versions_gatk4": [ + [ + "GATK4_MUTECT2", + "gatk4", + "4.7.0.0" + ] + ] + } + ], + "timestamp": "2026-08-18T13:05:54.271544", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "human - bam - tumor_normal_pair_f1r2": { + "content": [ + "7418ed45a029394253817a5eb7149334", + [ + "test.vcf.gz.tbi" + ], + [ + [ + { + "id": "test", + "normal_id": "normal", + "tumor_id": "tumour" + }, + "test.vcf.gz.stats:md5,17d2091015d04cbd4a26b7a67dc659e6" + ] + ], + [ + "test.f1r2.tar.gz" + ], + { + "versions_gatk4": [ + [ + "GATK4_MUTECT2", + "gatk4", + "4.7.0.0" + ] + ] + } + ], + "timestamp": "2026-08-18T12:48:06.591264", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/gatk4/mutect2/tests/nextflow.config b/modules/nf-core/gatk4/mutect2/tests/nextflow.config new file mode 100644 index 00000000..08e94285 --- /dev/null +++ b/modules/nf-core/gatk4/mutect2/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: GATK4_MUTECT2 { + ext.args = params.module_args + } +} diff --git a/subworkflows/local/cram_call_mutect2/main.nf b/subworkflows/local/cram_call_mutect2/main.nf new file mode 100644 index 00000000..a58e95b4 --- /dev/null +++ b/subworkflows/local/cram_call_mutect2/main.nf @@ -0,0 +1,58 @@ +// +// Call the variants using HaplotypeCaller +// + +include { GATK4_MUTECT2 } from '../../../modules/nf-core/gatk4/mutect2/main' +include { BCFTOOLS_STATS } from '../../../modules/nf-core/bcftools/stats/main' + +include { VCF_CONCAT_BCFTOOLS } from '../vcf_concat_bcftools/main' + +workflow CRAM_CALL_MUTECT2 { + take: + ch_input // channel: [mandatory] [ val(meta), path(cram), path(crai), path(bed) ] => sample CRAM files and their indexes with the split bed files + ch_fasta // channel: [mandatory] [ val(meta), path(fasta) ] => fasta reference + ch_fai // channel: [mandatory] [ val(meta), path(fai) ] => fasta reference index + ch_dict // channel: [mandatory] [ val(meta), path(dict) ] => sequence dictionary + ch_dbsnp // channel: [optional] [ path(dbsnp) ] => The VCF containing the dbsnp variants + ch_dbsnp_tbi // channel: [optional] [ path(dbsnp_tbi) ] => The index of the dbsnp VCF + ch_panel_of_normals // channel: [optional] [ path(panel_of_normals) ] => The VCF containing the panel of normals variants + ch_panel_of_normals_tbi // channel: [optional] [ path(panel_of_normals_tbi) ] => The index of the panel of normals VCF + + main: + + GATK4_MUTECT2( + ch_input.map { meta, cram, crai, bed -> + def new_meta = meta + [caller:'mutect2'] + tuple(new_meta, cram, crai, bed) + }, + ch_fasta, + ch_fai.map { meta, fai -> tuple(meta, fai, [])}, + ch_dict, + [], + [], + ch_dbsnp, + ch_dbsnp_tbi, + ch_panel_of_normals, + ch_panel_of_normals_tbi + ) + + def ch_calls = GATK4_MUTECT2.out.vcf + .join(GATK4_MUTECT2.out.tbi, failOnDuplicate: true, failOnMismatch: true) + + VCF_CONCAT_BCFTOOLS( + ch_calls + ) + + BCFTOOLS_STATS( + VCF_CONCAT_BCFTOOLS.out.vcfs, + [[],[]], + [[],[]], + [[],[]], + [[],[]], + [[],[]] + ) + + emit: + vcfs = VCF_CONCAT_BCFTOOLS.out.vcfs // channel: [ val(meta), path(vcf), path(tbi) ] + reports = BCFTOOLS_STATS.out.stats // channel: [ val(meta), path(stats) ] +} diff --git a/subworkflows/local/cram_call_mutect2/tests/main.nf.test b/subworkflows/local/cram_call_mutect2/tests/main.nf.test new file mode 100644 index 00000000..b0efc0e3 --- /dev/null +++ b/subworkflows/local/cram_call_mutect2/tests/main.nf.test @@ -0,0 +1,68 @@ +nextflow_workflow { + + name "Test Workflow CRAM_CALL_MUTECT2" + script "../main.nf" + workflow "CRAM_CALL_MUTECT2" + + tag "subworkflows" + tag "subworkflows_local" + tag "CRAM_CALL_MUTECT2" + tag "vcf_concat_bcftools" + + test("CRAM_CALL_MUTECT2 - default") { + + + when { + params { + callers = "mutect2" + } + workflow { + """ + input[0] = channel.of([ + [id:"NA24143.00001", sample:"NA24143", family:"Ashkenazim", family_samples:"NA24143", split_count:3], + file(params.cram1, checkIfExists:true), + file(params.crai1, checkIfExists:true), + file(params.split1, checkIfExists:true) + ],[ + [id:"NA24143.00002", sample:"NA24143", family:"Ashkenazim", family_samples:"NA24143", split_count:3], + file(params.cram1, checkIfExists:true), + file(params.crai1, checkIfExists:true), + file(params.split2, checkIfExists:true) + ],[ + [id:"NA24143.00003", sample:"NA24143", family:"Ashkenazim", family_samples:"NA24143", split_count:3], + file(params.cram1, checkIfExists:true), + file(params.crai1, checkIfExists:true), + file(params.split3, checkIfExists:true) + ]) + input[1] = channel.value([ + [id:"fasta"], + file(params.fasta, checkIfExists:true) + ]) + input[2] = channel.value([ + [id:"fai"], + file(params.fai, checkIfExists:true) + ]) + input[3] = channel.value([ + [id:"dict"], + file(params.dict, checkIfExists:true) + ]) + input[4] = [] + input[5] = [] + input[6] = [] + input[7] = [] + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot( + workflow.out.vcfs.collect { [it[0], "${file(it[1]).name},variantsMD5:${path(it[1]).vcf.variantsMD5}", file(it[2]).name] }, + workflow.out.reports + ).match() } + ) + } + + } +} diff --git a/subworkflows/local/cram_call_mutect2/tests/main.nf.test.snap b/subworkflows/local/cram_call_mutect2/tests/main.nf.test.snap new file mode 100644 index 00000000..0bd0a655 --- /dev/null +++ b/subworkflows/local/cram_call_mutect2/tests/main.nf.test.snap @@ -0,0 +1,44 @@ +{ + "CRAM_CALL_MUTECT2 - default": { + "content": [ + [ + [ + { + "groupSize": 3, + "groupTarget": { + "id": "NA24143", + "sample": "NA24143", + "family": "Ashkenazim", + "family_samples": "NA24143", + "split_count": 3, + "caller": "mutect2" + } + }, + "NA24143.concat.vcf.gz,variantsMD5:53d915ee4a2d5e44402c7d223699f792", + "NA24143.concat.vcf.gz.tbi" + ] + ], + [ + [ + { + "groupSize": 3, + "groupTarget": { + "id": "NA24143", + "sample": "NA24143", + "family": "Ashkenazim", + "family_samples": "NA24143", + "split_count": 3, + "caller": "mutect2" + } + }, + "NA24143.bcftools_stats.txt:md5,062d7725d095dbdbf7fefbe6ea8079dc" + ] + ] + ], + "timestamp": "2026-09-07T15:18:23.905783673", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + } +} \ No newline at end of file From 8cb789deb8383b63892bd9b3c55ec6a7bb8f92f5 Mon Sep 17 00:00:00 2001 From: Nicolas Vannieuwkerke Date: Mon, 7 Sep 2026 16:52:44 +0200 Subject: [PATCH 04/12] implement subwf in main flow --- conf/modules.config | 15 ++++++++++ main.nf | 4 +-- nextflow_schema.json | 5 ++-- subworkflows/local/cram_call_mutect2/main.nf | 8 ++++-- tests/pipeline/callers/main.nf.test | 29 ++++++++++++++++++++ tests/pipeline/callers/main.nf.test.snap | 22 +++++++++++++++ workflows/smallvariants.nf | 22 +++++++++++++++ 7 files changed, 98 insertions(+), 7 deletions(-) diff --git a/conf/modules.config b/conf/modules.config index fbaee064..b862fc1c 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -212,6 +212,21 @@ process { withName: "^.*CRAM_CALL_MUTECT2:MUTECT2\$" { time = { 16.h * task.attempt } + ext.args = [ + "--annotation ClippingRankSumTest", + "--annotation DepthPerSampleHC", + "--annotation MappingQualityRankSumTest", + "--annotation MappingQualityZero", + "--annotation QualByDepth", + "--annotation ReadPosRankSumTest", + "--annotation RMSMappingQuality", + "--annotation FisherStrand", + "--annotation MappingQuality", + "--annotation DepthPerAlleleBySample", + "--annotation Coverage", + "--read-validation-stringency LENIENT", + "--interval-set-rule INTERSECTION" + ].join(" ") } withName: "^.*CRAM_CALL_MUTECT2:VCF_CONCAT_BCFTOOLS:BCFTOOLS_CONCAT\$" { diff --git a/main.nf b/main.nf index 9b874826..abb1baec 100644 --- a/main.nf +++ b/main.nf @@ -333,7 +333,7 @@ workflow { // Check for dependencies between parameters // - def List available_callers = ["haplotypecaller", "vardict", "elprep"] + def List available_callers = ["haplotypecaller", "vardict", "elprep", "mutect2"] if(params.dbsnp_tbi && !params.dbsnp){ error("Please specify the dbsnp VCF with --dbsnp VCF") @@ -358,7 +358,7 @@ workflow { def callers = params.callers.tokenize(",") callers.each { caller -> - if(!(caller in available_callers)) { error("\"${caller}\" is not a supported callers please use one or more of these instead: ${available_callers.join(', ')}") } + if(!(caller in available_callers)) { error("\"${caller}\" is not a supported caller please use one or more of these instead: ${available_callers.join(', ')}") } } /* diff --git a/nextflow_schema.json b/nextflow_schema.json index 47936957..a5e6f10b 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -242,8 +242,9 @@ }, "callers": { "type": "string", - "description": "A comma delimited string of the available callers. Current options are: `haplotypecaller` and `vardict`.", - "default": "haplotypecaller" + "description": "A comma delimited string of the available callers. Current options are: `haplotypecaller`, `vardict`, `elprep` and `mutect2`.", + "default": "haplotypecaller", + "pattern": "^((haplotypecaller|vardict|elprep|mutect2)?,?)*(? - def new_meta = meta + [caller:'mutect2'] + def new_meta = meta + [caller:'mutect2', family_samples: meta.sample] tuple(new_meta, cram, crai, bed) }, ch_fasta, @@ -30,8 +30,8 @@ workflow CRAM_CALL_MUTECT2 { ch_dict, [], [], - ch_dbsnp, - ch_dbsnp_tbi, + ch_dbsnp == [[],[]] ? [] : ch_dbsnp.map { _meta, dbsnp -> dbsnp }, + ch_dbsnp_tbi == [[],[]] ? [] : ch_dbsnp_tbi.map { _meta, dbsnp_tbi -> dbsnp_tbi }, ch_panel_of_normals, ch_panel_of_normals_tbi ) @@ -39,6 +39,8 @@ workflow CRAM_CALL_MUTECT2 { def ch_calls = GATK4_MUTECT2.out.vcf .join(GATK4_MUTECT2.out.tbi, failOnDuplicate: true, failOnMismatch: true) + // TODO run filtermutectcalls + VCF_CONCAT_BCFTOOLS( ch_calls ) diff --git a/tests/pipeline/callers/main.nf.test b/tests/pipeline/callers/main.nf.test index de3ac974..5a129c49 100644 --- a/tests/pipeline/callers/main.nf.test +++ b/tests/pipeline/callers/main.nf.test @@ -37,6 +37,35 @@ nextflow_pipeline { } + test("pipeline_callers - mutect2") { + + when { + params { + outdir = "${outputDir}" + callers = "mutect2" + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot( + path("${outputDir}") + .list() + .collect { Utils.getRecursiveFileNames(it, "${outputDir}") } + .flatten() + .findAll { + !(it.contains("/execution_") || it.contains("/params_") || it.contains("/pipeline_") || it.contains("DONE")) + } + .collect { + it.replace(Utils.getDynamicOutputName(), "_") + } + ).match() } + ) + } + + } + test("pipeline_callers - haplotypecaller") { when { diff --git a/tests/pipeline/callers/main.nf.test.snap b/tests/pipeline/callers/main.nf.test.snap index afd4979c..2b39f0c8 100644 --- a/tests/pipeline/callers/main.nf.test.snap +++ b/tests/pipeline/callers/main.nf.test.snap @@ -229,5 +229,27 @@ "nf-test": "0.9.5", "nextflow": "25.10.4" } + }, + "pipeline_callers - mutect2": { + "content": [ + [ + "Ashkenazim/NA24149__/NA24149.bed", + "Ashkenazim/NA24149__/NA24149.per-base.bed.gz", + "Ashkenazim/NA24149__/NA24149.per-base.bed.gz.csi", + "Ashkenazim/NA24149__/mosdepth/NA24149.global.dist.txt", + "Ashkenazim/NA24149__/mosdepth/NA24149.region.dist.txt", + "Ashkenazim/NA24149__/mosdepth/NA24149.summary.txt", + "Ashkenazim/NA24149__/msi/NA24149_all_msi", + "Ashkenazim/NA24149__/msi/NA24149_dis_msi", + "Ashkenazim/NA24149__/msi/NA24149_summary_msi", + "Ashkenazim/NA24149__/msi/NA24149_unstable_msi", + "_/samplesheet.csv" + ] + ], + "timestamp": "2026-09-07T16:14:51.995560164", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } } } \ No newline at end of file diff --git a/workflows/smallvariants.nf b/workflows/smallvariants.nf index ed61a9ec..d7de69e6 100644 --- a/workflows/smallvariants.nf +++ b/workflows/smallvariants.nf @@ -21,6 +21,7 @@ include { CRAM_CALL_HAPLOTYPECALLER } from '../subworkflows/local/cram_c include { GVCF_JOINT_GENOTYPE_GATK4 } from '../subworkflows/local/gvcf_joint_genotype_gatk4/main' include { BAM_CALL_ELPREP } from '../subworkflows/local/bam_call_elprep/main' include { BAM_CALL_VARDICTJAVA } from '../subworkflows/local/bam_call_vardictjava/main' +include { CRAM_CALL_MUTECT2 } from '../subworkflows/local/cram_call_mutect2/main' include { VCF_EXTRACT_RELATE_SOMALIER } from '../subworkflows/local/vcf_extract_relate_somalier/main' include { VCF_PED_RTGTOOLS } from '../subworkflows/local/vcf_ped_rtgtools/main' include { VCF_ANNOTATION } from '../subworkflows/local/vcf_annotation/main' @@ -591,6 +592,27 @@ workflow SMALLVARIANTS { ch_calls = ch_calls.mix(BAM_CALL_VARDICTJAVA.out.vcfs) } + if("mutect2" in callers) { + // + // Call variants with GATK4 Mutect2 + // + + CRAM_CALL_MUTECT2( + ch_caller_inputs.cram.filter { meta, _cram, _crai, _bed -> + // Filter out the entries that already have a GVCF + meta.type == "cram" + }, + ch_fasta_ready, + ch_fai_ready, + ch_dict_ready, + ch_dbsnp_ready, + ch_dbsnp_tbi_ready, + [], // TODO add panel of normals support + [] + ) + ch_calls = ch_calls.mix(CRAM_CALL_MUTECT2.out.vcfs) + } + // Stop pipeline execution when only calls should happen def ch_gvcfs_final = ch_gvcfs_ready.filter { !only_call } From c6eaf21c6055aa93406c1cb7db279d53067f443b Mon Sep 17 00:00:00 2001 From: Nicolas Vannieuwkerke Date: Tue, 8 Sep 2026 09:57:11 +0200 Subject: [PATCH 05/12] check the amount of variants before automap --- CHANGELOG.md | 4 ++++ subworkflows/local/vcf_roh_automap/main.nf | 24 +++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd27c002..cb232caf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## v1.14.0dev + +- Check that the VCF contains enough variants before running automap (it needs to have at least 10000 variants) + ## v1.13.6 - Revert the default configs base to the nf-core configs in preparation of converting nf-cmgg/configs to a private repo diff --git a/subworkflows/local/vcf_roh_automap/main.nf b/subworkflows/local/vcf_roh_automap/main.nf index aa184acf..e12aa451 100644 --- a/subworkflows/local/vcf_roh_automap/main.nf +++ b/subworkflows/local/vcf_roh_automap/main.nf @@ -30,8 +30,30 @@ workflow VCF_ROH_AUTOMAP { ch_valid_repeats = ch_repeats } + def ch_automap_input = ch_vcfs.filter { meta, vcf, tbi -> + // Check the amount of variants for all VCFs that are under 1 MB (automap requires at least 10000 variants to run) + println vcf.size() + if(vcf?.size() < 1000000) { + def var_count = 0 + vcf.withInputStream { stream -> + new java.util.zip.GZIPInputStream(stream).withReader('UTF-8') { reader -> + reader.eachLine { line -> + if(!line.startsWith("#")) { + var_count += 1 + } + } + } + } + if(var_count < 10000) { + log.warn("VCF file '${vcf}' for sample '${meta.id}' only has ${var_count} variants. Automap requires at least 10000 variants to run. Skipping this VCF.") + return false + } + } + return true + } + AUTOMAP_AUTOMAP( - ch_vcfs, + ch_automap_input, ch_valid_repeats, ch_panel, hg_genome From f7015d1c1c605d4cd5727c4d95f339d08579e46f Mon Sep 17 00:00:00 2001 From: Nicolas Vannieuwkerke Date: Tue, 8 Sep 2026 10:48:28 +0200 Subject: [PATCH 06/12] implement filtermutectcalls --- conf/modules.config | 6 +- modules.json | 5 + .../gatk4/filtermutectcalls/environment.yml | 10 + .../nf-core/gatk4/filtermutectcalls/main.nf | 62 ++++++ .../nf-core/gatk4/filtermutectcalls/meta.yml | 161 ++++++++++++++++ .../filtermutectcalls/tests/main.nf.test | 178 ++++++++++++++++++ .../filtermutectcalls/tests/main.nf.test.snap | 145 ++++++++++++++ .../filtermutectcalls/tests/nextflow.config | 5 + subworkflows/local/cram_call_mutect2/main.nf | 22 ++- 9 files changed, 588 insertions(+), 6 deletions(-) create mode 100644 modules/nf-core/gatk4/filtermutectcalls/environment.yml create mode 100644 modules/nf-core/gatk4/filtermutectcalls/main.nf create mode 100644 modules/nf-core/gatk4/filtermutectcalls/meta.yml create mode 100644 modules/nf-core/gatk4/filtermutectcalls/tests/main.nf.test create mode 100644 modules/nf-core/gatk4/filtermutectcalls/tests/main.nf.test.snap create mode 100644 modules/nf-core/gatk4/filtermutectcalls/tests/nextflow.config diff --git a/conf/modules.config b/conf/modules.config index b862fc1c..90ffefac 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -210,7 +210,7 @@ process { ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ - withName: "^.*CRAM_CALL_MUTECT2:MUTECT2\$" { + withName: "^.*CRAM_CALL_MUTECT2:GATK4_MUTECT2\$" { time = { 16.h * task.attempt } ext.args = [ "--annotation ClippingRankSumTest", @@ -229,6 +229,10 @@ process { ].join(" ") } + withName: "^.*CRAM_CALL_MUTECT2:GATK4_FILTERMUTECTCALLS\$" { + ext.prefix = {"${meta.id}.filtered"} + } + withName: "^.*CRAM_CALL_MUTECT2:VCF_CONCAT_BCFTOOLS:BCFTOOLS_CONCAT\$" { ext.args = '--allow-overlaps --output-type z --write-index=tbi' ext.prefix = {"${meta.id}.concat"} diff --git a/modules.json b/modules.json index 11baf70c..81d3d86d 100644 --- a/modules.json +++ b/modules.json @@ -102,6 +102,11 @@ "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", "installed_by": ["modules"] }, + "gatk4/filtermutectcalls": { + "branch": "master", + "git_sha": "d5de7a949aa84689f4098c958f05733a6c3804f5", + "installed_by": ["modules"] + }, "gatk4/genomicsdbimport": { "branch": "master", "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", diff --git a/modules/nf-core/gatk4/filtermutectcalls/environment.yml b/modules/nf-core/gatk4/filtermutectcalls/environment.yml new file mode 100644 index 00000000..84f68508 --- /dev/null +++ b/modules/nf-core/gatk4/filtermutectcalls/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/gatk4-main + - bioconda::gatk4-main=4.7.0.0 + # renovate: datasource=conda depName=bioconda/gcnvkernel + - bioconda::gcnvkernel=0.9 diff --git a/modules/nf-core/gatk4/filtermutectcalls/main.nf b/modules/nf-core/gatk4/filtermutectcalls/main.nf new file mode 100644 index 00000000..4f33e1c5 --- /dev/null +++ b/modules/nf-core/gatk4/filtermutectcalls/main.nf @@ -0,0 +1,62 @@ +process GATK4_FILTERMUTECTCALLS { + tag "${meta.id}" + label 'process_low' + + 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/b9/b9822b92da68a3e7916072218082e3fa79bebc2f377947c363613adeecd56ec5/data' + : 'community.wave.seqera.io/library/gatk4-main_gcnvkernel:961440660027ec01'}" + + input: + tuple val(meta), path(vcf), path(vcf_tbi), path(stats), path(orientationbias), path(segmentation), path(table), val(estimate) + tuple val(meta2), path(fasta) + tuple val(meta3), path(fai) + tuple val(meta4), path(dict) + + output: + tuple val(meta), path("*.vcf.gz"), emit: vcf + tuple val(meta), path("*.vcf.gz.tbi"), emit: tbi + tuple val(meta), path("*.filteringStats.tsv"), emit: stats + tuple val("${task.process}"), val('gatk4'), eval("gatk --version | sed -n '/GATK.*v/s/.*v//p'"), topic: versions, emit: versions_gatk4 + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + + def orientationbias_command = orientationbias ? orientationbias.collect { orientationbias_ -> "--orientation-bias-artifact-priors ${orientationbias_}" }.join(' ') : '' + def segmentation_command = segmentation ? segmentation.collect { segmentation_ -> "--tumor-segmentation ${segmentation_}" }.join(' ') : '' + def estimate_command = estimate ? " --contamination-estimate ${estimate} " : '' + def table_command = table ? table.collect { table_ -> "--contamination-table ${table_}" }.join(' ') : '' + + def avail_mem = 3072 + if (!task.memory) { + log.info('[GATK FilterMutectCalls] Available memory not known - defaulting to 3GB. Specify process memory requirements to change this.') + } + else { + avail_mem = (task.memory.mega * 0.8).intValue() + } + """ + gatk --java-options "-Xmx${avail_mem}M -XX:-UsePerfData" \\ + FilterMutectCalls \\ + --variant ${vcf} \\ + --output ${prefix}.vcf.gz \\ + --reference ${fasta} \\ + ${orientationbias_command} \\ + ${segmentation_command} \\ + ${estimate_command} \\ + ${table_command} \\ + --tmp-dir . \\ + ${args} + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + echo "" | gzip > ${prefix}.vcf.gz + touch ${prefix}.vcf.gz.tbi + touch ${prefix}.vcf.gz.filteringStats.tsv + """ +} diff --git a/modules/nf-core/gatk4/filtermutectcalls/meta.yml b/modules/nf-core/gatk4/filtermutectcalls/meta.yml new file mode 100644 index 00000000..eb3e9baf --- /dev/null +++ b/modules/nf-core/gatk4/filtermutectcalls/meta.yml @@ -0,0 +1,161 @@ +name: gatk4_filtermutectcalls +description: | + Filters the raw output of mutect2, can optionally use outputs of calculatecontamination and learnreadorientationmodel to improve filtering. +keywords: + - filtermutectcalls + - filter + - gatk4 + - mutect2 + - vcf +tools: + - gatk4: + description: | + Developed in the Data Sciences Platform at the Broad Institute, the toolkit offers a wide variety of tools + with a primary focus on variant discovery and genotyping. Its powerful processing engine + and high-performance computing features make it capable of taking on projects of any size. + homepage: https://gatk.broadinstitute.org/hc/en-us + documentation: https://gatk.broadinstitute.org/hc/en-us/categories/360002369672s + doi: 10.1158/1538-7445.AM2017-3590 + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - vcf: + type: file + description: compressed vcf file of mutect2calls + pattern: "*.vcf.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + - vcf_tbi: + type: file + description: Tabix index of vcf file + pattern: "*vcf.gz.tbi" + ontologies: [] + - stats: + type: file + description: Stats file that pairs with output vcf file + pattern: "*vcf.gz.stats" + ontologies: [] + - orientationbias: + type: file + description: files containing artifact priors for input vcf. Optional input. + pattern: "*.artifact-prior.tar.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + - segmentation: + type: file + description: tables containing segmentation information for input vcf. Optional + input. + pattern: "*.segmentation.table" + ontologies: [] + - table: + type: file + description: table(s) containing contamination data for input vcf. Optional + input, takes priority over estimate. + pattern: "*.contamination.table" + ontologies: [] + - estimate: + type: float + description: estimation of contamination value as a double. Optional input, + will only be used if table is not specified. + - - meta2: + type: map + description: | + Groovy Map containing reference information + e.g. [ id:'genome' ] + - fasta: + type: file + description: The reference fasta file + pattern: "*.fasta" + ontologies: [] + - - meta3: + type: map + description: | + Groovy Map containing reference information + e.g. [ id:'genome' ] + - fai: + type: file + description: Index of reference fasta file + pattern: "*.fasta.fai" + ontologies: [] + - - meta4: + type: map + description: | + Groovy Map containing reference information + e.g. [ id:'genome' ] + - dict: + type: file + description: GATK sequence dictionary + pattern: "*.dict" + ontologies: [] +output: + vcf: + - - meta: + type: file + description: file containing filtered mutect2 calls. + pattern: "*.vcf.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + - "*.vcf.gz": + type: file + description: file containing filtered mutect2 calls. + pattern: "*.vcf.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + tbi: + - - meta: + type: file + description: file containing filtered mutect2 calls. + pattern: "*.vcf.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + - "*.vcf.gz.tbi": + type: file + description: tbi file that pairs with vcf. + pattern: "*.vcf.gz.tbi" + ontologies: [] + stats: + - - meta: + type: file + description: file containing filtered mutect2 calls. + pattern: "*.vcf.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + - "*.filteringStats.tsv": + type: file + description: file containing statistics of the filtermutectcalls run. + pattern: "*.filteringStats.tsv" + ontologies: + - edam: http://edamontology.org/format_3475 # TSV + versions_gatk4: + - - ${task.process}: + type: string + description: The name of the process + - gatk4: + type: string + description: The name of the tool + - gatk --version | sed -n '/GATK.*v/s/.*v//p': + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - gatk4: + type: string + description: The name of the tool + - gatk --version | sed -n '/GATK.*v/s/.*v//p': + type: eval + description: The expression to obtain the version of the tool +authors: + - "@GCJMackenzie" + - "@maxulysse" + - "@ramprasadn" +maintainers: + - "@GCJMackenzie" + - "@maxulysse" + - "@ramprasadn" diff --git a/modules/nf-core/gatk4/filtermutectcalls/tests/main.nf.test b/modules/nf-core/gatk4/filtermutectcalls/tests/main.nf.test new file mode 100644 index 00000000..64007fc1 --- /dev/null +++ b/modules/nf-core/gatk4/filtermutectcalls/tests/main.nf.test @@ -0,0 +1,178 @@ +nextflow_process { + + name "Test Process GATK4_FILTERMUTECTCALLS" + script "../main.nf" + process "GATK4_FILTERMUTECTCALLS" + config "./nextflow.config" + + tag "modules" + tag "modules_nfcore" + tag "gatk4" + tag "gatk4/filtermutectcalls" + + test("human - vcf - base") { + + when { + process { + """ + input[0] = [ + [ id:'test'], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/gatk/paired_mutect2_calls/test_test2_paired_mutect2_calls.vcf.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/gatk/paired_mutect2_calls/test_test2_paired_mutect2_calls.vcf.gz.tbi', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/gatk/paired_mutect2_calls/test_test2_paired_mutect2_calls.vcf.gz.stats', checkIfExists: true), + [], + [], + [], + [] + ] + + input[1] = [ [ id:'genome' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta', checkIfExists: true) + ] + input[2] = [ [ id:'genome' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta.fai', checkIfExists: true) + ] + input[3] = [ [ id:'genome' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.dict', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.findAll { key, val -> key.startsWith("versions") }, + path(process.out.vcf[0][1]).vcf.variantsMD5, + path(process.out.tbi[0][1]).linesGzip.toString().contains("TBI"), + file(process.out.stats[0][1]).readLines()[0] + ).match() } + ) + } + + } + + test("human - vcf - with-files") { + + when { + process { + """ + input[0] = [ + [ id:'test'], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/gatk/paired_mutect2_calls/test_test2_paired_mutect2_calls.vcf.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/gatk/paired_mutect2_calls/test_test2_paired_mutect2_calls.vcf.gz.tbi', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/gatk/paired_mutect2_calls/test_test2_paired_mutect2_calls.vcf.gz.stats', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/gatk/test_test2_paired_mutect2_calls.artifact-prior.tar.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/gatk/test_test2_paired.segmentation.table', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/gatk/test_test2_paired.contamination.table', checkIfExists: true), + [] + ] + + input[1] = [ [ id:'genome' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta', checkIfExists: true) + ] + input[2] = [ [ id:'genome' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta.fai', checkIfExists: true) + ] + input[3] = [ [ id:'genome' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.dict', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.findAll { key, val -> key.startsWith("versions") }, + path(process.out.vcf[0][1]).vcf.variantsMD5, + path(process.out.tbi[0][1]).linesGzip.toString().contains("TBI"), + file(process.out.stats[0][1]).readLines()[0..5] + ).match() } + ) + } + } + + test("human - vcf - use-val") { + + when { + process { + """ + input[0] = [ + [ id:'test'], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/gatk/paired_mutect2_calls/test_test2_paired_mutect2_calls.vcf.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/gatk/paired_mutect2_calls/test_test2_paired_mutect2_calls.vcf.gz.tbi', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/gatk/paired_mutect2_calls/test_test2_paired_mutect2_calls.vcf.gz.stats', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/gatk/test_test2_paired_mutect2_calls.artifact-prior.tar.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/gatk/test_test2_paired.segmentation.table', checkIfExists: true), + [], + '20.0' + ] + + input[1] = [ [ id:'genome' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta', checkIfExists: true) + ] + input[2] = [ [ id:'genome' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta.fai', checkIfExists: true) + ] + input[3] = [ [ id:'genome' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.dict', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.findAll { key, val -> key.startsWith("versions") }, + path(process.out.vcf[0][1]).vcf.variantsMD5, + path(process.out.tbi[0][1]).linesGzip.toString().contains("TBI"), + file(process.out.stats[0][1]).readLines()[0] + ).match() } + ) + } + } + + test("human - vcf - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'test'], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/gatk/paired_mutect2_calls/test_test2_paired_mutect2_calls.vcf.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/gatk/paired_mutect2_calls/test_test2_paired_mutect2_calls.vcf.gz.tbi', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/gatk/paired_mutect2_calls/test_test2_paired_mutect2_calls.vcf.gz.stats', checkIfExists: true), + [], + [], + [], + [] + ] + + input[1] = [ [ id:'genome' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta', checkIfExists: true) + ] + input[2] = [ [ id:'genome' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta.fai', checkIfExists: true) + ] + input[3] = [ [ id:'genome' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.dict', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } +} diff --git a/modules/nf-core/gatk4/filtermutectcalls/tests/main.nf.test.snap b/modules/nf-core/gatk4/filtermutectcalls/tests/main.nf.test.snap new file mode 100644 index 00000000..fc598755 --- /dev/null +++ b/modules/nf-core/gatk4/filtermutectcalls/tests/main.nf.test.snap @@ -0,0 +1,145 @@ +{ + "human - vcf - use-val": { + "content": [ + { + "versions_gatk4": [ + [ + "GATK4_FILTERMUTECTCALLS", + "gatk4", + "4.7.0.0" + ] + ] + }, + "4f8cfebe28e5fd097a7bfc2e556983d3", + true, + "#Ln prior of deletion of length 10=-20.72326583694641" + ], + "timestamp": "2026-08-18T11:17:58.502348", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "human - vcf - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.filtered.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test.filtered.vcf.gz.tbi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "test.filtered.vcf.gz.filteringStats.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + [ + "GATK4_FILTERMUTECTCALLS", + "gatk4", + "4.7.0.0" + ] + ], + "stats": [ + [ + { + "id": "test" + }, + "test.filtered.vcf.gz.filteringStats.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "tbi": [ + [ + { + "id": "test" + }, + "test.filtered.vcf.gz.tbi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "vcf": [ + [ + { + "id": "test" + }, + "test.filtered.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "versions_gatk4": [ + [ + "GATK4_FILTERMUTECTCALLS", + "gatk4", + "4.7.0.0" + ] + ] + } + ], + "timestamp": "2026-08-18T11:18:14.487412", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "human - vcf - with-files": { + "content": [ + { + "versions_gatk4": [ + [ + "GATK4_FILTERMUTECTCALLS", + "gatk4", + "4.7.0.0" + ] + ] + }, + "54e439a79e4b6956a7f2c5b0730c9918", + true, + [ + "#Ln prior of deletion of length 10=-20.72326583694641", + "#Ln prior of deletion of length 9=-20.72326583694641", + "#Ln prior of deletion of length 8=-20.72326583694641", + "#Ln prior of deletion of length 7=-20.72326583694641", + "#Ln prior of deletion of length 6=-20.72326583694641", + "#Ln prior of deletion of length 5=-20.72326583694641" + ] + ], + "timestamp": "2026-08-18T11:17:37.331961", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "human - vcf - base": { + "content": [ + { + "versions_gatk4": [ + [ + "GATK4_FILTERMUTECTCALLS", + "gatk4", + "4.7.0.0" + ] + ] + }, + "5352aa0f3ea986008a2652eba7c0fdba", + true, + "#Ln prior of deletion of length 10=-20.72326583694641" + ], + "timestamp": "2026-08-18T11:17:14.332579", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/gatk4/filtermutectcalls/tests/nextflow.config b/modules/nf-core/gatk4/filtermutectcalls/tests/nextflow.config new file mode 100644 index 00000000..12fa58fb --- /dev/null +++ b/modules/nf-core/gatk4/filtermutectcalls/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: GATK4_FILTERMUTECTCALLS { + ext.prefix = { "${meta.id}.filtered" } + } +} diff --git a/subworkflows/local/cram_call_mutect2/main.nf b/subworkflows/local/cram_call_mutect2/main.nf index 1d695724..de71d7bd 100644 --- a/subworkflows/local/cram_call_mutect2/main.nf +++ b/subworkflows/local/cram_call_mutect2/main.nf @@ -2,10 +2,11 @@ // Call the variants using HaplotypeCaller // -include { GATK4_MUTECT2 } from '../../../modules/nf-core/gatk4/mutect2/main' -include { BCFTOOLS_STATS } from '../../../modules/nf-core/bcftools/stats/main' +include { GATK4_MUTECT2 } from '../../../modules/nf-core/gatk4/mutect2/main' +include { GATK4_FILTERMUTECTCALLS } from '../../../modules/nf-core/gatk4/filtermutectcalls/main' +include { BCFTOOLS_STATS } from '../../../modules/nf-core/bcftools/stats/main' -include { VCF_CONCAT_BCFTOOLS } from '../vcf_concat_bcftools/main' +include { VCF_CONCAT_BCFTOOLS } from '../vcf_concat_bcftools/main' workflow CRAM_CALL_MUTECT2 { take: @@ -36,10 +37,21 @@ workflow CRAM_CALL_MUTECT2 { ch_panel_of_normals_tbi ) - def ch_calls = GATK4_MUTECT2.out.vcf + // TODO handle stats + def ch_mutect_calls = GATK4_MUTECT2.out.vcf .join(GATK4_MUTECT2.out.tbi, failOnDuplicate: true, failOnMismatch: true) + .join(GATK4_MUTECT2.out.stats, failOnDuplicate: true, failOnMismatch: true) + .map { meta, vcf, tbi, stats -> tuple(meta, vcf, tbi, stats, [], [], [], []) } - // TODO run filtermutectcalls + GATK4_FILTERMUTECTCALLS( + ch_mutect_calls, + ch_fasta, + ch_fai, + ch_dict + ) + + def ch_calls = GATK4_FILTERMUTECTCALLS.out.vcf + .join(GATK4_FILTERMUTECTCALLS.out.tbi, failOnDuplicate: true, failOnMismatch: true) VCF_CONCAT_BCFTOOLS( ch_calls From 328c2070f4431de491ef942b35792004435c584b Mon Sep 17 00:00:00 2001 From: Nicolas Vannieuwkerke Date: Tue, 8 Sep 2026 10:49:40 +0200 Subject: [PATCH 07/12] bump changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb232caf..98892736 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## v1.14.0dev +### New features + +- Added a new caller: `GATK4_MUTECT2` followed by `GATK4_FILTERMUTECTCALLS`. Pass `mutect2` to the `--callers` parameter to use it. + +### Changes + - Check that the VCF contains enough variants before running automap (it needs to have at least 10000 variants) ## v1.13.6 From d35e68ef10c0191f45a584f5e752404122cfd434 Mon Sep 17 00:00:00 2001 From: Nicolas Vannieuwkerke Date: Tue, 8 Sep 2026 11:07:29 +0200 Subject: [PATCH 08/12] add panel of normals parameters --- CHANGELOG.md | 7 +++++ main.nf | 12 +++++++++ nextflow_schema.json | 14 ++++++++++ workflows/smallvariants.nf | 54 ++++++++++++++++++++------------------ 4 files changed, 61 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98892736..ac861fa8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Check that the VCF contains enough variants before running automap (it needs to have at least 10000 variants) +### Parameters + +| Old | New | +| --- | ------------------------ | +| | `--panel_of_normals` | +| | `--panel_of_normals_tbi` | + ## v1.13.6 - Revert the default configs base to the nf-core configs in preparation of converting nf-cmgg/configs to a private repo diff --git a/main.nf b/main.nf index abb1baec..50e1c7f5 100644 --- a/main.nf +++ b/main.nf @@ -318,6 +318,12 @@ params { // A semicolon-seperated list of resource files for VCFanno, please also supply their indices using this parameter. vcfanno_resources: String? = getGenomeAttribute('vcfanno_resources', params.genomes, params.genome) + + // Path to the panel of normals VCF file. + panel_of_normals: Path? = getGenomeAttribute('panel_of_normals', params.genomes, params.genome) + + // Path to the index of the panel of normals VCF file. + panel_of_normals_tbi: Path? = getGenomeAttribute('panel_of_normals_tbi', params.genomes, params.genome) } workflow { @@ -339,6 +345,10 @@ workflow { error("Please specify the dbsnp VCF with --dbsnp VCF") } + if(params.panel_of_normals_tbi && !params.panel_of_normals){ + error("Please specify the panel of normals VCF with --panel_of_normals VCF") + } + if (params.annotate) { // Check if a genome is given if (!params.genome) { error("A genome should be supplied for annotation (use --genome)") } @@ -432,6 +442,8 @@ workflow { params.elsites, params.msi_baseline, params.updio_regions, + params.panel_of_normals, + params.panel_of_normals_tbi, // Boolean inputs params.dragstr, diff --git a/nextflow_schema.json b/nextflow_schema.json index a5e6f10b..7048158d 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -140,6 +140,20 @@ "exists": true, "pattern": "^\\S+\\.list$", "description": "Path to the MSI baseline VCF file." + }, + "panel_of_normals": { + "type": "string", + "format": "file-path", + "exists": true, + "pattern": "^\\S+\\.vcf\\.gz$", + "description": "Path to the panel of normals VCF file. Only used when `mutect2` is one of the specified callers." + }, + "panel_of_normals_tbi": { + "type": "string", + "format": "file-path", + "exists": true, + "pattern": "^\\S+\\.vcf\\.gz\\.tbi$", + "description": "Path to the index of the panel of normals VCF file. Only used when `mutect2` is one of the specified callers." } }, "required": ["fasta"] diff --git a/workflows/smallvariants.nf b/workflows/smallvariants.nf index d7de69e6..e685170e 100644 --- a/workflows/smallvariants.nf +++ b/workflows/smallvariants.nf @@ -107,6 +107,8 @@ workflow SMALLVARIANTS { elsites // path: path to the elsites file for elprep msi_baseline // path: path to the msi_baseline file updio_regions // path: path to the BED file with regions to be used by UPDio + panel_of_normals // path: path to the panel of normals VCF file + panel_of_normals_tbi // path: path to the index of the panel of normals VCF file // Boolean inputs dragstr // boolean: create a dragstr model and use it for haplotypecaller @@ -149,36 +151,39 @@ workflow SMALLVARIANTS { // Importing and convert the input files passed through the parameters to channels // - def ch_fasta_ready = channel.value([[id:"reference"], fasta]) - def ch_fai = fai ? channel.value([[id:"reference"], fai]) : null - def ch_dict = dict ? channel.value([[id:"reference"], dict]) : null - def ch_elfasta = elfasta ? channel.value([[id:"reference"], elfasta]) : null - def ch_strtablefile = strtablefile ? channel.value([[id:"reference"], strtablefile]) : null - def ch_sdf = sdf ? channel.value([[id:'reference'], sdf]) : null + def ch_fasta_ready = channel.value([[id:"reference"], fasta]) + def ch_fai = fai ? channel.value([[id:"reference"], fai]) : null + def ch_dict = dict ? channel.value([[id:"reference"], dict]) : null + def ch_elfasta = elfasta ? channel.value([[id:"reference"], elfasta]) : null + def ch_strtablefile = strtablefile ? channel.value([[id:"reference"], strtablefile]) : null + def ch_sdf = sdf ? channel.value([[id:'reference'], sdf]) : null - def ch_default_roi = roi ? channel.value(roi) : [] + def ch_default_roi = roi ? channel.value(roi) : [] - def ch_dbsnp_ready = dbsnp ? channel.value([[id:"dbsnp"], dbsnp]) : [[],[]] - def ch_dbsnp_tbi = dbsnp_tbi ? channel.value([[id:"dbsnp"], dbsnp_tbi]) : [[],[]] + def ch_dbsnp_ready = dbsnp ? channel.value([[id:"dbsnp"], dbsnp]) : [[],[]] + def ch_dbsnp_tbi = dbsnp_tbi ? channel.value([[id:"dbsnp"], dbsnp_tbi]) : [[],[]] - def ch_somalier_sites = somalier_sites ? channel.value([[id:"somalier_sites"], somalier_sites]) : [[],[]] + def ch_somalier_sites = somalier_sites ? channel.value([[id:"somalier_sites"], somalier_sites]) : [[],[]] - def ch_vep_cache = vep_cache ? channel.value([[id:'cache'], file(vep_cache)]) : [] + def ch_vep_cache = vep_cache ? channel.value([[id:'cache'], file(vep_cache)]) : [] - def ch_vcfanno_config = vcfanno_config ? channel.value(vcfanno_config) : [] - def ch_vcfanno_lua = vcfanno_lua ? channel.value(vcfanno_lua) : [] - def ch_vcfanno_resources = vcfanno_resources ? channel.value(vcfanno_resources.split(";").collect{ res -> files(res, checkIfExists:true) }.flatten()) : [] + def ch_vcfanno_config = vcfanno_config ? channel.value(vcfanno_config) : [] + def ch_vcfanno_lua = vcfanno_lua ? channel.value(vcfanno_lua) : [] + def ch_vcfanno_resources = vcfanno_resources ? channel.value(vcfanno_resources.split(";").collect{ res -> files(res, checkIfExists:true) }.flatten()) : [] - def ch_updio_common_cnvs = updio_common_cnvs ? channel.value([[id:'updio_cnv'], updio_common_cnvs]) : [[],[]] + def ch_updio_common_cnvs = updio_common_cnvs ? channel.value([[id:'updio_cnv'], updio_common_cnvs]) : [[],[]] - def ch_automap_repeats = automap_repeats ? channel.value([[id:"repeats"], automap_repeats]) : [] - def ch_automap_panel = automap_panel ? channel.value([[id:"automap_panel"], automap_panel]) : [[],[]] + def ch_automap_repeats = automap_repeats ? channel.value([[id:"repeats"], automap_repeats]) : [] + def ch_automap_panel = automap_panel ? channel.value([[id:"automap_panel"], automap_panel]) : [[],[]] - def ch_elsites = elsites ? channel.fromPath(elsites).map{ elsites_file -> [[id:'elsites'], elsites_file] }.collect() : [[],[]] + def ch_elsites = elsites ? channel.fromPath(elsites).map{ elsites_file -> [[id:'elsites'], elsites_file] }.collect() : [[],[]] - def ch_msi_baseline = msi_baseline ? channel.value([[id:"msi_baseline"], msi_baseline]) : [[],[]] + def ch_msi_baseline = msi_baseline ? channel.value([[id:"msi_baseline"], msi_baseline]) : [[],[]] - def ch_updio_regions = updio_regions ? channel.value(updio_regions) : [] + def ch_updio_regions = updio_regions ? channel.value(updio_regions) : [] + + def ch_panel_of_normals = panel_of_normals ? channel.value(panel_of_normals) : [] + def ch_panel_of_normals_tbi = panel_of_normals_tbi ? channel.value(panel_of_normals_tbi) : [] // // Check for the presence of EnsemblVEP plugins that use extra files @@ -598,17 +603,14 @@ workflow SMALLVARIANTS { // CRAM_CALL_MUTECT2( - ch_caller_inputs.cram.filter { meta, _cram, _crai, _bed -> - // Filter out the entries that already have a GVCF - meta.type == "cram" - }, + ch_caller_inputs.cram, ch_fasta_ready, ch_fai_ready, ch_dict_ready, ch_dbsnp_ready, ch_dbsnp_tbi_ready, - [], // TODO add panel of normals support - [] + ch_panel_of_normals, + ch_panel_of_normals_tbi ) ch_calls = ch_calls.mix(CRAM_CALL_MUTECT2.out.vcfs) } From 198c5b685b5a85902be8b668794fdbb9d82adf4c Mon Sep 17 00:00:00 2001 From: Nicolas Vannieuwkerke Date: Wed, 9 Sep 2026 10:28:03 +0200 Subject: [PATCH 09/12] exclude mutect2 vcfs from vcf2db --- subworkflows/local/vcf_roh_automap/main.nf | 1 - workflows/smallvariants.nf | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/subworkflows/local/vcf_roh_automap/main.nf b/subworkflows/local/vcf_roh_automap/main.nf index e12aa451..3f41dc86 100644 --- a/subworkflows/local/vcf_roh_automap/main.nf +++ b/subworkflows/local/vcf_roh_automap/main.nf @@ -32,7 +32,6 @@ workflow VCF_ROH_AUTOMAP { def ch_automap_input = ch_vcfs.filter { meta, vcf, tbi -> // Check the amount of variants for all VCFs that are under 1 MB (automap requires at least 10000 variants to run) - println vcf.size() if(vcf?.size() < 1000000) { def var_count = 0 vcf.withInputStream { stream -> diff --git a/workflows/smallvariants.nf b/workflows/smallvariants.nf index e685170e..1bd7b391 100644 --- a/workflows/smallvariants.nf +++ b/workflows/smallvariants.nf @@ -870,6 +870,11 @@ workflow SMALLVARIANTS { if(gemini){ def ch_vcf2db_input = ch_final_vcfs.map { meta, vcf, _tbi -> [ meta, vcf ]} .join(ch_final_peds, failOnMismatch:true, failOnDuplicate:true) + .filter { meta, _vcf, _ped -> meta.caller != 'mutect2' } // Exclude Mutect2 from vcf2db due to occurence of triploid variants + + if (callers.contains('mutect2')) { + log.warn("Mutect2 caller is present but will be excluded from vcf2db due to occurrence of triploid variants.") + } VCF2DB( ch_vcf2db_input From e38b072bd6c77a2255e351a0c557f5f935bd90b2 Mon Sep 17 00:00:00 2001 From: Nicolas Vannieuwkerke Date: Wed, 9 Sep 2026 10:29:05 +0200 Subject: [PATCH 10/12] typo --- subworkflows/local/vcf_roh_automap/main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subworkflows/local/vcf_roh_automap/main.nf b/subworkflows/local/vcf_roh_automap/main.nf index 3f41dc86..cb89532a 100644 --- a/subworkflows/local/vcf_roh_automap/main.nf +++ b/subworkflows/local/vcf_roh_automap/main.nf @@ -30,7 +30,7 @@ workflow VCF_ROH_AUTOMAP { ch_valid_repeats = ch_repeats } - def ch_automap_input = ch_vcfs.filter { meta, vcf, tbi -> + def ch_automap_input = ch_vcfs.filter { meta, vcf, _tbi -> // Check the amount of variants for all VCFs that are under 1 MB (automap requires at least 10000 variants to run) if(vcf?.size() < 1000000) { def var_count = 0 From c202a674350e3ed7b01824bf9c373be029d715c8 Mon Sep 17 00:00:00 2001 From: Nicolas Vannieuwkerke Date: Wed, 9 Sep 2026 14:45:19 +0200 Subject: [PATCH 11/12] remove mutect2 filter for vcf2db --- workflows/smallvariants.nf | 5 ----- 1 file changed, 5 deletions(-) diff --git a/workflows/smallvariants.nf b/workflows/smallvariants.nf index 1bd7b391..e685170e 100644 --- a/workflows/smallvariants.nf +++ b/workflows/smallvariants.nf @@ -870,11 +870,6 @@ workflow SMALLVARIANTS { if(gemini){ def ch_vcf2db_input = ch_final_vcfs.map { meta, vcf, _tbi -> [ meta, vcf ]} .join(ch_final_peds, failOnMismatch:true, failOnDuplicate:true) - .filter { meta, _vcf, _ped -> meta.caller != 'mutect2' } // Exclude Mutect2 from vcf2db due to occurence of triploid variants - - if (callers.contains('mutect2')) { - log.warn("Mutect2 caller is present but will be excluded from vcf2db due to occurrence of triploid variants.") - } VCF2DB( ch_vcf2db_input From 96765d01592053b5ddfc8c3638d5255880c3da48 Mon Sep 17 00:00:00 2001 From: Nicolas Vannieuwkerke Date: Tue, 29 Sep 2026 15:31:51 +0200 Subject: [PATCH 12/12] add additional argument to mutect2 --- conf/modules.config | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/conf/modules.config b/conf/modules.config index 90ffefac..ce84f384 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -225,7 +225,11 @@ process { "--annotation DepthPerAlleleBySample", "--annotation Coverage", "--read-validation-stringency LENIENT", - "--interval-set-rule INTERSECTION" + "--interval-set-rule INTERSECTION", + "--max-reads-per-alignment-start 0", + "--initial-tumor-lod 0", + "--tumor-lod-to-emit 0", + "--min-base-quality-score 20" ].join(" ") }