From eda8ca9e4823bbc462181a4c25cb2161d9fb7924 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 7 Jul 2026 16:19:29 -0400 Subject: [PATCH 1/2] feat (closes #7246): add new component: leafcutter/differentialsplicing --- .../differentialsplicing/environment.yml | 7 + .../leafcutter/differentialsplicing/main.nf | 42 ++++++ .../leafcutter/differentialsplicing/meta.yml | 80 ++++++++++++ .../differentialsplicing/tests/main.nf.test | 121 ++++++++++++++++++ .../tests/main.nf.test.snap | 93 ++++++++++++++ .../tests/nextflow.config | 7 + 6 files changed, 350 insertions(+) create mode 100644 modules/nf-core/leafcutter/differentialsplicing/environment.yml create mode 100644 modules/nf-core/leafcutter/differentialsplicing/main.nf create mode 100644 modules/nf-core/leafcutter/differentialsplicing/meta.yml create mode 100644 modules/nf-core/leafcutter/differentialsplicing/tests/main.nf.test create mode 100644 modules/nf-core/leafcutter/differentialsplicing/tests/main.nf.test.snap create mode 100644 modules/nf-core/leafcutter/differentialsplicing/tests/nextflow.config diff --git a/modules/nf-core/leafcutter/differentialsplicing/environment.yml b/modules/nf-core/leafcutter/differentialsplicing/environment.yml new file mode 100644 index 000000000000..e4c971910da0 --- /dev/null +++ b/modules/nf-core/leafcutter/differentialsplicing/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::leafcutter=2.0.3" diff --git a/modules/nf-core/leafcutter/differentialsplicing/main.nf b/modules/nf-core/leafcutter/differentialsplicing/main.nf new file mode 100644 index 000000000000..7e2279bc76fc --- /dev/null +++ b/modules/nf-core/leafcutter/differentialsplicing/main.nf @@ -0,0 +1,42 @@ +process LEAFCUTTER_DIFFERENTIALSPLICING { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/leafcutter:2.0.3--pyhd8ed1ab_0': + 'quay.io/biocontainers/leafcutter:2.0.3--pyhd8ed1ab_0' }" + + input: + tuple val(meta), path(counts), path(groups) + + output: + tuple val(meta), path("*_cluster_significance.txt"), emit: cluster_significance + tuple val(meta), path("*_effect_sizes.txt") , emit: effect_sizes + // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. + tuple val("${task.process}"), val('leafcutter'), val("2.0.3"), topic: versions, emit: versions_leafcutter + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + """ + export USER=\${USER:-nobody} + + leafcutter-ds \\ + $counts \\ + $groups \\ + --output_prefix ${prefix}_results \\ + --num_threads $task.cpus \\ + $args + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}_results_cluster_significance.txt + touch ${prefix}_results_effect_sizes.txt + """ +} diff --git a/modules/nf-core/leafcutter/differentialsplicing/meta.yml b/modules/nf-core/leafcutter/differentialsplicing/meta.yml new file mode 100644 index 000000000000..cd5a48fb2bd9 --- /dev/null +++ b/modules/nf-core/leafcutter/differentialsplicing/meta.yml @@ -0,0 +1,80 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "leafcutter_differentialsplicing" +description: Perform differential splicing analysis to identify junctions with significant changes in splicing between conditions +keywords: + - alternative splicing + - differential splicing + - RNA-seq + - intron usage +tools: + - "leafcutter": + description: "Annotation-free quantification of RNA splicing from RNA-seq data" + homepage: "https://leafcutter2.github.io/leafcutter-ds/" + documentation: "https://leafcutter2.github.io/leafcutter-ds/tutorials/differential-splicing/" + tool_dev_url: "https://github.com/leafcutter2/leafcutter-ds" + doi: "10.1038/s41588-017-0004-9" + licence: ["Apache-2.0"] + identifier: biotools:leafcutter + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test' ]` + - counts: + type: file + description: Intron usage counts file from leafcutter-cluster (either perind.counts.gz or perind_numers.counts.gz) + pattern: "*.counts.gz" + - groups: + type: file + description: Tab-delimited file with sample names in column 1 and sample group/phenotype assignments in column 2 + pattern: "*.txt" + +output: + cluster_significance: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test' ]` + - "*_cluster_significance.txt": + type: file + description: Cluster-level differential splicing statistics including p-values and test status + pattern: "*_cluster_significance.txt" + effect_sizes: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test' ]` + - "*_effect_sizes.txt": + type: file + description: Per-intron junction effect sizes, PSI values, and delta PSI for each condition + pattern: "*_effect_sizes.txt" + versions_leafcutter: + - - "${task.process}": + type: string + description: The name of the process + - "leafcutter": + type: string + description: The name of the tool + - "2.0.3": + type: string + description: The version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - leafcutter: + type: string + description: The name of the tool + - "2.0.3": + type: string + description: The version of the tool +authors: + - "@JTL-lab" +maintainers: + - "@JTL-lab" diff --git a/modules/nf-core/leafcutter/differentialsplicing/tests/main.nf.test b/modules/nf-core/leafcutter/differentialsplicing/tests/main.nf.test new file mode 100644 index 000000000000..a0625cd168e4 --- /dev/null +++ b/modules/nf-core/leafcutter/differentialsplicing/tests/main.nf.test @@ -0,0 +1,121 @@ +nextflow_process { + + name "Test Process LEAFCUTTER_DIFFERENTIALSPLICING" + script "../main.nf" + process "LEAFCUTTER_DIFFERENTIALSPLICING" + + tag "modules" + tag "modules_nfcore" + tag "leafcutter" + tag "leafcutter/differentialsplicing" + tag "leafcutter/clusterregtools" + tag "regtools" + tag "regtools/junctionsextract" + + test("homo_sapiens - bam - differential splicing") { + + config "./nextflow.config" + + setup { + run("REGTOOLS_JUNCTIONSEXTRACT") { + script "../../../regtools/junctionsextract/main.nf" + process { + """ + input[0] = Channel.of( + [ + [ id:'ctrl_rep1', single_end:false ], + file(params.modules_testdata_base_path + "genomics/homo_sapiens/illumina/bam/test.rna.paired_end.sorted.chr6.bam", checkIfExists: true), + file(params.modules_testdata_base_path + "genomics/homo_sapiens/illumina/bam/test.rna.paired_end.sorted.chr6.bam.bai", checkIfExists: true) + ], + [ + [ id:'ctrl_rep2', single_end:false ], + file(params.modules_testdata_base_path + "genomics/homo_sapiens/illumina/bam/test.rna.paired_end.sorted.chr6.bam", checkIfExists: true), + file(params.modules_testdata_base_path + "genomics/homo_sapiens/illumina/bam/test.rna.paired_end.sorted.chr6.bam.bai", checkIfExists: true) + ], + [ + [ id:'kd_rep1', single_end:false ], + file(params.modules_testdata_base_path + "genomics/homo_sapiens/illumina/bam/test.rna.paired_end.sorted.chr6.bam", checkIfExists: true), + file(params.modules_testdata_base_path + "genomics/homo_sapiens/illumina/bam/test.rna.paired_end.sorted.chr6.bam.bai", checkIfExists: true) + ], + [ + [ id:'kd_rep2', single_end:false ], + file(params.modules_testdata_base_path + "genomics/homo_sapiens/illumina/bam/test.rna.paired_end.sorted.chr6.bam", checkIfExists: true), + file(params.modules_testdata_base_path + "genomics/homo_sapiens/illumina/bam/test.rna.paired_end.sorted.chr6.bam.bai", checkIfExists: true) + ] + ) + input[1] = '' + """ + } + } + + run("LEAFCUTTER_CLUSTERREGTOOLS") { + script "../../../leafcutter/clusterregtools/main.nf" + process { + """ + input[0] = REGTOOLS_JUNCTIONSEXTRACT.out.junc + .map { meta, junc -> junc } + .collect() + .map { junc_files -> [ [ id:'test_ds' ], junc_files ] } + """ + } + } + } + + when { + process { + """ + def groups_content = '''ctrl_rep1\\tCONTROL +ctrl_rep2\\tCONTROL +kd_rep1\\tKNOCKDOWN +kd_rep2\\tKNOCKDOWN +''' + def groups_file = file("\${workDir}/groups_file.txt") + groups_file.text = groups_content + + input[0] = LEAFCUTTER_CLUSTERREGTOOLS.out.numers + .map { meta, counts -> [ meta, counts, groups_file ] } + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.cluster_significance, + process.out.effect_sizes, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + + } + + test("homo_sapiens - bam - differential splicing - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file('test_perind.counts.gz'), + file('groups_file.txt') + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() }, + { assert process.out.cluster_significance != null }, + { assert process.out.effect_sizes != null } + ) + } + + } + +} diff --git a/modules/nf-core/leafcutter/differentialsplicing/tests/main.nf.test.snap b/modules/nf-core/leafcutter/differentialsplicing/tests/main.nf.test.snap new file mode 100644 index 000000000000..bf1193bdcd1e --- /dev/null +++ b/modules/nf-core/leafcutter/differentialsplicing/tests/main.nf.test.snap @@ -0,0 +1,93 @@ +{ + "homo_sapiens - bam - differential splicing": { + "content": [ + [ + [ + { + "id": "test_ds" + }, + "test_ds_results_cluster_significance.txt:md5,222cfb8c25277ec24f84934b684d195a" + ] + ], + [ + [ + { + "id": "test_ds" + }, + "test_ds_results_effect_sizes.txt:md5,8535426635beacabae83a5b8cc6ed385" + ] + ], + { + "versions_leafcutter": [ + [ + "LEAFCUTTER_DIFFERENTIALSPLICING", + "leafcutter", + "2.0.3" + ] + ] + } + ], + "timestamp": "2026-07-07T15:56:29.391725645", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.0" + } + }, + "homo_sapiens - bam - differential splicing - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_results_cluster_significance.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test_results_effect_sizes.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + "LEAFCUTTER_DIFFERENTIALSPLICING", + "leafcutter", + "2.0.3" + ] + ], + "cluster_significance": [ + [ + { + "id": "test" + }, + "test_results_cluster_significance.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "effect_sizes": [ + [ + { + "id": "test" + }, + "test_results_effect_sizes.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_leafcutter": [ + [ + "LEAFCUTTER_DIFFERENTIALSPLICING", + "leafcutter", + "2.0.3" + ] + ] + } + ], + "timestamp": "2026-07-07T15:56:35.602866997", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.0" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/leafcutter/differentialsplicing/tests/nextflow.config b/modules/nf-core/leafcutter/differentialsplicing/tests/nextflow.config new file mode 100644 index 000000000000..b859f0a57a23 --- /dev/null +++ b/modules/nf-core/leafcutter/differentialsplicing/tests/nextflow.config @@ -0,0 +1,7 @@ +process { + + withName: 'LEAFCUTTER_DIFFERENTIALSPLICING' { + ext.args = '--min_samples_per_group 2 --min_samples_per_intron 2 --num_threads 1' + } + +} From 58b53d42168b0db24d4ad7fd9ed82685da40104c Mon Sep 17 00:00:00 2001 From: = Date: Wed, 8 Jul 2026 14:49:27 -0400 Subject: [PATCH 2/2] fix: update main.nf.test for non-deterministic outputs, update snapshot, set python environment hash variable --- .../leafcutter/differentialsplicing/main.nf | 2 ++ .../differentialsplicing/tests/main.nf.test | 11 +++++--- .../tests/main.nf.test.snap | 28 ++++++------------- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/modules/nf-core/leafcutter/differentialsplicing/main.nf b/modules/nf-core/leafcutter/differentialsplicing/main.nf index 7e2279bc76fc..af77288e2ab7 100644 --- a/modules/nf-core/leafcutter/differentialsplicing/main.nf +++ b/modules/nf-core/leafcutter/differentialsplicing/main.nf @@ -24,6 +24,8 @@ process LEAFCUTTER_DIFFERENTIALSPLICING { def prefix = task.ext.prefix ?: "${meta.id}" """ export USER=\${USER:-nobody} + export PYTHONHASHSEED=0 + export CUBLAS_WORKSPACE_CONFIG=:4096:8 leafcutter-ds \\ $counts \\ diff --git a/modules/nf-core/leafcutter/differentialsplicing/tests/main.nf.test b/modules/nf-core/leafcutter/differentialsplicing/tests/main.nf.test index a0625cd168e4..e65c5a4c5a69 100644 --- a/modules/nf-core/leafcutter/differentialsplicing/tests/main.nf.test +++ b/modules/nf-core/leafcutter/differentialsplicing/tests/main.nf.test @@ -82,10 +82,13 @@ kd_rep2\\tKNOCKDOWN assertAll( { assert process.success }, { assert snapshot( - process.out.cluster_significance, - process.out.effect_sizes, - process.out.findAll { key, val -> key.startsWith('versions') } - ).match() } + process.out.findAll { key, val -> key.startsWith('versions') }, + process.out.cluster_significance.collect { file(it[1]).getName() }, + process.out.effect_sizes.collect { file(it[1]).getName() } + ).match() }, + // Check non-deterministic files exist + { assert file(process.out.cluster_significance[0][1]).exists() }, + { assert file(process.out.effect_sizes[0][1]).exists() } ) } diff --git a/modules/nf-core/leafcutter/differentialsplicing/tests/main.nf.test.snap b/modules/nf-core/leafcutter/differentialsplicing/tests/main.nf.test.snap index bf1193bdcd1e..35c2e90c45dd 100644 --- a/modules/nf-core/leafcutter/differentialsplicing/tests/main.nf.test.snap +++ b/modules/nf-core/leafcutter/differentialsplicing/tests/main.nf.test.snap @@ -1,22 +1,6 @@ { "homo_sapiens - bam - differential splicing": { "content": [ - [ - [ - { - "id": "test_ds" - }, - "test_ds_results_cluster_significance.txt:md5,222cfb8c25277ec24f84934b684d195a" - ] - ], - [ - [ - { - "id": "test_ds" - }, - "test_ds_results_effect_sizes.txt:md5,8535426635beacabae83a5b8cc6ed385" - ] - ], { "versions_leafcutter": [ [ @@ -25,9 +9,15 @@ "2.0.3" ] ] - } + }, + [ + "test_ds_results_cluster_significance.txt" + ], + [ + "test_ds_results_effect_sizes.txt" + ] ], - "timestamp": "2026-07-07T15:56:29.391725645", + "timestamp": "2026-07-08T14:38:48.580335473", "meta": { "nf-test": "0.9.5", "nextflow": "25.10.0" @@ -84,7 +74,7 @@ ] } ], - "timestamp": "2026-07-07T15:56:35.602866997", + "timestamp": "2026-07-08T14:39:52.087856717", "meta": { "nf-test": "0.9.5", "nextflow": "25.10.0"