diff --git a/modules/nf-core/multiseqdemux/environment.yml b/modules/nf-core/multiseqdemux/environment.yml new file mode 100644 index 00000000000..7a40ab60b86 --- /dev/null +++ b/modules/nf-core/multiseqdemux/environment.yml @@ -0,0 +1,8 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - "conda-forge::r-seurat=5.3.0" + - "conda-forge::r-seuratobject=5.1.0" diff --git a/modules/nf-core/multiseqdemux/main.nf b/modules/nf-core/multiseqdemux/main.nf new file mode 100644 index 00000000000..de8d91efa4a --- /dev/null +++ b/modules/nf-core/multiseqdemux/main.nf @@ -0,0 +1,47 @@ +process MULTISEQDEMUX { + tag "$meta.id" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'oras://community.wave.seqera.io/library/r-seurat_r-seuratobject:4c5a804804327d29': + 'community.wave.seqera.io/library/r-seurat_r-seuratobject:b11306d1bdc82827' }" + + input: + tuple val(meta), path(seurat_object), val(assay) + + output: + tuple val(meta), path("*_params_multiseqdemux.csv") , emit: params + tuple val(meta), path("*_res_multiseqdemux.csv") , emit: results + tuple val(meta), path("*_multiseqdemux.rds") , emit: rds + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + quantile = task.ext.assay ?: "0.7" + autoThresh = task.ext.autoThresh ?: "TRUE" + maxiter = task.ext.maxiter ?: "5" + qrangeFrom = task.ext.qrangeFrom ?: "0.1" + qrangeTo = task.ext.qrangeTo ?: "0.9" + qrangeBy = task.ext.qrangeBy ?: "0.05" + verbose = task.ext.verbose ?: 'TRUE' + prefix = task.ext.prefix ?: "${meta.id}" + + template 'MultiSeqDemux.R' + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}_params_multiseqdemux.csv + touch ${prefix}_res_multiseqdemux.csv + touch ${prefix}_multiseqdemux.rds + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + r-seurat: \$(Rscript -e "library(Seurat); cat(as.character(packageVersion('Seurat')))") + r-base: \$(Rscript -e "cat(strsplit(R.version[['version.string']], ' ')[[1]][3])") + END_VERSIONS + """ +} diff --git a/modules/nf-core/multiseqdemux/meta.yml b/modules/nf-core/multiseqdemux/meta.yml new file mode 100644 index 00000000000..b1541b6dcf2 --- /dev/null +++ b/modules/nf-core/multiseqdemux/meta.yml @@ -0,0 +1,78 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "multiseqdemux" +description: Identify singlets, doublets and negative cells from multiplexing experiments. Annotate singlets by tags. +keywords: + - demultiplexing + - hashing-based deconvolution + - single-cell +tools: + - "multiseqdemux": + description: "MULTIseqDemux is the demultiplexing module of Seurat, which demultiplex samples based on data from cell hashing." + homepage: "https://satijalab.org/seurat/reference/multiseqdemux" + documentation: "https://satijalab.org/seurat/reference/multiseqdemux" + tool_dev_url: "https://github.com/satijalab/seurat" + doi: "10.1038/s41592-019-0433-8" + licence: ["MIT"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - seurat_object: + type: file + description: | + A `.rds` file containing the seurat object. Assumes that the hash tag oligo (HTO) data has been added and normalized. + - assay: + type: string + description: | + Name of the Hashtag assay, usually called "HTO" by default. Use the custom name if the assay has been named differently. + +output: + + - params: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_params_multiseqdemux.csv": + type: file + description: The used parameters to call MULTIseqDemux in the R-Script. + pattern: "_params_multiseqdemux.csv" + + - results: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_res_multiseqdemux.csv": + type: file + description: Resuls of MULTIseqDemux. + pattern: "_res_multiseqdemux.csv" + + - rds: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_multiseqdemux.rds": + type: file + description: SeuratObject saved as RDS. + pattern: "_multiseqdemux.rds" + + - versions: + - "versions.yml": + type: file + description: File containing software versions + pattern: "versions.yml" + +authors: + - "@LuisHeinzlmeier" +maintainers: + - "@LuisHeinzlmeier" diff --git a/modules/nf-core/multiseqdemux/templates/MultiSeqDemux.R b/modules/nf-core/multiseqdemux/templates/MultiSeqDemux.R new file mode 100755 index 00000000000..8cd14dc9c99 --- /dev/null +++ b/modules/nf-core/multiseqdemux/templates/MultiSeqDemux.R @@ -0,0 +1,86 @@ +#!/usr/bin/env Rscript + +################################################ +################################################ +## USE PARAMETERS FROM NEXTFLOW ## +################################################ +################################################ + +# cast parameters from nextflow +seuratObj = '$seurat_object' +options(digits=5) +quantile = as.double('$quantile') +autoThresh = as.logical('$autoThresh') +maxiter = as.integer('$maxiter') +qrangeFrom = as.double('$qrangeFrom') +qrangeTo = as.double('$qrangeTo') +qrangeBy = as.double('$qrangeBy') +verbose = as.logical('$verbose') +assay ='$assay' +prefix = '$prefix' + +if (! file.exists(seuratObj)){ + stop(paste0(seuratObj, ' is not a valid file')) +} + +################################################ +################################################ +## Finish loading libraries ## +################################################ +################################################ + +library(Seurat) + +################################################ +################################################ +## Main Process ## +################################################ +################################################ + +# Loading Seurat object +hashtag <- readRDS(seuratObj) + +# Demultiplex cells +if (autoThresh == TRUE) { + hashtag <- MULTIseqDemux(hashtag, assay = assay, quantile = quantile, autoThresh = TRUE, maxiter = maxiter, qrange = seq(from = qrangeFrom, to = qrangeTo, by = qrangeBy), verbose = verbose) +} else { + hashtag <- MULTIseqDemux(hashtag, assay = assay, quantile = quantile, verbose = verbose) +} + +################################################ +################################################ +## SAVING RESULTS ## +################################################ +################################################ + +# create a data frame to save the used parameters in a csv file +Argument <- c("seuratObjectPath", "quantile", "autoThresh", "maxiter", "qrangeFrom", "qrangeTo", "qrangeBy", "verbose", "assay") +Value <- c(seuratObj, quantile, autoThresh, maxiter, qrangeFrom, qrangeTo, qrangeBy, verbose, assay) +params <- data.frame(Argument, Value) +write.csv(params, paste0(prefix ,"_params_multiseqdemux.csv")) + +# save the results from MULTIseqDemux() +write.csv(hashtag\$MULTI_ID, paste0(prefix , "_res_multiseqdemux.csv")) +saveRDS(hashtag, file = paste0(prefix ,"_multiseqdemux.rds")) + +################################################ +################################################ +## VERSIONS FILE ## +################################################ +################################################ + +r.version <- paste(R.version[['major']],R.version[['minor']], sep = ".") +seurat.version <- as.character(packageVersion('Seurat')) + +writeLines( + c( + '"${task.process}":', + paste(' r-base:', r.version), + paste(' r-seurat:', seurat.version) + ), +'versions.yml') + +################################################ +################################################ +################################################ +################################################ diff --git a/modules/nf-core/multiseqdemux/tests/main.nf.test b/modules/nf-core/multiseqdemux/tests/main.nf.test new file mode 100644 index 00000000000..4ab4ea538a6 --- /dev/null +++ b/modules/nf-core/multiseqdemux/tests/main.nf.test @@ -0,0 +1,59 @@ +nextflow_process { + + name "Test Process MULTISEQDEMUX" + script "../main.nf" + process "MULTISEQDEMUX" + + tag "modules" + tag "modules_nfcore" + tag "multiseqdemux" + + test("seuratObject - rds") { + + when { + process { + """ + input[0] = [ + [ id:'test'], // meta map + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/htodemux.rds', checkIfExists: true), + "HTO" + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + + test("seuratObject - rds - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'test'], // meta map + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/htodemux.rds', checkIfExists: true), + "HTO" + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/nf-core/multiseqdemux/tests/main.nf.test.snap b/modules/nf-core/multiseqdemux/tests/main.nf.test.snap new file mode 100644 index 00000000000..29e56e27b1e --- /dev/null +++ b/modules/nf-core/multiseqdemux/tests/main.nf.test.snap @@ -0,0 +1,132 @@ +{ + "seuratObject - rds": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_params_multiseqdemux.csv:md5,03b37983ec6c4e9eabb789a81e936a4e" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test_res_multiseqdemux.csv:md5,9d4475b8c2778e7e361747dd521072e4" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "test_multiseqdemux.rds:md5,f9b34865c0845ffaa1e0b0447adda202" + ] + ], + "3": [ + "versions.yml:md5,010faafd9d09f3a732eca24e43b9abf7" + ], + "params": [ + [ + { + "id": "test" + }, + "test_params_multiseqdemux.csv:md5,03b37983ec6c4e9eabb789a81e936a4e" + ] + ], + "rds": [ + [ + { + "id": "test" + }, + "test_multiseqdemux.rds:md5,f9b34865c0845ffaa1e0b0447adda202" + ] + ], + "results": [ + [ + { + "id": "test" + }, + "test_res_multiseqdemux.csv:md5,9d4475b8c2778e7e361747dd521072e4" + ] + ], + "versions": [ + "versions.yml:md5,010faafd9d09f3a732eca24e43b9abf7" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.3" + }, + "timestamp": "2025-06-15T16:33:22.37618" + }, + "seuratObject - rds - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_params_multiseqdemux.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test_res_multiseqdemux.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "test_multiseqdemux.rds:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + "versions.yml:md5,f9d8b4d613b9361323afb1ba5f0c5528" + ], + "params": [ + [ + { + "id": "test" + }, + "test_params_multiseqdemux.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "rds": [ + [ + { + "id": "test" + }, + "test_multiseqdemux.rds:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "results": [ + [ + { + "id": "test" + }, + "test_res_multiseqdemux.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,f9d8b4d613b9361323afb1ba5f0c5528" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.3" + }, + "timestamp": "2025-06-16T12:07:37.705758" + } +} \ No newline at end of file