diff --git a/.Rbuildignore b/.Rbuildignore index 0819606..86d3451 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -1,7 +1,10 @@ ^.*\.Rproj$ ^\.Rproj\.user$ ^doc$ +^docker$ ^Meta$ ^validation$ ^CLAUDE\.md$ ^CLAUDE_CHANGELOG\.md$ +^\.positai$ +^\.claude$ diff --git a/.gitignore b/.gitignore index 33d8edd..1907ee1 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ CoSIA.Rproj /doc/ /Meta/ +.positai diff --git a/R/CoSIA-plots-method.R b/R/CoSIA-plots-method.R index 6442891..caec6d0 100755 --- a/R/CoSIA-plots-method.R +++ b/R/CoSIA-plots-method.R @@ -74,7 +74,7 @@ setMethod( filter_gex <- filter_gex[ filter_gex$Anatomical_entity_name %in% single_tissue, ] - filter_gex <- tidyr::separate_rows(filter_gex, VST) + filter_gex <- tidyr::unnest(filter_gex, VST) filter_gex$VST <- as.numeric(filter_gex$VST) filter_gex <- as.data.frame(filter_gex) brewer.pal.info <- RColorBrewer::brewer.pal.info @@ -102,6 +102,7 @@ setMethod( fig <- fig %>% plotly::add_trace( type = "violin", + mode = NULL, spanmode = "hard", showlegend = FALSE ) @@ -212,7 +213,7 @@ setMethod( ids <- as.character(ids) filter_gex <- gex_dataframe[gex_dataframe$Ensembl_ID %in% ids, ] filter_gex <- filter_gex[filter_gex$Species %in% single_species, ] - filter_gex <- tidyr::separate_rows(filter_gex, VST) + filter_gex <- tidyr::unnest(filter_gex, VST) filter_gex$VST <- as.numeric(filter_gex$VST) brewer.pal.info <- RColorBrewer::brewer.pal.info @@ -242,6 +243,7 @@ setMethod( fig <- fig %>% plotly::add_trace( type = "violin", + mode = NULL, spanmode = "hard", showlegend = FALSE ) @@ -403,7 +405,7 @@ setMethod("plotDSGEx", signature(object = "CoSIAn"), function(object) { #' "h_sapiens", "r_norvegicus" #' ), output_ids = c("Ensembl_id", "Symbol"), #' mapping_tool = "annotationDBI", ortholog_database = "HomoloGene", -#' map_tissues = c("adult mammalian kidney","heart"), +#' map_tissues = c("adult mammalian kidney","heart"), #' map_species = c("h_sapiens", "r_norvegicus"), #' metric_type = "CV_Tissue" #' ) @@ -429,15 +431,15 @@ setGeneric("plotCVGEx", function(object) standardGeneric("plotCVGEx")) #' "h_sapiens", "r_norvegicus" #' ), output_ids = c("Ensembl_id", "Symbol"), #' mapping_tool = "annotationDBI", ortholog_database = "HomoloGene", -#' map_tissues = c("adult mammalian kidney","heart"), +#' map_tissues = c("adult mammalian kidney","heart"), #' map_species = c("h_sapiens", "r_norvegicus"), #' metric_type = "CV_Tissue" #' ) #' Kidney_gene_conversion <- CoSIA::getConversion(Kidney_Genes) #' Kidney_gene_metric <- getGExMetrics(Kidney_gene_conversion) #' plot <- plotCVGEx(Kidney_gene_metric) -#' @references Dot plot in R with the dotchart function [with examples]. -#' R CODER. (2020, November 20). Retrieved from https://r-coder.com/dot-plot-r/ +#' @references Dot plot in R with the dotchart function [with examples]. +#' R CODER. (2020, November 20). Retrieved from https://r-coder.com/dot-plot-r/ setMethod("plotCVGEx", signature(object = "CoSIAn"), function(object) { # set object slots into variables metric_type <- object@metric_type diff --git a/docker/Dockerfile_1.10.1 b/docker/Dockerfile_1.10.1 new file mode 100644 index 0000000..178ddbd --- /dev/null +++ b/docker/Dockerfile_1.10.1 @@ -0,0 +1,83 @@ +FROM bioconductor/bioconductor:devel-r-4.6.0 +# includes: BIOCONDUCTOR_DOCKER_VERSION=3.23.22 + +##----------------------------------------------------------------------------## +## SYSTEM PACKAGES +##----------------------------------------------------------------------------## + +RUN apt-get update --yes && \ + apt-get upgrade --yes + +RUN apt-get install --yes \ + python3-pip \ + python3-dev \ + python3-pip \ + python3-tk + + +RUN apt-get install --yes \ + libcurl4-openssl-dev \ + libssl-dev \ + libcairo2-dev libxt-dev \ + libxml2-dev \ + libudunits2-dev \ + libhdf5-dev \ + libv8-dev \ + libgdal-dev \ + xorg libx11-dev libglu1-mesa-dev \ + samtools libboost-all-dev \ + libgsl-dev + +RUN apt-get install -y \ + wget \ + unzip \ + git + + +##----------------------------------------------------------------------------## +## R PACKAGES +##----------------------------------------------------------------------------## + +#### Leave the top line the same for each new project. Uncomment lines to add #### +RUN R -e 'install.packages(c("tidyverse", "remotes", "BiocManager", "devtools", "knitr", "lintr", "styler", "here", "markdown", "rmarkdown", "reticulate"))' \ +# && R -e 'install.packages(c("< insert CRAN repos here >"))' \ +# && R -e 'devtools::install_github("< insert GitHub repos here >")' \ +# && R -e 'remotes::install_github("< insert GitHub repos here >")' \ + && R -e 'BiocManager::install(c("BiocFileCache", "BiocGenerics", "AnnotationHub", "ExperimentHub", "CoSIA", "CoSIAdata"))' + + +##----------------------------------------------------------------------------## +## PYTHON PACKAGES +##----------------------------------------------------------------------------## + + +RUN pip3 install kaleido plotly --break-system-packages +RUN wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | apt-key add - \ + && echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list \ + && apt-get update --yes \ + && apt-get install --yes google-chrome-stable + +##----------------------------------------------------------------------------## +## 3RD PARTY PROGRAMS +##----------------------------------------------------------------------------## + +#### Uncomment these lines if you need a conda env in your docker #### + +# Install miniconda +# ENV CONDA_DIR /opt/conda +# RUN wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda.sh && \ +# /bin/bash ~/miniconda.sh -b -p /opt/conda +# ENV PATH=$CONDA_DIR/bin:$PATH + + +##----------------------------------------------------------------------------## +## CONDA ENV +##----------------------------------------------------------------------------## + +#### Uncomment these lines if you need a conda env in your docker #### + +#RUN conda config --set channel_priority false && \ +# conda env create -f opt/BLAZE-1.1.0/conda_env/environment.yml + +#SHELL ["conda", "run", "-n", "blaze", "/bin/bash", "-c"] + diff --git a/tests/test.sh b/tests/test.sh index 6f44f20..2df6f89 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -1,11 +1,10 @@ #!/usr/bin/env bash set -euo pipefail -PROJECT_PATH="/data/user/$USER/CoSIA" -DEV_PATH="/data/user/$USER/CoSIA" +PROJECT_PATH="$(pwd)" CONTAINER_PATH="$PROJECT_PATH/bin/container" -SIF="${CONTAINER_PATH}/bioc_cosia_1.10.0.sif" -SIF_URI="${COSIA_SIF_URI:-docker://lizzyr/bioc_cosia:1.10.0}" +SIF="${CONTAINER_PATH}/bioc_cosia_1.10.1.sif" +SIF_URI="${COSIA_SIF_URI:-docker://lasseignelab/bioc_cosia:1.10.1}" ensure_sif() { mkdir -p "$CONTAINER_PATH" @@ -33,7 +32,6 @@ mkdir -p \ singularity exec --cleanenv \ --containall \ --home "${CACHE_BASE}/home:/home/${USER}" \ - -B "${DEV_PATH}" \ -B "${PROJECT_PATH}" \ -B "${CACHE_BASE}:${CACHE_BASE}" \ -B "${TMP_BASE}:/tmp" \ diff --git a/tests/testthat/test-plots.R b/tests/testthat/test-plots.R new file mode 100644 index 0000000..3c619c7 --- /dev/null +++ b/tests/testthat/test-plots.R @@ -0,0 +1,97 @@ +# plotSpeciesGEx and plotTissueGEx tests +# These run offline — VST data is injected as a list column to match the real +# ExperimentHub format and catch the separate_rows vs unnest regression. + +make_plot_cosian <- function() { + obj <- make_cosian(map_species = "m_musculus", map_tissues = "heart") + + mock_gex <- data.frame( + Anatomical_entity_name = c("heart", "heart"), + Ensembl_ID = c("ENSG00000008710", "ENSMUSG00000032855"), + Sample_size = c(3L, 3L), + Experiment_ID = c("EXP1", "EXP2"), + Anatomical_entity_ID = c("UBERON:0000948", "UBERON:0000948"), + Species = c("Homo_sapiens", "Mus_musculus"), + Scaled_Median_VST = c(1.0, 1.0), + mad = c(0.1, 0.1), + stringsAsFactors = FALSE + ) + mock_gex$VST <- list(c(10.5, 11.0, 12.0), c(11.0, 11.5, 11.8)) + + obj@gex <- mock_gex + obj@converted_id <- data.frame( + h_sapiens_ensembl_id = "ENSG00000008710", + h_sapiens_symbol = "PKD1", + m_musculus_ensembl_id = "ENSMUSG00000032855", + m_musculus_symbol = "Pkd1", + stringsAsFactors = FALSE + ) + obj +} + +# --- plotSpeciesGEx --- + +test_that("plotSpeciesGEx returns a plotly object", { + obj <- make_plot_cosian() + fig <- plotSpeciesGEx(obj, "heart", "ENSG00000008710") + expect_true(inherits(fig, "plotly")) +}) + +test_that("plotSpeciesGEx VST values are non-NA (list-column unnest)", { + obj <- make_plot_cosian() + fig <- plotly::plotly_build(plotSpeciesGEx(obj, "heart", "ENSG00000008710")) + + all_y <- unlist(lapply(fig$x$data, function(tr) tr$y)) + expect_false( + all(is.na(all_y)), + label = "all VST values are NA — separate_rows regression likely" + ) + expect_true(all(all_y > 0, na.rm = TRUE)) +}) + + +# --- plotTissueGEx --- + +make_tissue_cosian <- function() { + obj <- make_cosian(map_species = "h_sapiens", map_tissues = c("heart", "brain")) + + mock_gex <- data.frame( + Anatomical_entity_name = c("heart", "brain"), + Ensembl_ID = c("ENSG00000008710", "ENSG00000008710"), + Sample_size = c(3L, 4L), + Experiment_ID = c("EXP1", "EXP2"), + Anatomical_entity_ID = c("UBERON:0000948", "UBERON:0000955"), + Species = c("Homo_sapiens", "Homo_sapiens"), + Scaled_Median_VST = c(1.0, 1.0), + mad = c(0.1, 0.1), + stringsAsFactors = FALSE + ) + mock_gex$VST <- list(c(10.5, 11.0, 12.0), c(9.5, 10.0, 10.5, 11.0)) + + obj@gex <- mock_gex + obj@converted_id <- data.frame( + h_sapiens_ensembl_id = "ENSG00000008710", + h_sapiens_symbol = "PKD1", + stringsAsFactors = FALSE + ) + obj +} + +test_that("plotTissueGEx returns a plotly object", { + obj <- make_tissue_cosian() + fig <- plotTissueGEx(obj, "h_sapiens", "ENSG00000008710") + expect_true(inherits(fig, "plotly")) +}) + +test_that("plotTissueGEx VST values are non-NA (list-column unnest)", { + obj <- make_tissue_cosian() + fig <- plotly::plotly_build(plotTissueGEx(obj, "h_sapiens", "ENSG00000008710")) + + all_y <- unlist(lapply(fig$x$data, function(tr) tr$y)) + expect_false( + all(is.na(all_y)), + label = "all VST values are NA — separate_rows regression likely" + ) + expect_true(all(all_y > 0, na.rm = TRUE)) +}) + diff --git a/validation/gene_expression.R b/validation/gene_expression.R new file mode 100644 index 0000000..cf7d7e8 --- /dev/null +++ b/validation/gene_expression.R @@ -0,0 +1,69 @@ +# validation/gene_expression.R +# +# Creates the gene expression plot that was not working after updating CoSIA. +# +# Usage (from repo root): +# Run the script in RStudio. +# +# Output: Gene expression plot in the RStudio Viewer window + +devtools::load_all(".") + +loaded_version <- as.character(packageVersion("CoSIA")) +loaded_path <- find.package("CoSIA") + +cat("CoSIA version :", loaded_version, "\n") +cat("CoSIA path :", loaded_path, "\n\n") + +GENE_SET <- c("ENSG00000008710", "ENSG00000118762") +I_SPECIES <- "h_sapiens" +INPUT_ID <- "Ensembl_id" +OUTPUT_IDS <- c("Ensembl_id") +TOOL <- "annotationDBI" +ORTHO_DB <- "HomoloGene" +TISSUE <- "brain" +MULTI_SPECIES <- c("h_sapiens", "m_musculus") + +make_obj <- function(map_species, map_tissues, metric_type, + o_species = map_species) { + obj <- CoSIAn( + gene_set = GENE_SET, + i_species = I_SPECIES, + input_id = INPUT_ID, + o_species = o_species, + output_ids = OUTPUT_IDS, + mapping_tool = TOOL, + ortholog_database = ORTHO_DB, + map_tissues = map_tissues, + map_species = map_species, + metric_type = metric_type + ) + getConversion(obj) +} + +run_metric <- function(label, ...) { + cat("---", label, "---\n") + tryCatch({ + obj <- make_obj(...) + obj <- getGEx(obj) + cat(" SUCCESS \n") + obj + }, error = function(e) { + cat(" ERROR:", conditionMessage(e), "\n\n") + NULL + }) +} + +gex_species_multi = run_metric( + "DS_Gene multi-species", + map_species = MULTI_SPECIES, + map_tissues = TISSUE, + metric_type = "DS_Gene", + o_species = MULTI_SPECIES +) + +# print(CoSIA::plotSpeciesGEx(gex_species_multi, "brain", "ENSG00000008710")) + +dir.create("validation/output", showWarnings = FALSE, recursive = TRUE) +p <- CoSIA::plotSpeciesGEx(gex_species_multi, "brain", "ENSG00000008710") +plotly::save_image(p, "validation/output/gex_plot.png", width = 800, height = 600, scale = 2) diff --git a/validation/gene_expression.sh b/validation/gene_expression.sh new file mode 100755 index 0000000..0d07086 --- /dev/null +++ b/validation/gene_expression.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -euo pipefail + +PROJECT_PATH="$(pwd)" +CONTAINER_PATH="$PROJECT_PATH/bin/container" +SIF="${CONTAINER_PATH}/bioc_cosia_1.10.1.sif" +SIF_URI="${COSIA_SIF_URI:-docker://lasseignelab/bioc_cosia:1.10.1}" + +ensure_sif() { + mkdir -p "$CONTAINER_PATH" + if [[ -f "$SIF" ]]; then + return + fi + echo "SIF not found: $SIF" + echo "Pulling from $SIF_URI ..." + singularity pull "$SIF" "$SIF_URI" +} + +CACHE_BASE="/data/user/$USER/bioconductor/bioc-cache" +TMP_BASE="/data/user/$USER/bioconductor/bioc-tmp" + +ensure_sif + +mkdir -p \ + "$CACHE_BASE/home" \ + "$CACHE_BASE/annotationhub" \ + "$CACHE_BASE/experimenthub" \ + "$CACHE_BASE/biocfilecache" \ + "$CACHE_BASE/xdg" \ + "$TMP_BASE" + +singularity exec --cleanenv \ + --containall \ + --home "${CACHE_BASE}/home:/home/${USER}" \ + -B "${PROJECT_PATH}" \ + -B "${CACHE_BASE}:${CACHE_BASE}" \ + -B "${TMP_BASE}:/tmp" \ + -B "${TMP_BASE}:/var/tmp" \ + --env TMPDIR="/tmp" \ + --env TMP="/tmp" \ + --env TEMP="/tmp" \ + --env XDG_CACHE_HOME="${CACHE_BASE}/xdg" \ + --env ANNOTATION_HUB_CACHE="${CACHE_BASE}/annotationhub" \ + --env EXPERIMENT_HUB_CACHE="${CACHE_BASE}/experimenthub" \ + --env BFC_CACHE="${CACHE_BASE}/biocfilecache" \ + --env RETICULATE_PYTHON="/usr/bin/python3" \ + --pwd "${PROJECT_PATH}" \ + "$SIF" \ + Rscript validation/gene_expression.R