diff --git a/modules/nf-core/immunedeconv/environment.yml b/modules/nf-core/immunedeconv/environment.yml new file mode 100644 index 00000000000..8d81153aae6 --- /dev/null +++ b/modules/nf-core/immunedeconv/environment.yml @@ -0,0 +1,5 @@ +channels: + - conda-forge + - bioconda +dependencies: + - bioconda::immunedeconv=2.1.2 diff --git a/modules/nf-core/immunedeconv/main.nf b/modules/nf-core/immunedeconv/main.nf new file mode 100644 index 00000000000..db0aa7b56a9 --- /dev/null +++ b/modules/nf-core/immunedeconv/main.nf @@ -0,0 +1,37 @@ +process IMMUNEDECONV { + 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/22/22cb85f1b69ceff45b83e0fdb7b96d9ae29c8aafeaa0707d64cc4628982977ab/data' : + 'community.wave.seqera.io/library/r-immunedeconv:2.1.2--e1bb1ea1cf505cb3' }" + + input: + tuple val(meta), path(input_file), val(method), val(function) + val gene_symbol_col + + output: + tuple val(meta), path("*.deconvolution_results.tsv"), emit: deconv_table + tuple val(meta), path("*.png"), emit: deconv_plots, optional: true + path "versions.yml", emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + template 'immunedeconv.R' + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}.deconvolution_results.tsv + touch ${prefix}.plot1_stacked_bar_chart.png + touch ${prefix}.plot2_points_with_facets.png + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + r-immunedeconv: \$(Rscript -e "cat(as.character(packageVersion('immunedeconv')))") + END_VERSIONS + """ +} diff --git a/modules/nf-core/immunedeconv/meta.yml b/modules/nf-core/immunedeconv/meta.yml new file mode 100644 index 00000000000..dd005a7d52f --- /dev/null +++ b/modules/nf-core/immunedeconv/meta.yml @@ -0,0 +1,49 @@ +name: untar #TODO update for module +description: Extract files. +keywords: + - untar + - uncompress + - extract +tools: + - untar: + description: | + Extract tar.gz files. + documentation: https://www.gnu.org/software/tar/manual/ + licence: ["GPL-3.0-or-later"] + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - archive: + type: file + description: File to be untar + pattern: "*.{tar}.{gz}" +output: + - untar: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - $prefix: + type: directory + description: Directory containing contents of archive + pattern: "*/" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@joseespinosa" + - "@drpatelh" + - "@matthdsm" + - "@jfy133" +maintainers: + - "@joseespinosa" + - "@drpatelh" + - "@matthdsm" + - "@jfy133" diff --git a/modules/nf-core/immunedeconv/templates/immunedeconv.R b/modules/nf-core/immunedeconv/templates/immunedeconv.R new file mode 100644 index 00000000000..6f0c7bad1a3 --- /dev/null +++ b/modules/nf-core/immunedeconv/templates/immunedeconv.R @@ -0,0 +1,89 @@ +#!/usr/bin/env Rscript + +library(dplyr) +library(ggplot2) +library(tidyr) +library(immunedeconv) +library(tibble) +library(readr) + +#Load prefix +prefix = ifelse('$task.ext.prefix' == 'null', '$meta.id', '$task.ext.prefix') + +# Load the TSV file and keep only $gene_symbol_col column + counts +gene_expression_matrix <- readr::read_tsv('$input_file') %>% + as.data.frame() %>% + dplyr::select( + dplyr::all_of('$gene_symbol_col'), # Keep the '$gene_symbol_col' column + where(~ !is.character(.)) # Include all non-string columns + ) %>% + tibble::column_to_rownames('$gene_symbol_col') + +# Check if the data is log-transformed or TPM-transformed +# Check range of values +min_value <- min(gene_expression_matrix, na.rm = TRUE) +max_value <- max(gene_expression_matrix, na.rm = TRUE) + +# Detect log-transformed data (values typically within a compressed range) +if (max_value < 100 && min_value >= 0) { + warning("The data appears to be log-transformed. Please provide TPM-transformed data.") +} + +# Detect TPM-transformed data (column sums should be close to 1,000,000) +column_sums <- colSums(gene_expression_matrix, na.rm = TRUE) +if (!all(abs(column_sums - 1e6) < 1e4)) { + warning("The data does not appear to be properly TPM-transformed. Ensure the data is normalized.") +} + +# Generate results +result <- immunedeconv::${function}(gene_expression_matrix, method = '$method') + +# Save the result to a CSV file +readr::write_tsv(result, paste0(prefix,'.deconvolution_results.tsv')) + +# Plot and save results +# Plot 1: Stacked bar chart +plot1 <- result %>% + gather(sample, fraction, -cell_type) %>% + ggplot(aes(x = sample, y = fraction, fill = cell_type)) + + geom_bar(stat = 'identity') + + coord_flip() + + scale_fill_brewer(palette = 'Paired') + + scale_x_discrete(limits = rev(levels(result))) + +# Save Plot 1 +ggsave(paste0(prefix,'.plot1_stacked_bar_chart.png'), plot = plot1, dpi = 300, width = 10, height = 8) + +# Plot 2: Points with facets +plot2 <- result %>% + gather(sample, score, -cell_type) %>% + ggplot(aes(x = sample, y = score, color = cell_type)) + + geom_point(size = 4) + + facet_wrap(~cell_type, scales = 'free_x', ncol = 3) + + scale_color_brewer(palette = 'Paired', guide = FALSE) + + coord_flip() + + theme_bw() + + theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)) + +# Save Plot 2 +ggsave(paste0(prefix,'.plot2_points_with_facets.png'), plot = plot2, dpi = 300, width = 12, height = 10) + +################################################ +################################################ +## VERSIONS FILE ## +################################################ +################################################ + +immunedeconv_version <- as.character(packageVersion('immunedeconv')) + +writeLines( + c( + '"${task.process}":', + paste(' r-immunedeconv:', immunedeconv_version) + ), +'versions.yml') + +################################################ +################################################ +################################################ +################################################ \ No newline at end of file diff --git a/modules/nf-core/immunedeconv/tests/main.nf.test b/modules/nf-core/immunedeconv/tests/main.nf.test new file mode 100644 index 00000000000..8d131c51219 --- /dev/null +++ b/modules/nf-core/immunedeconv/tests/main.nf.test @@ -0,0 +1,87 @@ +nextflow_process { + + name "Test Process IMMUNEDECONV" + script "../main.nf" + process "IMMUNEDECONV" + tag "modules" + tag "modules_nfcore" + tag "immunedeconv" + + test("test_immunedeconv_bulkmat") { + + when { + process { + """ + input[0] = [ [id:"immunedeconv"], file('https://raw.githubusercontent.com/omnideconv/immunedeconv/refs/heads/master/tests/testthat/bulk_mat.tsv', checkIfExists: true), "quantiseq", "deconvolute" ] + input[1] = "gene_symbol" + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() }, + ) + } + } + + test("test_immunedeconv_custom_data_mouse") { + + when { + process { + """ + input[0] = [ [id:"immunedeconv"], file('https://github.com/omnideconv/immunedeconv/raw/refs/heads/master/tests/testthat/bulk_mat_mouse.tsv', checkIfExists: true), "mmcp_counter", "deconvolute_mouse" ] + input[1] = "gene_symbol" + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() }, + ) + } + } + + test("test_immunedeconv_bulkmat_mouse") { + + when { + process { + """ + input[0] = [ [id:"immunedeconv"], file('https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/genomics/mus_musculus/rnaseq_expression/SRP254919.salmon.merged.gene_counts.top1000cov.tsv', checkIfExists: true), "mmcp_counter", "deconvolute_mouse" ] + input[1] = "gene_name" + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() }, + ) + } + } + + test("test_immunedeconv - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ [id:"immunedeconv"], file('https://raw.githubusercontent.com/omnideconv/immunedeconv/refs/heads/master/tests/testthat/bulk_mat.tsv', checkIfExists: true), "quantiseq", "deconvolute" ] + input[1] = "gene_symbol" + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() }, + ) + } + } +} diff --git a/modules/nf-core/immunedeconv/tests/main.nf.test.snap b/modules/nf-core/immunedeconv/tests/main.nf.test.snap new file mode 100644 index 00000000000..95a200362a0 --- /dev/null +++ b/modules/nf-core/immunedeconv/tests/main.nf.test.snap @@ -0,0 +1,222 @@ +{ + "test_immunedeconv_bulkmat": { + "content": [ + { + "0": [ + [ + { + "id": "immunedeconv" + }, + "immunedeconv.deconvolution_results.tsv:md5,7ea22a01f198957cbe8dae92015583ff" + ] + ], + "1": [ + [ + { + "id": "immunedeconv" + }, + [ + "immunedeconv.plot1_stacked_bar_chart.png:md5,68884a86fc1daeaa8128f913c4d0505f", + "immunedeconv.plot2_points_with_facets.png:md5,9ca7caf63afefe6c38fe13f6a7a834f7" + ] + ] + ], + "2": [ + "versions.yml:md5,e155e29c7a7b3ba2ef8e3ed4c495b929" + ], + "deconv_plots": [ + [ + { + "id": "immunedeconv" + }, + [ + "immunedeconv.plot1_stacked_bar_chart.png:md5,68884a86fc1daeaa8128f913c4d0505f", + "immunedeconv.plot2_points_with_facets.png:md5,9ca7caf63afefe6c38fe13f6a7a834f7" + ] + ] + ], + "deconv_table": [ + [ + { + "id": "immunedeconv" + }, + "immunedeconv.deconvolution_results.tsv:md5,7ea22a01f198957cbe8dae92015583ff" + ] + ], + "versions": [ + "versions.yml:md5,e155e29c7a7b3ba2ef8e3ed4c495b929" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.3" + }, + "timestamp": "2024-12-20T17:22:20.866159151" + }, + "test_immunedeconv_bulkmat_mouse": { + "content": [ + { + "0": [ + [ + { + "id": "immunedeconv" + }, + "immunedeconv.deconvolution_results.tsv:md5,3d7ed4550a0da2f63fcfd2e74605e7a3" + ] + ], + "1": [ + [ + { + "id": "immunedeconv" + }, + [ + "immunedeconv.plot1_stacked_bar_chart.png:md5,a4bed902e7df56a836b78d68e06d1359", + "immunedeconv.plot2_points_with_facets.png:md5,a6a510a099f7a7e27893a9b5ef45c8b1" + ] + ] + ], + "2": [ + "versions.yml:md5,e155e29c7a7b3ba2ef8e3ed4c495b929" + ], + "deconv_plots": [ + [ + { + "id": "immunedeconv" + }, + [ + "immunedeconv.plot1_stacked_bar_chart.png:md5,a4bed902e7df56a836b78d68e06d1359", + "immunedeconv.plot2_points_with_facets.png:md5,a6a510a099f7a7e27893a9b5ef45c8b1" + ] + ] + ], + "deconv_table": [ + [ + { + "id": "immunedeconv" + }, + "immunedeconv.deconvolution_results.tsv:md5,3d7ed4550a0da2f63fcfd2e74605e7a3" + ] + ], + "versions": [ + "versions.yml:md5,e155e29c7a7b3ba2ef8e3ed4c495b929" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.3" + }, + "timestamp": "2024-12-24T15:37:32.223013092" + }, + "test_immunedeconv - stub": { + "content": [ + { + "0": [ + [ + { + "id": "immunedeconv" + }, + "immunedeconv.deconvolution_results.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "immunedeconv" + }, + [ + "immunedeconv.plot1_stacked_bar_chart.png:md5,d41d8cd98f00b204e9800998ecf8427e", + "immunedeconv.plot2_points_with_facets.png:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "2": [ + "versions.yml:md5,e155e29c7a7b3ba2ef8e3ed4c495b929" + ], + "deconv_plots": [ + [ + { + "id": "immunedeconv" + }, + [ + "immunedeconv.plot1_stacked_bar_chart.png:md5,d41d8cd98f00b204e9800998ecf8427e", + "immunedeconv.plot2_points_with_facets.png:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "deconv_table": [ + [ + { + "id": "immunedeconv" + }, + "immunedeconv.deconvolution_results.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,e155e29c7a7b3ba2ef8e3ed4c495b929" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.3" + }, + "timestamp": "2024-12-20T17:22:46.218182472" + }, + "test_immunedeconv_custom_data_mouse": { + "content": [ + { + "0": [ + [ + { + "id": "immunedeconv" + }, + "immunedeconv.deconvolution_results.tsv:md5,5c69779bac914a28737eb40460807bec" + ] + ], + "1": [ + [ + { + "id": "immunedeconv" + }, + [ + "immunedeconv.plot1_stacked_bar_chart.png:md5,1907610f4d0712cc2e75eb64066719de", + "immunedeconv.plot2_points_with_facets.png:md5,565088d5475a9be0dc0dadfc0f2e63d2" + ] + ] + ], + "2": [ + "versions.yml:md5,e155e29c7a7b3ba2ef8e3ed4c495b929" + ], + "deconv_plots": [ + [ + { + "id": "immunedeconv" + }, + [ + "immunedeconv.plot1_stacked_bar_chart.png:md5,1907610f4d0712cc2e75eb64066719de", + "immunedeconv.plot2_points_with_facets.png:md5,565088d5475a9be0dc0dadfc0f2e63d2" + ] + ] + ], + "deconv_table": [ + [ + { + "id": "immunedeconv" + }, + "immunedeconv.deconvolution_results.tsv:md5,5c69779bac914a28737eb40460807bec" + ] + ], + "versions": [ + "versions.yml:md5,e155e29c7a7b3ba2ef8e3ed4c495b929" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.3" + }, + "timestamp": "2024-12-24T15:35:38.017862149" + } +} \ No newline at end of file