diff --git a/modules/nf-core/savana/run/environment.yml b/modules/nf-core/savana/run/environment.yml new file mode 100644 index 00000000000..60063b5bf6b --- /dev/null +++ b/modules/nf-core/savana/run/environment.yml @@ -0,0 +1,7 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - "bioconda::savana=1.3.7" diff --git a/modules/nf-core/savana/run/main.nf b/modules/nf-core/savana/run/main.nf new file mode 100644 index 00000000000..ff55160da49 --- /dev/null +++ b/modules/nf-core/savana/run/main.nf @@ -0,0 +1,50 @@ +process SAVANA_RUN { + tag "${meta.id}" + label 'process_high' + + conda "${moduleDir}/environment.yml" + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container + ? 'https://depot.galaxyproject.org/singularity/savana:1.3.7--pyhdfd78af_0' + : 'quay.io/biocontainers/savana:1.3.7--pyhdfd78af_0'}" + + input: + tuple val(meta), path(tumour), path(tumour_index), path(normal), path(normal_index) + tuple val(meta2),path(ref), path(ref_index) + + output: + tuple val(meta), path("${prefix}.sv_breakpoints.vcf"), emit: sv_breakpoints_vcf + tuple val(meta), path("${prefix}.sv_breakpoints.bedpe"), emit: sv_breakpoints_bedpe + tuple val(meta), path("${prefix}.sv_breakpoints_read_support.tsv"), emit: sv_breakpoints_read_support + tuple val(meta), path("${prefix}.inserted_sequences.fa"), emit: inserted_sequences + tuple val("${task.process}"), val("savana"), eval("python -c \"import importlib.metadata as m; print(m.version('savana'))\""), emit: versions_savana, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = (task.ext.args ?: '').trim() + prefix = task.ext.prefix ?: "${meta.id}" + def outdir = "." + """ + savana run \\ + --tumour ${tumour} \\ + --normal ${normal} \\ + --ref ${ref} \\ + --ref_index ${ref_index} \\ + --outdir ${outdir} \\ + --sample ${prefix} \\ + --threads ${task.cpus ?: 1} \\ + ${args} + """ + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + def outdir = "." + """ + mkdir -p ${outdir} + touch ${outdir}/${prefix}.sv_breakpoints.vcf + touch ${outdir}/${prefix}.sv_breakpoints.bedpe + touch ${outdir}/${prefix}.sv_breakpoints_read_support.tsv + touch ${outdir}/${prefix}.inserted_sequences.fa + """ +} diff --git a/modules/nf-core/savana/run/meta.yml b/modules/nf-core/savana/run/meta.yml new file mode 100644 index 00000000000..4061b89912d --- /dev/null +++ b/modules/nf-core/savana/run/meta.yml @@ -0,0 +1,130 @@ +name: "savana_run" +description: "Identify and cluster SV breakpoints from long-read alignments." +keywords: + - structural variants + - long read + - genomics +tools: + - "savana": + description: "SAVANA: a somatic structural variant and copy-number caller for + long-read data." + homepage: "https://github.com/cortes-ciriano-lab/savana" + documentation: "https://github.com/cortes-ciriano-lab/savana" + tool_dev_url: "https://github.com/cortes-ciriano-lab/savana" + doi: "10.1038/s41592-025-02708-0" + licence: + - "Apache-2.0" + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - tumour: + type: file + description: Tumour BAM/CRAM file + pattern: "*.{bam,cram}" + ontologies: + - edam: "http://edamontology.org/format_2572" # BAM + - edam: "http://edamontology.org/format_3462" # CRAM + - tumour_index: + type: file + description: Tumour BAM/CRAM index + pattern: "*.{bai,crai}" + ontologies: [] + - normal: + type: file + description: Normal BAM/CRAM file + pattern: "*.{bam,cram}" + ontologies: + - edam: "http://edamontology.org/format_2572" # BAM + - edam: "http://edamontology.org/format_3462" # CRAM + - normal_index: + type: file + description: Normal BAM/CRAM index + pattern: "*.{bai,crai}" + ontologies: [] + - - meta2: + type: map + description: | + Groovy Map containing reference information + e.g. `[ id:'hg38' ]` + - ref: + type: file + description: Reference genome FASTA file used to align tumor and normal + BAM/CRAM files + pattern: "*.{fa,fasta}" + ontologies: + - edam: "http://edamontology.org/format_1929" # FASTA + - ref_index: + type: file + description: Reference FASTA index + pattern: "*.fai" + ontologies: [] +output: + sv_breakpoints_vcf: + - - meta: + type: map + description: Sample information + - ${prefix}.sv_breakpoints.vcf: + type: file + description: VCF file containing raw SV breakpoints + pattern: "*.sv_breakpoints.vcf" + ontologies: + - edam: "http://edamontology.org/format_3016" + sv_breakpoints_bedpe: + - - meta: + type: map + description: Sample information + - ${prefix}.sv_breakpoints.bedpe: + type: file + description: BEDPE file containing SV breakpoints + pattern: "*.sv_breakpoints.bedpe" + ontologies: + - edam: "http://edamontology.org/format_3003" # BED + sv_breakpoints_read_support: + - - meta: + type: map + description: Sample information + - ${prefix}.sv_breakpoints_read_support.tsv: + type: file + description: TSV file containing read support information for each SV breakpoint + pattern: "*.sv_breakpoints_read_support.tsv" + ontologies: + - edam: "http://edamontology.org/format_3475" # TSV + inserted_sequences: + - - meta: + type: map + description: Sample information + - ${prefix}.inserted_sequences.fa: + type: file + description: FASTA file containing inserted sequences + pattern: "*.inserted_sequences.fa" + ontologies: + - edam: "http://edamontology.org/format_1929" # FASTA + versions_savana: + - - ${task.process}: + type: string + description: Process name + - savana: + type: string + description: Tool version + - python -c "import importlib.metadata as m; print(m.version('savana'))": + type: eval + description: Command to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: Process name + - savana: + type: string + description: Tool version + - python -c "import importlib.metadata as m; print(m.version('savana'))": + type: eval + description: Command to obtain the version of the tool +authors: + - "@manascripts" +maintainers: + - "@manascripts" diff --git a/modules/nf-core/savana/run/tests/main.nf.test b/modules/nf-core/savana/run/tests/main.nf.test new file mode 100644 index 00000000000..f397e17585c --- /dev/null +++ b/modules/nf-core/savana/run/tests/main.nf.test @@ -0,0 +1,113 @@ +nextflow_process { + + name "Test Process SAVANA_RUN" + script "../main.nf" + process "SAVANA_RUN" + + tag "modules" + tag "modules_nfcore" + tag "savana" + tag "savana/run" + + test("homo_sapiens - nanopore") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test2.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test2.sorted.bam.bai', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test.sorted.phased.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test.sorted.phased.bam.bai', checkIfExists: true) + ] + + input[1] = [ + [ id:'ref' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + path(process.out.sv_breakpoints_vcf.get(0).get(1)).vcf.summary, + file(process.out.sv_breakpoints_bedpe.get(0).get(1)).name, + file(process.out.sv_breakpoints_read_support.get(0).get(1)).readLines().size(), + file(process.out.inserted_sequences.get(0).get(1)).name, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + } + + test("homo_sapiens - pacbio") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/bam/test.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/bam/test.sorted.bam.bai', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/bam/test_hifi_aligned_to_assembly.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/bam/test_hifi_aligned_to_assembly.bam.bai', checkIfExists: true) + ] + + input[1] = [ + [ id:'ref' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + path(process.out.sv_breakpoints_vcf.get(0).get(1)).vcf.summary, + file(process.out.sv_breakpoints_bedpe.get(0).get(1)).name, + file(process.out.sv_breakpoints_read_support.get(0).get(1)).readLines().size(), + file(process.out.inserted_sequences.get(0).get(1)).name, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + } + + test("homo_sapiens - nanopore - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test2.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test2.sorted.bam.bai', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test.sorted.phased.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/nanopore/bam/test.sorted.phased.bam.bai', checkIfExists: true) + ] + + input[1] = [ + [ id:'ref' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) + ] + """ + } + } + + then { + assert process.success + assert snapshot(sanitizeOutput(process.out)).match() + } + } +} diff --git a/modules/nf-core/savana/run/tests/main.nf.test.snap b/modules/nf-core/savana/run/tests/main.nf.test.snap new file mode 100644 index 00000000000..3e5563f05bc --- /dev/null +++ b/modules/nf-core/savana/run/tests/main.nf.test.snap @@ -0,0 +1,96 @@ +{ + "homo_sapiens - pacbio": { + "content": [ + "VcfFile [chromosomes=[], sampleCount=1, variantCount=0, phased=true, phasedAutodetect=true]", + "test.sv_breakpoints.bedpe", + 1, + "test.inserted_sequences.fa", + { + "versions_savana": [ + [ + "SAVANA_RUN", + "savana", + "1.3.7" + ] + ] + } + ], + "timestamp": "2026-05-15T14:24:46.790612822", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.4" + } + }, + "homo_sapiens - nanopore": { + "content": [ + "VcfFile [chromosomes=[chr22], sampleCount=1, variantCount=28, phased=false, phasedAutodetect=false]", + "test.sv_breakpoints.bedpe", + 16, + "test.inserted_sequences.fa", + { + "versions_savana": [ + [ + "SAVANA_RUN", + "savana", + "1.3.7" + ] + ] + } + ], + "timestamp": "2026-05-15T14:24:30.660792995", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.4" + } + }, + "homo_sapiens - nanopore - stub": { + "content": [ + { + "inserted_sequences": [ + [ + { + "id": "test" + }, + "test.inserted_sequences.fa:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "sv_breakpoints_bedpe": [ + [ + { + "id": "test" + }, + "test.sv_breakpoints.bedpe:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "sv_breakpoints_read_support": [ + [ + { + "id": "test" + }, + "test.sv_breakpoints_read_support.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "sv_breakpoints_vcf": [ + [ + { + "id": "test" + }, + "test.sv_breakpoints.vcf:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_savana": [ + [ + "SAVANA_RUN", + "savana", + "1.3.7" + ] + ] + } + ], + "timestamp": "2026-05-15T14:24:57.388853983", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.4" + } + } +} \ No newline at end of file