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..af77288e2ab7 --- /dev/null +++ b/modules/nf-core/leafcutter/differentialsplicing/main.nf @@ -0,0 +1,44 @@ +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} + export PYTHONHASHSEED=0 + export CUBLAS_WORKSPACE_CONFIG=:4096:8 + + 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..e65c5a4c5a69 --- /dev/null +++ b/modules/nf-core/leafcutter/differentialsplicing/tests/main.nf.test @@ -0,0 +1,124 @@ +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.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() } + ) + } + + } + + 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..35c2e90c45dd --- /dev/null +++ b/modules/nf-core/leafcutter/differentialsplicing/tests/main.nf.test.snap @@ -0,0 +1,83 @@ +{ + "homo_sapiens - bam - differential splicing": { + "content": [ + { + "versions_leafcutter": [ + [ + "LEAFCUTTER_DIFFERENTIALSPLICING", + "leafcutter", + "2.0.3" + ] + ] + }, + [ + "test_ds_results_cluster_significance.txt" + ], + [ + "test_ds_results_effect_sizes.txt" + ] + ], + "timestamp": "2026-07-08T14:38:48.580335473", + "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-08T14:39:52.087856717", + "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' + } + +}