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/CHANGELOG.md b/CHANGELOG.md index bd27c002..ac861fa8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,23 @@ 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 + +### 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) + +### 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/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/conf/modules.config b/conf/modules.config index 1cc3aff5..ce84f384 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,44 @@ process { ext.prefix = {"${meta.id}.concat"} } + /* + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + MUTECT2 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + + withName: "^.*CRAM_CALL_MUTECT2:GATK4_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", + "--max-reads-per-alignment-start 0", + "--initial-tumor-lod 0", + "--tumor-lod-to-emit 0", + "--min-base-quality-score 20" + ].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"} + } + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ FILTER diff --git a/main.nf b/main.nf index 9b874826..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 { @@ -333,12 +339,16 @@ 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") } + 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)") } @@ -358,7 +368,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(', ')}") } } /* @@ -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/modules.json b/modules.json index b0708491..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", @@ -117,6 +122,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/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/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/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/nextflow_schema.json b/nextflow_schema.json index 47936957..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"] @@ -242,8 +256,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)?,?)*(?=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", 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/subworkflows/local/cram_call_mutect2/main.nf b/subworkflows/local/cram_call_mutect2/main.nf new file mode 100644 index 00000000..de71d7bd --- /dev/null +++ b/subworkflows/local/cram_call_mutect2/main.nf @@ -0,0 +1,72 @@ +// +// Call the variants using HaplotypeCaller +// + +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' + +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', family_samples: meta.sample] + tuple(new_meta, cram, crai, bed) + }, + ch_fasta, + ch_fai.map { meta, fai -> tuple(meta, fai, [])}, + ch_dict, + [], + [], + 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 + ) + + // 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, [], [], [], []) } + + 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 + ) + + 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 diff --git a/subworkflows/local/vcf_roh_automap/main.nf b/subworkflows/local/vcf_roh_automap/main.nf index aa184acf..cb89532a 100644 --- a/subworkflows/local/vcf_roh_automap/main.nf +++ b/subworkflows/local/vcf_roh_automap/main.nf @@ -30,8 +30,29 @@ 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) + 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 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 636d120c..e685170e 100644 --- a/workflows/smallvariants.nf +++ b/workflows/smallvariants.nf @@ -17,10 +17,11 @@ 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' +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' @@ -106,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 @@ -148,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 @@ -538,7 +544,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 +557,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) { @@ -591,6 +597,24 @@ 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, + ch_fasta_ready, + ch_fai_ready, + ch_dict_ready, + ch_dbsnp_ready, + ch_dbsnp_tbi_ready, + ch_panel_of_normals, + ch_panel_of_normals_tbi + ) + 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 }