diff --git a/modules/nf-core/saltshaker/classify/environment.yml b/modules/nf-core/saltshaker/classify/environment.yml new file mode 100644 index 00000000000..f76c4bf5a59 --- /dev/null +++ b/modules/nf-core/saltshaker/classify/environment.yml @@ -0,0 +1,7 @@ +channels: + - conda-forge + - bioconda +dependencies: + - pip==26.0.1 + - pip: + - saltshaker==1.0.0 diff --git a/modules/nf-core/saltshaker/classify/main.nf b/modules/nf-core/saltshaker/classify/main.nf new file mode 100644 index 00000000000..20931297404 --- /dev/null +++ b/modules/nf-core/saltshaker/classify/main.nf @@ -0,0 +1,56 @@ +process SALTSHAKER_CLASSIFY { + tag "$meta.id" + label 'process_single' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/e9/e93d703b195dd27cd920cee46669d3f51043216c12fd05168c937e93adf170e8/data': + 'community.wave.seqera.io/library/pip_saltshaker:e08e38a6d45f8f32' }" + + input: + tuple val(meta), path(call) + val dominant_fraction + val group_radius + val high_heteroplasmy + val multiple_threshold + val noise_threshold + + output: + tuple val(meta), path("*_classify_metadata.tsv"), emit: classify + tuple val(meta), path("*_classify.txt") , emit: txt + tuple val(meta), path("*saltshaker.vcf") , emit: vcf, optional: true + tuple val("${task.process}"), val('saltshaker'), val("1.0.0"), topic: versions, emit: versions_saltshaker + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + saltshaker classify \\ + --prefix $prefix \\ + --input-dir . \\ + --dominant-fraction $dominant_fraction \\ + --radius $group_radius \\ + --high-het $high_heteroplasmy \\ + --multiple-threshold $multiple_threshold \\ + --noise $noise_threshold \\ + $args + + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def touch_vcf = args.contains('--vcf') ? "touch ${prefix}.saltshaker.vcf" : '' + + """ + echo $args + + $touch_vcf + touch ${prefix}.saltshaker_classify.txt + touch ${prefix}.saltshaker_classify_metadata.tsv + """ +} diff --git a/modules/nf-core/saltshaker/classify/meta.yml b/modules/nf-core/saltshaker/classify/meta.yml new file mode 100644 index 00000000000..182df6566cd --- /dev/null +++ b/modules/nf-core/saltshaker/classify/meta.yml @@ -0,0 +1,106 @@ +name: "saltshaker_classify" +description: mtDNA deletion and duplication classification downstream of mitosalt +keywords: + - saltshaker + - mitosalt + - mtDNA + - structural-variant calling +tools: + - "saltshaker": + description: "A Python package for classifying and visualizing mitochondrial structural variants from MitoSAlt pipeline output." + homepage: "https://github.com/aksenia/saltshaker" + documentation: "https://github.com/aksenia/saltshaker/tree/main/saltshaker/docs" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - call: + type: file + description: call metadata file from saltshaker_call + pattern: "*_call_metadata.tsv" + ontologies: + - edam: http://edamontology.org/operation_3227 #variant calling + - edam: http://edamontology.org/format_3475 #tsv + - dominant_fraction: + type: float + description: Minimum heteroplasmy fraction to classify as dominant in saltshaker + - group_radius: + type: integer + description: Spatial clustering radius for saltshaker grouping + - high_heteroplasmy: + type: float + description: High heteroplasmy threshold for saltshaker classification + - multiple_threshold: + type: float + description: Heteroplasmy threshold for multiple classification in saltshaker + - noise_threshold: + type: float + description: Heteroplasmy threshold for noise in saltshaker classification +output: + classify: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_classify_metadata.tsv": + type: file + description: tsv with classified call metadata to be used in saltshaker_plot + pattern: "*_classify_metadata.tsv" + ontologies: + - edam: http://edamontology.org/operation_3225 #classification + - edam: http://edamontology.org/format_3475 #tsv + txt: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_classify.txt": + type: file + description: txt file with case classification + pattern: "*_classify.txt" + ontologies: + - edam: http://edamontology.org/operation_3225 #classification + - edam: http://edamontology.org/format_2330 #txt + vcf: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*saltshaker.vcf": + type: file + description: vcf file with classified calls + pattern: "*saltshaker.vcf" + ontologies: + - edam: http://edamontology.org/operation_3225 #classification + - edam: http://edamontology.org/format_3016 #vcf + versions_saltshaker: + - - ${task.process}: + type: string + description: The name of the process + - saltshaker: + type: string + description: The name of the tool + - 1.0.0: + type: string + description: Hardcoded version of saltshaker used in the module +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - saltshaker: + type: string + description: The name of the tool + - 1.0.0: + type: string + description: Hardcoded version of saltshaker used in the module +authors: + - "@ieduba" +maintainers: + - "@ieduba" diff --git a/modules/nf-core/saltshaker/classify/tests/main.nf.test b/modules/nf-core/saltshaker/classify/tests/main.nf.test new file mode 100644 index 00000000000..12cc1ec945b --- /dev/null +++ b/modules/nf-core/saltshaker/classify/tests/main.nf.test @@ -0,0 +1,164 @@ +nextflow_process { + + name "Test Process SALTSHAKER_CLASSIFY" + script "../main.nf" + process "SALTSHAKER_CLASSIFY" + config "./nextflow.config" + + tag "modules" + tag "modules_nfcore" + tag "saltshaker" + tag "saltshaker/call" + tag "saltshaker/classify" + + setup { + run("SALTSHAKER_CALL") { + script "../../call/main.nf" + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/mitosalt/test_mitosalt.breakpoint', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/mitosalt/test_mitosalt.cluster', checkIfExists: true), + ] + + input[1] = [ + [ id:'hg38' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/human_mt_rCRS.fasta', checkIfExists: true), + ] + + input[2] = 15 + input[3] = 0.01 + input[4] = 16569 + input[5] = 16081 + input[6] = 407 + input[7] = 5730 + input[8] = 5763 + """ + } + } + } + + test("classify - vcf") { + + when { + params { + module_args = '--blacklist --vcf' + } + process { + """ + input[0] = SALTSHAKER_CALL.out.call + input[1] = 0.5 + input[2] = 600 + input[3] = 10 + input[4] = 5 + input[5] = 0.3 + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot( + sanitizeOutput(process.out), + ).match() } + + ) + } + + } + + test("classify - vcf - stub") { + + options "-stub" + + when { + params { + module_args = '--blacklist --vcf' + } + process { + """ + input[0] = SALTSHAKER_CALL.out.call + input[1] = 0.5 + input[2] = 600 + input[3] = 10 + input[4] = 5 + input[5] = 0.3 + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot( + sanitizeOutput(process.out), + ).match() } + ) + } + + } + + test("classify - no-vcf") { + + when { + params { + module_args = '--blacklist' + } + process { + """ + input[0] = SALTSHAKER_CALL.out.call + input[1] = 0.5 + input[2] = 600 + input[3] = 10 + input[4] = 5 + input[5] = 0.3 + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot( + sanitizeOutput(process.out), + ).match() } + + ) + } + + } + + test("classify - no-vcf - stub") { + + options "-stub" + + when { + params { + module_args = '--blacklist' + } + process { + """ + input[0] = SALTSHAKER_CALL.out.call + input[1] = 0.5 + input[2] = 600 + input[3] = 10 + input[4] = 5 + input[5] = 0.3 + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot( + sanitizeOutput(process.out), + ).match() } + ) + } + + } + +} diff --git a/modules/nf-core/saltshaker/classify/tests/main.nf.test.snap b/modules/nf-core/saltshaker/classify/tests/main.nf.test.snap new file mode 100644 index 00000000000..ff226554f23 --- /dev/null +++ b/modules/nf-core/saltshaker/classify/tests/main.nf.test.snap @@ -0,0 +1,160 @@ +{ + "classify - no-vcf": { + "content": [ + { + "classify": [ + [ + { + "id": "test" + }, + "test.saltshaker_classify_metadata.tsv:md5,0c86c16ae38a5dc7070ad76e764aee6d" + ] + ], + "txt": [ + [ + { + "id": "test" + }, + "test.saltshaker_classify.txt:md5,8e11ad30539d0b5e0ab9a42b4d68b208" + ] + ], + "vcf": [ + + ], + "versions_saltshaker": [ + [ + "SALTSHAKER_CLASSIFY", + "saltshaker", + "1.0.0" + ] + ] + } + ], + "timestamp": "2026-03-17T11:02:38.830976", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.0" + } + }, + "classify - vcf - stub": { + "content": [ + { + "classify": [ + [ + { + "id": "test" + }, + "test.saltshaker_classify_metadata.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "txt": [ + [ + { + "id": "test" + }, + "test.saltshaker_classify.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "vcf": [ + [ + { + "id": "test" + }, + "test.saltshaker.vcf:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_saltshaker": [ + [ + "SALTSHAKER_CLASSIFY", + "saltshaker", + "1.0.0" + ] + ] + } + ], + "timestamp": "2026-03-16T10:53:38.916497", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.0" + } + }, + "classify - vcf": { + "content": [ + { + "classify": [ + [ + { + "id": "test" + }, + "test.saltshaker_classify_metadata.tsv:md5,0c86c16ae38a5dc7070ad76e764aee6d" + ] + ], + "txt": [ + [ + { + "id": "test" + }, + "test.saltshaker_classify.txt:md5,8e11ad30539d0b5e0ab9a42b4d68b208" + ] + ], + "vcf": [ + [ + { + "id": "test" + }, + "test.saltshaker.vcf:md5,6669688cfb486e6c98f0aadb8e41ed88" + ] + ], + "versions_saltshaker": [ + [ + "SALTSHAKER_CLASSIFY", + "saltshaker", + "1.0.0" + ] + ] + } + ], + "timestamp": "2026-03-17T10:17:34.236265", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.0" + } + }, + "classify - no-vcf - stub": { + "content": [ + { + "classify": [ + [ + { + "id": "test" + }, + "test.saltshaker_classify_metadata.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "txt": [ + [ + { + "id": "test" + }, + "test.saltshaker_classify.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "vcf": [ + + ], + "versions_saltshaker": [ + [ + "SALTSHAKER_CLASSIFY", + "saltshaker", + "1.0.0" + ] + ] + } + ], + "timestamp": "2026-03-17T11:02:43.86661", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.0" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/saltshaker/classify/tests/nextflow.config b/modules/nf-core/saltshaker/classify/tests/nextflow.config new file mode 100644 index 00000000000..97d25b454bf --- /dev/null +++ b/modules/nf-core/saltshaker/classify/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: 'SALTSHAKER_CLASSIFY' { + ext.args = params.module_args + } +}