From 1f1e708a507870fa3dd891c8f68d37b182874ff4 Mon Sep 17 00:00:00 2001 From: miguelrosell Date: Thu, 12 Feb 2026 13:51:03 +0100 Subject: [PATCH 01/10] New module: cosimflow for cosine similarity --- modules/nf-core/cosimflow/environment.yml | 13 +++ modules/nf-core/cosimflow/main.nf | 114 +++++++++++++++++++ modules/nf-core/cosimflow/meta.yml | 56 +++++++++ modules/nf-core/cosimflow/tests/main.nf.test | 42 +++++++ 4 files changed, 225 insertions(+) create mode 100644 modules/nf-core/cosimflow/environment.yml create mode 100644 modules/nf-core/cosimflow/main.nf create mode 100644 modules/nf-core/cosimflow/meta.yml create mode 100644 modules/nf-core/cosimflow/tests/main.nf.test diff --git a/modules/nf-core/cosimflow/environment.yml b/modules/nf-core/cosimflow/environment.yml new file mode 100644 index 000000000000..0581255153cc --- /dev/null +++ b/modules/nf-core/cosimflow/environment.yml @@ -0,0 +1,13 @@ +name: cosimflow +channels: + - conda-forge + - bioconda + - defaults +dependencies: + - r-base + - r-lsa + - r-pheatmap + - r-optparse + - r-readr + - r-readxl + - r-dplyr diff --git a/modules/nf-core/cosimflow/main.nf b/modules/nf-core/cosimflow/main.nf new file mode 100644 index 000000000000..ba2a6e3a4b53 --- /dev/null +++ b/modules/nf-core/cosimflow/main.nf @@ -0,0 +1,114 @@ +process COSIMFLOW { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/mulled-v2-ad9dd5f398966bf899ae05f8e7c54d0d94018089:00bd7e543b7135f299839496732948679f2e3073-0' : + 'biocontainers/mulled-v2-ad9dd5f398966bf899ae05f8e7c54d0d94018089:00bd7e543b7135f299839496732948679f2e3073-0' }" + + input: + tuple val(meta), path(expression_matrix) + + output: + tuple val(meta), path("*_matrix.csv") , emit: matrix + tuple val(meta), path("*_heatmap.png"), emit: heatmap + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + #!/usr/bin/env Rscript + + suppressPackageStartupMessages({ + library(optparse) + library(readr) + library(readxl) + library(dplyr) + library(lsa) + library(pheatmap) + }) + + option_list = list( + make_option(c("-i","--input"), type="character", help="Input file"), + make_option(c("-o","--out_prefix"), type="character", default="cosine"), + make_option(c("-m","--method"), type="character", default="cosine"), + make_option(c("-g","--min_gene_mean"), type="double", default=0.0), + make_option(c("-s","--sample_cols"), type="character", default="auto") + ) + + # AQUÍ PASAMOS LOS ARGUMENTOS DE NEXTFLOW DIRECTAMENTE + args_list <- c("--input", "$expression_matrix", "--out_prefix", "$prefix") + + # Añadimos los argumentos extra si existen + extra_args <- "$args" + if (nchar(extra_args) > 0) { + args_list <- c(args_list, unlist(strsplit(extra_args, "\\\\s+"))) + } + + opt <- parse_args(OptionParser(option_list=option_list), args=args_list) + + # ---- Load table ---- + if (grepl("\\\\.xlsx?\$", opt\$input, ignore.case=TRUE)) { + df <- as.data.frame(read_excel(opt\$input)) + } else { + df <- as.data.frame(read_csv(opt\$input, col_types = cols(.default = col_guess()))) + } + + # ---- Set rownames ---- + first_col <- names(df)[1] + if (tolower(first_col) %in% c("gene","id", "gene_id")) { + if (is.character(df[[first_col]]) || is.factor(df[[first_col]])) { + rownames(df) <- df[[first_col]] + df[[first_col]] <- NULL + } + } + + # ---- Select sample columns ---- + if (opt\$sample_cols != "auto") { + cols <- trimws(unlist(strsplit(opt\$sample_cols,","))) + mat <- as.matrix(df[, cols, drop=FALSE]) + } else { + num_cols <- sapply(df, is.numeric) + mat <- as.matrix(df[, num_cols, drop=FALSE]) + } + + # ---- Filter ---- + if (opt\$min_gene_mean > 0) { + keep <- rowMeans(mat, na.rm=TRUE) >= opt\$min_gene_mean + mat <- mat[keep, , drop=FALSE] + } + + # ---- Compute ---- + method <- tolower(opt\$method) + if (method == "cosine") { + sim <- lsa::cosine(mat) + } else { + sim <- cor(mat, method = method, use = "pairwise.complete.obs") + } + + colnames(sim) <- colnames(mat) + rownames(sim) <- colnames(mat) + + # ---- Save ---- + write.csv(sim, paste0(opt\$out_prefix, "_matrix.csv"), quote=FALSE, row.names=TRUE) + + png(paste0(opt\$out_prefix, "_heatmap.png"), width=900, height=700) + pheatmap(sim, display_numbers=TRUE, cluster_rows=FALSE, cluster_cols=FALSE) + dev.off() + + # ---- Versions ---- + r_version <- paste0(R.version\$major, ".", R.version\$minor) + writeLines(c( + "\\"${task.process}\\":", + paste0(" r-base: ", r_version), + paste0(" r-lsa: ", packageVersion("lsa")), + paste0(" r-pheatmap: ", packageVersion("pheatmap")) + ), "versions.yml") + """ +} diff --git a/modules/nf-core/cosimflow/meta.yml b/modules/nf-core/cosimflow/meta.yml new file mode 100644 index 000000000000..884d5b2419aa --- /dev/null +++ b/modules/nf-core/cosimflow/meta.yml @@ -0,0 +1,56 @@ +name: "cosimflow" +description: Calculates the cosine similarity matrix between samples based on a gene expression matrix. +keywords: + - similarity + - cosine + - clustering + - rnaseq + - heatmap +tools: + - "r": + description: "R is a free software environment for statistical computing and graphics." + homepage: "https://www.r-project.org/" + documentation: "https://cran.r-project.org/manuals.html" + tool_dev_url: "https://github.com/miguelrosell/nf-cosimflow" + licence: ["MIT"] + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information, e.g. [ id:'test' ]. + - expression_matrix: + type: file + description: | + CSV or XLSX file containing the expression matrix. + Rows should be features (genes) and columns should be samples. + First column must be gene IDs. + pattern: "*.{csv,xlsx}" + +output: + - matrix: + - meta: + type: map + description: | + Groovy Map containing sample information, e.g. [ id:'test' ]. + - "*_matrix.csv": + type: file + description: A square matrix (CSV) containing pairwise cosine similarity scores (0-1). + pattern: "*_matrix.csv" + - heatmap: + - meta: + type: map + description: | + Groovy Map containing sample information, e.g. [ id:'test' ]. + - "*_heatmap.png": + type: file + description: A PNG image visualizing the cosine similarity matrix as a heatmap. + pattern: "*_heatmap.png" + - versions: + - versions.yml: + type: file + description: File containing software versions. + pattern: "versions.yml" + +authors: + - "@miguelrosell" diff --git a/modules/nf-core/cosimflow/tests/main.nf.test b/modules/nf-core/cosimflow/tests/main.nf.test new file mode 100644 index 000000000000..ae0e68088992 --- /dev/null +++ b/modules/nf-core/cosimflow/tests/main.nf.test @@ -0,0 +1,42 @@ +nextflow_process { + + name "Test Process COSIMFLOW" + script "../main.nf" + process "COSIMFLOW" + + tag "modules" + tag "modules_nfcore" + tag "cosimflow" + + test("Should run successfully with a simple CSV") { + + when { + process { + """ + // 1. Creamos un CSV dummy al vuelo para el test + def test_csv = file("test_data.csv") + test_csv.text = '''gene_id,sample_A,sample_B,sample_C + GENE_1,10.0,10.5,0.0 + GENE_2,50.0,50.5,2.0 + GENE_3,0.0,0.0,100.0 + GENE_4,100.0,99.5,10.0''' + + // 2. Definimos el input: [ meta, archivo ] + input[0] = [ [id:'test_sample'], test_csv ] + """ + } + } + + then { + // 3. Verificamos que termine con éxito + assert process.success + + // 4. Verificamos que se generen los archivos + assert process.out.matrix + assert process.out.heatmap + assert process.out.versions + } + + } + +} From 7c119563d434962c781664f4edca094700e8b8a3 Mon Sep 17 00:00:00 2001 From: miguelrosell Date: Fri, 13 Feb 2026 01:22:40 +0100 Subject: [PATCH 02/10] Refactor module: Rename to lsa/cosine, update deps, add stub and snapshots --- modules/nf-core/cosimflow/environment.yml | 13 -- modules/nf-core/cosimflow/main.nf | 114 ------------------ modules/nf-core/lsa/cosine/environment.yml | 13 ++ modules/nf-core/lsa/cosine/main.nf | 41 +++++++ .../{cosimflow => lsa/cosine}/meta.yml | 30 +++-- modules/nf-core/lsa/cosine/templates/cosine.R | 83 +++++++++++++ .../cosine}/tests/main.nf.test | 0 tests/modules/nf-core/lsa/cosine/main.nf.test | 63 ++++++++++ 8 files changed, 218 insertions(+), 139 deletions(-) delete mode 100644 modules/nf-core/cosimflow/environment.yml delete mode 100644 modules/nf-core/cosimflow/main.nf create mode 100644 modules/nf-core/lsa/cosine/environment.yml create mode 100644 modules/nf-core/lsa/cosine/main.nf rename modules/nf-core/{cosimflow => lsa/cosine}/meta.yml (59%) create mode 100644 modules/nf-core/lsa/cosine/templates/cosine.R rename modules/nf-core/{cosimflow => lsa/cosine}/tests/main.nf.test (100%) create mode 100644 tests/modules/nf-core/lsa/cosine/main.nf.test diff --git a/modules/nf-core/cosimflow/environment.yml b/modules/nf-core/cosimflow/environment.yml deleted file mode 100644 index 0581255153cc..000000000000 --- a/modules/nf-core/cosimflow/environment.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: cosimflow -channels: - - conda-forge - - bioconda - - defaults -dependencies: - - r-base - - r-lsa - - r-pheatmap - - r-optparse - - r-readr - - r-readxl - - r-dplyr diff --git a/modules/nf-core/cosimflow/main.nf b/modules/nf-core/cosimflow/main.nf deleted file mode 100644 index ba2a6e3a4b53..000000000000 --- a/modules/nf-core/cosimflow/main.nf +++ /dev/null @@ -1,114 +0,0 @@ -process COSIMFLOW { - tag "$meta.id" - label 'process_medium' - - conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/mulled-v2-ad9dd5f398966bf899ae05f8e7c54d0d94018089:00bd7e543b7135f299839496732948679f2e3073-0' : - 'biocontainers/mulled-v2-ad9dd5f398966bf899ae05f8e7c54d0d94018089:00bd7e543b7135f299839496732948679f2e3073-0' }" - - input: - tuple val(meta), path(expression_matrix) - - output: - tuple val(meta), path("*_matrix.csv") , emit: matrix - tuple val(meta), path("*_heatmap.png"), emit: heatmap - path "versions.yml" , emit: versions - - when: - task.ext.when == null || task.ext.when - - script: - def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" - - """ - #!/usr/bin/env Rscript - - suppressPackageStartupMessages({ - library(optparse) - library(readr) - library(readxl) - library(dplyr) - library(lsa) - library(pheatmap) - }) - - option_list = list( - make_option(c("-i","--input"), type="character", help="Input file"), - make_option(c("-o","--out_prefix"), type="character", default="cosine"), - make_option(c("-m","--method"), type="character", default="cosine"), - make_option(c("-g","--min_gene_mean"), type="double", default=0.0), - make_option(c("-s","--sample_cols"), type="character", default="auto") - ) - - # AQUÍ PASAMOS LOS ARGUMENTOS DE NEXTFLOW DIRECTAMENTE - args_list <- c("--input", "$expression_matrix", "--out_prefix", "$prefix") - - # Añadimos los argumentos extra si existen - extra_args <- "$args" - if (nchar(extra_args) > 0) { - args_list <- c(args_list, unlist(strsplit(extra_args, "\\\\s+"))) - } - - opt <- parse_args(OptionParser(option_list=option_list), args=args_list) - - # ---- Load table ---- - if (grepl("\\\\.xlsx?\$", opt\$input, ignore.case=TRUE)) { - df <- as.data.frame(read_excel(opt\$input)) - } else { - df <- as.data.frame(read_csv(opt\$input, col_types = cols(.default = col_guess()))) - } - - # ---- Set rownames ---- - first_col <- names(df)[1] - if (tolower(first_col) %in% c("gene","id", "gene_id")) { - if (is.character(df[[first_col]]) || is.factor(df[[first_col]])) { - rownames(df) <- df[[first_col]] - df[[first_col]] <- NULL - } - } - - # ---- Select sample columns ---- - if (opt\$sample_cols != "auto") { - cols <- trimws(unlist(strsplit(opt\$sample_cols,","))) - mat <- as.matrix(df[, cols, drop=FALSE]) - } else { - num_cols <- sapply(df, is.numeric) - mat <- as.matrix(df[, num_cols, drop=FALSE]) - } - - # ---- Filter ---- - if (opt\$min_gene_mean > 0) { - keep <- rowMeans(mat, na.rm=TRUE) >= opt\$min_gene_mean - mat <- mat[keep, , drop=FALSE] - } - - # ---- Compute ---- - method <- tolower(opt\$method) - if (method == "cosine") { - sim <- lsa::cosine(mat) - } else { - sim <- cor(mat, method = method, use = "pairwise.complete.obs") - } - - colnames(sim) <- colnames(mat) - rownames(sim) <- colnames(mat) - - # ---- Save ---- - write.csv(sim, paste0(opt\$out_prefix, "_matrix.csv"), quote=FALSE, row.names=TRUE) - - png(paste0(opt\$out_prefix, "_heatmap.png"), width=900, height=700) - pheatmap(sim, display_numbers=TRUE, cluster_rows=FALSE, cluster_cols=FALSE) - dev.off() - - # ---- Versions ---- - r_version <- paste0(R.version\$major, ".", R.version\$minor) - writeLines(c( - "\\"${task.process}\\":", - paste0(" r-base: ", r_version), - paste0(" r-lsa: ", packageVersion("lsa")), - paste0(" r-pheatmap: ", packageVersion("pheatmap")) - ), "versions.yml") - """ -} diff --git a/modules/nf-core/lsa/cosine/environment.yml b/modules/nf-core/lsa/cosine/environment.yml new file mode 100644 index 000000000000..3b26d2dcaa0a --- /dev/null +++ b/modules/nf-core/lsa/cosine/environment.yml @@ -0,0 +1,13 @@ +--- +name: lsa_cosine +channels: + - conda-forge + - bioconda +dependencies: + - conda-forge::r-base=4.5.2 + - conda-forge::r-lsa=0.73.4 + - conda-forge::r-pheatmap=1.0.13 + - conda-forge::r-optparse=1.7.5 + - conda-forge::r-readr=2.1.6 + - conda-forge::r-readxl=1.4.5 + - conda-forge::r-dplyr=1.2.0 diff --git a/modules/nf-core/lsa/cosine/main.nf b/modules/nf-core/lsa/cosine/main.nf new file mode 100644 index 000000000000..8d3d4b4d37c9 --- /dev/null +++ b/modules/nf-core/lsa/cosine/main.nf @@ -0,0 +1,41 @@ +process LSA_COSINE { + tag "$meta.id" + label 'process_medium' + + // Contenedores nuevos generados por el revisor + 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/4d/4d94f159b95315adf8bf54fdc9db88db10a5aef72dca6245dd163b91e9e0437e/data' : + 'community.wave.seqera.io/library/r-base_r-lsa_r-pheatmap_r-optparse_pruned:901156bc11e60b28' }" + + input: + tuple val(meta), path(expression_matrix) + + output: + tuple val(meta), path("*_matrix.csv") , emit: matrix + tuple val(meta), path("*_heatmap.png"), emit: heatmap + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + template 'cosine.R' + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}_matrix.csv + touch ${prefix}_heatmap.png + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + r-base: \$(R --version | sed -n 1p | sed 's/R version //g' | sed 's/ (.*//g') + r-lsa: 0.73.3 + r-pheatmap: 1.0.12 + END_VERSIONS + """ +} diff --git a/modules/nf-core/cosimflow/meta.yml b/modules/nf-core/lsa/cosine/meta.yml similarity index 59% rename from modules/nf-core/cosimflow/meta.yml rename to modules/nf-core/lsa/cosine/meta.yml index 884d5b2419aa..03954a9a1cfc 100644 --- a/modules/nf-core/cosimflow/meta.yml +++ b/modules/nf-core/lsa/cosine/meta.yml @@ -1,4 +1,5 @@ -name: "cosimflow" +# name matches directory structure +name: "lsa_cosine" description: Calculates the cosine similarity matrix between samples based on a gene expression matrix. keywords: - similarity @@ -7,12 +8,16 @@ keywords: - rnaseq - heatmap tools: - - "r": - description: "R is a free software environment for statistical computing and graphics." - homepage: "https://www.r-project.org/" - documentation: "https://cran.r-project.org/manuals.html" - tool_dev_url: "https://github.com/miguelrosell/nf-cosimflow" - licence: ["MIT"] + - "lsa": + description: "Latent Semantic Analysis (LSA) package for R." + homepage: "https://cran.r-project.org/web/packages/lsa/index.html" + documentation: "https://cran.r-project.org/web/packages/lsa/lsa.pdf" + licence: ["GPL-2"] + - "pheatmap": + description: "Pretty Heatmaps package for R." + homepage: "https://cran.r-project.org/web/packages/pheatmap/index.html" + documentation: "https://cran.r-project.org/web/packages/pheatmap/pheatmap.pdf" + licence: ["GPL-2"] input: - - meta: @@ -22,10 +27,9 @@ input: - expression_matrix: type: file description: | - CSV or XLSX file containing the expression matrix. + CSV file containing the expression matrix. Rows should be features (genes) and columns should be samples. - First column must be gene IDs. - pattern: "*.{csv,xlsx}" + pattern: "*.csv" output: - matrix: @@ -35,7 +39,7 @@ output: Groovy Map containing sample information, e.g. [ id:'test' ]. - "*_matrix.csv": type: file - description: A square matrix (CSV) containing pairwise cosine similarity scores (0-1). + description: A square matrix (CSV) containing pairwise similarity scores. pattern: "*_matrix.csv" - heatmap: - meta: @@ -44,7 +48,7 @@ output: Groovy Map containing sample information, e.g. [ id:'test' ]. - "*_heatmap.png": type: file - description: A PNG image visualizing the cosine similarity matrix as a heatmap. + description: A PNG image visualizing the similarity matrix as a heatmap. pattern: "*_heatmap.png" - versions: - versions.yml: @@ -54,3 +58,5 @@ output: authors: - "@miguelrosell" +maintainers: + - "@miguelrosell" diff --git a/modules/nf-core/lsa/cosine/templates/cosine.R b/modules/nf-core/lsa/cosine/templates/cosine.R new file mode 100644 index 000000000000..3f3f46fcb421 --- /dev/null +++ b/modules/nf-core/lsa/cosine/templates/cosine.R @@ -0,0 +1,83 @@ +#!/usr/bin/env Rscript + +suppressPackageStartupMessages({ + library(optparse) + library(readr) + library(lsa) + library(pheatmap) +}) + +# ---- Configurable options via task.ext.args ---- +option_list <- list( + make_option(c("-m", "--method"), + type = "character", default = "cosine", + help = "Similarity method: cosine, pearson, spearman"), + make_option(c("-g", "--min_gene_mean"), + type = "double", default = 0.0, + help = "Minimum gene mean expression filter") +) + +opt <- parse_args( + OptionParser(option_list = option_list), + args = strsplit("$args", "[[:space:]]+")[[1]] +) + +# ---- Load table ---- +df <- as.data.frame( + read_csv("${expression_matrix}", + col_types = cols(.default = col_guess())) +) + +# ---- Set rownames from first column if character ---- +first_col <- names(df)[1] +if (is.character(df[[first_col]]) || is.factor(df[[first_col]])) { + rownames(df) <- df[[first_col]] + df[[first_col]] <- NULL +} + +# ---- Select numeric columns as the expression matrix ---- +mat <- as.matrix( + df[, sapply(df, is.numeric), drop = FALSE] +) + +# ---- Filter low-expression genes ---- +if (opt\$min_gene_mean > 0) { + keep <- rowMeans(mat, na.rm = TRUE) >= opt\$min_gene_mean + mat <- mat[keep, , drop = FALSE] +} + +# ---- Compute similarity ---- +if (opt\$method == "cosine") { + sim <- lsa::cosine(mat) +} else { + sim <- cor(mat, + method = opt\$method, use = "pairwise.complete.obs") +} +colnames(sim) <- colnames(mat) +rownames(sim) <- colnames(mat) + +# ---- Save matrix ---- +write.csv(sim, "${prefix}_matrix.csv", + quote = FALSE, row.names = TRUE) + +# ---- Save heatmap ---- +png("${prefix}_heatmap.png", width = 900, height = 700) +pheatmap(sim, + display_numbers = TRUE, + cluster_rows = FALSE, + cluster_cols = FALSE) +dev.off() + +# ---- Versions ---- +writeLines( + c( + '"${task.process}":', + paste(" r-base:", + paste0(R.version\$major, ".", R.version\$minor)), + paste(" r-lsa:", + as.character(packageVersion("lsa"))), + paste(" r-pheatmap:", + as.character(packageVersion("pheatmap"))) + ), + "versions.yml" +) diff --git a/modules/nf-core/cosimflow/tests/main.nf.test b/modules/nf-core/lsa/cosine/tests/main.nf.test similarity index 100% rename from modules/nf-core/cosimflow/tests/main.nf.test rename to modules/nf-core/lsa/cosine/tests/main.nf.test diff --git a/tests/modules/nf-core/lsa/cosine/main.nf.test b/tests/modules/nf-core/lsa/cosine/main.nf.test new file mode 100644 index 000000000000..c7134596a4ee --- /dev/null +++ b/tests/modules/nf-core/lsa/cosine/main.nf.test @@ -0,0 +1,63 @@ +nextflow_process { + + name "Test Process LSA_COSINE" + script "../main.nf" + process "LSA_COSINE" + + tag "modules" + tag "modules_nfcore" + tag "lsa" + tag "lsa/cosine" + + // Test 1: Real run + test("Should run successfully with a simple CSV") { + + when { + process { + """ + // Create dummy CSV + def test_csv = file("test_data.csv") + test_csv.text = '''gene_id,sample_A,sample_B,sample_C + GENE_1,10.0,10.5,0.0 + GENE_2,50.0,50.5,2.0 + GENE_3,0.0,0.0,100.0 + GENE_4,100.0,99.5,10.0''' + + input[0] = [ [id:'test_sample'], test_csv ] + """ + } + } + + then { + assert process.success + // Snapshot versions and the matrix content (heatmap is binary, so we check name only) + assert snapshot( + process.out.matrix, + process.out.versions, + file(process.out.heatmap[0][1]).name + ).match() + } + + } + + // Test 2: Stub run (fast check) + test("Stub run") { + options "-stub" + + when { + process { + """ + def test_csv = file("test_data.csv") + test_csv.text = '''gene_id,sample_A,sample_B''' // content doesn't matter for stub + input[0] = [ [id:'test_sample'], test_csv ] + """ + } + } + + then { + assert process.success + assert snapshot(process.out).match() + } + } + +} From 3294995fb1b7bb0a7872d649f6d5847705aabd41 Mon Sep 17 00:00:00 2001 From: miguelrosell Date: Fri, 13 Feb 2026 01:58:01 +0100 Subject: [PATCH 03/10] Fix: remove duplicate tests folder inside module directory --- modules/nf-core/lsa/cosine/tests/main.nf.test | 42 ------------------- 1 file changed, 42 deletions(-) delete mode 100644 modules/nf-core/lsa/cosine/tests/main.nf.test diff --git a/modules/nf-core/lsa/cosine/tests/main.nf.test b/modules/nf-core/lsa/cosine/tests/main.nf.test deleted file mode 100644 index ae0e68088992..000000000000 --- a/modules/nf-core/lsa/cosine/tests/main.nf.test +++ /dev/null @@ -1,42 +0,0 @@ -nextflow_process { - - name "Test Process COSIMFLOW" - script "../main.nf" - process "COSIMFLOW" - - tag "modules" - tag "modules_nfcore" - tag "cosimflow" - - test("Should run successfully with a simple CSV") { - - when { - process { - """ - // 1. Creamos un CSV dummy al vuelo para el test - def test_csv = file("test_data.csv") - test_csv.text = '''gene_id,sample_A,sample_B,sample_C - GENE_1,10.0,10.5,0.0 - GENE_2,50.0,50.5,2.0 - GENE_3,0.0,0.0,100.0 - GENE_4,100.0,99.5,10.0''' - - // 2. Definimos el input: [ meta, archivo ] - input[0] = [ [id:'test_sample'], test_csv ] - """ - } - } - - then { - // 3. Verificamos que termine con éxito - assert process.success - - // 4. Verificamos que se generen los archivos - assert process.out.matrix - assert process.out.heatmap - assert process.out.versions - } - - } - -} From a1dca67d5731e598046607ff17f7989361f58081 Mon Sep 17 00:00:00 2001 From: miguelrosell Date: Fri, 13 Feb 2026 02:28:21 +0100 Subject: [PATCH 04/10] Fix: remove 'def' from script variables so that template can access them --- modules/nf-core/lsa/cosine/main.nf | 8 +-- .../nf-core/lsa/cosine/tests}/main.nf.test | 0 .../lsa/cosine/tests/main.nf.test.snap | 72 +++++++++++++++++++ 3 files changed, 76 insertions(+), 4 deletions(-) rename {tests/modules/nf-core/lsa/cosine => modules/nf-core/lsa/cosine/tests}/main.nf.test (100%) create mode 100644 modules/nf-core/lsa/cosine/tests/main.nf.test.snap diff --git a/modules/nf-core/lsa/cosine/main.nf b/modules/nf-core/lsa/cosine/main.nf index 8d3d4b4d37c9..0c4008d732d4 100644 --- a/modules/nf-core/lsa/cosine/main.nf +++ b/modules/nf-core/lsa/cosine/main.nf @@ -2,7 +2,6 @@ process LSA_COSINE { tag "$meta.id" label 'process_medium' - // Contenedores nuevos generados por el revisor 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/4d/4d94f159b95315adf8bf54fdc9db88db10a5aef72dca6245dd163b91e9e0437e/data' : @@ -14,14 +13,15 @@ process LSA_COSINE { output: tuple val(meta), path("*_matrix.csv") , emit: matrix tuple val(meta), path("*_heatmap.png"), emit: heatmap - path "versions.yml" , emit: versions + path "versions.yml" , emit: versions when: task.ext.when == null || task.ext.when script: - def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" + // HEMOS QUITADO EL 'def' DE AQUÍ ABAJO PARA QUE LA PLANTILLA LO VEA + args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" template 'cosine.R' stub: diff --git a/tests/modules/nf-core/lsa/cosine/main.nf.test b/modules/nf-core/lsa/cosine/tests/main.nf.test similarity index 100% rename from tests/modules/nf-core/lsa/cosine/main.nf.test rename to modules/nf-core/lsa/cosine/tests/main.nf.test diff --git a/modules/nf-core/lsa/cosine/tests/main.nf.test.snap b/modules/nf-core/lsa/cosine/tests/main.nf.test.snap new file mode 100644 index 000000000000..93cf015c5f23 --- /dev/null +++ b/modules/nf-core/lsa/cosine/tests/main.nf.test.snap @@ -0,0 +1,72 @@ +{ + "Stub run": { + "content": [ + { + "0": [ + [ + { + "id": "test_sample" + }, + "test_sample_matrix.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test_sample" + }, + "test_sample_heatmap.png:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + "versions.yml:md5,ccaf13b49437e0a66e949615becf4292" + ], + "heatmap": [ + [ + { + "id": "test_sample" + }, + "test_sample_heatmap.png:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "matrix": [ + [ + { + "id": "test_sample" + }, + "test_sample_matrix.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,ccaf13b49437e0a66e949615becf4292" + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.04.7" + }, + "timestamp": "2026-02-13T02:14:38.341084051" + }, + "Should run successfully with a simple CSV": { + "content": [ + [ + [ + { + "id": "test_sample" + }, + "test_sample_matrix.csv:md5,d84209a90c22f35ef4e6ea89a9d11770" + ] + ], + [ + "versions.yml:md5,b77351a76d91db12e57dd88f7e2e0184" + ], + "test_sample_heatmap.png" + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.04.7" + }, + "timestamp": "2026-02-13T02:24:53.88495333" + } +} \ No newline at end of file From a52a2b6f3a155f3ae82e9819b11284e2e4d08017 Mon Sep 17 00:00:00 2001 From: miguelrosell Date: Fri, 13 Feb 2026 02:52:08 +0100 Subject: [PATCH 05/10] Fix: removing Spanish comment --- modules/nf-core/lsa/cosine/main.nf | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/nf-core/lsa/cosine/main.nf b/modules/nf-core/lsa/cosine/main.nf index 0c4008d732d4..2dba59c45e22 100644 --- a/modules/nf-core/lsa/cosine/main.nf +++ b/modules/nf-core/lsa/cosine/main.nf @@ -19,7 +19,6 @@ process LSA_COSINE { task.ext.when == null || task.ext.when script: - // HEMOS QUITADO EL 'def' DE AQUÍ ABAJO PARA QUE LA PLANTILLA LO VEA args = task.ext.args ?: '' prefix = task.ext.prefix ?: "${meta.id}" template 'cosine.R' From fdffc3c8dc1bb2b2c1b039786a9ba2255d2135eb Mon Sep 17 00:00:00 2001 From: miguelrosell Date: Fri, 13 Feb 2026 14:25:49 +0100 Subject: [PATCH 06/10] Fix: removed unused dependencies and use dynamic versioning in stub --- modules/nf-core/lsa/cosine/environment.yml | 2 -- modules/nf-core/lsa/cosine/main.nf | 10 +++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/modules/nf-core/lsa/cosine/environment.yml b/modules/nf-core/lsa/cosine/environment.yml index 3b26d2dcaa0a..6ef0aff9fbc4 100644 --- a/modules/nf-core/lsa/cosine/environment.yml +++ b/modules/nf-core/lsa/cosine/environment.yml @@ -9,5 +9,3 @@ dependencies: - conda-forge::r-pheatmap=1.0.13 - conda-forge::r-optparse=1.7.5 - conda-forge::r-readr=2.1.6 - - conda-forge::r-readxl=1.4.5 - - conda-forge::r-dplyr=1.2.0 diff --git a/modules/nf-core/lsa/cosine/main.nf b/modules/nf-core/lsa/cosine/main.nf index 2dba59c45e22..661241c1cafd 100644 --- a/modules/nf-core/lsa/cosine/main.nf +++ b/modules/nf-core/lsa/cosine/main.nf @@ -13,7 +13,7 @@ process LSA_COSINE { output: tuple val(meta), path("*_matrix.csv") , emit: matrix tuple val(meta), path("*_heatmap.png"), emit: heatmap - path "versions.yml" , emit: versions + path "versions.yml" , emit: versions when: task.ext.when == null || task.ext.when @@ -24,8 +24,8 @@ process LSA_COSINE { template 'cosine.R' stub: - def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" + args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" """ touch ${prefix}_matrix.csv touch ${prefix}_heatmap.png @@ -33,8 +33,8 @@ process LSA_COSINE { cat <<-END_VERSIONS > versions.yml "${task.process}": r-base: \$(R --version | sed -n 1p | sed 's/R version //g' | sed 's/ (.*//g') - r-lsa: 0.73.3 - r-pheatmap: 1.0.12 + r-lsa: \$(Rscript -e "library(lsa); cat(as.character(packageVersion('lsa')))") + r-pheatmap: \$(Rscript -e "library(pheatmap); cat(as.character(packageVersion('pheatmap')))") END_VERSIONS """ } From 335a553cb7b3147a7539033996f2376ce1f7bd38 Mon Sep 17 00:00:00 2001 From: miguelrosell Date: Fri, 20 Feb 2026 15:56:52 +0100 Subject: [PATCH 07/10] Fix: linting errors, yaml structure, empty stubs, and updated snapshots --- modules/nf-core/lsa/cosine/environment.yml | 1 - modules/nf-core/lsa/cosine/main.nf | 4 ++-- modules/nf-core/lsa/cosine/meta.yml | 6 ++--- modules/nf-core/lsa/cosine/tests/main.nf.test | 4 ++-- .../lsa/cosine/tests/main.nf.test.snap | 22 +++++++++---------- 5 files changed, 18 insertions(+), 19 deletions(-) diff --git a/modules/nf-core/lsa/cosine/environment.yml b/modules/nf-core/lsa/cosine/environment.yml index 6ef0aff9fbc4..67fda59b5823 100644 --- a/modules/nf-core/lsa/cosine/environment.yml +++ b/modules/nf-core/lsa/cosine/environment.yml @@ -1,5 +1,4 @@ --- -name: lsa_cosine channels: - conda-forge - bioconda diff --git a/modules/nf-core/lsa/cosine/main.nf b/modules/nf-core/lsa/cosine/main.nf index 661241c1cafd..ba242bd0da37 100644 --- a/modules/nf-core/lsa/cosine/main.nf +++ b/modules/nf-core/lsa/cosine/main.nf @@ -27,8 +27,8 @@ process LSA_COSINE { args = task.ext.args ?: '' prefix = task.ext.prefix ?: "${meta.id}" """ - touch ${prefix}_matrix.csv - touch ${prefix}_heatmap.png + echo "dummy matrix data" > ${prefix}_matrix.csv + echo "dummy matrix data" > ${prefix}_heatmap.png cat <<-END_VERSIONS > versions.yml "${task.process}": diff --git a/modules/nf-core/lsa/cosine/meta.yml b/modules/nf-core/lsa/cosine/meta.yml index 03954a9a1cfc..236dff62c24a 100644 --- a/modules/nf-core/lsa/cosine/meta.yml +++ b/modules/nf-core/lsa/cosine/meta.yml @@ -33,7 +33,7 @@ input: output: - matrix: - - meta: + meta: type: map description: | Groovy Map containing sample information, e.g. [ id:'test' ]. @@ -42,7 +42,7 @@ output: description: A square matrix (CSV) containing pairwise similarity scores. pattern: "*_matrix.csv" - heatmap: - - meta: + meta: type: map description: | Groovy Map containing sample information, e.g. [ id:'test' ]. @@ -51,7 +51,7 @@ output: description: A PNG image visualizing the similarity matrix as a heatmap. pattern: "*_heatmap.png" - versions: - - versions.yml: + "versions.yml": type: file description: File containing software versions. pattern: "versions.yml" diff --git a/modules/nf-core/lsa/cosine/tests/main.nf.test b/modules/nf-core/lsa/cosine/tests/main.nf.test index c7134596a4ee..5210aa68513a 100644 --- a/modules/nf-core/lsa/cosine/tests/main.nf.test +++ b/modules/nf-core/lsa/cosine/tests/main.nf.test @@ -22,7 +22,7 @@ nextflow_process { GENE_2,50.0,50.5,2.0 GENE_3,0.0,0.0,100.0 GENE_4,100.0,99.5,10.0''' - + input[0] = [ [id:'test_sample'], test_csv ] """ } @@ -43,7 +43,7 @@ nextflow_process { // Test 2: Stub run (fast check) test("Stub run") { options "-stub" - + when { process { """ diff --git a/modules/nf-core/lsa/cosine/tests/main.nf.test.snap b/modules/nf-core/lsa/cosine/tests/main.nf.test.snap index 93cf015c5f23..cb48832156a0 100644 --- a/modules/nf-core/lsa/cosine/tests/main.nf.test.snap +++ b/modules/nf-core/lsa/cosine/tests/main.nf.test.snap @@ -7,7 +7,7 @@ { "id": "test_sample" }, - "test_sample_matrix.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + "test_sample_matrix.csv:md5,3932b231a8a14016bc1e7a245f05246c" ] ], "1": [ @@ -15,18 +15,18 @@ { "id": "test_sample" }, - "test_sample_heatmap.png:md5,d41d8cd98f00b204e9800998ecf8427e" + "test_sample_heatmap.png:md5,3932b231a8a14016bc1e7a245f05246c" ] ], "2": [ - "versions.yml:md5,ccaf13b49437e0a66e949615becf4292" + "versions.yml:md5,b77351a76d91db12e57dd88f7e2e0184" ], "heatmap": [ [ { "id": "test_sample" }, - "test_sample_heatmap.png:md5,d41d8cd98f00b204e9800998ecf8427e" + "test_sample_heatmap.png:md5,3932b231a8a14016bc1e7a245f05246c" ] ], "matrix": [ @@ -34,19 +34,19 @@ { "id": "test_sample" }, - "test_sample_matrix.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + "test_sample_matrix.csv:md5,3932b231a8a14016bc1e7a245f05246c" ] ], "versions": [ - "versions.yml:md5,ccaf13b49437e0a66e949615becf4292" + "versions.yml:md5,b77351a76d91db12e57dd88f7e2e0184" ] } ], + "timestamp": "2026-02-20T15:51:35.612855607", "meta": { - "nf-test": "0.9.3", + "nf-test": "0.9.4", "nextflow": "25.04.7" - }, - "timestamp": "2026-02-13T02:14:38.341084051" + } }, "Should run successfully with a simple CSV": { "content": [ @@ -63,10 +63,10 @@ ], "test_sample_heatmap.png" ], + "timestamp": "2026-02-13T02:24:53.88495333", "meta": { "nf-test": "0.9.3", "nextflow": "25.04.7" - }, - "timestamp": "2026-02-13T02:24:53.88495333" + } } } \ No newline at end of file From dc20e9186ac1fa6679ab330fc9f626a1968d4866 Mon Sep 17 00:00:00 2001 From: miguelrosell Date: Fri, 20 Feb 2026 16:19:00 +0100 Subject: [PATCH 08/10] Fix: update meta.yml structure, keys and trailing whitespaces --- modules/nf-core/lsa/cosine/meta.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/nf-core/lsa/cosine/meta.yml b/modules/nf-core/lsa/cosine/meta.yml index 236dff62c24a..94895a286612 100644 --- a/modules/nf-core/lsa/cosine/meta.yml +++ b/modules/nf-core/lsa/cosine/meta.yml @@ -1,5 +1,5 @@ # name matches directory structure -name: "lsa_cosine" +name: "lsa/cosine" description: Calculates the cosine similarity matrix between samples based on a gene expression matrix. keywords: - similarity @@ -27,31 +27,31 @@ input: - expression_matrix: type: file description: | - CSV file containing the expression matrix. + CSV file containing the expression matrix. Rows should be features (genes) and columns should be samples. pattern: "*.csv" output: - matrix: - meta: + - meta: type: map description: | Groovy Map containing sample information, e.g. [ id:'test' ]. - - "*_matrix.csv": + - matrix: type: file description: A square matrix (CSV) containing pairwise similarity scores. pattern: "*_matrix.csv" - heatmap: - meta: + - meta: type: map description: | Groovy Map containing sample information, e.g. [ id:'test' ]. - - "*_heatmap.png": + - heatmap: type: file description: A PNG image visualizing the similarity matrix as a heatmap. pattern: "*_heatmap.png" - versions: - "versions.yml": + - versions: type: file description: File containing software versions. pattern: "versions.yml" From 6075c1a203a4529fad74f2ec83887278e9f1cde9 Mon Sep 17 00:00:00 2001 From: miguelrosell Date: Fri, 20 Feb 2026 16:33:25 +0100 Subject: [PATCH 09/10] Fix: strict nf-core schema for meta.yml outputs and remove trailing whitespace --- modules/nf-core/lsa/cosine/main.nf | 2 +- modules/nf-core/lsa/cosine/meta.yml | 40 ++++++++++++++--------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/modules/nf-core/lsa/cosine/main.nf b/modules/nf-core/lsa/cosine/main.nf index ba242bd0da37..adff29bf8021 100644 --- a/modules/nf-core/lsa/cosine/main.nf +++ b/modules/nf-core/lsa/cosine/main.nf @@ -29,7 +29,7 @@ process LSA_COSINE { """ echo "dummy matrix data" > ${prefix}_matrix.csv echo "dummy matrix data" > ${prefix}_heatmap.png - + cat <<-END_VERSIONS > versions.yml "${task.process}": r-base: \$(R --version | sed -n 1p | sed 's/R version //g' | sed 's/ (.*//g') diff --git a/modules/nf-core/lsa/cosine/meta.yml b/modules/nf-core/lsa/cosine/meta.yml index 94895a286612..6a1d1fde0453 100644 --- a/modules/nf-core/lsa/cosine/meta.yml +++ b/modules/nf-core/lsa/cosine/meta.yml @@ -33,28 +33,28 @@ input: output: - matrix: - - meta: - type: map - description: | - Groovy Map containing sample information, e.g. [ id:'test' ]. - - matrix: - type: file - description: A square matrix (CSV) containing pairwise similarity scores. - pattern: "*_matrix.csv" + meta: + type: map + description: | + Groovy Map containing sample information, e.g. [ id:'test' ]. + "*_matrix.csv": + type: file + description: A square matrix (CSV) containing pairwise similarity scores. + pattern: "*_matrix.csv" - heatmap: - - meta: - type: map - description: | - Groovy Map containing sample information, e.g. [ id:'test' ]. - - heatmap: - type: file - description: A PNG image visualizing the similarity matrix as a heatmap. - pattern: "*_heatmap.png" + meta: + type: map + description: | + Groovy Map containing sample information, e.g. [ id:'test' ]. + "*_heatmap.png": + type: file + description: A PNG image visualizing the similarity matrix as a heatmap. + pattern: "*_heatmap.png" - versions: - - versions: - type: file - description: File containing software versions. - pattern: "versions.yml" + "versions.yml": + type: file + description: File containing software versions. + pattern: "versions.yml" authors: - "@miguelrosell" From 7cb29c7bff5d5fdb868e3ff03df75d01317e1340 Mon Sep 17 00:00:00 2001 From: miguelrosell Date: Fri, 20 Feb 2026 16:55:20 +0100 Subject: [PATCH 10/10] Fix: correct process name and use lint --fix for matching output structure --- modules/nf-core/lsa/cosine/meta.yml | 55 +++++++++++++++++------------ 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/modules/nf-core/lsa/cosine/meta.yml b/modules/nf-core/lsa/cosine/meta.yml index 6a1d1fde0453..964cac6cd7de 100644 --- a/modules/nf-core/lsa/cosine/meta.yml +++ b/modules/nf-core/lsa/cosine/meta.yml @@ -1,6 +1,7 @@ # name matches directory structure -name: "lsa/cosine" -description: Calculates the cosine similarity matrix between samples based on a gene expression matrix. +name: "lsa_cosine" +description: Calculates the cosine similarity matrix between samples based on a gene + expression matrix. keywords: - similarity - cosine @@ -13,11 +14,13 @@ tools: homepage: "https://cran.r-project.org/web/packages/lsa/index.html" documentation: "https://cran.r-project.org/web/packages/lsa/lsa.pdf" licence: ["GPL-2"] + identifier: "" - "pheatmap": description: "Pretty Heatmaps package for R." homepage: "https://cran.r-project.org/web/packages/pheatmap/index.html" documentation: "https://cran.r-project.org/web/packages/pheatmap/pheatmap.pdf" licence: ["GPL-2"] + identifier: biotools:pheatmap input: - - meta: @@ -30,32 +33,38 @@ input: CSV file containing the expression matrix. Rows should be features (genes) and columns should be samples. pattern: "*.csv" - + ontologies: + - edam: http://edamontology.org/format_3752 # CSV output: - - matrix: - meta: - type: map - description: | - Groovy Map containing sample information, e.g. [ id:'test' ]. - "*_matrix.csv": - type: file - description: A square matrix (CSV) containing pairwise similarity scores. - pattern: "*_matrix.csv" - - heatmap: - meta: - type: map - description: | - Groovy Map containing sample information, e.g. [ id:'test' ]. - "*_heatmap.png": - type: file - description: A PNG image visualizing the similarity matrix as a heatmap. - pattern: "*_heatmap.png" - - versions: - "versions.yml": + matrix: + - - meta: + type: map + description: | + Groovy Map containing sample information, e.g. [ id:'test' ]. + - "*_matrix.csv": + type: file + description: A square matrix (CSV) containing pairwise similarity scores. + pattern: "*_matrix.csv" + ontologies: + - edam: http://edamontology.org/format_3752 # CSV + heatmap: + - - meta: + type: map + description: | + Groovy Map containing sample information, e.g. [ id:'test' ]. + - "*_heatmap.png": + type: file + description: A PNG image visualizing the similarity matrix as a heatmap. + pattern: "*_heatmap.png" + ontologies: [] + versions: + - versions.yml: type: file description: File containing software versions. pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML authors: - "@miguelrosell" maintainers: