From c9e01b22a3551d93e9004c9d62a4256c414be6b3 Mon Sep 17 00:00:00 2001 From: lshep Date: Tue, 28 Apr 2026 08:34:13 -0400 Subject: [PATCH 01/36] bump x.y.z version to even y prior to creation of RELEASE_3_23 branch --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 6f46909e..3ff17fc4 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: spatialLIBD Title: spatialLIBD: an R/Bioconductor package to visualize spatially-resolved transcriptomics data -Version: 1.23.2 +Version: 1.24.0 Date: 2026-01-09 Authors@R: c( From 7026a9317b94cefde65a98d96185c55f76fd8727 Mon Sep 17 00:00:00 2001 From: lshep Date: Tue, 28 Apr 2026 08:34:13 -0400 Subject: [PATCH 02/36] bump x.y.z version to odd y following creation of RELEASE_3_23 branch --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 3ff17fc4..a9856a9c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: spatialLIBD Title: spatialLIBD: an R/Bioconductor package to visualize spatially-resolved transcriptomics data -Version: 1.24.0 +Version: 1.25.0 Date: 2026-01-09 Authors@R: c( From 42fcec07e455d3d0046d74c5f898e7221950c368 Mon Sep 17 00:00:00 2001 From: lahuuki Date: Tue, 5 May 2026 14:24:04 -0400 Subject: [PATCH 03/36] Add datatype param, check for correct input formats --- R/vis_clus.R | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/R/vis_clus.R b/R/vis_clus.R index e10c4e72..0e25825f 100644 --- a/R/vis_clus.R +++ b/R/vis_clus.R @@ -40,7 +40,16 @@ #' particular, expects a logical colData column `exclude_overlapping` #' specifying which spots to exclude from the plot. Sets `auto_crop = FALSE`. #' @param guide_point_size A `numeric(1)` specifying the size of the points in -#' guide. Defaults to `point_size`. Increase to improve visability. +#' guide. Defaults to `point_size`. Increase to improve visibility. +#' @param datatype A `character(1)` specifying the type of spatial transcriptomics +#' data stored in `spe`. Supported options are: +#' \describe{ +#' \item{`"Visium"`}{(Default) Expects `pxl_col_in_fullres` and +#' `pxl_row_in_fullres` as columns of `spatialCoords(spe)`. Enables +#' image handling via the `spatialData` slot.} +#' \item{`"Xenium"`}{Expects `x_centroid` and `y_centroid` as columns +#' of `spatialCoords(spe)`.} +#' } #' @param ... Passed to [paste0()][base::paste] for making the title of the #' plot following the `sampleid`. #' @@ -145,6 +154,7 @@ vis_clus <- function( na_color = "#CCCCCC40", is_stitched = FALSE, guide_point_size = point_size, + datatype = c("Visium", "Xenium"), ...) { # Verify existence and legitimacy of 'sampleid' if ( @@ -158,11 +168,19 @@ vis_clus <- function( call. = FALSE ) } + + ## Check for valid datatype + datatype <- match.arg(datatype) - # Check validity of spatial coordinates - if (!setequal(c("pxl_col_in_fullres", "pxl_row_in_fullres"), colnames(spatialCoords(spe)))) { + # Check validity of spatial coordinates by datatype + if (datatype == "Visium" & !setequal(c("pxl_col_in_fullres", "pxl_row_in_fullres"), colnames(spatialCoords(spe)))) { stop( - "Abnormal spatial coordinates: should have 'pxl_row_in_fullres' and 'pxl_col_in_fullres' columns.", + "Abnormal spatial coordinates for Visium datatype: should have 'pxl_row_in_fullres' and 'pxl_col_in_fullres' columns.", + call. = FALSE + ) + } else if (datatype == "Xenium" & !setequal(c("x_centroid", "y_centroid"), colnames(spatialCoords(spe)))) { + stop( + "Abnormal spatial coordinates for Xisium datatype: should have 'x_centroid' and 'y_centroid' columns.", call. = FALSE ) } From b9dcff862b3ae3759d2346a6409c797c04c928dc Mon Sep 17 00:00:00 2001 From: lahuuki Date: Tue, 5 May 2026 14:25:05 -0400 Subject: [PATCH 04/36] Add vis_clus_c() function for handling visulization of Xenium/centroid based data --- NAMESPACE | 1 + R/vis_clus_c.R | 116 ++++++++++++++++++++++++++++++++++++++++++++++ man/vis_clus_c.Rd | 108 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 225 insertions(+) create mode 100644 R/vis_clus_c.R create mode 100644 man/vis_clus_c.Rd diff --git a/NAMESPACE b/NAMESPACE index 68a61fe9..1d34a0d6 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -40,6 +40,7 @@ export(sig_genes_extract) export(sig_genes_extract_all) export(sort_clusters) export(vis_clus) +export(vis_clus_c) export(vis_clus_p) export(vis_gene) export(vis_gene_p) diff --git a/R/vis_clus_c.R b/R/vis_clus_c.R new file mode 100644 index 00000000..4428ae9f --- /dev/null +++ b/R/vis_clus_c.R @@ -0,0 +1,116 @@ +#' Sample spatial cluster visualization workhorse function for centroid data +#' +#' This function visualizes the clusters for one given sample at the spot-level +#' using (by default) the histology information on the background. This is the +#' function that does all the plotting behind [vis_clus()]. To visualize +#' gene-level (or any continuous variable) use [vis_gene_p()]. +#' +#' @inheritParams vis_clus +#' @param d A `data.frame()` with the sample-level information. This is +#' typically obtained using `cbind(colData(spe), spatialCoords(spe))`. +#' @param title The title for the plot. +#' +#' @return A [ggplot2][ggplot2::ggplot] object. +#' @export +#' @importFrom tibble tibble +#' @importFrom SpatialExperiment imgData scaleFactors +#' @importFrom S4Vectors metadata +#' @importFrom grid rasterGrob unit +#' @family Spatial cluster visualization functions +#' +#' @examples +#' +#' if (enough_ram()) { +#' ## Obtain the necessary data +#' if (!exists("spe")) spe <- fetch_data("spe") +#' +#' spe_sub <- qs2::qs_read("../Xenium/XeniumIO_test/spe_Xenium_test_Br1039.qs2") +#' table(spe_sub$sample_id) +#' head(spatialCoords(spe_sub)) +#' +#' summary(spatialCoords(spe_sub)[,"x_centroid"]) +#' summary(spatialCoords(spe_sub)[,"y_centroid"]) +#' +#' ## add catagorical variable +#' spe_sub$x_half <- ifelse(spatialCoords(spe_sub)[,"x_centroid"] < 3088, "west", "east") +#' table(spe_sub$x_half) +#' +#' p <- vis_clus_c( +#' spe = spe_sub, +#' d = as.data.frame(cbind(colData(spe_sub), SpatialExperiment::spatialCoords(spe_sub)), optional = TRUE), +#' clustervar = "x_half", +#' sampleid = "sample01.1", +#' #colors = libd_layer_colors, +#' colors = c(east = "red", west = "blue"), +#' title = "Xenium test", +#' point_size = 1, +#' alpha = 0.5 +#' ) +#' print(p) +#' +#' ## Clean up +#' rm(spe_sub) +#' } +vis_clus_c <- + function(spe, + d, + clustervar, + sampleid = unique(spe$sample_id)[1], + colors, + title, + alpha = NA, + point_size = 1, + auto_crop = TRUE, + na_color = "#CCCCCC40") { + ## Some variables + x_centroid <- y_centroid <- key <- NULL + # stopifnot(all(c("x_centroid", "y_centroid", "key") %in% colnames(d))) + + + # ## Crop the image if needed + # if (auto_crop) { + # frame_lims <- + # frame_limits(spe, sampleid = sampleid, image_id = image_id) + # img <- + # img[frame_lims$y_min:frame_lims$y_max, frame_lims$x_min:frame_lims$x_max] + # adjust <- + # list(x = frame_lims$x_min, y = frame_lims$y_min) + # } else { + adjust <- list(x = 0, y = 0) + # } + # + p <- ggplot( + d, + aes( + x = x_centroid, + y = y_centroid, + fill = factor(!!sym(clustervar)), + key = key + ) + ) + + p <- p + + geom_point( + shape = 21, + size = point_size, + stroke = 0, + colour = "transparent", + alpha = alpha + ) + + coord_fixed(expand = FALSE) + + scale_fill_manual(values = colors, na.value = na_color) + + xlab("") + ylab("") + + labs(fill = NULL) + + ggtitle(title) + + theme_set(theme_bw(base_size = 20)) + + theme( + panel.grid.major = element_blank(), + panel.grid.minor = element_blank(), + panel.background = element_blank(), + axis.line = element_blank(), + axis.text = element_blank(), + axis.ticks = element_blank(), + legend.box.spacing = unit(0, "µm") + ) + return(p) + } diff --git a/man/vis_clus_c.Rd b/man/vis_clus_c.Rd new file mode 100644 index 00000000..595b0912 --- /dev/null +++ b/man/vis_clus_c.Rd @@ -0,0 +1,108 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/vis_clus_c.R +\name{vis_clus_c} +\alias{vis_clus_c} +\title{Sample spatial cluster visualization workhorse function} +\usage{ +vis_clus_c( + spe, + d, + clustervar, + sampleid = unique(spe$sample_id)[1], + colors, + title, + alpha = NA, + point_size = 2, + auto_crop = TRUE, + na_color = "#CCCCCC40" +) +} +\arguments{ +\item{spe}{A +\link[SpatialExperiment:SpatialExperiment]{SpatialExperiment-class} +object. See \code{\link[=fetch_data]{fetch_data()}} for how to download some example objects or +\code{\link[=read10xVisiumWrapper]{read10xVisiumWrapper()}} to read in \code{spaceranger --count} output files and +build your own \code{spe} object.} + +\item{d}{A \code{data.frame()} with the sample-level information. This is +typically obtained using \code{cbind(colData(spe), spatialCoords(spe))}.} + +\item{clustervar}{A \code{character(1)} with the name of the \code{colData(spe)} +column that has the cluster values.} + +\item{sampleid}{A \code{character(1)} specifying which sample to plot from +\code{colData(spe)$sample_id} (formerly \code{colData(spe)$sample_name}).} + +\item{colors}{A vector of colors to use for visualizing the clusters +from \code{clustervar}. If the vector has names, then those should match the +values of \code{clustervar}.} + +\item{title}{The title for the plot.} + +\item{alpha}{A \code{numeric(1)} in the \verb{[0, 1]} range that specifies the +transparency level of the data on the spots.} + +\item{point_size}{A \code{numeric(1)} specifying the size of the points. Defaults +to \code{1.25}. Some colors look better if you use \code{2} for instance.} + +\item{auto_crop}{A \code{logical(1)} indicating whether to automatically crop +the image / plotting area, which is useful if the Visium capture area is +not centered on the image and if the image is not a square.} + +\item{na_color}{A \code{character(1)} specifying a color for the NA values. +If you set \code{alpha = NA} then it's best to set \code{na_color} to a color that has +alpha blending already, which will make non-NA values pop up more and the NA +values will show with a lighter color. This behavior is lost when \code{alpha} is +set to a non-\code{NA} value.} +} +\value{ +A \link[ggplot2:ggplot]{ggplot2} object. +} +\description{ +This function visualizes the clusters for one given sample at the spot-level +using (by default) the histology information on the background. This is the +function that does all the plotting behind \code{\link[=vis_clus]{vis_clus()}}. To visualize +gene-level (or any continuous variable) use \code{\link[=vis_gene_p]{vis_gene_p()}}. +} +\examples{ + +if (enough_ram()) { + ## Obtain the necessary data + if (!exists("spe")) spe <- fetch_data("spe") + + spe_sub <- qs2::qs_read("../Xenium/XeniumIO_test/spe_Xenium_test_Br1039.qs2") + table(spe_sub$sample_id) + head(spatialCoords(spe_sub)) + + summary(spatialCoords(spe_sub)[,"x_centroid"]) + summary(spatialCoords(spe_sub)[,"y_centroid"]) + + ## add catagorical variable + spe_sub$x_half <- ifelse(spatialCoords(spe_sub)[,"x_centroid"] < 3088, "west", "east") + table(spe_sub$x_half) + + p <- vis_clus_c( + spe = spe_sub, + d = as.data.frame(cbind(colData(spe_sub), SpatialExperiment::spatialCoords(spe_sub)), optional = TRUE), + clustervar = "x_half", + sampleid = "sample01.1", + #colors = libd_layer_colors, + colors = c(east = "red", west = "blue"), + title = "Xenium test", + point_size = 1 + ) + print(p) + + ## Clean up + rm(spe_sub) +} +} +\seealso{ +Other Spatial cluster visualization functions: +\code{\link{frame_limits}()}, +\code{\link{vis_clus}()}, +\code{\link{vis_clus_p}()}, +\code{\link{vis_grid_clus}()}, +\code{\link{vis_image}()} +} +\concept{Spatial cluster visualization functions} From e534d9f2c02a4e0715826809a5deb52c46b9f44e Mon Sep 17 00:00:00 2001 From: lahuuki Date: Tue, 5 May 2026 14:30:02 -0400 Subject: [PATCH 05/36] Update desciptions for vis_clus_*() functions --- R/vis_clus_c.R | 11 ++++++----- R/vis_clus_p.R | 12 +++++++----- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/R/vis_clus_c.R b/R/vis_clus_c.R index 4428ae9f..83c6ad8f 100644 --- a/R/vis_clus_c.R +++ b/R/vis_clus_c.R @@ -1,9 +1,10 @@ -#' Sample spatial cluster visualization workhorse function for centroid data +#' Sample spatial cluster visualization workhorse function for Xenium data with +#' centroid based spatailCoords #' -#' This function visualizes the clusters for one given sample at the spot-level -#' using (by default) the histology information on the background. This is the -#' function that does all the plotting behind [vis_clus()]. To visualize -#' gene-level (or any continuous variable) use [vis_gene_p()]. +#' This function visualizes clusters or categorical variables for one given +#' sample at the cell-level. This is the function that does all the plotting +#' behind [vis_clus(datatype = "Xenium")]. To visualize gene-level +#' (or any continuous variable) use [vis_gene_c()]. #' #' @inheritParams vis_clus #' @param d A `data.frame()` with the sample-level information. This is diff --git a/R/vis_clus_p.R b/R/vis_clus_p.R index b7a381d6..bd88149e 100644 --- a/R/vis_clus_p.R +++ b/R/vis_clus_p.R @@ -1,9 +1,11 @@ -#' Sample spatial cluster visualization workhorse function +#' Sample spatial cluster visualization workhorse function for Visium data with +#' pixel based spatailCoords aligned to images. #' -#' This function visualizes the clusters for one given sample at the spot-level -#' using (by default) the histology information on the background. This is the -#' function that does all the plotting behind [vis_clus()]. To visualize -#' gene-level (or any continuous variable) use [vis_gene_p()]. +#' This function visualizes the clusters or categorical variables for one given +#' sample at the spot-level using (by default) the histology information on the +#' background. This is the function that does all the plotting behind +#' [vis_clus()]. To visualize gene-level (or any continuous variable) use +#' [vis_gene_p()]. #' #' @inheritParams vis_clus #' @param d A `data.frame()` with the sample-level information. This is From 4ac9bc9d74b94464d7aea434ee55115b1cab0385 Mon Sep 17 00:00:00 2001 From: lahuuki Date: Tue, 5 May 2026 14:30:27 -0400 Subject: [PATCH 06/36] Update docs --- man/frame_limits.Rd | 1 + man/vis_clus.Rd | 14 +++++++++++++- man/vis_clus_c.Rd | 16 +++++++++------- man/vis_clus_p.Rd | 13 ++++++++----- man/vis_grid_clus.Rd | 3 ++- man/vis_image.Rd | 1 + 6 files changed, 34 insertions(+), 14 deletions(-) diff --git a/man/frame_limits.Rd b/man/frame_limits.Rd index b24b2af4..d224682f 100644 --- a/man/frame_limits.Rd +++ b/man/frame_limits.Rd @@ -58,6 +58,7 @@ if (enough_ram()) { \seealso{ Other Spatial cluster visualization functions: \code{\link{vis_clus}()}, +\code{\link{vis_clus_c}()}, \code{\link{vis_clus_p}()}, \code{\link{vis_grid_clus}()}, \code{\link{vis_image}()} diff --git a/man/vis_clus.Rd b/man/vis_clus.Rd index c01c8028..61014f7c 100644 --- a/man/vis_clus.Rd +++ b/man/vis_clus.Rd @@ -18,6 +18,7 @@ vis_clus( na_color = "#CCCCCC40", is_stitched = FALSE, guide_point_size = point_size, + datatype = c("Visium", "Xenium"), ... ) } @@ -69,7 +70,17 @@ particular, expects a logical colData column \code{exclude_overlapping} specifying which spots to exclude from the plot. Sets \code{auto_crop = FALSE}.} \item{guide_point_size}{A \code{numeric(1)} specifying the size of the points in -guide. Defaults to \code{point_size}. Increase to improve visability.} +guide. Defaults to \code{point_size}. Increase to improve visibility.} + +\item{datatype}{A \code{character(1)} specifying the type of spatial transcriptomics +data stored in \code{spe}. Supported options are: +\describe{ +\item{\code{"Visium"}}{(Default) Expects \code{pxl_col_in_fullres} and +\code{pxl_row_in_fullres} as columns of \code{spatialCoords(spe)}. Enables +image handling via the \code{spatialData} slot.} +\item{\code{"Xenium"}}{Expects \code{x_centroid} and \code{y_centroid} as columns +of \code{spatialCoords(spe)}.} +}} \item{...}{Passed to \link[base:paste]{paste0()} for making the title of the plot following the \code{sampleid}.} @@ -158,6 +169,7 @@ if (enough_ram()) { \seealso{ Other Spatial cluster visualization functions: \code{\link{frame_limits}()}, +\code{\link{vis_clus_c}()}, \code{\link{vis_clus_p}()}, \code{\link{vis_grid_clus}()}, \code{\link{vis_image}()} diff --git a/man/vis_clus_c.Rd b/man/vis_clus_c.Rd index 595b0912..d0cb1dcb 100644 --- a/man/vis_clus_c.Rd +++ b/man/vis_clus_c.Rd @@ -2,7 +2,8 @@ % Please edit documentation in R/vis_clus_c.R \name{vis_clus_c} \alias{vis_clus_c} -\title{Sample spatial cluster visualization workhorse function} +\title{Sample spatial cluster visualization workhorse function for Xenium data with +centroid based spatailCoords} \usage{ vis_clus_c( spe, @@ -12,7 +13,7 @@ vis_clus_c( colors, title, alpha = NA, - point_size = 2, + point_size = 1, auto_crop = TRUE, na_color = "#CCCCCC40" ) @@ -59,10 +60,10 @@ set to a non-\code{NA} value.} A \link[ggplot2:ggplot]{ggplot2} object. } \description{ -This function visualizes the clusters for one given sample at the spot-level -using (by default) the histology information on the background. This is the -function that does all the plotting behind \code{\link[=vis_clus]{vis_clus()}}. To visualize -gene-level (or any continuous variable) use \code{\link[=vis_gene_p]{vis_gene_p()}}. +This function visualizes clusters or categorical variables for one given +sample at the cell-level. This is the function that does all the plotting +behind \link{vis_clus(datatype = "Xenium")}. To visualize gene-level +(or any continuous variable) use \code{\link[=vis_gene_c]{vis_gene_c()}}. } \examples{ @@ -89,7 +90,8 @@ if (enough_ram()) { #colors = libd_layer_colors, colors = c(east = "red", west = "blue"), title = "Xenium test", - point_size = 1 + point_size = 1, + alpha = 0.5 ) print(p) diff --git a/man/vis_clus_p.Rd b/man/vis_clus_p.Rd index ced22bc7..0a7b1dc0 100644 --- a/man/vis_clus_p.Rd +++ b/man/vis_clus_p.Rd @@ -2,7 +2,8 @@ % Please edit documentation in R/vis_clus_p.R \name{vis_clus_p} \alias{vis_clus_p} -\title{Sample spatial cluster visualization workhorse function} +\title{Sample spatial cluster visualization workhorse function for Visium data with +pixel based spatailCoords aligned to images.} \usage{ vis_clus_p( spe, @@ -68,10 +69,11 @@ set to a non-\code{NA} value.} A \link[ggplot2:ggplot]{ggplot2} object. } \description{ -This function visualizes the clusters for one given sample at the spot-level -using (by default) the histology information on the background. This is the -function that does all the plotting behind \code{\link[=vis_clus]{vis_clus()}}. To visualize -gene-level (or any continuous variable) use \code{\link[=vis_gene_p]{vis_gene_p()}}. +This function visualizes the clusters or categorical variables for one given +sample at the spot-level using (by default) the histology information on the +background. This is the function that does all the plotting behind +\code{\link[=vis_clus]{vis_clus()}}. To visualize gene-level (or any continuous variable) use +\code{\link[=vis_gene_p]{vis_gene_p()}}. } \examples{ @@ -101,6 +103,7 @@ if (enough_ram()) { Other Spatial cluster visualization functions: \code{\link{frame_limits}()}, \code{\link{vis_clus}()}, +\code{\link{vis_clus_c}()}, \code{\link{vis_grid_clus}()}, \code{\link{vis_image}()} } diff --git a/man/vis_grid_clus.Rd b/man/vis_grid_clus.Rd index ba69f862..30d292db 100644 --- a/man/vis_grid_clus.Rd +++ b/man/vis_grid_clus.Rd @@ -86,7 +86,7 @@ particular, expects a logical colData column \code{exclude_overlapping} specifying which spots to exclude from the plot. Sets \code{auto_crop = FALSE}.} \item{guide_point_size}{A \code{numeric(1)} specifying the size of the points in -guide. Defaults to \code{point_size}. Increase to improve visability.} +guide. Defaults to \code{point_size}. Increase to improve visibility.} \item{...}{Passed to \link[base:paste]{paste0()} for making the title of the plot following the \code{sampleid}.} @@ -130,6 +130,7 @@ if (enough_ram()) { Other Spatial cluster visualization functions: \code{\link{frame_limits}()}, \code{\link{vis_clus}()}, +\code{\link{vis_clus_c}()}, \code{\link{vis_clus_p}()}, \code{\link{vis_image}()} } diff --git a/man/vis_image.Rd b/man/vis_image.Rd index 943c3a6b..d0626aaa 100644 --- a/man/vis_image.Rd +++ b/man/vis_image.Rd @@ -77,6 +77,7 @@ if (enough_ram()) { Other Spatial cluster visualization functions: \code{\link{frame_limits}()}, \code{\link{vis_clus}()}, +\code{\link{vis_clus_c}()}, \code{\link{vis_clus_p}()}, \code{\link{vis_grid_clus}()} } From 90d348810d252563da55252defcf611680373666 Mon Sep 17 00:00:00 2001 From: lahuuki Date: Tue, 5 May 2026 16:28:51 -0400 Subject: [PATCH 07/36] Add xenium example to experimenthub metadata for LFF ERC --- inst/extdata/metadata_LFF_spatial_ERC.csv | 1 + 1 file changed, 1 insertion(+) diff --git a/inst/extdata/metadata_LFF_spatial_ERC.csv b/inst/extdata/metadata_LFF_spatial_ERC.csv index 91bcc02a..fac6d51c 100644 --- a/inst/extdata/metadata_LFF_spatial_ERC.csv +++ b/inst/extdata/metadata_LFF_spatial_ERC.csv @@ -7,3 +7,4 @@ "LFF_spatial_ERC_snRNAseq_pseudobulk_subcluster","Pseudo-bulked SingleCellExperiment object LFF_spatial_ERC human brain (ERC) snRNA-seq data (n = 31) at 38 subcluster resolution, from the Chromium platform from 10x Genomics generated by the Lieber Institute for Brain Development (LIBD) and available through the spatialLIBD Bioconductor package.","3.21","GRCh38","GTF","https://bioconductor.org/packages/spatialLIBD","Nov 20 2025","Homo sapiens",9606,TRUE,"LIBD","Leonardo Collado-Torres ","SingleCellExperiment","Rds","spatialLIBD/spatialLIBD_files/sce_subcluster_pseudobulk-cell_type_anno.rds","Visium_snRNAseq_AD_Alzheimer_Disease_APOE_ERC_spatialLIBD" "LFF_spatial_ERC_snRNAseq_modeling_results_broad","List of modeling results at the 9 broad cell type resolution for the LFF_spatial_ERC human brain (ERC) for snRNA-seq (n = 31) from the Chromium platform from 10x Genomics generated by the Lieber Institute for Brain Development (LIBD) and available through the spatialLIBD Bioconductor package.","3.21","GRCh38","GTF","https://bioconductor.org/packages/spatialLIBD","Nov 20 2025","Homo sapiens",9606,TRUE,"LIBD","Leonardo Collado-Torres ","list","Rds","spatialLIBD/spatialLIBD_files/sce_subcluster_pseudobulk-cell_type_broad.rds","Visium_snRNAseq_AD_Alzheimer_Disease_APOE_ERC_spatialLIBD" "LFF_spatial_ERC_snRNAseq_modeling_results_subcluster","List of modeling results at the 38 subcluster resolution for the LFF_spatial_ERC human brain (ERC) for snRNA-seq (n = 31) from the Chromium platform from 10x Genomics generated by the Lieber Institute for Brain Development (LIBD) and available through the spatialLIBD Bioconductor package.","3.21","GRCh38","GTF","https://bioconductor.org/packages/spatialLIBD","Nov 20 2025","Homo sapiens",9606,TRUE,"LIBD","Leonardo Collado-Torres ","list","Rds","spatialLIBD/spatialLIBD_files/sce_subcluster_modeling_results-cell_type_anno.rds","Visium_snRNAseq_AD_Alzheimer_Disease_APOE_ERC_spatialLIBD" +"spe_xenium_example","SpatialExperiment object of Xenium data for the LFF_spatial_ERC human brain (ERC) spatial transcriptomics data (n = 2) from the Xenium platform from 10x Genomics generated by the Lieber Institute for Brain Development (LIBD) and available through the spatialLIBD Bioconductor package.","3.24","GRCh38","GTF","https://bioconductor.org/packages/spatialLIBD","May 5 2026","Homo sapiens",9606,TRUE,"LIBD","Leonardo Collado-Torres ","SpatialExperiment","FilePath","spatialLIBD/spatialLIBD_files/spe_Xenium_test.rds","Visium_snRNAseq_AD_Alzheimer_Disease_APOE_ERC_spatialLIBD" \ No newline at end of file From 2fed7895e6d0ce4ab924871fedea9fbfd20c32ca Mon Sep 17 00:00:00 2001 From: lahuuki Date: Tue, 5 May 2026 16:44:46 -0400 Subject: [PATCH 08/36] Add spe_xenium_example to fetch_data (need to update github when ready) --- R/fetch_data.R | 13 ++++++++++++- man/fetch_data.Rd | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/R/fetch_data.R b/R/fetch_data.R index 76bfa185..83383b53 100644 --- a/R/fetch_data.R +++ b/R/fetch_data.R @@ -157,7 +157,8 @@ fetch_data <- "LFF_spatial_ERC_snRNAseq_pseudobulk_broad", "LFF_spatial_ERC_snRNAseq_pseudobulk_subcluster", "LFF_spatial_ERC_snRNAseq_modeling_results_broad", - "LFF_spatial_ERC_snRNAseq_modeling_results_subcluster" + "LFF_spatial_ERC_snRNAseq_modeling_results_subcluster", + "spe_xenium_example" ), destdir = tempdir(), eh = ExperimentHub::ExperimentHub(), @@ -409,6 +410,16 @@ fetch_data <- file_name <- "sce_subcluster_pseudobulk-cell_type_anno.rds" url <- "https://www.dropbox.com/scl/fi/y42pv7k02luvznwqii2rm/sce_subcluster_modeling_results-cell_type_anno.rds?rlkey=17c0ybowjpejdxc71tuxlcfna&dl=1" + } else if ( + type == "spe_xenium_example" + ) { + tag <- "LFF_spatial_ERC" + hub_title <- type + + ## While EH is not set-up + file_name <- "spe_Xenium_test.rds" + url <- + "https://www.dropbox.com/scl/fi/y42pv7k02luvznwqii2rm/sce_subcluster_modeling_results-cell_type_anno.rds?rlkey=17c0ybowjpejdxc71tuxlcfna&dl=1" } file_path <- file.path(destdir, file_name) diff --git a/man/fetch_data.Rd b/man/fetch_data.Rd index 1c90603f..73eed5fa 100644 --- a/man/fetch_data.Rd +++ b/man/fetch_data.Rd @@ -19,7 +19,7 @@ fetch_data( "LFF_spatial_ERC_snRNAseq_pseudobulk_broad", "LFF_spatial_ERC_snRNAseq_pseudobulk_subcluster", "LFF_spatial_ERC_snRNAseq_modeling_results_broad", - "LFF_spatial_ERC_snRNAseq_modeling_results_subcluster"), + "LFF_spatial_ERC_snRNAseq_modeling_results_subcluster", "spe_xenium_example"), destdir = tempdir(), eh = ExperimentHub::ExperimentHub(), bfc = BiocFileCache::BiocFileCache() From 22392ae0b811e21f7fe139bac5464c0d633db1df Mon Sep 17 00:00:00 2001 From: lahuuki Date: Tue, 5 May 2026 16:45:20 -0400 Subject: [PATCH 09/36] clean up vis_clus_c example --- R/vis_clus_c.R | 17 +++++++++-------- man/vis_clus_c.Rd | 17 +++++++++-------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/R/vis_clus_c.R b/R/vis_clus_c.R index 83c6ad8f..3dce112f 100644 --- a/R/vis_clus_c.R +++ b/R/vis_clus_c.R @@ -23,17 +23,18 @@ #' #' if (enough_ram()) { #' ## Obtain the necessary data -#' if (!exists("spe")) spe <- fetch_data("spe") +#' if (!exists("spe")) spe <- fetch_data("spe_Xenium_test") #' -#' spe_sub <- qs2::qs_read("../Xenium/XeniumIO_test/spe_Xenium_test_Br1039.qs2") -#' table(spe_sub$sample_id) -#' head(spatialCoords(spe_sub)) +#' # spe <- readRDS("../Xenium/XeniumIO_test/spe_Xenium_test.rds") +#' +#' ## Prepare the data for the plotting function +#' spe_sub <- spe[, spe$sample_id == "sample1"] #' -#' summary(spatialCoords(spe_sub)[,"x_centroid"]) -#' summary(spatialCoords(spe_sub)[,"y_centroid"]) +#' # summary(spatialCoords(spe_sub)[,"x_centroid"]) +#' # summary(spatialCoords(spe_sub)[,"y_centroid"]) #' #' ## add catagorical variable -#' spe_sub$x_half <- ifelse(spatialCoords(spe_sub)[,"x_centroid"] < 3088, "west", "east") +#' spe_sub$x_half <- ifelse(spatialCoords(spe_sub)[,"x_centroid"] < 3088, "left", "right") #' table(spe_sub$x_half) #' #' p <- vis_clus_c( @@ -42,7 +43,7 @@ #' clustervar = "x_half", #' sampleid = "sample01.1", #' #colors = libd_layer_colors, -#' colors = c(east = "red", west = "blue"), +#' colors = c(left = "red", right = "blue"), #' title = "Xenium test", #' point_size = 1, #' alpha = 0.5 diff --git a/man/vis_clus_c.Rd b/man/vis_clus_c.Rd index d0cb1dcb..c991baf4 100644 --- a/man/vis_clus_c.Rd +++ b/man/vis_clus_c.Rd @@ -69,17 +69,18 @@ behind \link{vis_clus(datatype = "Xenium")}. To visualize gene-level if (enough_ram()) { ## Obtain the necessary data - if (!exists("spe")) spe <- fetch_data("spe") + if (!exists("spe")) spe <- fetch_data("spe_Xenium_test") - spe_sub <- qs2::qs_read("../Xenium/XeniumIO_test/spe_Xenium_test_Br1039.qs2") - table(spe_sub$sample_id) - head(spatialCoords(spe_sub)) + # spe <- readRDS("../Xenium/XeniumIO_test/spe_Xenium_test.rds") + + ## Prepare the data for the plotting function + spe_sub <- spe[, spe$sample_id == "sample1"] - summary(spatialCoords(spe_sub)[,"x_centroid"]) - summary(spatialCoords(spe_sub)[,"y_centroid"]) + # summary(spatialCoords(spe_sub)[,"x_centroid"]) + # summary(spatialCoords(spe_sub)[,"y_centroid"]) ## add catagorical variable - spe_sub$x_half <- ifelse(spatialCoords(spe_sub)[,"x_centroid"] < 3088, "west", "east") + spe_sub$x_half <- ifelse(spatialCoords(spe_sub)[,"x_centroid"] < 3088, "left", "right") table(spe_sub$x_half) p <- vis_clus_c( @@ -88,7 +89,7 @@ if (enough_ram()) { clustervar = "x_half", sampleid = "sample01.1", #colors = libd_layer_colors, - colors = c(east = "red", west = "blue"), + colors = c(left = "red", right = "blue"), title = "Xenium test", point_size = 1, alpha = 0.5 From c981336f000e5aae202494ba7dfc9b3b594168e5 Mon Sep 17 00:00:00 2001 From: lahuuki Date: Tue, 5 May 2026 16:45:37 -0400 Subject: [PATCH 10/36] Add xenium example --- R/vis_clus.R | 21 ++++++++++++++++++++- man/vis_clus.Rd | 21 ++++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/R/vis_clus.R b/R/vis_clus.R index 0e25825f..cce03144 100644 --- a/R/vis_clus.R +++ b/R/vis_clus.R @@ -63,7 +63,7 @@ #' @examples #' #' if (enough_ram()) { -#' ## Obtain the necessary data +#' ## Obtain the necessary data: Visium example #' if (!exists("spe")) spe <- fetch_data("spe") #' #' ## Check the colors defined by Lukas M Weber @@ -126,6 +126,25 @@ #' ... = " LIBD Layers" #' ) #' print(p5) +#' +#' ## Obtain the necessary data: Xenium example +#' if (!exists("spe_xenium")) spe_xenium <- fetch_data("spe_Xenium_test") +#' +#' spe_xenium$x_half <- ifelse(spatialCoords(spe_xenium)[,"x_centroid"] < 3088, "left", "right") +#' +#' p6 <- vis_clus( +#' spe = spe_xenium, +#' clustervar = "layer_guess_reordered", +#' sampleid = "sample1", +#' colors = c(left = "red", right = "blue"), +#' na_color = "white", +#' point_size = 1, +#' guide_point_size = 3, +#' datatype = "Xenium" +#' ) +#' print(p6) +#' +#' #' #' } vis_clus <- function( diff --git a/man/vis_clus.Rd b/man/vis_clus.Rd index 61014f7c..a66dd7b7 100644 --- a/man/vis_clus.Rd +++ b/man/vis_clus.Rd @@ -100,7 +100,7 @@ data and title for \code{\link[=vis_clus_p]{vis_clus_p()}}. \examples{ if (enough_ram()) { - ## Obtain the necessary data + ## Obtain the necessary data: Visium example if (!exists("spe")) spe <- fetch_data("spe") ## Check the colors defined by Lukas M Weber @@ -163,6 +163,25 @@ if (enough_ram()) { ... = " LIBD Layers" ) print(p5) + + ## Obtain the necessary data: Xenium example + if (!exists("spe_xenium")) spe_xenium <- fetch_data("spe_Xenium_test") + + spe_xenium$x_half <- ifelse(spatialCoords(spe_xenium)[,"x_centroid"] < 3088, "left", "right") + + p6 <- vis_clus( + spe = spe_xenium, + clustervar = "layer_guess_reordered", + sampleid = "sample1", + colors = c(left = "red", right = "blue"), + na_color = "white", + point_size = 1, + guide_point_size = 3, + datatype = "Xenium" + ) + print(p6) + + } } From 1b734e2f2fbb8eb5116babfa0b091bb547da3852 Mon Sep 17 00:00:00 2001 From: lahuuki Date: Tue, 5 May 2026 16:52:23 -0400 Subject: [PATCH 11/36] use vis_clus_c for xenium datatype --- R/vis_clus.R | 102 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 66 insertions(+), 36 deletions(-) diff --git a/R/vis_clus.R b/R/vis_clus.R index cce03144..127fe07c 100644 --- a/R/vis_clus.R +++ b/R/vis_clus.R @@ -188,49 +188,79 @@ vis_clus <- function( ) } + ## subset spe to selected sample + spe_sub <- spe[, spe$sample_id == sampleid] + ## Check for valid datatype datatype <- match.arg(datatype) - - # Check validity of spatial coordinates by datatype - if (datatype == "Visium" & !setequal(c("pxl_col_in_fullres", "pxl_row_in_fullres"), colnames(spatialCoords(spe)))) { - stop( - "Abnormal spatial coordinates for Visium datatype: should have 'pxl_row_in_fullres' and 'pxl_col_in_fullres' columns.", - call. = FALSE - ) - } else if (datatype == "Xenium" & !setequal(c("x_centroid", "y_centroid"), colnames(spatialCoords(spe)))) { - stop( - "Abnormal spatial coordinates for Xisium datatype: should have 'x_centroid' and 'y_centroid' columns.", - call. = FALSE - ) + + if(datatype == "Visium"){ + + # Check validity of spatial coordinates by datatype + if (!setequal(c("pxl_col_in_fullres", "pxl_row_in_fullres"), colnames(spatialCoords(spe)))) { + stop( + "Abnormal spatial coordinates for Visium datatype: should have 'pxl_row_in_fullres' and 'pxl_col_in_fullres' columns.", + call. = FALSE + ) } - + + d <- as.data.frame(cbind(colData(spe_sub), SpatialExperiment::spatialCoords(spe_sub)), optional = TRUE) + spe_sub <- spe[, spe$sample_id == sampleid] - + if (is_stitched) { - # Drop excluded spots and calculate an appropriate point size - temp <- prep_stitched_data(spe_sub, point_size, image_id) - spe_sub <- temp$spe - point_size <- temp$point_size - - # Frame limits are poorly defined for stitched data - auto_crop <- FALSE + # Drop excluded spots and calculate an appropriate point size + temp <- prep_stitched_data(spe_sub, point_size, image_id) + spe_sub <- temp$spe + point_size <- temp$point_size + + # Frame limits are poorly defined for stitched data + auto_crop <- FALSE } - - d <- as.data.frame(cbind(colData(spe_sub), SpatialExperiment::spatialCoords(spe_sub)), optional = TRUE) - + vis_clus_p( - spe = spe_sub, - d = d, - clustervar = clustervar, - sampleid = sampleid, - spatial = spatial, - title = paste0(sampleid, ...), - colors = get_colors(colors, d[, clustervar]), - image_id = image_id, - alpha = alpha, - point_size = point_size, - auto_crop = auto_crop, - na_color = na_color + spe = spe_sub, + d = d, + clustervar = clustervar, + sampleid = sampleid, + spatial = spatial, + title = paste0(sampleid, ...), + colors = get_colors(colors, d[, clustervar]), + image_id = image_id, + alpha = alpha, + point_size = point_size, + auto_crop = auto_crop, + na_color = na_color ) + guides(fill = guide_legend(override.aes = list(size = guide_point_size))) + + } else if(datatype == "Xenium"){ + + if (datatype == "Xenium" & !setequal(c("x_centroid", "y_centroid"), colnames(spatialCoords(spe)))) { + stop( + "Abnormal spatial coordinates for Xisium datatype: should have 'x_centroid' and 'y_centroid' columns.", + call. = FALSE + ) + } + + vis_clus_c( + spe = spe_sub, + d = d, + clustervar = clustervar, + sampleid = sampleid, + title = paste0(sampleid, ...), + colors = get_colors(colors, d[, clustervar]), + alpha = alpha, + point_size = point_size, + na_color = na_color + ) + + guides(fill = guide_legend(override.aes = list(size = guide_point_size))) + + + } + + + + + } From 3c8795329e34162e43d6808c0c0fd0430d81aea4 Mon Sep 17 00:00:00 2001 From: lahuuki Date: Tue, 5 May 2026 16:57:33 -0400 Subject: [PATCH 12/36] fix unit --- R/vis_clus_c.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/vis_clus_c.R b/R/vis_clus_c.R index 3dce112f..baaec20c 100644 --- a/R/vis_clus_c.R +++ b/R/vis_clus_c.R @@ -112,7 +112,7 @@ vis_clus_c <- axis.line = element_blank(), axis.text = element_blank(), axis.ticks = element_blank(), - legend.box.spacing = unit(0, "µm") + legend.box.spacing = unit(0, "pt") ) return(p) } From 539787b855b7c67903bd24cb19413183f224e4f7 Mon Sep 17 00:00:00 2001 From: lahuuki Date: Tue, 5 May 2026 17:03:04 -0400 Subject: [PATCH 13/36] fix example, redefine d --- R/vis_clus.R | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/R/vis_clus.R b/R/vis_clus.R index 127fe07c..8036f2e4 100644 --- a/R/vis_clus.R +++ b/R/vis_clus.R @@ -134,11 +134,12 @@ #' #' p6 <- vis_clus( #' spe = spe_xenium, -#' clustervar = "layer_guess_reordered", +#' clustervar = "x_half", #' sampleid = "sample1", #' colors = c(left = "red", right = "blue"), #' na_color = "white", #' point_size = 1, +#' alpha = 0.5, #' guide_point_size = 3, #' datatype = "Xenium" #' ) @@ -205,9 +206,7 @@ vis_clus <- function( } d <- as.data.frame(cbind(colData(spe_sub), SpatialExperiment::spatialCoords(spe_sub)), optional = TRUE) - - spe_sub <- spe[, spe$sample_id == sampleid] - + if (is_stitched) { # Drop excluded spots and calculate an appropriate point size temp <- prep_stitched_data(spe_sub, point_size, image_id) @@ -243,6 +242,8 @@ vis_clus <- function( ) } + d <- as.data.frame(cbind(colData(spe_sub), SpatialExperiment::spatialCoords(spe_sub)), optional = TRUE) + vis_clus_c( spe = spe_sub, d = d, From 3587f09463841edef4fb091706e675bb6e136619 Mon Sep 17 00:00:00 2001 From: lahuuki Date: Tue, 5 May 2026 17:03:19 -0400 Subject: [PATCH 14/36] Update doc with fixed example --- man/vis_clus.Rd | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/man/vis_clus.Rd b/man/vis_clus.Rd index a66dd7b7..e8aa2ac1 100644 --- a/man/vis_clus.Rd +++ b/man/vis_clus.Rd @@ -171,11 +171,12 @@ if (enough_ram()) { p6 <- vis_clus( spe = spe_xenium, - clustervar = "layer_guess_reordered", + clustervar = "x_half", sampleid = "sample1", colors = c(left = "red", right = "blue"), na_color = "white", point_size = 1, + alpha = 0.5, guide_point_size = 3, datatype = "Xenium" ) From 92b7ae59f558d94a11cddefd0f4dcfa2b4d9939a Mon Sep 17 00:00:00 2001 From: lahuuki Date: Wed, 6 May 2026 14:39:13 -0400 Subject: [PATCH 15/36] Fix cross link in docs (vis_gene_c not defined yet) --- R/vis_clus.R | 4 ++-- R/vis_clus_c.R | 3 ++- man/vis_clus.Rd | 4 ++-- man/vis_clus_c.Rd | 3 ++- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/R/vis_clus.R b/R/vis_clus.R index 8036f2e4..6b5c658a 100644 --- a/R/vis_clus.R +++ b/R/vis_clus.R @@ -58,7 +58,7 @@ #' @export #' @importFrom SpatialExperiment spatialCoords #' @details This function subsets `spe` to the given sample and prepares the -#' data and title for [vis_clus_p()]. +#' data and title for [vis_clus_p()] or [vis_clus_c()]. #' #' @examples #' @@ -135,7 +135,7 @@ #' p6 <- vis_clus( #' spe = spe_xenium, #' clustervar = "x_half", -#' sampleid = "sample1", +#' sampleid = "sample2", #' colors = c(left = "red", right = "blue"), #' na_color = "white", #' point_size = 1, diff --git a/R/vis_clus_c.R b/R/vis_clus_c.R index baaec20c..002284b0 100644 --- a/R/vis_clus_c.R +++ b/R/vis_clus_c.R @@ -4,7 +4,8 @@ #' This function visualizes clusters or categorical variables for one given #' sample at the cell-level. This is the function that does all the plotting #' behind [vis_clus(datatype = "Xenium")]. To visualize gene-level -#' (or any continuous variable) use [vis_gene_c()]. +#' (or any continuous variable) use [vis_gene_p()] +#' TODO fix when vis_gene_c is defined. #' #' @inheritParams vis_clus #' @param d A `data.frame()` with the sample-level information. This is diff --git a/man/vis_clus.Rd b/man/vis_clus.Rd index e8aa2ac1..3ac00d7a 100644 --- a/man/vis_clus.Rd +++ b/man/vis_clus.Rd @@ -95,7 +95,7 @@ gene-level (or any continuous variable) use \code{\link[=vis_gene]{vis_gene()}}. } \details{ This function subsets \code{spe} to the given sample and prepares the -data and title for \code{\link[=vis_clus_p]{vis_clus_p()}}. +data and title for \code{\link[=vis_clus_p]{vis_clus_p()}} or \code{\link[=vis_clus_c]{vis_clus_c()}}. } \examples{ @@ -172,7 +172,7 @@ if (enough_ram()) { p6 <- vis_clus( spe = spe_xenium, clustervar = "x_half", - sampleid = "sample1", + sampleid = "sample2", colors = c(left = "red", right = "blue"), na_color = "white", point_size = 1, diff --git a/man/vis_clus_c.Rd b/man/vis_clus_c.Rd index c991baf4..f7ea4e30 100644 --- a/man/vis_clus_c.Rd +++ b/man/vis_clus_c.Rd @@ -63,7 +63,8 @@ A \link[ggplot2:ggplot]{ggplot2} object. This function visualizes clusters or categorical variables for one given sample at the cell-level. This is the function that does all the plotting behind \link{vis_clus(datatype = "Xenium")}. To visualize gene-level -(or any continuous variable) use \code{\link[=vis_gene_c]{vis_gene_c()}}. +(or any continuous variable) use \code{\link[=vis_gene_p]{vis_gene_p()}} +TODO fix when vis_gene_c is defined. } \examples{ From b84be4c35daaf8c5514de6735d374d0efa0d2943 Mon Sep 17 00:00:00 2001 From: lahuuki Date: Wed, 6 May 2026 14:40:57 -0400 Subject: [PATCH 16/36] Fix typo in example --- R/vis_clus.R | 2 +- man/vis_clus.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/vis_clus.R b/R/vis_clus.R index 6b5c658a..b0687378 100644 --- a/R/vis_clus.R +++ b/R/vis_clus.R @@ -128,7 +128,7 @@ #' print(p5) #' #' ## Obtain the necessary data: Xenium example -#' if (!exists("spe_xenium")) spe_xenium <- fetch_data("spe_Xenium_test") +#' if (!exists("spe_xenium")) spe_xenium <- fetch_data("spe_xenium_test") #' #' spe_xenium$x_half <- ifelse(spatialCoords(spe_xenium)[,"x_centroid"] < 3088, "left", "right") #' diff --git a/man/vis_clus.Rd b/man/vis_clus.Rd index 3ac00d7a..1561b23d 100644 --- a/man/vis_clus.Rd +++ b/man/vis_clus.Rd @@ -165,7 +165,7 @@ if (enough_ram()) { print(p5) ## Obtain the necessary data: Xenium example - if (!exists("spe_xenium")) spe_xenium <- fetch_data("spe_Xenium_test") + if (!exists("spe_xenium")) spe_xenium <- fetch_data("spe_xenium_test") spe_xenium$x_half <- ifelse(spatialCoords(spe_xenium)[,"x_centroid"] < 3088, "left", "right") From 1a0206d2dc676704a656da1432c9d77e789a08a0 Mon Sep 17 00:00:00 2001 From: lahuuki Date: Wed, 6 May 2026 14:44:43 -0400 Subject: [PATCH 17/36] Bump date and version --- DESCRIPTION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 6f46909e..1885d49e 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,8 +1,8 @@ Package: spatialLIBD Title: spatialLIBD: an R/Bioconductor package to visualize spatially-resolved transcriptomics data -Version: 1.23.2 -Date: 2026-01-09 +Version: 1.23.3 +Date: 2026-06-05 Authors@R: c( person("Leonardo", "Collado-Torres", role = c("aut", "cre"), From a4f21a32757422cb708e96ad7129e8184840db6b Mon Sep 17 00:00:00 2001 From: lahuuki Date: Wed, 6 May 2026 15:28:14 -0400 Subject: [PATCH 18/36] Add vis_gene_c --- R/vis_gene_c.R | 128 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 R/vis_gene_c.R diff --git a/R/vis_gene_c.R b/R/vis_gene_c.R new file mode 100644 index 00000000..ee3fe9e5 --- /dev/null +++ b/R/vis_gene_c.R @@ -0,0 +1,128 @@ +#' Sample spatial gene visualization workhorse function for Xenium data with +#' centroid based spatailCoords +#' +#' This function visualizes the gene expression stored in `assays(spe)` or any +#' continuous variable stored in `colData(spe)` for one given sample at the +#' spot-level using (by default) the histology information on the background. +#' This is the function that does all the plotting behind +#' [vis_gene(datatype = "Xenium")] +#' To visualize clusters (or any discrete variable) use [vis_clus_c()]. +#' +#' @param d A `data.frame()` with the sample-level information. This is +#' typically obtained using `cbind(colData(spe), spatialCoords(spe))`. +#' The `data.frame` has to contain +#' a column with the continuous variable data to plot stored under `d$COUNT`. +#' @param legend_title A `character(1)` specifying the legend title. +#' @inheritParams vis_clus_c +#' @inheritParams vis_gene +#' +#' @return A [ggplot2][ggplot2::ggplot] object. +#' @export +#' @importFrom tibble tibble +#' @importFrom SpatialExperiment imgData scaleFactors +#' @importFrom S4Vectors metadata +#' @importFrom grid rasterGrob unit +#' @family Spatial gene visualization functions +#' +#' @examples +#' +#' if (enough_ram()) { +#' ## Obtain the necessary data +#' if (!exists("spe")) spe <- fetch_data("spe_xenium_test") +#' +#' ## Prepare the data for the plotting function +#' spe_sub <- spe[, spe$sample_id == "sample1"] +#' df <- as.data.frame(cbind(colData(spe_sub), SpatialExperiment::spatialCoords(spe_sub)), optional = TRUE) +#' df$COUNT <- df$detected_gex +#' +#' ## Don't plot the histology information +#' p <- vis_gene_c( +#' spe = spe_sub, +#' d = df, +#' sampleid = "sample1", +#' title = "sample1 detected_gex", +#' point_size = 1 +#' ) +#' print(p) +#' +#' ## Clean up +#' rm(spe_sub) +#' } +vis_gene_c <- + function(spe, + d, + sampleid = unique(spe$sample_id)[1], + title, + viridis = TRUE, + alpha = NA, + cont_colors = if (viridis) viridisLite::viridis(21) else c("aquamarine4", "springgreen", "goldenrod", "red"), + point_size = 2, + na_color = "#CCCCCC40", + legend_title = "") { + ## Some variables + y_centroid <- x_centroid <- key <- COUNT <- NULL + # stopifnot(all(c("x_centroid", "y_centroid", "COUNT", "key") %in% colnames(d))) + + + # ## Crop the image if needed + # if (auto_crop) { + # frame_lims <- + # frame_limits(spe, sampleid = sampleid, image_id = image_id) + # img <- + # img[frame_lims$y_min:frame_lims$y_max, frame_lims$x_min:frame_lims$x_max] + # adjust <- + # list(x = frame_lims$x_min, y = frame_lims$y_min) + # } else { + adjust <- list(x = 0, y = 0) + # } + + p <- + ggplot( + d, + aes( + x = x_centroid, + y = y_centroid, + fill = COUNT, + color = COUNT, + key = key + ) + ) + + p <- p + + geom_point( + shape = 21, + size = point_size, + stroke = 0, + colour = "transparent", + alpha = alpha + ) + + coord_fixed(expand = FALSE) + + p <- p + scale_fill_gradientn( + name = legend_title, + colors = cont_colors, + na.value = na_color + ) + + scale_color_gradientn( + name = legend_title, + colors = cont_colors, + na.value = na_color + ) + + p <- p + + xlab("") + ylab("") + + labs(fill = NULL, color = NULL) + + ggtitle(title) + + theme_set(theme_bw(base_size = 20)) + + theme( + panel.grid.major = element_blank(), + panel.grid.minor = element_blank(), + panel.background = element_blank(), + axis.line = element_blank(), + axis.text = element_blank(), + axis.ticks = element_blank(), + legend.title = element_text(size = 10), + legend.box.spacing = unit(0, "pt") + ) + return(p) + } From 0823d989d3fcfba23bdb96dc1351055b431162a5 Mon Sep 17 00:00:00 2001 From: lahuuki Date: Wed, 6 May 2026 15:29:05 -0400 Subject: [PATCH 19/36] Update docs --- NAMESPACE | 1 + R/vis_clus_c.R | 5 +-- man/vis_clus_c.Rd | 5 +-- man/vis_gene.Rd | 1 + man/vis_gene_c.Rd | 105 +++++++++++++++++++++++++++++++++++++++++++ man/vis_gene_p.Rd | 1 + man/vis_grid_gene.Rd | 1 + 7 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 man/vis_gene_c.Rd diff --git a/NAMESPACE b/NAMESPACE index 1d34a0d6..bad76dfe 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -43,6 +43,7 @@ export(vis_clus) export(vis_clus_c) export(vis_clus_p) export(vis_gene) +export(vis_gene_c) export(vis_gene_p) export(vis_grid_clus) export(vis_grid_gene) diff --git a/R/vis_clus_c.R b/R/vis_clus_c.R index 002284b0..0c1981be 100644 --- a/R/vis_clus_c.R +++ b/R/vis_clus_c.R @@ -4,8 +4,7 @@ #' This function visualizes clusters or categorical variables for one given #' sample at the cell-level. This is the function that does all the plotting #' behind [vis_clus(datatype = "Xenium")]. To visualize gene-level -#' (or any continuous variable) use [vis_gene_p()] -#' TODO fix when vis_gene_c is defined. +#' (or any continuous variable) use [vis_gene_c()]. #' #' @inheritParams vis_clus #' @param d A `data.frame()` with the sample-level information. This is @@ -24,7 +23,7 @@ #' #' if (enough_ram()) { #' ## Obtain the necessary data -#' if (!exists("spe")) spe <- fetch_data("spe_Xenium_test") +#' if (!exists("spe")) spe <- fetch_data("spe_xenium_test") #' #' # spe <- readRDS("../Xenium/XeniumIO_test/spe_Xenium_test.rds") #' diff --git a/man/vis_clus_c.Rd b/man/vis_clus_c.Rd index f7ea4e30..8149ea63 100644 --- a/man/vis_clus_c.Rd +++ b/man/vis_clus_c.Rd @@ -63,14 +63,13 @@ A \link[ggplot2:ggplot]{ggplot2} object. This function visualizes clusters or categorical variables for one given sample at the cell-level. This is the function that does all the plotting behind \link{vis_clus(datatype = "Xenium")}. To visualize gene-level -(or any continuous variable) use \code{\link[=vis_gene_p]{vis_gene_p()}} -TODO fix when vis_gene_c is defined. +(or any continuous variable) use \code{\link[=vis_gene_c]{vis_gene_c()}}. } \examples{ if (enough_ram()) { ## Obtain the necessary data - if (!exists("spe")) spe <- fetch_data("spe_Xenium_test") + if (!exists("spe")) spe <- fetch_data("spe_xenium_test") # spe <- readRDS("../Xenium/XeniumIO_test/spe_Xenium_test.rds") diff --git a/man/vis_gene.Rd b/man/vis_gene.Rd index 49fe828c..a0f4fe0f 100644 --- a/man/vis_gene.Rd +++ b/man/vis_gene.Rd @@ -238,6 +238,7 @@ if (enough_ram()) { } \seealso{ Other Spatial gene visualization functions: +\code{\link{vis_gene_c}()}, \code{\link{vis_gene_p}()}, \code{\link{vis_grid_gene}()} } diff --git a/man/vis_gene_c.Rd b/man/vis_gene_c.Rd new file mode 100644 index 00000000..dfe88b29 --- /dev/null +++ b/man/vis_gene_c.Rd @@ -0,0 +1,105 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/vis_gene_c.R +\name{vis_gene_c} +\alias{vis_gene_c} +\title{Sample spatial gene visualization workhorse function for Xenium data with +centroid based spatailCoords} +\usage{ +vis_gene_c( + spe, + d, + sampleid = unique(spe$sample_id)[1], + title, + viridis = TRUE, + alpha = NA, + cont_colors = if (viridis) viridisLite::viridis(21) else c("aquamarine4", + "springgreen", "goldenrod", "red"), + point_size = 2, + na_color = "#CCCCCC40", + legend_title = "" +) +} +\arguments{ +\item{spe}{A +\link[SpatialExperiment:SpatialExperiment]{SpatialExperiment-class} +object. See \code{\link[=fetch_data]{fetch_data()}} for how to download some example objects or +\code{\link[=read10xVisiumWrapper]{read10xVisiumWrapper()}} to read in \code{spaceranger --count} output files and +build your own \code{spe} object.} + +\item{d}{A \code{data.frame()} with the sample-level information. This is +typically obtained using \code{cbind(colData(spe), spatialCoords(spe))}. +The \code{data.frame} has to contain +a column with the continuous variable data to plot stored under \code{d$COUNT}.} + +\item{sampleid}{A \code{character(1)} specifying which sample to plot from +\code{colData(spe)$sample_id} (formerly \code{colData(spe)$sample_name}).} + +\item{title}{The title for the plot.} + +\item{viridis}{A \code{logical(1)} whether to use the color-blind friendly +palette from \link[viridisLite:viridis]{viridis} or the color palette used +in the paper that was chosen for contrast when visualizing the data on +top of the histology image. One issue is being able to differentiate low +values from NA ones due to the purple-ish histology information that is +dependent on cell density.} + +\item{alpha}{A \code{numeric(1)} in the \verb{[0, 1]} range that specifies the +transparency level of the data on the spots.} + +\item{cont_colors}{A \code{character()} vector of colors that supersedes the +\code{viridis} argument.} + +\item{point_size}{A \code{numeric(1)} specifying the size of the points. Defaults +to \code{1.25}. Some colors look better if you use \code{2} for instance.} + +\item{na_color}{A \code{character(1)} specifying a color for the NA values. +If you set \code{alpha = NA} then it's best to set \code{na_color} to a color that has +alpha blending already, which will make non-NA values pop up more and the NA +values will show with a lighter color. This behavior is lost when \code{alpha} is +set to a non-\code{NA} value.} + +\item{legend_title}{A \code{character(1)} specifying the legend title.} +} +\value{ +A \link[ggplot2:ggplot]{ggplot2} object. +} +\description{ +This function visualizes the gene expression stored in \code{assays(spe)} or any +continuous variable stored in \code{colData(spe)} for one given sample at the +spot-level using (by default) the histology information on the background. +This is the function that does all the plotting behind +\link{vis_gene(datatype = "Xenium")} +To visualize clusters (or any discrete variable) use \code{\link[=vis_clus_c]{vis_clus_c()}}. +} +\examples{ + +if (enough_ram()) { + ## Obtain the necessary data + if (!exists("spe")) spe <- fetch_data("spe_xenium_test") + + ## Prepare the data for the plotting function + spe_sub <- spe[, spe$sample_id == "sample1"] + df <- as.data.frame(cbind(colData(spe_sub), SpatialExperiment::spatialCoords(spe_sub)), optional = TRUE) + df$COUNT <- df$detected_gex + + ## Don't plot the histology information + p <- vis_gene_c( + spe = spe_sub, + d = df, + sampleid = "sample1", + title = "sample1 detected_gex", + point_size = 1 + ) + print(p) + + ## Clean up + rm(spe_sub) +} +} +\seealso{ +Other Spatial gene visualization functions: +\code{\link{vis_gene}()}, +\code{\link{vis_gene_p}()}, +\code{\link{vis_grid_gene}()} +} +\concept{Spatial gene visualization functions} diff --git a/man/vis_gene_p.Rd b/man/vis_gene_p.Rd index ca9d6a29..294bab48 100644 --- a/man/vis_gene_p.Rd +++ b/man/vis_gene_p.Rd @@ -111,6 +111,7 @@ if (enough_ram()) { \seealso{ Other Spatial gene visualization functions: \code{\link{vis_gene}()}, +\code{\link{vis_gene_c}()}, \code{\link{vis_grid_gene}()} } \concept{Spatial gene visualization functions} diff --git a/man/vis_grid_gene.Rd b/man/vis_grid_gene.Rd index 647cdce9..77018545 100644 --- a/man/vis_grid_gene.Rd +++ b/man/vis_grid_gene.Rd @@ -147,6 +147,7 @@ if (enough_ram()) { \seealso{ Other Spatial gene visualization functions: \code{\link{vis_gene}()}, +\code{\link{vis_gene_c}()}, \code{\link{vis_gene_p}()} } \concept{Spatial gene visualization functions} From 25cb967f24d83cf279561ddc56210923398fd12d Mon Sep 17 00:00:00 2001 From: lahuuki Date: Wed, 6 May 2026 15:36:55 -0400 Subject: [PATCH 20/36] Add datatype arg to vis_gene --- R/vis_gene.R | 10 ++++++++++ man/vis_gene.Rd | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/R/vis_gene.R b/R/vis_gene.R index 1e129164..d87cc497 100644 --- a/R/vis_gene.R +++ b/R/vis_gene.R @@ -41,6 +41,15 @@ #' of 0.95 sets the top 5% of expression values to the 95th percentile value. #' This can help make the color scale more dynamic in the presence of high #' outliers. Defaults to `1`, which effectively performs no capping. +#' @param datatype A `character(1)` specifying the type of spatial transcriptomics +#' data stored in `spe`. Supported options are: +#' \describe{ +#' \item{`"Visium"`}{(Default) Expects `pxl_col_in_fullres` and +#' `pxl_row_in_fullres` as columns of `spatialCoords(spe)`. Enables +#' image handling via the `spatialData` slot.} +#' \item{`"Xenium"`}{Expects `x_centroid` and `y_centroid` as columns +#' of `spatialCoords(spe)`.} +#' } #' #' @return A [ggplot2][ggplot2::ggplot] object. #' @export @@ -180,6 +189,7 @@ vis_gene <- multi_gene_method = c("z_score", "pca", "sparsity"), is_stitched = FALSE, cap_percentile = 1, + datatype = c("Visium", "Xenium"), ...) { multi_gene_method <- rlang::arg_match(multi_gene_method) # Verify existence and legitimacy of 'sampleid' diff --git a/man/vis_gene.Rd b/man/vis_gene.Rd index a0f4fe0f..047a9217 100644 --- a/man/vis_gene.Rd +++ b/man/vis_gene.Rd @@ -22,6 +22,7 @@ vis_gene( multi_gene_method = c("z_score", "pca", "sparsity"), is_stitched = FALSE, cap_percentile = 1, + datatype = c("Visium", "Xenium"), ... ) } @@ -107,6 +108,16 @@ of 0.95 sets the top 5\% of expression values to the 95th percentile value. This can help make the color scale more dynamic in the presence of high outliers. Defaults to \code{1}, which effectively performs no capping.} +\item{datatype}{A \code{character(1)} specifying the type of spatial transcriptomics +data stored in \code{spe}. Supported options are: +\describe{ +\item{\code{"Visium"}}{(Default) Expects \code{pxl_col_in_fullres} and +\code{pxl_row_in_fullres} as columns of \code{spatialCoords(spe)}. Enables +image handling via the \code{spatialData} slot.} +\item{\code{"Xenium"}}{Expects \code{x_centroid} and \code{y_centroid} as columns +of \code{spatialCoords(spe)}.} +}} + \item{...}{Passed to \link[base:paste]{paste0()} for making the title of the plot following the \code{sampleid}.} } From ab82404d0debdbc19dc95b25259ec6490d1a43d1 Mon Sep 17 00:00:00 2001 From: lahuuki Date: Wed, 6 May 2026 15:48:14 -0400 Subject: [PATCH 21/36] Fix typo --- R/vis_clus.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/vis_clus.R b/R/vis_clus.R index b0687378..c4505429 100644 --- a/R/vis_clus.R +++ b/R/vis_clus.R @@ -237,7 +237,7 @@ vis_clus <- function( if (datatype == "Xenium" & !setequal(c("x_centroid", "y_centroid"), colnames(spatialCoords(spe)))) { stop( - "Abnormal spatial coordinates for Xisium datatype: should have 'x_centroid' and 'y_centroid' columns.", + "Abnormal spatial coordinates for Xenium datatype: should have 'x_centroid' and 'y_centroid' columns.", call. = FALSE ) } From 7d0ac11111ecd8fefa27563dc41b6b5269bf949b Mon Sep 17 00:00:00 2001 From: lahuuki Date: Wed, 6 May 2026 15:48:47 -0400 Subject: [PATCH 22/36] Adapt vis_gene for xenium data --- R/vis_gene.R | 270 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 194 insertions(+), 76 deletions(-) diff --git a/R/vis_gene.R b/R/vis_gene.R index d87cc497..b86b6e67 100644 --- a/R/vis_gene.R +++ b/R/vis_gene.R @@ -209,114 +209,118 @@ vis_gene <- if (!(assayname %in% names(assays(spe)))) { stop(sprintf("'%s' is not an assay in 'spe'", assayname), call. = FALSE) } - - # Check validity of spatial coordinates - if (!setequal(c("pxl_col_in_fullres", "pxl_row_in_fullres"), colnames(spatialCoords(spe)))) { + + ## Check for valid datatype + datatype <- match.arg(datatype) + + if(datatype == "Visium"){ + # Check validity of spatial coordinates + if (!setequal(c("pxl_col_in_fullres", "pxl_row_in_fullres"), colnames(spatialCoords(spe)))) { stop( - "Abnormal spatial coordinates: should have 'pxl_row_in_fullres' and 'pxl_col_in_fullres' columns.", - call. = FALSE + "Abnormal spatial coordinates for Visium datatype: should have 'pxl_row_in_fullres' and 'pxl_col_in_fullres' columns.", + call. = FALSE ) - } - - # Validate 'cap_percentile' - if (cap_percentile <= 0 || cap_percentile > 1) { + } + + # Validate 'cap_percentile' + if (cap_percentile <= 0 || cap_percentile > 1) { stop("'cap_percentile' must be in (0, 1]", call. = FALSE) - } - - spe_sub <- spe[, spe$sample_id == sampleid] - - if (is_stitched) { + } + + spe_sub <- spe[, spe$sample_id == sampleid] + + if (is_stitched) { # Drop excluded spots and calculate an appropriate point size temp <- prep_stitched_data(spe_sub, point_size, image_id) spe_sub <- temp$spe point_size <- temp$point_size - + # Frame limits are poorly defined for stitched data auto_crop <- FALSE - } - - d <- as.data.frame(cbind(colData(spe_sub), SpatialExperiment::spatialCoords(spe_sub)), optional = TRUE) - - # Verify legitimacy of names in geneid - geneid_is_valid <- (geneid %in% rowData(spe_sub)$gene_search) | + } + + d <- as.data.frame(cbind(colData(spe_sub), SpatialExperiment::spatialCoords(spe_sub)), optional = TRUE) + + # Verify legitimacy of names in geneid + geneid_is_valid <- (geneid %in% rowData(spe_sub)$gene_search) | (geneid %in% rownames(spe_sub)) | (geneid %in% colnames(colData(spe_sub))) - if (any(!geneid_is_valid)) { + if (any(!geneid_is_valid)) { stop( - "Could not find the 'geneid'(s) ", - paste(geneid[!geneid_is_valid], collapse = ", "), - call. = FALSE + "Could not find the 'geneid'(s) ", + paste(geneid[!geneid_is_valid], collapse = ", "), + call. = FALSE ) - } - - # Grab any continuous colData columns and verify they're all numeric - cont_cols <- colData(spe_sub)[ + } + + # Grab any continuous colData columns and verify they're all numeric + cont_cols <- colData(spe_sub)[ , geneid[geneid %in% colnames(colData(spe_sub))], drop = FALSE - ] - if (!all(sapply(cont_cols, class) %in% c("numeric", "integer"))) { + ] + if (!all(sapply(cont_cols, class) %in% c("numeric", "integer"))) { stop( - "'geneid' can not contain non-numeric colData columns.", - call. = FALSE + "'geneid' can not contain non-numeric colData columns.", + call. = FALSE ) - } - cont_cols <- as.matrix(cont_cols) - - # Get the integer indices of each gene in the SpatialExperiment, since we - # aren't guaranteed that rownames are gene names - remaining_geneid <- geneid[!(geneid %in% colnames(colData(spe_sub)))] - valid_gene_indices <- unique( + } + cont_cols <- as.matrix(cont_cols) + + # Get the integer indices of each gene in the SpatialExperiment, since we + # aren't guaranteed that rownames are gene names + remaining_geneid <- geneid[!(geneid %in% colnames(colData(spe_sub)))] + valid_gene_indices <- unique( c( - match(remaining_geneid, rowData(spe_sub)$gene_search), - match(remaining_geneid, rownames(spe_sub)) + match(remaining_geneid, rowData(spe_sub)$gene_search), + match(remaining_geneid, rownames(spe_sub)) ) - ) - valid_gene_indices <- valid_gene_indices[!is.na(valid_gene_indices)] - - # Grab any genes - gene_cols <- t( + ) + valid_gene_indices <- valid_gene_indices[!is.na(valid_gene_indices)] + + # Grab any genes + gene_cols <- t( as.matrix(assays(spe_sub[valid_gene_indices, ])[[assayname]]) - ) - - # Combine into one matrix where rows are samples and columns are continuous - # features - cont_matrix <- cbind(cont_cols, gene_cols) - - # Determine plot and legend titles - if (ncol(cont_matrix) == 1) { + ) + + # Combine into one matrix where rows are samples and columns are continuous + # features + cont_matrix <- cbind(cont_cols, gene_cols) + + # Determine plot and legend titles + if (ncol(cont_matrix) == 1) { plot_title <- paste(sampleid, geneid, ...) d$COUNT <- cont_matrix[, 1] if (!(geneid %in% colnames(colData(spe_sub)))) { - legend_title <- sprintf("%s\n min > %s", assayname, minCount) + legend_title <- sprintf("%s\n min > %s", assayname, minCount) } else { - legend_title <- sprintf("min > %s", minCount) + legend_title <- sprintf("min > %s", minCount) } - } else { + } else { plot_title <- paste(sampleid, ...) if (multi_gene_method == "z_score") { - d$COUNT <- multi_gene_z_score(cont_matrix) - legend_title <- paste("Z score\n min > ", minCount) + d$COUNT <- multi_gene_z_score(cont_matrix) + legend_title <- paste("Z score\n min > ", minCount) } else if (multi_gene_method == "sparsity") { - d$COUNT <- multi_gene_sparsity(cont_matrix) - legend_title <- paste("Prop. nonzero\n min > ", minCount) + d$COUNT <- multi_gene_sparsity(cont_matrix) + legend_title <- paste("Prop. nonzero\n min > ", minCount) } else { # must be 'pca' - d$COUNT <- multi_gene_pca(cont_matrix) - legend_title <- paste("PC1\n min > ", minCount) + d$COUNT <- multi_gene_pca(cont_matrix) + legend_title <- paste("PC1\n min > ", minCount) } - } - - # Cap the expression values at the given percentile, if applicable - if (cap_percentile < 1) { + } + + # Cap the expression values at the given percentile, if applicable + if (cap_percentile < 1) { sorted_count <- sort(d$COUNT) cap <- sorted_count[ - as.integer(round(length(sorted_count) * cap_percentile)) + as.integer(round(length(sorted_count) * cap_percentile)) ] d$COUNT[d$COUNT > cap] <- cap - } - - d$COUNT[d$COUNT <= minCount] <- NA - - p <- vis_gene_p( + } + + d$COUNT[d$COUNT <= minCount] <- NA + + p <- vis_gene_p( spe = spe_sub, d = d, sampleid = sampleid, @@ -330,6 +334,120 @@ vis_gene <- auto_crop = auto_crop, na_color = na_color, legend_title = legend_title - ) - return(p) + ) + return(p) + } else if(datatype == "Xenium"){ + # Check validity of spatial coordinates + if (!setequal(c("x_centroid", "y_centroid"), colnames(spatialCoords(spe)))) { + stop( + "Abnormal spatial coordinates for Xenium datatype: should have 'x_centroid' and 'y_centroid' columns.", + call. = FALSE + ) + } + + # Validate 'cap_percentile' + if (cap_percentile <= 0 || cap_percentile > 1) { + stop("'cap_percentile' must be in (0, 1]", call. = FALSE) + } + + spe_sub <- spe[, spe$sample_id == sampleid] + + d <- as.data.frame(cbind(colData(spe_sub), SpatialExperiment::spatialCoords(spe_sub)), optional = TRUE) + + # Verify legitimacy of names in geneid + geneid_is_valid <- (geneid %in% rowData(spe_sub)$gene_search) | + (geneid %in% rownames(spe_sub)) | + (geneid %in% colnames(colData(spe_sub))) + if (any(!geneid_is_valid)) { + stop( + "Could not find the 'geneid'(s) ", + paste(geneid[!geneid_is_valid], collapse = ", "), + call. = FALSE + ) + } + + # Grab any continuous colData columns and verify they're all numeric + cont_cols <- colData(spe_sub)[ + , geneid[geneid %in% colnames(colData(spe_sub))], + drop = FALSE + ] + if (!all(sapply(cont_cols, class) %in% c("numeric", "integer"))) { + stop( + "'geneid' can not contain non-numeric colData columns.", + call. = FALSE + ) + } + cont_cols <- as.matrix(cont_cols) + + # Get the integer indices of each gene in the SpatialExperiment, since we + # aren't guaranteed that rownames are gene names + remaining_geneid <- geneid[!(geneid %in% colnames(colData(spe_sub)))] + valid_gene_indices <- unique( + c( + match(remaining_geneid, rowData(spe_sub)$gene_search), + match(remaining_geneid, rownames(spe_sub)) + ) + ) + valid_gene_indices <- valid_gene_indices[!is.na(valid_gene_indices)] + + # Grab any genes + gene_cols <- t( + as.matrix(assays(spe_sub[valid_gene_indices, ])[[assayname]]) + ) + + # Combine into one matrix where rows are samples and columns are continuous + # features + cont_matrix <- cbind(cont_cols, gene_cols) + + # Determine plot and legend titles + if (ncol(cont_matrix) == 1) { + plot_title <- paste(sampleid, geneid, ...) + d$COUNT <- cont_matrix[, 1] + if (!(geneid %in% colnames(colData(spe_sub)))) { + legend_title <- sprintf("%s\n min > %s", assayname, minCount) + } else { + legend_title <- sprintf("min > %s", minCount) + } + } else { + plot_title <- paste(sampleid, ...) + if (multi_gene_method == "z_score") { + d$COUNT <- multi_gene_z_score(cont_matrix) + legend_title <- paste("Z score\n min > ", minCount) + } else if (multi_gene_method == "sparsity") { + d$COUNT <- multi_gene_sparsity(cont_matrix) + legend_title <- paste("Prop. nonzero\n min > ", minCount) + } else { # must be 'pca' + d$COUNT <- multi_gene_pca(cont_matrix) + legend_title <- paste("PC1\n min > ", minCount) + } + } + + # Cap the expression values at the given percentile, if applicable + if (cap_percentile < 1) { + sorted_count <- sort(d$COUNT) + cap <- sorted_count[ + as.integer(round(length(sorted_count) * cap_percentile)) + ] + d$COUNT[d$COUNT > cap] <- cap + } + + d$COUNT[d$COUNT <= minCount] <- NA + + p <- vis_gene_c( + spe = spe_sub, + d = d, + sampleid = sampleid, + title = plot_title, + viridis = viridis, + alpha = alpha, + cont_colors = cont_colors, + point_size = point_size, + auto_crop = auto_crop, + na_color = na_color, + legend_title = legend_title + ) + return(p) + } + + } From 4134d6a8631fbee403fc37d4ac121720b7e036d0 Mon Sep 17 00:00:00 2001 From: lahuuki Date: Wed, 6 May 2026 16:02:52 -0400 Subject: [PATCH 23/36] Add xenium example, fix auto_crop --- R/vis_gene.R | 15 ++++++++++++++- man/vis_gene.Rd | 14 ++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/R/vis_gene.R b/R/vis_gene.R index b86b6e67..8c8bced3 100644 --- a/R/vis_gene.R +++ b/R/vis_gene.R @@ -171,6 +171,20 @@ #' multi_gene_method = "pca" #' ) #' print(p8) +#' +#' ## Obtain the necessary data: Xenium example +#' if (!exists("spe_xenium")) spe_xenium <- fetch_data("spe_xenium_test") +#' +#' p9 <- vis_gene( +#' spe = spe_xenium, +#' sampleid = "sample2", +#' geneid = "MBP", +#' assayname = "counts", +#' point_size = 1, +#' datatype = "Xenium" +#' ) +#' print(p9) +#' #' } vis_gene <- function(spe, @@ -442,7 +456,6 @@ vis_gene <- alpha = alpha, cont_colors = cont_colors, point_size = point_size, - auto_crop = auto_crop, na_color = na_color, legend_title = legend_title ) diff --git a/man/vis_gene.Rd b/man/vis_gene.Rd index 047a9217..19236d96 100644 --- a/man/vis_gene.Rd +++ b/man/vis_gene.Rd @@ -245,6 +245,20 @@ if (enough_ram()) { multi_gene_method = "pca" ) print(p8) + + ## Obtain the necessary data: Xenium example + if (!exists("spe_xenium")) spe_xenium <- fetch_data("spe_xenium_test") + + p9 <- vis_gene( + spe = spe_xenium, + sampleid = "sample2", + geneid = "MBP", + assayname = "counts", + point_size = 1, + datatype = "Xenium" + ) + print(p9) + } } \seealso{ From 3649ebf28841be6bef2c0656455a872f885a71bf Mon Sep 17 00:00:00 2001 From: lahuuki Date: Thu, 7 May 2026 10:02:49 -0400 Subject: [PATCH 24/36] Update date --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 1885d49e..4817bb91 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -2,7 +2,7 @@ Package: spatialLIBD Title: spatialLIBD: an R/Bioconductor package to visualize spatially-resolved transcriptomics data Version: 1.23.3 -Date: 2026-06-05 +Date: 2026-06-07 Authors@R: c( person("Leonardo", "Collado-Torres", role = c("aut", "cre"), From dfb9a3c2e198f258501acd7b544b9485bf5dd801 Mon Sep 17 00:00:00 2001 From: lahuuki Date: Thu, 7 May 2026 10:47:04 -0400 Subject: [PATCH 25/36] fix spe_xenium_example name --- R/vis_clus.R | 2 +- R/vis_clus_c.R | 2 +- man/vis_clus.Rd | 2 +- man/vis_clus_c.Rd | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/R/vis_clus.R b/R/vis_clus.R index c4505429..02af7983 100644 --- a/R/vis_clus.R +++ b/R/vis_clus.R @@ -128,7 +128,7 @@ #' print(p5) #' #' ## Obtain the necessary data: Xenium example -#' if (!exists("spe_xenium")) spe_xenium <- fetch_data("spe_xenium_test") +#' if (!exists("spe_xenium")) spe_xenium <- fetch_data("spe_xenium_example") #' #' spe_xenium$x_half <- ifelse(spatialCoords(spe_xenium)[,"x_centroid"] < 3088, "left", "right") #' diff --git a/R/vis_clus_c.R b/R/vis_clus_c.R index 0c1981be..e1e9ba6e 100644 --- a/R/vis_clus_c.R +++ b/R/vis_clus_c.R @@ -23,7 +23,7 @@ #' #' if (enough_ram()) { #' ## Obtain the necessary data -#' if (!exists("spe")) spe <- fetch_data("spe_xenium_test") +#' if (!exists("spe")) spe <- fetch_data("spe_xenium_example") #' #' # spe <- readRDS("../Xenium/XeniumIO_test/spe_Xenium_test.rds") #' diff --git a/man/vis_clus.Rd b/man/vis_clus.Rd index 1561b23d..ce8faf8d 100644 --- a/man/vis_clus.Rd +++ b/man/vis_clus.Rd @@ -165,7 +165,7 @@ if (enough_ram()) { print(p5) ## Obtain the necessary data: Xenium example - if (!exists("spe_xenium")) spe_xenium <- fetch_data("spe_xenium_test") + if (!exists("spe_xenium")) spe_xenium <- fetch_data("spe_xenium_example") spe_xenium$x_half <- ifelse(spatialCoords(spe_xenium)[,"x_centroid"] < 3088, "left", "right") diff --git a/man/vis_clus_c.Rd b/man/vis_clus_c.Rd index 8149ea63..615c070e 100644 --- a/man/vis_clus_c.Rd +++ b/man/vis_clus_c.Rd @@ -69,7 +69,7 @@ behind \link{vis_clus(datatype = "Xenium")}. To visualize gene-level if (enough_ram()) { ## Obtain the necessary data - if (!exists("spe")) spe <- fetch_data("spe_xenium_test") + if (!exists("spe")) spe <- fetch_data("spe_xenium_example") # spe <- readRDS("../Xenium/XeniumIO_test/spe_Xenium_test.rds") From 3f56fba4ce535c06eb0b90dd6f1183c2615cfb99 Mon Sep 17 00:00:00 2001 From: lahuuki Date: Thu, 7 May 2026 10:50:42 -0400 Subject: [PATCH 26/36] Fix Cross-refrences in descriptions --- R/vis_clus_c.R | 2 +- R/vis_gene_c.R | 6 +++--- man/vis_clus_c.Rd | 2 +- man/vis_gene_c.Rd | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/R/vis_clus_c.R b/R/vis_clus_c.R index e1e9ba6e..59d2f366 100644 --- a/R/vis_clus_c.R +++ b/R/vis_clus_c.R @@ -3,7 +3,7 @@ #' #' This function visualizes clusters or categorical variables for one given #' sample at the cell-level. This is the function that does all the plotting -#' behind [vis_clus(datatype = "Xenium")]. To visualize gene-level +#' behind [vis_clus()] when `datatype = "Xenium"`. To visualize gene-level #' (or any continuous variable) use [vis_gene_c()]. #' #' @inheritParams vis_clus diff --git a/R/vis_gene_c.R b/R/vis_gene_c.R index ee3fe9e5..201330ae 100644 --- a/R/vis_gene_c.R +++ b/R/vis_gene_c.R @@ -4,9 +4,9 @@ #' This function visualizes the gene expression stored in `assays(spe)` or any #' continuous variable stored in `colData(spe)` for one given sample at the #' spot-level using (by default) the histology information on the background. -#' This is the function that does all the plotting behind -#' [vis_gene(datatype = "Xenium")] -#' To visualize clusters (or any discrete variable) use [vis_clus_c()]. +#' This is the function that does all the plotting behind [vis_clus()] when +#' `datatype = "Xenium"`. To visualize clusters (or any discrete variable) +#' use [vis_clus_c()]. #' #' @param d A `data.frame()` with the sample-level information. This is #' typically obtained using `cbind(colData(spe), spatialCoords(spe))`. diff --git a/man/vis_clus_c.Rd b/man/vis_clus_c.Rd index 615c070e..f049eafd 100644 --- a/man/vis_clus_c.Rd +++ b/man/vis_clus_c.Rd @@ -62,7 +62,7 @@ A \link[ggplot2:ggplot]{ggplot2} object. \description{ This function visualizes clusters or categorical variables for one given sample at the cell-level. This is the function that does all the plotting -behind \link{vis_clus(datatype = "Xenium")}. To visualize gene-level +behind \code{\link[=vis_clus]{vis_clus()}} when \code{datatype = "Xenium"}. To visualize gene-level (or any continuous variable) use \code{\link[=vis_gene_c]{vis_gene_c()}}. } \examples{ diff --git a/man/vis_gene_c.Rd b/man/vis_gene_c.Rd index dfe88b29..0b5f5f63 100644 --- a/man/vis_gene_c.Rd +++ b/man/vis_gene_c.Rd @@ -67,9 +67,9 @@ A \link[ggplot2:ggplot]{ggplot2} object. This function visualizes the gene expression stored in \code{assays(spe)} or any continuous variable stored in \code{colData(spe)} for one given sample at the spot-level using (by default) the histology information on the background. -This is the function that does all the plotting behind -\link{vis_gene(datatype = "Xenium")} -To visualize clusters (or any discrete variable) use \code{\link[=vis_clus_c]{vis_clus_c()}}. +This is the function that does all the plotting behind \code{\link[=vis_clus]{vis_clus()}} when +\code{datatype = "Xenium"}. To visualize clusters (or any discrete variable) +use \code{\link[=vis_clus_c]{vis_clus_c()}}. } \examples{ From 7d5640324c573799f0da193ff02dcc7f37effff8 Mon Sep 17 00:00:00 2001 From: lahuuki Date: Wed, 20 May 2026 12:08:18 -0400 Subject: [PATCH 27/36] Add datatype as param to run_app - update docs --- R/run_app.R | 13 +++++++++++++ man/run_app.Rd | 23 +++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/R/run_app.R b/R/run_app.R index 1ae89cc0..384c6c35 100644 --- a/R/run_app.R +++ b/R/run_app.R @@ -208,7 +208,19 @@ #' default_cluster = "scran_quick_cluster", #' is_stitched = TRUE #' ) +#' +#'## Example for Xenium object +#' +#' if (!exists("spe_xenium")) spe_xenium <- fetch_data("spe_xenium_example") +#' +#' run_app(spe_xenium, +#' sce_layer = NULL, modeling_results = NULL, sig_genes = NULL, +#' title = "spatialLIBD Xenium without layer info", +#' datatype = "Xenium" +#' ) +#' #' } +#' run_app <- function( spe = fetch_data(type = "spe"), sce_layer = fetch_data(type = "sce_layer"), @@ -258,6 +270,7 @@ run_app <- function( default_cluster = "spatialLIBD", auto_crop_default = TRUE, is_stitched = FALSE, + datatype = c("Visium", "Xenium"), ...) { ## Run the checks in the relevant order stopifnot(length(default_cluster) == 1) diff --git a/man/run_app.Rd b/man/run_app.Rd index dc46f93d..710cc7b1 100644 --- a/man/run_app.Rd +++ b/man/run_app.Rd @@ -25,6 +25,7 @@ run_app( default_cluster = "spatialLIBD", auto_crop_default = TRUE, is_stitched = FALSE, + datatype = c("Visium", "Xenium"), ... ) } @@ -84,6 +85,16 @@ with \code{visiumStitched::build_spe()}. particular, expects a logical colData column \code{exclude_overlapping} specifying which spots to exclude from the plot. Sets \code{auto_crop = FALSE}.} +\item{datatype}{A \code{character(1)} specifying the type of spatial transcriptomics +data stored in \code{spe}. Supported options are: +\describe{ +\item{\code{"Visium"}}{(Default) Expects \code{pxl_col_in_fullres} and +\code{pxl_row_in_fullres} as columns of \code{spatialCoords(spe)}. Enables +image handling via the \code{spatialData} slot.} +\item{\code{"Xenium"}}{Expects \code{x_centroid} and \code{y_centroid} as columns +of \code{spatialCoords(spe)}.} +}} + \item{...}{Other arguments passed to the list of golem options for running the application.} } @@ -264,5 +275,17 @@ run_app( default_cluster = "scran_quick_cluster", is_stitched = TRUE ) + +## Example for Xenium object + +if (!exists("spe_xenium")) spe_xenium <- fetch_data("spe_xenium_example") + + run_app(spe_xenium, + sce_layer = NULL, modeling_results = NULL, sig_genes = NULL, + title = "spatialLIBD Xenium without layer info", + datatype = "Xenium" + ) + } + } From 4cdc7be03f7f1f167e8816d4dbac5f9109c962b1 Mon Sep 17 00:00:00 2001 From: lahuuki Date: Wed, 20 May 2026 12:18:48 -0400 Subject: [PATCH 28/36] Add datatype checks --- R/check_spe.R | 19 ++++++++++++++----- R/run_app.R | 5 ++++- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/R/check_spe.R b/R/check_spe.R index 47de3c0a..d4056782 100644 --- a/R/check_spe.R +++ b/R/check_spe.R @@ -31,18 +31,27 @@ check_spe <- function(spe, "sum_gene", "expr_chrM", "expr_chrM_ratio" - )) { + ), + datatype = c("Visium", "Xenium") + ) { ## Should be a SpatialExperiment object stopifnot(is(spe, "SpatialExperiment")) - + + ## Check for valid datatype + datatype <- match.arg(datatype) + + if(datatype == "Visium"){ + ## Images data stored under imgData(sce) stopifnot(all(c( - "sample_id", "image_id", "data", - "scaleFactor" + "sample_id", "image_id", "data", + "scaleFactor" ) %in% colnames(imgData(spe)))) - + ## Check that the images have been loaded stopifnot(all(vapply(imgData(spe)$data, is, logical(1), "VirtualSpatialImage"))) + + } ## Check gene data stopifnot(all( diff --git a/R/run_app.R b/R/run_app.R index 384c6c35..bcc8e564 100644 --- a/R/run_app.R +++ b/R/run_app.R @@ -276,10 +276,13 @@ run_app <- function( stopifnot(length(default_cluster) == 1) stopifnot(default_cluster %in% spe_discrete_vars) if (is_stitched) auto_crop_default <- FALSE + ## Check for valid datatype + datatype <- match.arg(datatype) spe <- check_spe(spe, - variables = c(spe_discrete_vars, spe_continuous_vars) + variables = c(spe_discrete_vars, spe_continuous_vars), + datatype = datatype ) ## Check sce_layer and modeling_results if needed From ed9c5f2be68c21e4a99937b7d7cf449973c80f0a Mon Sep 17 00:00:00 2001 From: lahuuki Date: Wed, 20 May 2026 14:20:40 -0400 Subject: [PATCH 29/36] get datatype arg in app_ui and app_server --- R/app_server.R | 1 + R/app_ui.R | 1 + 2 files changed, 2 insertions(+) diff --git a/R/app_server.R b/R/app_server.R index fe432807..ae68b8be 100644 --- a/R/app_server.R +++ b/R/app_server.R @@ -26,6 +26,7 @@ app_server <- function(input, output, session) { sig_genes <- golem::get_golem_options("sig_genes") default_cluster <- golem::get_golem_options("default_cluster") is_stitched <- golem::get_golem_options("is_stitched") + datatype <- golem::get_golem_options("datatype") # List the first level callModules here diff --git a/R/app_ui.R b/R/app_ui.R index e88763d1..9dd9dfd0 100644 --- a/R/app_ui.R +++ b/R/app_ui.R @@ -15,6 +15,7 @@ app_ui <- function() { modeling_results <- golem::get_golem_options("modeling_results") sig_genes <- golem::get_golem_options("sig_genes") auto_crop_default <- golem::get_golem_options("auto_crop_default") + datatype <- golem::get_golem_options("datatype") red_dim_names <- reducedDimNames(spe) if (length(red_dim_names) > 0) { From faff1678edeb21c8254f86c80460a4408670cf5f Mon Sep 17 00:00:00 2001 From: lahuuki Date: Wed, 20 May 2026 14:20:51 -0400 Subject: [PATCH 30/36] Update docs --- man/check_spe.Rd | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/man/check_spe.Rd b/man/check_spe.Rd index 577afeec..30c7d5d8 100644 --- a/man/check_spe.Rd +++ b/man/check_spe.Rd @@ -6,7 +6,8 @@ \usage{ check_spe( spe, - variables = c("sum_umi", "sum_gene", "expr_chrM", "expr_chrM_ratio") + variables = c("sum_umi", "sum_gene", "expr_chrM", "expr_chrM_ratio"), + datatype = c("Visium", "Xenium") ) } \arguments{ @@ -18,6 +19,16 @@ build your own \code{spe} object.} \item{variables}{A \code{character()} vector of variable names expected to be present in \code{colData(spe)}.} + +\item{datatype}{A \code{character(1)} specifying the type of spatial transcriptomics +data stored in \code{spe}. Supported options are: +\describe{ +\item{\code{"Visium"}}{(Default) Expects \code{pxl_col_in_fullres} and +\code{pxl_row_in_fullres} as columns of \code{spatialCoords(spe)}. Enables +image handling via the \code{spatialData} slot.} +\item{\code{"Xenium"}}{Expects \code{x_centroid} and \code{y_centroid} as columns +of \code{spatialCoords(spe)}.} +}} } \value{ The input object if all checks are passed. From 4ca4cb58b7d7756459a404af0f4083bda496e6c4 Mon Sep 17 00:00:00 2001 From: lcolladotor Date: Wed, 20 May 2026 14:32:12 -0400 Subject: [PATCH 31/36] vis_gene already inherits params from vis_clus --- R/vis_gene.R | 547 +++++++++++++++++++++++++++------------------------ 1 file changed, 291 insertions(+), 256 deletions(-) diff --git a/R/vis_gene.R b/R/vis_gene.R index 8c8bced3..b620438e 100644 --- a/R/vis_gene.R +++ b/R/vis_gene.R @@ -41,15 +41,6 @@ #' of 0.95 sets the top 5% of expression values to the 95th percentile value. #' This can help make the color scale more dynamic in the presence of high #' outliers. Defaults to `1`, which effectively performs no capping. -#' @param datatype A `character(1)` specifying the type of spatial transcriptomics -#' data stored in `spe`. Supported options are: -#' \describe{ -#' \item{`"Visium"`}{(Default) Expects `pxl_col_in_fullres` and -#' `pxl_row_in_fullres` as columns of `spatialCoords(spe)`. Enables -#' image handling via the `spatialData` slot.} -#' \item{`"Xenium"`}{Expects `x_centroid` and `y_centroid` as columns -#' of `spatialCoords(spe)`.} -#' } #' #' @return A [ggplot2][ggplot2::ggplot] object. #' @export @@ -171,10 +162,10 @@ #' multi_gene_method = "pca" #' ) #' print(p8) -#' +#' #' ## Obtain the necessary data: Xenium example #' if (!exists("spe_xenium")) spe_xenium <- fetch_data("spe_xenium_test") -#' +#' #' p9 <- vis_gene( #' spe = spe_xenium, #' sampleid = "sample2", @@ -184,27 +175,33 @@ #' datatype = "Xenium" #' ) #' print(p9) -#' +#' #' } vis_gene <- - function(spe, - sampleid = unique(spe$sample_id)[1], - geneid = rowData(spe)$gene_search[1], - spatial = TRUE, - assayname = "logcounts", - minCount = 0, - viridis = TRUE, - image_id = "lowres", - alpha = NA, - cont_colors = if (viridis) viridisLite::viridis(21) else c("aquamarine4", "springgreen", "goldenrod", "red"), - point_size = 2, - auto_crop = TRUE, - na_color = "#CCCCCC40", - multi_gene_method = c("z_score", "pca", "sparsity"), - is_stitched = FALSE, - cap_percentile = 1, - datatype = c("Visium", "Xenium"), - ...) { + function( + spe, + sampleid = unique(spe$sample_id)[1], + geneid = rowData(spe)$gene_search[1], + spatial = TRUE, + assayname = "logcounts", + minCount = 0, + viridis = TRUE, + image_id = "lowres", + alpha = NA, + cont_colors = if (viridis) { + viridisLite::viridis(21) + } else { + c("aquamarine4", "springgreen", "goldenrod", "red") + }, + point_size = 2, + auto_crop = TRUE, + na_color = "#CCCCCC40", + multi_gene_method = c("z_score", "pca", "sparsity"), + is_stitched = FALSE, + cap_percentile = 1, + datatype = c("Visium", "Xenium"), + ... + ) { multi_gene_method <- rlang::arg_match(multi_gene_method) # Verify existence and legitimacy of 'sampleid' if ( @@ -213,7 +210,8 @@ vis_gene <- ) { stop( paste( - "'spe$sample_id' must exist and contain the ID", sampleid + "'spe$sample_id' must exist and contain the ID", + sampleid ), call. = FALSE ) @@ -221,246 +219,283 @@ vis_gene <- # Verify 'assayname' if (!(assayname %in% names(assays(spe)))) { - stop(sprintf("'%s' is not an assay in 'spe'", assayname), call. = FALSE) + stop( + sprintf("'%s' is not an assay in 'spe'", assayname), + call. = FALSE + ) } - + ## Check for valid datatype datatype <- match.arg(datatype) - - if(datatype == "Visium"){ - # Check validity of spatial coordinates - if (!setequal(c("pxl_col_in_fullres", "pxl_row_in_fullres"), colnames(spatialCoords(spe)))) { - stop( - "Abnormal spatial coordinates for Visium datatype: should have 'pxl_row_in_fullres' and 'pxl_col_in_fullres' columns.", - call. = FALSE - ) - } - - # Validate 'cap_percentile' - if (cap_percentile <= 0 || cap_percentile > 1) { - stop("'cap_percentile' must be in (0, 1]", call. = FALSE) - } - - spe_sub <- spe[, spe$sample_id == sampleid] - - if (is_stitched) { - # Drop excluded spots and calculate an appropriate point size - temp <- prep_stitched_data(spe_sub, point_size, image_id) - spe_sub <- temp$spe - point_size <- temp$point_size - - # Frame limits are poorly defined for stitched data - auto_crop <- FALSE - } - - d <- as.data.frame(cbind(colData(spe_sub), SpatialExperiment::spatialCoords(spe_sub)), optional = TRUE) - - # Verify legitimacy of names in geneid - geneid_is_valid <- (geneid %in% rowData(spe_sub)$gene_search) | - (geneid %in% rownames(spe_sub)) | - (geneid %in% colnames(colData(spe_sub))) - if (any(!geneid_is_valid)) { - stop( - "Could not find the 'geneid'(s) ", - paste(geneid[!geneid_is_valid], collapse = ", "), - call. = FALSE + + if (datatype == "Visium") { + # Check validity of spatial coordinates + if ( + !setequal( + c("pxl_col_in_fullres", "pxl_row_in_fullres"), + colnames(spatialCoords(spe)) + ) + ) { + stop( + "Abnormal spatial coordinates for Visium datatype: should have 'pxl_row_in_fullres' and 'pxl_col_in_fullres' columns.", + call. = FALSE + ) + } + + # Validate 'cap_percentile' + if (cap_percentile <= 0 || cap_percentile > 1) { + stop("'cap_percentile' must be in (0, 1]", call. = FALSE) + } + + spe_sub <- spe[, spe$sample_id == sampleid] + + if (is_stitched) { + # Drop excluded spots and calculate an appropriate point size + temp <- prep_stitched_data(spe_sub, point_size, image_id) + spe_sub <- temp$spe + point_size <- temp$point_size + + # Frame limits are poorly defined for stitched data + auto_crop <- FALSE + } + + d <- as.data.frame( + cbind( + colData(spe_sub), + SpatialExperiment::spatialCoords(spe_sub) + ), + optional = TRUE ) - } - - # Grab any continuous colData columns and verify they're all numeric - cont_cols <- colData(spe_sub)[ - , geneid[geneid %in% colnames(colData(spe_sub))], - drop = FALSE - ] - if (!all(sapply(cont_cols, class) %in% c("numeric", "integer"))) { - stop( - "'geneid' can not contain non-numeric colData columns.", - call. = FALSE + + # Verify legitimacy of names in geneid + geneid_is_valid <- (geneid %in% rowData(spe_sub)$gene_search) | + (geneid %in% rownames(spe_sub)) | + (geneid %in% colnames(colData(spe_sub))) + if (any(!geneid_is_valid)) { + stop( + "Could not find the 'geneid'(s) ", + paste(geneid[!geneid_is_valid], collapse = ", "), + call. = FALSE + ) + } + + # Grab any continuous colData columns and verify they're all numeric + cont_cols <- colData(spe_sub)[, + geneid[geneid %in% colnames(colData(spe_sub))], + drop = FALSE + ] + if (!all(sapply(cont_cols, class) %in% c("numeric", "integer"))) { + stop( + "'geneid' can not contain non-numeric colData columns.", + call. = FALSE + ) + } + cont_cols <- as.matrix(cont_cols) + + # Get the integer indices of each gene in the SpatialExperiment, since we + # aren't guaranteed that rownames are gene names + remaining_geneid <- geneid[ + !(geneid %in% colnames(colData(spe_sub))) + ] + valid_gene_indices <- unique( + c( + match(remaining_geneid, rowData(spe_sub)$gene_search), + match(remaining_geneid, rownames(spe_sub)) + ) ) - } - cont_cols <- as.matrix(cont_cols) - - # Get the integer indices of each gene in the SpatialExperiment, since we - # aren't guaranteed that rownames are gene names - remaining_geneid <- geneid[!(geneid %in% colnames(colData(spe_sub)))] - valid_gene_indices <- unique( - c( - match(remaining_geneid, rowData(spe_sub)$gene_search), - match(remaining_geneid, rownames(spe_sub)) + valid_gene_indices <- valid_gene_indices[!is.na(valid_gene_indices)] + + # Grab any genes + gene_cols <- t( + as.matrix(assays(spe_sub[valid_gene_indices, ])[[assayname]]) ) - ) - valid_gene_indices <- valid_gene_indices[!is.na(valid_gene_indices)] - - # Grab any genes - gene_cols <- t( - as.matrix(assays(spe_sub[valid_gene_indices, ])[[assayname]]) - ) - - # Combine into one matrix where rows are samples and columns are continuous - # features - cont_matrix <- cbind(cont_cols, gene_cols) - - # Determine plot and legend titles - if (ncol(cont_matrix) == 1) { - plot_title <- paste(sampleid, geneid, ...) - d$COUNT <- cont_matrix[, 1] - if (!(geneid %in% colnames(colData(spe_sub)))) { - legend_title <- sprintf("%s\n min > %s", assayname, minCount) + + # Combine into one matrix where rows are samples and columns are continuous + # features + cont_matrix <- cbind(cont_cols, gene_cols) + + # Determine plot and legend titles + if (ncol(cont_matrix) == 1) { + plot_title <- paste(sampleid, geneid, ...) + d$COUNT <- cont_matrix[, 1] + if (!(geneid %in% colnames(colData(spe_sub)))) { + legend_title <- sprintf( + "%s\n min > %s", + assayname, + minCount + ) + } else { + legend_title <- sprintf("min > %s", minCount) + } } else { - legend_title <- sprintf("min > %s", minCount) + plot_title <- paste(sampleid, ...) + if (multi_gene_method == "z_score") { + d$COUNT <- multi_gene_z_score(cont_matrix) + legend_title <- paste("Z score\n min > ", minCount) + } else if (multi_gene_method == "sparsity") { + d$COUNT <- multi_gene_sparsity(cont_matrix) + legend_title <- paste("Prop. nonzero\n min > ", minCount) + } else { + # must be 'pca' + d$COUNT <- multi_gene_pca(cont_matrix) + legend_title <- paste("PC1\n min > ", minCount) + } } - } else { - plot_title <- paste(sampleid, ...) - if (multi_gene_method == "z_score") { - d$COUNT <- multi_gene_z_score(cont_matrix) - legend_title <- paste("Z score\n min > ", minCount) - } else if (multi_gene_method == "sparsity") { - d$COUNT <- multi_gene_sparsity(cont_matrix) - legend_title <- paste("Prop. nonzero\n min > ", minCount) - } else { # must be 'pca' - d$COUNT <- multi_gene_pca(cont_matrix) - legend_title <- paste("PC1\n min > ", minCount) + + # Cap the expression values at the given percentile, if applicable + if (cap_percentile < 1) { + sorted_count <- sort(d$COUNT) + cap <- sorted_count[ + as.integer(round(length(sorted_count) * cap_percentile)) + ] + d$COUNT[d$COUNT > cap] <- cap } - } - - # Cap the expression values at the given percentile, if applicable - if (cap_percentile < 1) { - sorted_count <- sort(d$COUNT) - cap <- sorted_count[ - as.integer(round(length(sorted_count) * cap_percentile)) - ] - d$COUNT[d$COUNT > cap] <- cap - } - - d$COUNT[d$COUNT <= minCount] <- NA - - p <- vis_gene_p( - spe = spe_sub, - d = d, - sampleid = sampleid, - spatial = spatial, - title = plot_title, - viridis = viridis, - image_id = image_id, - alpha = alpha, - cont_colors = cont_colors, - point_size = point_size, - auto_crop = auto_crop, - na_color = na_color, - legend_title = legend_title - ) - return(p) - } else if(datatype == "Xenium"){ - # Check validity of spatial coordinates - if (!setequal(c("x_centroid", "y_centroid"), colnames(spatialCoords(spe)))) { - stop( - "Abnormal spatial coordinates for Xenium datatype: should have 'x_centroid' and 'y_centroid' columns.", - call. = FALSE + + d$COUNT[d$COUNT <= minCount] <- NA + + p <- vis_gene_p( + spe = spe_sub, + d = d, + sampleid = sampleid, + spatial = spatial, + title = plot_title, + viridis = viridis, + image_id = image_id, + alpha = alpha, + cont_colors = cont_colors, + point_size = point_size, + auto_crop = auto_crop, + na_color = na_color, + legend_title = legend_title ) - } - - # Validate 'cap_percentile' - if (cap_percentile <= 0 || cap_percentile > 1) { - stop("'cap_percentile' must be in (0, 1]", call. = FALSE) - } - - spe_sub <- spe[, spe$sample_id == sampleid] - - d <- as.data.frame(cbind(colData(spe_sub), SpatialExperiment::spatialCoords(spe_sub)), optional = TRUE) - - # Verify legitimacy of names in geneid - geneid_is_valid <- (geneid %in% rowData(spe_sub)$gene_search) | - (geneid %in% rownames(spe_sub)) | - (geneid %in% colnames(colData(spe_sub))) - if (any(!geneid_is_valid)) { - stop( - "Could not find the 'geneid'(s) ", - paste(geneid[!geneid_is_valid], collapse = ", "), - call. = FALSE + return(p) + } else if (datatype == "Xenium") { + # Check validity of spatial coordinates + if ( + !setequal( + c("x_centroid", "y_centroid"), + colnames(spatialCoords(spe)) + ) + ) { + stop( + "Abnormal spatial coordinates for Xenium datatype: should have 'x_centroid' and 'y_centroid' columns.", + call. = FALSE + ) + } + + # Validate 'cap_percentile' + if (cap_percentile <= 0 || cap_percentile > 1) { + stop("'cap_percentile' must be in (0, 1]", call. = FALSE) + } + + spe_sub <- spe[, spe$sample_id == sampleid] + + d <- as.data.frame( + cbind( + colData(spe_sub), + SpatialExperiment::spatialCoords(spe_sub) + ), + optional = TRUE ) - } - - # Grab any continuous colData columns and verify they're all numeric - cont_cols <- colData(spe_sub)[ - , geneid[geneid %in% colnames(colData(spe_sub))], - drop = FALSE - ] - if (!all(sapply(cont_cols, class) %in% c("numeric", "integer"))) { - stop( - "'geneid' can not contain non-numeric colData columns.", - call. = FALSE + + # Verify legitimacy of names in geneid + geneid_is_valid <- (geneid %in% rowData(spe_sub)$gene_search) | + (geneid %in% rownames(spe_sub)) | + (geneid %in% colnames(colData(spe_sub))) + if (any(!geneid_is_valid)) { + stop( + "Could not find the 'geneid'(s) ", + paste(geneid[!geneid_is_valid], collapse = ", "), + call. = FALSE + ) + } + + # Grab any continuous colData columns and verify they're all numeric + cont_cols <- colData(spe_sub)[, + geneid[geneid %in% colnames(colData(spe_sub))], + drop = FALSE + ] + if (!all(sapply(cont_cols, class) %in% c("numeric", "integer"))) { + stop( + "'geneid' can not contain non-numeric colData columns.", + call. = FALSE + ) + } + cont_cols <- as.matrix(cont_cols) + + # Get the integer indices of each gene in the SpatialExperiment, since we + # aren't guaranteed that rownames are gene names + remaining_geneid <- geneid[ + !(geneid %in% colnames(colData(spe_sub))) + ] + valid_gene_indices <- unique( + c( + match(remaining_geneid, rowData(spe_sub)$gene_search), + match(remaining_geneid, rownames(spe_sub)) + ) ) - } - cont_cols <- as.matrix(cont_cols) - - # Get the integer indices of each gene in the SpatialExperiment, since we - # aren't guaranteed that rownames are gene names - remaining_geneid <- geneid[!(geneid %in% colnames(colData(spe_sub)))] - valid_gene_indices <- unique( - c( - match(remaining_geneid, rowData(spe_sub)$gene_search), - match(remaining_geneid, rownames(spe_sub)) + valid_gene_indices <- valid_gene_indices[!is.na(valid_gene_indices)] + + # Grab any genes + gene_cols <- t( + as.matrix(assays(spe_sub[valid_gene_indices, ])[[assayname]]) ) - ) - valid_gene_indices <- valid_gene_indices[!is.na(valid_gene_indices)] - - # Grab any genes - gene_cols <- t( - as.matrix(assays(spe_sub[valid_gene_indices, ])[[assayname]]) - ) - - # Combine into one matrix where rows are samples and columns are continuous - # features - cont_matrix <- cbind(cont_cols, gene_cols) - - # Determine plot and legend titles - if (ncol(cont_matrix) == 1) { - plot_title <- paste(sampleid, geneid, ...) - d$COUNT <- cont_matrix[, 1] - if (!(geneid %in% colnames(colData(spe_sub)))) { - legend_title <- sprintf("%s\n min > %s", assayname, minCount) + + # Combine into one matrix where rows are samples and columns are continuous + # features + cont_matrix <- cbind(cont_cols, gene_cols) + + # Determine plot and legend titles + if (ncol(cont_matrix) == 1) { + plot_title <- paste(sampleid, geneid, ...) + d$COUNT <- cont_matrix[, 1] + if (!(geneid %in% colnames(colData(spe_sub)))) { + legend_title <- sprintf( + "%s\n min > %s", + assayname, + minCount + ) + } else { + legend_title <- sprintf("min > %s", minCount) + } } else { - legend_title <- sprintf("min > %s", minCount) + plot_title <- paste(sampleid, ...) + if (multi_gene_method == "z_score") { + d$COUNT <- multi_gene_z_score(cont_matrix) + legend_title <- paste("Z score\n min > ", minCount) + } else if (multi_gene_method == "sparsity") { + d$COUNT <- multi_gene_sparsity(cont_matrix) + legend_title <- paste("Prop. nonzero\n min > ", minCount) + } else { + # must be 'pca' + d$COUNT <- multi_gene_pca(cont_matrix) + legend_title <- paste("PC1\n min > ", minCount) + } } - } else { - plot_title <- paste(sampleid, ...) - if (multi_gene_method == "z_score") { - d$COUNT <- multi_gene_z_score(cont_matrix) - legend_title <- paste("Z score\n min > ", minCount) - } else if (multi_gene_method == "sparsity") { - d$COUNT <- multi_gene_sparsity(cont_matrix) - legend_title <- paste("Prop. nonzero\n min > ", minCount) - } else { # must be 'pca' - d$COUNT <- multi_gene_pca(cont_matrix) - legend_title <- paste("PC1\n min > ", minCount) + + # Cap the expression values at the given percentile, if applicable + if (cap_percentile < 1) { + sorted_count <- sort(d$COUNT) + cap <- sorted_count[ + as.integer(round(length(sorted_count) * cap_percentile)) + ] + d$COUNT[d$COUNT > cap] <- cap } - } - - # Cap the expression values at the given percentile, if applicable - if (cap_percentile < 1) { - sorted_count <- sort(d$COUNT) - cap <- sorted_count[ - as.integer(round(length(sorted_count) * cap_percentile)) - ] - d$COUNT[d$COUNT > cap] <- cap - } - - d$COUNT[d$COUNT <= minCount] <- NA - - p <- vis_gene_c( - spe = spe_sub, - d = d, - sampleid = sampleid, - title = plot_title, - viridis = viridis, - alpha = alpha, - cont_colors = cont_colors, - point_size = point_size, - na_color = na_color, - legend_title = legend_title - ) - return(p) - } - + d$COUNT[d$COUNT <= minCount] <- NA + + p <- vis_gene_c( + spe = spe_sub, + d = d, + sampleid = sampleid, + title = plot_title, + viridis = viridis, + alpha = alpha, + cont_colors = cont_colors, + point_size = point_size, + na_color = na_color, + legend_title = legend_title + ) + return(p) + } } From f2750c5751bee04e85a4101446eb259da60c5300 Mon Sep 17 00:00:00 2001 From: lcolladotor Date: Wed, 20 May 2026 14:43:27 -0400 Subject: [PATCH 32/36] Make vis_grid_gene() and vis_grid_clus() support `datatype` from vis_clus() and vis_gene() --- R/vis_grid_clus.R | 2 ++ R/vis_grid_gene.R | 48 ++++++++++++++++++++++++++------------------ man/vis_gene.Rd | 14 ++++++++----- man/vis_grid_clus.Rd | 11 ++++++++++ man/vis_grid_gene.Rd | 19 ++++++++++++++++-- 5 files changed, 67 insertions(+), 27 deletions(-) diff --git a/R/vis_grid_clus.R b/R/vis_grid_clus.R index 64007900..a4fdf450 100644 --- a/R/vis_grid_clus.R +++ b/R/vis_grid_clus.R @@ -65,6 +65,7 @@ vis_grid_clus <- na_color = "#CCCCCC40", is_stitched = FALSE, guide_point_size = point_size, + datatype = c("Visium", "Xenium"), ... ) { stopifnot(all(sample_order %in% unique(spe$sample_id))) @@ -88,6 +89,7 @@ vis_grid_clus <- na_color = na_color, is_stitched = is_stitched, guide_point_size = guide_point_size, + datatype = datatype, ... ) }) diff --git a/R/vis_grid_gene.R b/R/vis_grid_gene.R index 6cccee2a..ef7e997b 100644 --- a/R/vis_grid_gene.R +++ b/R/vis_grid_gene.R @@ -35,26 +35,33 @@ #' cowplot::plot_grid(plotlist = p_list, ncol = 2) #' } vis_grid_gene <- - function(spe, - geneid = rowData(spe)$gene_search[1], - pdf_file, - assayname = "logcounts", - minCount = 0, - return_plots = FALSE, - spatial = TRUE, - viridis = TRUE, - height = 24, - width = 36, - image_id = "lowres", - alpha = NA, - cont_colors = if (viridis) viridisLite::viridis(21) else c("aquamarine4", "springgreen", "goldenrod", "red"), - sample_order = unique(spe$sample_id), - point_size = 2, - auto_crop = TRUE, - na_color = "#CCCCCC40", - is_stitched = FALSE, - cap_percentile = 1, - ...) { + function( + spe, + geneid = rowData(spe)$gene_search[1], + pdf_file, + assayname = "logcounts", + minCount = 0, + return_plots = FALSE, + spatial = TRUE, + viridis = TRUE, + height = 24, + width = 36, + image_id = "lowres", + alpha = NA, + cont_colors = if (viridis) { + viridisLite::viridis(21) + } else { + c("aquamarine4", "springgreen", "goldenrod", "red") + }, + sample_order = unique(spe$sample_id), + point_size = 2, + auto_crop = TRUE, + na_color = "#CCCCCC40", + is_stitched = FALSE, + cap_percentile = 1, + datatype = c("Visium", "Xenium"), + ... + ) { stopifnot(all(sample_order %in% unique(spe$sample_id))) plots <- lapply(sample_order, function(sampleid) { @@ -74,6 +81,7 @@ vis_grid_gene <- na_color = na_color, is_stitched = is_stitched, cap_percentile = cap_percentile, + datatype = datatype, ... ) }) diff --git a/man/vis_gene.Rd b/man/vis_gene.Rd index 19236d96..c0ec0684 100644 --- a/man/vis_gene.Rd +++ b/man/vis_gene.Rd @@ -14,8 +14,12 @@ vis_gene( viridis = TRUE, image_id = "lowres", alpha = NA, - cont_colors = if (viridis) viridisLite::viridis(21) else c("aquamarine4", - "springgreen", "goldenrod", "red"), + cont_colors = if (viridis) { + viridisLite::viridis(21) + } else { + + c("aquamarine4", "springgreen", "goldenrod", "red") + }, point_size = 2, auto_crop = TRUE, na_color = "#CCCCCC40", @@ -245,10 +249,10 @@ if (enough_ram()) { multi_gene_method = "pca" ) print(p8) - + ## Obtain the necessary data: Xenium example if (!exists("spe_xenium")) spe_xenium <- fetch_data("spe_xenium_test") - + p9 <- vis_gene( spe = spe_xenium, sampleid = "sample2", @@ -258,7 +262,7 @@ if (enough_ram()) { datatype = "Xenium" ) print(p9) - + } } \seealso{ diff --git a/man/vis_grid_clus.Rd b/man/vis_grid_clus.Rd index 30d292db..58026a73 100644 --- a/man/vis_grid_clus.Rd +++ b/man/vis_grid_clus.Rd @@ -22,6 +22,7 @@ vis_grid_clus( na_color = "#CCCCCC40", is_stitched = FALSE, guide_point_size = point_size, + datatype = c("Visium", "Xenium"), ... ) } @@ -88,6 +89,16 @@ specifying which spots to exclude from the plot. Sets \code{auto_crop = FALSE}.} \item{guide_point_size}{A \code{numeric(1)} specifying the size of the points in guide. Defaults to \code{point_size}. Increase to improve visibility.} +\item{datatype}{A \code{character(1)} specifying the type of spatial transcriptomics +data stored in \code{spe}. Supported options are: +\describe{ +\item{\code{"Visium"}}{(Default) Expects \code{pxl_col_in_fullres} and +\code{pxl_row_in_fullres} as columns of \code{spatialCoords(spe)}. Enables +image handling via the \code{spatialData} slot.} +\item{\code{"Xenium"}}{Expects \code{x_centroid} and \code{y_centroid} as columns +of \code{spatialCoords(spe)}.} +}} + \item{...}{Passed to \link[base:paste]{paste0()} for making the title of the plot following the \code{sampleid}.} } diff --git a/man/vis_grid_gene.Rd b/man/vis_grid_gene.Rd index 77018545..63305f9c 100644 --- a/man/vis_grid_gene.Rd +++ b/man/vis_grid_gene.Rd @@ -17,14 +17,19 @@ vis_grid_gene( width = 36, image_id = "lowres", alpha = NA, - cont_colors = if (viridis) viridisLite::viridis(21) else c("aquamarine4", - "springgreen", "goldenrod", "red"), + cont_colors = if (viridis) { + viridisLite::viridis(21) + } else { + + c("aquamarine4", "springgreen", "goldenrod", "red") + }, sample_order = unique(spe$sample_id), point_size = 2, auto_crop = TRUE, na_color = "#CCCCCC40", is_stitched = FALSE, cap_percentile = 1, + datatype = c("Visium", "Xenium"), ... ) } @@ -108,6 +113,16 @@ of 0.95 sets the top 5\% of expression values to the 95th percentile value. This can help make the color scale more dynamic in the presence of high outliers. Defaults to \code{1}, which effectively performs no capping.} +\item{datatype}{A \code{character(1)} specifying the type of spatial transcriptomics +data stored in \code{spe}. Supported options are: +\describe{ +\item{\code{"Visium"}}{(Default) Expects \code{pxl_col_in_fullres} and +\code{pxl_row_in_fullres} as columns of \code{spatialCoords(spe)}. Enables +image handling via the \code{spatialData} slot.} +\item{\code{"Xenium"}}{Expects \code{x_centroid} and \code{y_centroid} as columns +of \code{spatialCoords(spe)}.} +}} + \item{...}{Passed to \link[base:paste]{paste0()} for making the title of the plot following the \code{sampleid}.} } From ed5d03a8e86db5344d51928fd170e8fa80dc5146 Mon Sep 17 00:00:00 2001 From: lcolladotor Date: Wed, 20 May 2026 15:52:39 -0400 Subject: [PATCH 33/36] Fix several things about the Xenium support in vis_clus(), vis_gene() and related functions. Fix examples. --- R/vis_clus.R | 232 +++++++++++++++++----------------- R/vis_clus_c.R | 176 +++++++++++++------------- R/vis_gene.R | 308 ++++++++++++++++------------------------------ R/vis_gene_c.R | 177 +++++++++++++------------- R/vis_gene_p.R | 80 ++++++++---- man/vis_clus.Rd | 16 ++- man/vis_clus_c.Rd | 44 ++++--- man/vis_gene.Rd | 6 +- man/vis_gene_c.Rd | 16 ++- man/vis_gene_p.Rd | 8 +- 10 files changed, 505 insertions(+), 558 deletions(-) diff --git a/R/vis_clus.R b/R/vis_clus.R index 02af7983..4b925cf6 100644 --- a/R/vis_clus.R +++ b/R/vis_clus.R @@ -39,8 +39,8 @@ #' ; in #' particular, expects a logical colData column `exclude_overlapping` #' specifying which spots to exclude from the plot. Sets `auto_crop = FALSE`. -#' @param guide_point_size A `numeric(1)` specifying the size of the points in -#' guide. Defaults to `point_size`. Increase to improve visibility. +#' @param guide_point_size A `numeric(1)` specifying the size of the points in +#' guide. Defaults to `point_size`. Increase to improve visibility. #' @param datatype A `character(1)` specifying the type of spatial transcriptomics #' data stored in `spe`. Supported options are: #' \describe{ @@ -113,7 +113,7 @@ #' ... = " LIBD Layers" #' ) #' print(p4) -#' +#' #' ## edit plot point size but keep guide size larger #' p5 <- vis_clus( #' spe = spe, @@ -126,16 +126,16 @@ #' ... = " LIBD Layers" #' ) #' print(p5) -#' +#' #' ## Obtain the necessary data: Xenium example #' if (!exists("spe_xenium")) spe_xenium <- fetch_data("spe_xenium_example") -#' +#' #' spe_xenium$x_half <- ifelse(spatialCoords(spe_xenium)[,"x_centroid"] < 3088, "left", "right") -#' -#' p6 <- vis_clus( +#' +#' p6 <- vis_clus( #' spe = spe_xenium, #' clustervar = "x_half", -#' sampleid = "sample2", +#' sampleid = "Br1556", #' colors = c(left = "red", right = "blue"), #' na_color = "white", #' point_size = 1, @@ -144,38 +144,37 @@ #' datatype = "Xenium" #' ) #' print(p6) -#' -#' -#' +#' #' } vis_clus <- function( - spe, - sampleid = unique(spe$sample_id)[1], - clustervar, - colors = c( - "#b2df8a", - "#e41a1c", - "#377eb8", - "#4daf4a", - "#ff7f00", - "gold", - "#a65628", - "#999999", - "black", - "grey", - "white", - "purple" - ), - spatial = TRUE, - image_id = "lowres", - alpha = NA, - point_size = 2, - auto_crop = TRUE, - na_color = "#CCCCCC40", - is_stitched = FALSE, - guide_point_size = point_size, - datatype = c("Visium", "Xenium"), - ...) { + spe, + sampleid = unique(spe$sample_id)[1], + clustervar, + colors = c( + "#b2df8a", + "#e41a1c", + "#377eb8", + "#4daf4a", + "#ff7f00", + "gold", + "#a65628", + "#999999", + "black", + "grey", + "white", + "purple" + ), + spatial = TRUE, + image_id = "lowres", + alpha = NA, + point_size = 2, + auto_crop = TRUE, + na_color = "#CCCCCC40", + is_stitched = FALSE, + guide_point_size = point_size, + datatype = c("Visium", "Xenium"), + ... +) { # Verify existence and legitimacy of 'sampleid' if ( !("sample_id" %in% colnames(colData(spe))) || @@ -183,85 +182,96 @@ vis_clus <- function( ) { stop( paste( - "'spe$sample_id' must exist and contain the ID", sampleid + "'spe$sample_id' must exist and contain the ID", + sampleid ), call. = FALSE ) } - - ## subset spe to selected sample - spe_sub <- spe[, spe$sample_id == sampleid] - - ## Check for valid datatype - datatype <- match.arg(datatype) - - if(datatype == "Visium"){ - - # Check validity of spatial coordinates by datatype - if (!setequal(c("pxl_col_in_fullres", "pxl_row_in_fullres"), colnames(spatialCoords(spe)))) { - stop( - "Abnormal spatial coordinates for Visium datatype: should have 'pxl_row_in_fullres' and 'pxl_col_in_fullres' columns.", - call. = FALSE - ) - } - - d <- as.data.frame(cbind(colData(spe_sub), SpatialExperiment::spatialCoords(spe_sub)), optional = TRUE) - if (is_stitched) { - # Drop excluded spots and calculate an appropriate point size - temp <- prep_stitched_data(spe_sub, point_size, image_id) - spe_sub <- temp$spe - point_size <- temp$point_size - - # Frame limits are poorly defined for stitched data - auto_crop <- FALSE - } - - vis_clus_p( - spe = spe_sub, - d = d, - clustervar = clustervar, - sampleid = sampleid, - spatial = spatial, - title = paste0(sampleid, ...), - colors = get_colors(colors, d[, clustervar]), - image_id = image_id, - alpha = alpha, - point_size = point_size, - auto_crop = auto_crop, - na_color = na_color - ) + - guides(fill = guide_legend(override.aes = list(size = guide_point_size))) - - } else if(datatype == "Xenium"){ - - if (datatype == "Xenium" & !setequal(c("x_centroid", "y_centroid"), colnames(spatialCoords(spe)))) { - stop( - "Abnormal spatial coordinates for Xenium datatype: should have 'x_centroid' and 'y_centroid' columns.", - call. = FALSE - ) - } - - d <- as.data.frame(cbind(colData(spe_sub), SpatialExperiment::spatialCoords(spe_sub)), optional = TRUE) - - vis_clus_c( - spe = spe_sub, - d = d, - clustervar = clustervar, - sampleid = sampleid, - title = paste0(sampleid, ...), - colors = get_colors(colors, d[, clustervar]), - alpha = alpha, - point_size = point_size, - na_color = na_color - ) + - guides(fill = guide_legend(override.aes = list(size = guide_point_size))) - - - } + ## subset spe to selected sample + spe_sub <- spe[, spe$sample_id == sampleid] + + ## Check for valid datatype + datatype <- match.arg(datatype) + + d <- as.data.frame( + cbind(colData(spe_sub), SpatialExperiment::spatialCoords(spe_sub)), + optional = TRUE + ) - - + if (datatype == "Visium") { + # Check validity of spatial coordinates by datatype + if ( + !setequal( + c("pxl_col_in_fullres", "pxl_row_in_fullres"), + colnames(spatialCoords(spe_sub)) + ) + ) { + stop( + "Abnormal spatial coordinates for Visium datatype: should have 'pxl_row_in_fullres' and 'pxl_col_in_fullres' columns.", + call. = FALSE + ) + } + if (is_stitched) { + # Drop excluded spots and calculate an appropriate point size + temp <- prep_stitched_data(spe_sub, point_size, image_id) + spe_sub <- temp$spe + point_size <- temp$point_size + # Frame limits are poorly defined for stitched data + auto_crop <- FALSE + } + + vis_clus_p( + spe = spe_sub, + d = d, + clustervar = clustervar, + sampleid = sampleid, + spatial = spatial, + title = paste0(sampleid, ...), + colors = get_colors(colors, d[, clustervar]), + image_id = image_id, + alpha = alpha, + point_size = point_size, + auto_crop = auto_crop, + na_color = na_color + ) + + guides( + fill = guide_legend( + override.aes = list(size = guide_point_size) + ) + ) + } else if (datatype == "Xenium") { + if ( + datatype == "Xenium" & + !setequal( + c("x_centroid", "y_centroid"), + colnames(spatialCoords(spe_sub)) + ) + ) { + stop( + "Abnormal spatial coordinates for Xenium datatype: should have 'x_centroid' and 'y_centroid' columns.", + call. = FALSE + ) + } + + vis_clus_c( + spe = spe_sub, + d = d, + clustervar = clustervar, + sampleid = sampleid, + title = paste0(sampleid, ...), + colors = get_colors(colors, d[, clustervar]), + alpha = alpha, + point_size = point_size, + na_color = na_color + ) + + guides( + fill = guide_legend( + override.aes = list(size = guide_point_size) + ) + ) + } } diff --git a/R/vis_clus_c.R b/R/vis_clus_c.R index 59d2f366..eef72b31 100644 --- a/R/vis_clus_c.R +++ b/R/vis_clus_c.R @@ -1,15 +1,13 @@ #' Sample spatial cluster visualization workhorse function for Xenium data with #' centroid based spatailCoords #' -#' This function visualizes clusters or categorical variables for one given -#' sample at the cell-level. This is the function that does all the plotting -#' behind [vis_clus()] when `datatype = "Xenium"`. To visualize gene-level +#' This function visualizes clusters or categorical variables for one given +#' sample at the cell-level. This is the function that does all the plotting +#' behind [vis_clus()] when `datatype = "Xenium"`. To visualize gene-level #' (or any continuous variable) use [vis_gene_c()]. #' #' @inheritParams vis_clus -#' @param d A `data.frame()` with the sample-level information. This is -#' typically obtained using `cbind(colData(spe), spatialCoords(spe))`. -#' @param title The title for the plot. +#' @inheritParams vis_clus_p #' #' @return A [ggplot2][ggplot2::ggplot] object. #' @export @@ -23,96 +21,96 @@ #' #' if (enough_ram()) { #' ## Obtain the necessary data -#' if (!exists("spe")) spe <- fetch_data("spe_xenium_example") -#' -#' # spe <- readRDS("../Xenium/XeniumIO_test/spe_Xenium_test.rds") -#' +#' if (!exists("spe_xenium")) spe_xenium <- fetch_data("spe_xenium_example") +#' #' ## Prepare the data for the plotting function -#' spe_sub <- spe[, spe$sample_id == "sample1"] -#' -#' # summary(spatialCoords(spe_sub)[,"x_centroid"]) -#' # summary(spatialCoords(spe_sub)[,"y_centroid"]) -#' -#' ## add catagorical variable -#' spe_sub$x_half <- ifelse(spatialCoords(spe_sub)[,"x_centroid"] < 3088, "left", "right") -#' table(spe_sub$x_half) +#' spe_sub <- spe_xenium[, spe_xenium$sample_id == "Br1039"] +#' +#' # summary(spatialCoords(spe_sub)[,"x_centroid"]) +#' # summary(spatialCoords(spe_sub)[,"y_centroid"]) +#' +#' ## add catagorical variable +#' spe_sub$x_half <- ifelse(spatialCoords(spe_sub)[,"x_centroid"] < 3088, "left", "right") +#' table(spe_sub$x_half) #' -#' p <- vis_clus_c( -#' spe = spe_sub, -#' d = as.data.frame(cbind(colData(spe_sub), SpatialExperiment::spatialCoords(spe_sub)), optional = TRUE), -#' clustervar = "x_half", -#' sampleid = "sample01.1", -#' #colors = libd_layer_colors, -#' colors = c(left = "red", right = "blue"), -#' title = "Xenium test", -#' point_size = 1, -#' alpha = 0.5 -#' ) +#' p <- vis_clus_c( +#' spe = spe_sub, +#' d = as.data.frame(cbind(colData(spe_sub), SpatialExperiment::spatialCoords(spe_sub)), optional = TRUE), +#' clustervar = "x_half", +#' sampleid = "sample01.1", +#' #colors = libd_layer_colors, +#' colors = c(left = "red", right = "blue"), +#' title = "Xenium test", +#' point_size = 1, +#' alpha = 0.5 +#' ) #' print(p) #' #' ## Clean up #' rm(spe_sub) #' } vis_clus_c <- - function(spe, - d, - clustervar, - sampleid = unique(spe$sample_id)[1], - colors, - title, - alpha = NA, - point_size = 1, - auto_crop = TRUE, - na_color = "#CCCCCC40") { - ## Some variables - x_centroid <- y_centroid <- key <- NULL - # stopifnot(all(c("x_centroid", "y_centroid", "key") %in% colnames(d))) - + function( + spe, + d, + clustervar, + sampleid = unique(spe$sample_id)[1], + colors, + title, + alpha = NA, + point_size = 1, + auto_crop = TRUE, + na_color = "#CCCCCC40" + ) { + ## Some variables + x_centroid <- y_centroid <- key <- NULL + # stopifnot(all(c("x_centroid", "y_centroid", "key") %in% colnames(d))) - # ## Crop the image if needed - # if (auto_crop) { - # frame_lims <- - # frame_limits(spe, sampleid = sampleid, image_id = image_id) - # img <- - # img[frame_lims$y_min:frame_lims$y_max, frame_lims$x_min:frame_lims$x_max] - # adjust <- - # list(x = frame_lims$x_min, y = frame_lims$y_min) - # } else { - adjust <- list(x = 0, y = 0) - # } - # - p <- ggplot( - d, - aes( - x = x_centroid, - y = y_centroid, - fill = factor(!!sym(clustervar)), - key = key - ) - ) + # ## Crop the image if needed + # if (auto_crop) { + # frame_lims <- + # frame_limits(spe, sampleid = sampleid, image_id = image_id) + # img <- + # img[frame_lims$y_min:frame_lims$y_max, frame_lims$x_min:frame_lims$x_max] + # adjust <- + # list(x = frame_lims$x_min, y = frame_lims$y_min) + # } else { + adjust <- list(x = 0, y = 0) + # } + # + p <- ggplot( + d, + aes( + x = x_centroid, + y = y_centroid, + fill = factor(!!sym(clustervar)), + key = key + ) + ) - p <- p + - geom_point( - shape = 21, - size = point_size, - stroke = 0, - colour = "transparent", - alpha = alpha - ) + - coord_fixed(expand = FALSE) + - scale_fill_manual(values = colors, na.value = na_color) + - xlab("") + ylab("") + - labs(fill = NULL) + - ggtitle(title) + - theme_set(theme_bw(base_size = 20)) + - theme( - panel.grid.major = element_blank(), - panel.grid.minor = element_blank(), - panel.background = element_blank(), - axis.line = element_blank(), - axis.text = element_blank(), - axis.ticks = element_blank(), - legend.box.spacing = unit(0, "pt") - ) - return(p) - } + p <- p + + geom_point( + shape = 21, + size = point_size, + stroke = 0, + colour = "transparent", + alpha = alpha + ) + + coord_fixed(expand = FALSE) + + scale_fill_manual(values = colors, na.value = na_color) + + xlab("") + + ylab("") + + labs(fill = NULL) + + ggtitle(title) + + theme_set(theme_bw(base_size = 20)) + + theme( + panel.grid.major = element_blank(), + panel.grid.minor = element_blank(), + panel.background = element_blank(), + axis.line = element_blank(), + axis.text = element_blank(), + axis.ticks = element_blank(), + legend.box.spacing = unit(0, "pt") + ) + return(p) + } diff --git a/R/vis_gene.R b/R/vis_gene.R index b620438e..05077565 100644 --- a/R/vis_gene.R +++ b/R/vis_gene.R @@ -166,10 +166,10 @@ #' ## Obtain the necessary data: Xenium example #' if (!exists("spe_xenium")) spe_xenium <- fetch_data("spe_xenium_test") #' -#' p9 <- vis_gene( +#' p9 <- vis_gene( #' spe = spe_xenium, -#' sampleid = "sample2", -#' geneid = "MBP", +#' sampleid = "Br1556", +#' geneid = rownames(spe_xenium)[which(rowData(spe_xenium)$gene_name == "MBP")], #' assayname = "counts", #' point_size = 1, #' datatype = "Xenium" @@ -228,6 +228,14 @@ vis_gene <- ## Check for valid datatype datatype <- match.arg(datatype) + # Validate 'cap_percentile' + if (cap_percentile <= 0 || cap_percentile > 1) { + stop("'cap_percentile' must be in (0, 1]", call. = FALSE) + } + + ## Subset to current sample + spe_sub <- spe[, spe$sample_id == sampleid] + if (datatype == "Visium") { # Check validity of spatial coordinates if ( @@ -242,13 +250,6 @@ vis_gene <- ) } - # Validate 'cap_percentile' - if (cap_percentile <= 0 || cap_percentile > 1) { - stop("'cap_percentile' must be in (0, 1]", call. = FALSE) - } - - spe_sub <- spe[, spe$sample_id == sampleid] - if (is_stitched) { # Drop excluded spots and calculate an appropriate point size temp <- prep_stitched_data(spe_sub, point_size, image_id) @@ -258,101 +259,116 @@ vis_gene <- # Frame limits are poorly defined for stitched data auto_crop <- FALSE } - - d <- as.data.frame( - cbind( - colData(spe_sub), - SpatialExperiment::spatialCoords(spe_sub) - ), - optional = TRUE - ) - - # Verify legitimacy of names in geneid - geneid_is_valid <- (geneid %in% rowData(spe_sub)$gene_search) | - (geneid %in% rownames(spe_sub)) | - (geneid %in% colnames(colData(spe_sub))) - if (any(!geneid_is_valid)) { - stop( - "Could not find the 'geneid'(s) ", - paste(geneid[!geneid_is_valid], collapse = ", "), - call. = FALSE + } else if (datatype == "Xenium") { + # Check validity of spatial coordinates + if ( + !setequal( + c("x_centroid", "y_centroid"), + colnames(spatialCoords(spe_sub)) ) - } - - # Grab any continuous colData columns and verify they're all numeric - cont_cols <- colData(spe_sub)[, - geneid[geneid %in% colnames(colData(spe_sub))], - drop = FALSE - ] - if (!all(sapply(cont_cols, class) %in% c("numeric", "integer"))) { + ) { stop( - "'geneid' can not contain non-numeric colData columns.", + "Abnormal spatial coordinates for Xenium datatype: should have 'x_centroid' and 'y_centroid' columns.", call. = FALSE ) } - cont_cols <- as.matrix(cont_cols) + } - # Get the integer indices of each gene in the SpatialExperiment, since we - # aren't guaranteed that rownames are gene names - remaining_geneid <- geneid[ - !(geneid %in% colnames(colData(spe_sub))) - ] - valid_gene_indices <- unique( - c( - match(remaining_geneid, rowData(spe_sub)$gene_search), - match(remaining_geneid, rownames(spe_sub)) - ) + d <- as.data.frame( + cbind( + colData(spe_sub), + SpatialExperiment::spatialCoords(spe_sub) + ), + optional = TRUE + ) + + # Verify legitimacy of names in geneid + geneid_is_valid <- (geneid %in% rowData(spe_sub)$gene_search) | + (geneid %in% rownames(spe_sub)) | + (geneid %in% colnames(colData(spe_sub))) + if (any(!geneid_is_valid)) { + stop( + "Could not find the 'geneid'(s) ", + paste(geneid[!geneid_is_valid], collapse = ", "), + call. = FALSE + ) + } + + # Grab any continuous colData columns and verify they're all numeric + cont_cols <- colData(spe_sub)[, + geneid[geneid %in% colnames(colData(spe_sub))], + drop = FALSE + ] + if (!all(sapply(cont_cols, class) %in% c("numeric", "integer"))) { + stop( + "'geneid' can not contain non-numeric colData columns.", + call. = FALSE ) - valid_gene_indices <- valid_gene_indices[!is.na(valid_gene_indices)] + } + cont_cols <- as.matrix(cont_cols) - # Grab any genes - gene_cols <- t( - as.matrix(assays(spe_sub[valid_gene_indices, ])[[assayname]]) + # Get the integer indices of each gene in the SpatialExperiment, since we + # aren't guaranteed that rownames are gene names + remaining_geneid <- geneid[ + !(geneid %in% colnames(colData(spe_sub))) + ] + valid_gene_indices <- unique( + c( + match(remaining_geneid, rowData(spe_sub)$gene_search), + match(remaining_geneid, rownames(spe_sub)) ) + ) + valid_gene_indices <- valid_gene_indices[!is.na(valid_gene_indices)] + + # Grab any genes + gene_cols <- t( + as.matrix(assays(spe_sub[valid_gene_indices, ])[[assayname]]) + ) - # Combine into one matrix where rows are samples and columns are continuous - # features - cont_matrix <- cbind(cont_cols, gene_cols) + # Combine into one matrix where rows are samples and columns are continuous + # features + cont_matrix <- cbind(cont_cols, gene_cols) - # Determine plot and legend titles - if (ncol(cont_matrix) == 1) { - plot_title <- paste(sampleid, geneid, ...) - d$COUNT <- cont_matrix[, 1] - if (!(geneid %in% colnames(colData(spe_sub)))) { - legend_title <- sprintf( - "%s\n min > %s", - assayname, - minCount - ) - } else { - legend_title <- sprintf("min > %s", minCount) - } + # Determine plot and legend titles + if (ncol(cont_matrix) == 1) { + plot_title <- paste(sampleid, geneid, ...) + d$COUNT <- cont_matrix[, 1] + if (!(geneid %in% colnames(colData(spe_sub)))) { + legend_title <- sprintf( + "%s\n min > %s", + assayname, + minCount + ) } else { - plot_title <- paste(sampleid, ...) - if (multi_gene_method == "z_score") { - d$COUNT <- multi_gene_z_score(cont_matrix) - legend_title <- paste("Z score\n min > ", minCount) - } else if (multi_gene_method == "sparsity") { - d$COUNT <- multi_gene_sparsity(cont_matrix) - legend_title <- paste("Prop. nonzero\n min > ", minCount) - } else { - # must be 'pca' - d$COUNT <- multi_gene_pca(cont_matrix) - legend_title <- paste("PC1\n min > ", minCount) - } + legend_title <- sprintf("min > %s", minCount) } - - # Cap the expression values at the given percentile, if applicable - if (cap_percentile < 1) { - sorted_count <- sort(d$COUNT) - cap <- sorted_count[ - as.integer(round(length(sorted_count) * cap_percentile)) - ] - d$COUNT[d$COUNT > cap] <- cap + } else { + plot_title <- paste(sampleid, ...) + if (multi_gene_method == "z_score") { + d$COUNT <- multi_gene_z_score(cont_matrix) + legend_title <- paste("Z score\n min > ", minCount) + } else if (multi_gene_method == "sparsity") { + d$COUNT <- multi_gene_sparsity(cont_matrix) + legend_title <- paste("Prop. nonzero\n min > ", minCount) + } else { + # must be 'pca' + d$COUNT <- multi_gene_pca(cont_matrix) + legend_title <- paste("PC1\n min > ", minCount) } + } - d$COUNT[d$COUNT <= minCount] <- NA + # Cap the expression values at the given percentile, if applicable + if (cap_percentile < 1) { + sorted_count <- sort(d$COUNT) + cap <- sorted_count[ + as.integer(round(length(sorted_count) * cap_percentile)) + ] + d$COUNT[d$COUNT > cap] <- cap + } + d$COUNT[d$COUNT <= minCount] <- NA + + if (datatype == "Visium") { p <- vis_gene_p( spe = spe_sub, d = d, @@ -370,120 +386,6 @@ vis_gene <- ) return(p) } else if (datatype == "Xenium") { - # Check validity of spatial coordinates - if ( - !setequal( - c("x_centroid", "y_centroid"), - colnames(spatialCoords(spe)) - ) - ) { - stop( - "Abnormal spatial coordinates for Xenium datatype: should have 'x_centroid' and 'y_centroid' columns.", - call. = FALSE - ) - } - - # Validate 'cap_percentile' - if (cap_percentile <= 0 || cap_percentile > 1) { - stop("'cap_percentile' must be in (0, 1]", call. = FALSE) - } - - spe_sub <- spe[, spe$sample_id == sampleid] - - d <- as.data.frame( - cbind( - colData(spe_sub), - SpatialExperiment::spatialCoords(spe_sub) - ), - optional = TRUE - ) - - # Verify legitimacy of names in geneid - geneid_is_valid <- (geneid %in% rowData(spe_sub)$gene_search) | - (geneid %in% rownames(spe_sub)) | - (geneid %in% colnames(colData(spe_sub))) - if (any(!geneid_is_valid)) { - stop( - "Could not find the 'geneid'(s) ", - paste(geneid[!geneid_is_valid], collapse = ", "), - call. = FALSE - ) - } - - # Grab any continuous colData columns and verify they're all numeric - cont_cols <- colData(spe_sub)[, - geneid[geneid %in% colnames(colData(spe_sub))], - drop = FALSE - ] - if (!all(sapply(cont_cols, class) %in% c("numeric", "integer"))) { - stop( - "'geneid' can not contain non-numeric colData columns.", - call. = FALSE - ) - } - cont_cols <- as.matrix(cont_cols) - - # Get the integer indices of each gene in the SpatialExperiment, since we - # aren't guaranteed that rownames are gene names - remaining_geneid <- geneid[ - !(geneid %in% colnames(colData(spe_sub))) - ] - valid_gene_indices <- unique( - c( - match(remaining_geneid, rowData(spe_sub)$gene_search), - match(remaining_geneid, rownames(spe_sub)) - ) - ) - valid_gene_indices <- valid_gene_indices[!is.na(valid_gene_indices)] - - # Grab any genes - gene_cols <- t( - as.matrix(assays(spe_sub[valid_gene_indices, ])[[assayname]]) - ) - - # Combine into one matrix where rows are samples and columns are continuous - # features - cont_matrix <- cbind(cont_cols, gene_cols) - - # Determine plot and legend titles - if (ncol(cont_matrix) == 1) { - plot_title <- paste(sampleid, geneid, ...) - d$COUNT <- cont_matrix[, 1] - if (!(geneid %in% colnames(colData(spe_sub)))) { - legend_title <- sprintf( - "%s\n min > %s", - assayname, - minCount - ) - } else { - legend_title <- sprintf("min > %s", minCount) - } - } else { - plot_title <- paste(sampleid, ...) - if (multi_gene_method == "z_score") { - d$COUNT <- multi_gene_z_score(cont_matrix) - legend_title <- paste("Z score\n min > ", minCount) - } else if (multi_gene_method == "sparsity") { - d$COUNT <- multi_gene_sparsity(cont_matrix) - legend_title <- paste("Prop. nonzero\n min > ", minCount) - } else { - # must be 'pca' - d$COUNT <- multi_gene_pca(cont_matrix) - legend_title <- paste("PC1\n min > ", minCount) - } - } - - # Cap the expression values at the given percentile, if applicable - if (cap_percentile < 1) { - sorted_count <- sort(d$COUNT) - cap <- sorted_count[ - as.integer(round(length(sorted_count) * cap_percentile)) - ] - d$COUNT[d$COUNT > cap] <- cap - } - - d$COUNT[d$COUNT <= minCount] <- NA - p <- vis_gene_c( spe = spe_sub, d = d, diff --git a/R/vis_gene_c.R b/R/vis_gene_c.R index 201330ae..82632ff0 100644 --- a/R/vis_gene_c.R +++ b/R/vis_gene_c.R @@ -4,15 +4,11 @@ #' This function visualizes the gene expression stored in `assays(spe)` or any #' continuous variable stored in `colData(spe)` for one given sample at the #' spot-level using (by default) the histology information on the background. -#' This is the function that does all the plotting behind [vis_clus()] when -#' `datatype = "Xenium"`. To visualize clusters (or any discrete variable) +#' This is the function that does all the plotting behind [vis_clus()] when +#' `datatype = "Xenium"`. To visualize clusters (or any discrete variable) #' use [vis_clus_c()]. #' -#' @param d A `data.frame()` with the sample-level information. This is -#' typically obtained using `cbind(colData(spe), spatialCoords(spe))`. -#' The `data.frame` has to contain -#' a column with the continuous variable data to plot stored under `d$COUNT`. -#' @param legend_title A `character(1)` specifying the legend title. +#' @inheritParams vis_gene_p #' @inheritParams vis_clus_c #' @inheritParams vis_gene #' @@ -28,10 +24,10 @@ #' #' if (enough_ram()) { #' ## Obtain the necessary data -#' if (!exists("spe")) spe <- fetch_data("spe_xenium_test") +#' if (!exists("spe_xenium")) spe_xenium <- fetch_data("spe_xenium_test") #' #' ## Prepare the data for the plotting function -#' spe_sub <- spe[, spe$sample_id == "sample1"] +#' spe_sub <- spe_xenium[, spe_xenium$sample_id == "Br1039"] #' df <- as.data.frame(cbind(colData(spe_sub), SpatialExperiment::spatialCoords(spe_sub)), optional = TRUE) #' df$COUNT <- df$detected_gex #' @@ -39,8 +35,8 @@ #' p <- vis_gene_c( #' spe = spe_sub, #' d = df, -#' sampleid = "sample1", -#' title = "sample1 detected_gex", +#' sampleid = "Br1039", +#' title = "Br1039 detected_gex", #' point_size = 1 #' ) #' print(p) @@ -49,80 +45,87 @@ #' rm(spe_sub) #' } vis_gene_c <- - function(spe, - d, - sampleid = unique(spe$sample_id)[1], - title, - viridis = TRUE, - alpha = NA, - cont_colors = if (viridis) viridisLite::viridis(21) else c("aquamarine4", "springgreen", "goldenrod", "red"), - point_size = 2, - na_color = "#CCCCCC40", - legend_title = "") { - ## Some variables - y_centroid <- x_centroid <- key <- COUNT <- NULL - # stopifnot(all(c("x_centroid", "y_centroid", "COUNT", "key") %in% colnames(d))) - - - # ## Crop the image if needed - # if (auto_crop) { - # frame_lims <- - # frame_limits(spe, sampleid = sampleid, image_id = image_id) - # img <- - # img[frame_lims$y_min:frame_lims$y_max, frame_lims$x_min:frame_lims$x_max] - # adjust <- - # list(x = frame_lims$x_min, y = frame_lims$y_min) - # } else { - adjust <- list(x = 0, y = 0) - # } - - p <- - ggplot( + function( + spe, d, - aes( - x = x_centroid, - y = y_centroid, - fill = COUNT, - color = COUNT, - key = key - ) - ) - - p <- p + - geom_point( - shape = 21, - size = point_size, - stroke = 0, - colour = "transparent", - alpha = alpha - ) + - coord_fixed(expand = FALSE) - - p <- p + scale_fill_gradientn( - name = legend_title, - colors = cont_colors, - na.value = na_color - ) + - scale_color_gradientn( - name = legend_title, - colors = cont_colors, - na.value = na_color - ) - - p <- p + - xlab("") + ylab("") + - labs(fill = NULL, color = NULL) + - ggtitle(title) + - theme_set(theme_bw(base_size = 20)) + - theme( - panel.grid.major = element_blank(), - panel.grid.minor = element_blank(), - panel.background = element_blank(), - axis.line = element_blank(), - axis.text = element_blank(), - axis.ticks = element_blank(), - legend.title = element_text(size = 10), - legend.box.spacing = unit(0, "pt") - ) - return(p) - } + sampleid = unique(spe$sample_id)[1], + title, + viridis = TRUE, + alpha = NA, + cont_colors = if (viridis) { + viridisLite::viridis(21) + } else { + c("aquamarine4", "springgreen", "goldenrod", "red") + }, + point_size = 2, + na_color = "#CCCCCC40", + legend_title = "" + ) { + ## Some variables + y_centroid <- x_centroid <- key <- COUNT <- NULL + # stopifnot(all(c("x_centroid", "y_centroid", "COUNT", "key") %in% colnames(d))) + + # ## Crop the image if needed + # if (auto_crop) { + # frame_lims <- + # frame_limits(spe, sampleid = sampleid, image_id = image_id) + # img <- + # img[frame_lims$y_min:frame_lims$y_max, frame_lims$x_min:frame_lims$x_max] + # adjust <- + # list(x = frame_lims$x_min, y = frame_lims$y_min) + # } else { + adjust <- list(x = 0, y = 0) + # } + + p <- + ggplot( + d, + aes( + x = x_centroid, + y = y_centroid, + fill = COUNT, + color = COUNT, + key = key + ) + ) + + p <- p + + geom_point( + shape = 21, + size = point_size, + stroke = 0, + colour = "transparent", + alpha = alpha + ) + + coord_fixed(expand = FALSE) + + p <- p + + scale_fill_gradientn( + name = legend_title, + colors = cont_colors, + na.value = na_color + ) + + scale_color_gradientn( + name = legend_title, + colors = cont_colors, + na.value = na_color + ) + + p <- p + + xlab("") + + ylab("") + + labs(fill = NULL, color = NULL) + + ggtitle(title) + + theme_set(theme_bw(base_size = 20)) + + theme( + panel.grid.major = element_blank(), + panel.grid.minor = element_blank(), + panel.background = element_blank(), + axis.line = element_blank(), + axis.text = element_blank(), + axis.ticks = element_blank(), + legend.title = element_text(size = 10), + legend.box.spacing = unit(0, "pt") + ) + return(p) + } diff --git a/R/vis_gene_p.R b/R/vis_gene_p.R index 900bd651..eacad349 100644 --- a/R/vis_gene_p.R +++ b/R/vis_gene_p.R @@ -47,32 +47,46 @@ #' rm(spe_sub) #' } vis_gene_p <- - function(spe, - d, - sampleid = unique(spe$sample_id)[1], - spatial, - title, - viridis = TRUE, - image_id = "lowres", - alpha = NA, - cont_colors = if (viridis) viridisLite::viridis(21) else c("aquamarine4", "springgreen", "goldenrod", "red"), - point_size = 2, - auto_crop = TRUE, - na_color = "#CCCCCC40", - legend_title = "") { + function( + spe, + + d, + sampleid = unique(spe$sample_id)[1], + spatial, + title, + viridis = TRUE, + image_id = "lowres", + alpha = NA, + cont_colors = if (viridis) { + viridisLite::viridis(21) + } else { + c("aquamarine4", "springgreen", "goldenrod", "red") + }, + point_size = 2, + auto_crop = TRUE, + na_color = "#CCCCCC40", + legend_title = "" + ) { ## Some variables pxl_row_in_fullres <- pxl_col_in_fullres <- key <- COUNT <- NULL # stopifnot(all(c("pxl_col_in_fullres", "pxl_row_in_fullres", "COUNT", "key") %in% colnames(d))) img <- - SpatialExperiment::imgRaster(spe, sample_id = sampleid, image_id = image_id) + SpatialExperiment::imgRaster( + spe, + sample_id = sampleid, + image_id = image_id + ) ## Crop the image if needed if (auto_crop) { frame_lims <- frame_limits(spe, sampleid = sampleid, image_id = image_id) img <- - img[frame_lims$y_min:frame_lims$y_max, frame_lims$x_min:frame_lims$x_max] + img[ + frame_lims$y_min:frame_lims$y_max, + frame_lims$x_min:frame_lims$x_max + ] adjust <- list(x = frame_lims$x_min, y = frame_lims$y_min) } else { @@ -83,8 +97,20 @@ vis_gene_p <- ggplot( d, aes( - x = pxl_col_in_fullres * SpatialExperiment::scaleFactors(spe, sample_id = sampleid, image_id = image_id) - adjust$x, - y = pxl_row_in_fullres * SpatialExperiment::scaleFactors(spe, sample_id = sampleid, image_id = image_id) - adjust$y, + x = pxl_col_in_fullres * + SpatialExperiment::scaleFactors( + spe, + sample_id = sampleid, + image_id = image_id + ) - + adjust$x, + y = pxl_row_in_fullres * + SpatialExperiment::scaleFactors( + spe, + sample_id = sampleid, + image_id = image_id + ) - + adjust$y, fill = COUNT, color = COUNT, key = key @@ -93,12 +119,14 @@ vis_gene_p <- if (spatial) { grob <- - grid::rasterGrob(img, + grid::rasterGrob( + img, width = grid::unit(1, "npc"), height = grid::unit(1, "npc") ) p <- - p + geom_spatial( + p + + geom_spatial( data = tibble::tibble(grob = list(grob)), aes(grob = grob), x = 0.5, @@ -116,11 +144,12 @@ vis_gene_p <- ) + coord_fixed(expand = FALSE) - p <- p + scale_fill_gradientn( - name = legend_title, - colors = cont_colors, - na.value = na_color - ) + + p <- p + + scale_fill_gradientn( + name = legend_title, + colors = cont_colors, + na.value = na_color + ) + scale_color_gradientn( name = legend_title, colors = cont_colors, @@ -130,7 +159,8 @@ vis_gene_p <- p <- p + xlim(0, ncol(img)) + ylim(nrow(img), 0) + - xlab("") + ylab("") + + xlab("") + + ylab("") + labs(fill = NULL, color = NULL) + ggtitle(title) + theme_set(theme_bw(base_size = 20)) + diff --git a/man/vis_clus.Rd b/man/vis_clus.Rd index ce8faf8d..1cafb6c5 100644 --- a/man/vis_clus.Rd +++ b/man/vis_clus.Rd @@ -150,7 +150,7 @@ if (enough_ram()) { ... = " LIBD Layers" ) print(p4) - + ## edit plot point size but keep guide size larger p5 <- vis_clus( spe = spe, @@ -163,16 +163,16 @@ if (enough_ram()) { ... = " LIBD Layers" ) print(p5) - + ## Obtain the necessary data: Xenium example if (!exists("spe_xenium")) spe_xenium <- fetch_data("spe_xenium_example") - + spe_xenium$x_half <- ifelse(spatialCoords(spe_xenium)[,"x_centroid"] < 3088, "left", "right") - - p6 <- vis_clus( + + p6 <- vis_clus( spe = spe_xenium, clustervar = "x_half", - sampleid = "sample2", + sampleid = "Br1556", colors = c(left = "red", right = "blue"), na_color = "white", point_size = 1, @@ -181,9 +181,7 @@ if (enough_ram()) { datatype = "Xenium" ) print(p6) - - - + } } \seealso{ diff --git a/man/vis_clus_c.Rd b/man/vis_clus_c.Rd index f049eafd..981f9683 100644 --- a/man/vis_clus_c.Rd +++ b/man/vis_clus_c.Rd @@ -69,31 +69,29 @@ behind \code{\link[=vis_clus]{vis_clus()}} when \code{datatype = "Xenium"}. To v if (enough_ram()) { ## Obtain the necessary data - if (!exists("spe")) spe <- fetch_data("spe_xenium_example") - - # spe <- readRDS("../Xenium/XeniumIO_test/spe_Xenium_test.rds") - + if (!exists("spe_xenium")) spe_xenium <- fetch_data("spe_xenium_example") + ## Prepare the data for the plotting function - spe_sub <- spe[, spe$sample_id == "sample1"] - - # summary(spatialCoords(spe_sub)[,"x_centroid"]) - # summary(spatialCoords(spe_sub)[,"y_centroid"]) - - ## add catagorical variable - spe_sub$x_half <- ifelse(spatialCoords(spe_sub)[,"x_centroid"] < 3088, "left", "right") - table(spe_sub$x_half) + spe_sub <- spe_xenium[, spe_xenium$sample_id == "Br1039"] + + # summary(spatialCoords(spe_sub)[,"x_centroid"]) + # summary(spatialCoords(spe_sub)[,"y_centroid"]) + + ## add catagorical variable + spe_sub$x_half <- ifelse(spatialCoords(spe_sub)[,"x_centroid"] < 3088, "left", "right") + table(spe_sub$x_half) - p <- vis_clus_c( - spe = spe_sub, - d = as.data.frame(cbind(colData(spe_sub), SpatialExperiment::spatialCoords(spe_sub)), optional = TRUE), - clustervar = "x_half", - sampleid = "sample01.1", - #colors = libd_layer_colors, - colors = c(left = "red", right = "blue"), - title = "Xenium test", - point_size = 1, - alpha = 0.5 - ) + p <- vis_clus_c( + spe = spe_sub, + d = as.data.frame(cbind(colData(spe_sub), SpatialExperiment::spatialCoords(spe_sub)), optional = TRUE), + clustervar = "x_half", + sampleid = "sample01.1", + #colors = libd_layer_colors, + colors = c(left = "red", right = "blue"), + title = "Xenium test", + point_size = 1, + alpha = 0.5 + ) print(p) ## Clean up diff --git a/man/vis_gene.Rd b/man/vis_gene.Rd index c0ec0684..6a85ae2c 100644 --- a/man/vis_gene.Rd +++ b/man/vis_gene.Rd @@ -253,10 +253,10 @@ if (enough_ram()) { ## Obtain the necessary data: Xenium example if (!exists("spe_xenium")) spe_xenium <- fetch_data("spe_xenium_test") - p9 <- vis_gene( + p9 <- vis_gene( spe = spe_xenium, - sampleid = "sample2", - geneid = "MBP", + sampleid = "Br1556", + geneid = rownames(spe_xenium)[which(rowData(spe_xenium)$gene_name == "MBP")], assayname = "counts", point_size = 1, datatype = "Xenium" diff --git a/man/vis_gene_c.Rd b/man/vis_gene_c.Rd index 0b5f5f63..6fa1e11b 100644 --- a/man/vis_gene_c.Rd +++ b/man/vis_gene_c.Rd @@ -12,8 +12,12 @@ vis_gene_c( title, viridis = TRUE, alpha = NA, - cont_colors = if (viridis) viridisLite::viridis(21) else c("aquamarine4", - "springgreen", "goldenrod", "red"), + cont_colors = if (viridis) { + viridisLite::viridis(21) + } else { + + c("aquamarine4", "springgreen", "goldenrod", "red") + }, point_size = 2, na_color = "#CCCCCC40", legend_title = "" @@ -75,10 +79,10 @@ use \code{\link[=vis_clus_c]{vis_clus_c()}}. if (enough_ram()) { ## Obtain the necessary data - if (!exists("spe")) spe <- fetch_data("spe_xenium_test") + if (!exists("spe_xenium")) spe_xenium <- fetch_data("spe_xenium_test") ## Prepare the data for the plotting function - spe_sub <- spe[, spe$sample_id == "sample1"] + spe_sub <- spe_xenium[, spe_xenium$sample_id == "Br1039"] df <- as.data.frame(cbind(colData(spe_sub), SpatialExperiment::spatialCoords(spe_sub)), optional = TRUE) df$COUNT <- df$detected_gex @@ -86,8 +90,8 @@ if (enough_ram()) { p <- vis_gene_c( spe = spe_sub, d = df, - sampleid = "sample1", - title = "sample1 detected_gex", + sampleid = "Br1039", + title = "Br1039 detected_gex", point_size = 1 ) print(p) diff --git a/man/vis_gene_p.Rd b/man/vis_gene_p.Rd index 294bab48..60ffadf0 100644 --- a/man/vis_gene_p.Rd +++ b/man/vis_gene_p.Rd @@ -13,8 +13,12 @@ vis_gene_p( viridis = TRUE, image_id = "lowres", alpha = NA, - cont_colors = if (viridis) viridisLite::viridis(21) else c("aquamarine4", - "springgreen", "goldenrod", "red"), + cont_colors = if (viridis) { + viridisLite::viridis(21) + } else { + + c("aquamarine4", "springgreen", "goldenrod", "red") + }, point_size = 2, auto_crop = TRUE, na_color = "#CCCCCC40", From ea237b0dc5f26dfac7cc9c20296b9789ff1e7f83 Mon Sep 17 00:00:00 2001 From: lcolladotor Date: Wed, 20 May 2026 16:23:48 -0400 Subject: [PATCH 34/36] Add support on the shiny app for Xenium data --- R/app_server.R | 310 ++++++++++++++++++++++++++----------------- R/app_ui.R | 348 +++++++++++++++++++++++++------------------------ R/run_app.R | 142 +++++++++++--------- man/run_app.Rd | 18 ++- 4 files changed, 460 insertions(+), 358 deletions(-) diff --git a/R/app_server.R b/R/app_server.R index ae68b8be..62ee8ae5 100644 --- a/R/app_server.R +++ b/R/app_server.R @@ -179,9 +179,10 @@ app_server <- function(input, output, session) { auto_crop = input$auto_crop, is_stitched = is_stitched, guide_point_size = input$guidepointsize, + datatype = datatype, ... = paste(" with", input$cluster) ) - if (!input$side_by_side_histology) { + if (!input$side_by_side_histology || datatype == "Xenium") { return(p) } else { p_no_spots <- p @@ -221,6 +222,7 @@ app_server <- function(input, output, session) { auto_crop = isolate(input$auto_crop), is_stitched = is_stitched, guide_point_size = isolate(input$guidepointsize), + datatype = datatype, ... = paste(" with", isolate(input$cluster)) ) cowplot::plot_grid( @@ -247,9 +249,10 @@ app_server <- function(input, output, session) { point_size = input$pointsize, auto_crop = input$auto_crop, is_stitched = is_stitched, - cap_percentile = input$cap_percentile + cap_percentile = input$cap_percentile, + datatype = datatype ) - if (!input$side_by_side_gene) { + if (!input$side_by_side_gene | datatype == "Xenium") { p_result <- p } else { p_no_spots <- p @@ -298,7 +301,8 @@ app_server <- function(input, output, session) { sample_order = isolate(input$gene_grid_samples), auto_crop = isolate(input$auto_crop), is_stitched = is_stitched, - cap_percentile = isolate(input$cap_percentile) + cap_percentile = isolate(input$cap_percentile), + datatype = datatype ) }, warning = function(w) { @@ -662,32 +666,40 @@ app_server <- function(input, output, session) { # reduced_name <- 'TSNE_perplexity50' # genecolor <- "viridis" - ## Read in the histology image - img <- - SpatialExperiment::imgRaster( - spe, - sample_id = sampleid, - image_id = input$imageid - ) - if (input$auto_crop) { - frame_lims <- - frame_limits(spe, sampleid = sampleid, image_id = input$imageid) - img <- - img[ - frame_lims$y_min:frame_lims$y_max, - frame_lims$x_min:frame_lims$x_max - ] - } - ## From vis_gene() in global.R spe_sub <- spe[, spe$sample_id == sampleid] point_size <- input$pointsize - if (is_stitched) { - # Drop excluded spots and calculate an appropriate point size - temp <- prep_stitched_data(spe_sub, point_size, input$imageid) - spe_sub <- temp$spe - point_size <- temp$point_size + + ## Interactive for Visium + if (datatype == "Visium") { + ## Read in the histology image + img <- + SpatialExperiment::imgRaster( + spe_sub, + sample_id = sampleid, + image_id = input$imageid + ) + if (input$auto_crop) { + frame_lims <- + frame_limits( + spe_sub, + sampleid = sampleid, + image_id = input$imageid + ) + img <- + img[ + frame_lims$y_min:frame_lims$y_max, + frame_lims$x_min:frame_lims$x_max + ] + } + + if (is_stitched) { + # Drop excluded spots and calculate an appropriate point size + temp <- prep_stitched_data(spe_sub, point_size, input$imageid) + spe_sub <- temp$spe + point_size <- temp$point_size + } } d <- @@ -708,7 +720,9 @@ app_server <- function(input, output, session) { # Get the integer indices of each gene in the SpatialExperiment, since we # aren't guaranteed that rownames are gene names - remaining_geneid <- geneid[!(geneid %in% colnames(colData(spe_sub)))] + remaining_geneid <- geneid[ + !(geneid %in% colnames(colData(spe_sub))) + ] valid_gene_indices <- unique( c( match(remaining_geneid, rowData(spe_sub)$gene_search), @@ -751,7 +765,11 @@ app_server <- function(input, output, session) { plot_title <- paste(sampleid, "Z-score min > ", minCount) } else if (input$multi_gene_method == "sparsity") { d$COUNT <- multi_gene_sparsity(cont_matrix) - plot_title <- paste(sampleid, "Prop. nonzero min > ", minCount) + plot_title <- paste( + sampleid, + "Prop. nonzero min > ", + minCount + ) } else { # must be 'pca' d$COUNT <- multi_gene_pca(cont_matrix) @@ -763,7 +781,9 @@ app_server <- function(input, output, session) { if (input$cap_percentile < 1) { sorted_count <- sort(d$COUNT) cap <- sorted_count[ - as.integer(round(length(sorted_count) * input$cap_percentile)) + as.integer(round( + length(sorted_count) * input$cap_percentile + )) ] d$COUNT[d$COUNT > cap] <- cap } @@ -784,41 +804,72 @@ app_server <- function(input, output, session) { ## Use client-side highlighting d_key <- highlight_key(d, ~key) - ## Make the cluster plot - p_clus <- vis_clus_p( - spe = spe, - d = d_key, - clustervar = clustervar, - sampleid = sampleid, - colors = get_colors(colors, d[, clustervar]), - spatial = FALSE, - title = plot_title, - image_id = input$imageid, - alpha = input$alphalevel, - point_size = point_size, - auto_crop = input$auto_crop - ) + if (datatype == "Visium") { + ## Make the cluster plot + p_clus <- vis_clus_p( + spe = spe_sub, + d = d_key, + clustervar = clustervar, + sampleid = sampleid, + colors = get_colors(colors, d[, clustervar]), + spatial = FALSE, + title = plot_title, + image_id = input$imageid, + alpha = input$alphalevel, + point_size = point_size, + auto_crop = input$auto_crop + ) - ## Next the gene plot - p_gene <- vis_gene_p( - spe = spe, - d = d_key, - sampleid = sampleid, - spatial = FALSE, - title = "", - cont_colors = cont_colors(), - image_id = input$imageid, - alpha = input$alphalevel, - point_size = point_size, - auto_crop = input$auto_crop - ) + - geom_point( - shape = 21, - size = point_size, - stroke = 0, - alpha = input$alphalevel + ## Next the gene plot + p_gene <- vis_gene_p( + spe = spe_sub, + d = d_key, + sampleid = sampleid, + spatial = FALSE, + title = "", + cont_colors = cont_colors(), + image_id = input$imageid, + alpha = input$alphalevel, + point_size = point_size, + auto_crop = input$auto_crop + ) + + geom_point( + shape = 21, + size = point_size, + stroke = 0, + alpha = input$alphalevel + ) + } else if (datatype == "Xenium") { + ## Make the cluster plot + p_clus <- vis_clus_c( + spe = spe_sub, + d = d_key, + clustervar = clustervar, + sampleid = sampleid, + title = plot_title, + colors = get_colors(colors, d[, clustervar]), + alpha = input$alphalevel, + point_size = point_size ) + ## Next the gene plot + p_gene <- vis_gene_c( + spe = spe_sub, + d = d_key, + sampleid = sampleid, + title = "", + alpha = input$alphalevel, + cont_colors = cont_colors(), + point_size = point_size + ) + + geom_point( + shape = 21, + size = point_size, + stroke = 0, + alpha = input$alphalevel + ) + } + ## Make the reduced dimensions ggplot if (reduced_name != "") { p_dim <- ggplot( @@ -838,7 +889,9 @@ app_server <- function(input, output, session) { scale_fill_manual( values = get_colors( colors, - colData(spe)[[clustervar]][spe$sample_id == sampleid] + colData(spe)[[clustervar]][ + spe$sample_id == sampleid + ] ) ) + guides(fill = "none") + @@ -906,22 +959,26 @@ app_server <- function(input, output, session) { source = "plotly_histology", tooltip = c("fill", "key") ), - images = list( + images = if (datatype == "Xenium") { + NULL + } else { list( - source = raster2uri(img), - layer = "below", - xanchor = "left", - yanchor = "bottom", - xref = "x", - yref = "y", - sizing = "stretch", - x = 0, - y = -nrow(img), - sizex = ncol(img), - sizey = nrow(img), - opacity = 0.8 + list( + source = raster2uri(img), + layer = "below", + xanchor = "left", + yanchor = "bottom", + xref = "x", + yref = "y", + sizing = "stretch", + x = 0, + y = -nrow(img), + sizex = ncol(img), + sizey = nrow(img), + opacity = 0.8 + ) ) - ), + }, dragmode = "lasso" ) @@ -931,22 +988,26 @@ app_server <- function(input, output, session) { source = "plotly_histology", tooltip = c("fill", "key") ), - images = list( + images = if (datatype == "Xenium") { + NULL + } else { list( - source = raster2uri(img), - layer = "below", - xanchor = "left", - yanchor = "bottom", - xref = "x", - yref = "y", - sizing = "stretch", - x = 0, - y = -nrow(img), - sizex = ncol(img), - sizey = nrow(img), - opacity = 0.8 + list( + source = raster2uri(img), + layer = "below", + xanchor = "left", + yanchor = "bottom", + xref = "x", + yref = "y", + sizing = "stretch", + x = 0, + y = -nrow(img), + sizex = ncol(img), + sizey = nrow(img), + opacity = 0.8 + ) ) - ), + }, dragmode = "lasso" ) @@ -1065,7 +1126,8 @@ app_server <- function(input, output, session) { point_size = input$pointsize, auto_crop = input$auto_crop, is_stitched = is_stitched, - cap_percentile = input$cap_percentile + cap_percentile = input$cap_percentile, + datatype = datatype ) + geom_point( shape = 21, @@ -1077,25 +1139,27 @@ app_server <- function(input, output, session) { ## Update the reactiveValues data rv$ContCount <- p$data[, c("key", "COUNT")] - ## Read in the histology image - img <- - SpatialExperiment::imgRaster( - spe, - sample_id = input$sample, - image_id = input$imageid - ) - if (input$auto_crop) { - frame_lims <- - frame_limits( + if (datatype == "Visium") { + ## Read in the histology image + img <- + SpatialExperiment::imgRaster( spe, - sampleid = input$sample, + sample_id = input$sample, image_id = input$imageid ) - img <- - img[ - frame_lims$y_min:frame_lims$y_max, - frame_lims$x_min:frame_lims$x_max - ] + if (input$auto_crop) { + frame_lims <- + frame_limits( + spe, + sampleid = input$sample, + image_id = input$imageid + ) + img <- + img[ + frame_lims$y_min:frame_lims$y_max, + frame_lims$x_min:frame_lims$x_max + ] + } } suppressMessages(suppressWarnings(toWebGL( @@ -1107,22 +1171,26 @@ app_server <- function(input, output, session) { source = "plotly_gene", tooltip = c("fill", "key") ), - images = list( + images = if (datatype == "Xenium") { + NULL + } else { list( - source = raster2uri(img), - layer = "below", - xanchor = "left", - yanchor = "bottom", - xref = "x", - yref = "y", - sizing = "stretch", - x = 0, - y = -nrow(img), - sizex = ncol(img), - sizey = nrow(img), - opacity = 0.8 + list( + source = raster2uri(img), + layer = "below", + xanchor = "left", + yanchor = "bottom", + xref = "x", + yref = "y", + sizing = "stretch", + x = 0, + y = -nrow(img), + sizex = ncol(img), + sizey = nrow(img), + opacity = 0.8 + ) ) - ), + }, dragmode = "lasso" ) ))) diff --git a/R/app_ui.R b/R/app_ui.R index 9dd9dfd0..ca4e663f 100644 --- a/R/app_ui.R +++ b/R/app_ui.R @@ -69,7 +69,11 @@ app_ui <- function() { inputId = "imageid", label = "Image name", choices = c( - "edited_imaged", + if (datatype == "Xenium") { + NULL + } else { + "edited_imaged" + }, unique(imgData(spe)$image_id) ), selected = unique(imgData(spe)$image_id)[1] @@ -566,183 +570,187 @@ app_ui <- function() { tags$br(), tags$br() ), - tabPanel( - "Edit image", - helpText( - "Edit the selected image by manipulating the colors and apperance, which can be useful when inspecting the selected image from the left menu ('image name'). Once you have a set of edits you like, click the 'update custom image' button below to save your edits. Next, select on the left menu ('image name') the 'edited_image' option to use your new image as the background image in the rest of the visualizations. Most of these image manipulations are explained at", - HTML( - "the magick R package documentation." - ) - ), - helpText( - "If you want a uniform colored background, set the brightness to 0 which will make it black, then either proceeed or select the 'negate' checkbox for white, click the 'edit custom image' button, and select the input 'image name' as 'edited image'. Instead of using 'negate' you could use 'transparent (color)' and type 'black' then under 'background (color)' type a valid R color name such as 'purple' or 'lightblue' or a color HEX value such as '#e1eb34'." - ), - hr(), - fluidRow( - column( - width = 4, - selectInput( - inputId = "editImg_channel", - label = "Select an image channel such as 'Red' or 'Blue'", - choices = c( - "", - magick::channel_types() - ), - selected = "" - ), - helpText( - "Leave this empty if you don't want to select a channel. Note that the definition of channel here is different from a multi-channel image from say VisiumIF." - ), - hr(), - numericInput( - "editImg_brightness", - label = "Image brightness level", - value = 100, - min = 0, - max = 100 - ), - numericInput( - "editImg_saturation", - label = "Image saturation level", - value = 100, - min = 0, - max = 100 - ), - numericInput( - "editImg_hue", - label = "Image hue level", - value = 100, - min = 0, - max = 200 - ), - helpText( - "Modulate the colors in the image. Brightness and saturation are in percents while hue has a range of 0 to 200. Use 100 for all 3 options for no change." + if (datatype == "Xenium") { + NULL + } else { + tabPanel( + "Edit image", + helpText( + "Edit the selected image by manipulating the colors and apperance, which can be useful when inspecting the selected image from the left menu ('image name'). Once you have a set of edits you like, click the 'update custom image' button below to save your edits. Next, select on the left menu ('image name') the 'edited_image' option to use your new image as the background image in the rest of the visualizations. Most of these image manipulations are explained at", + HTML( + "the magick R package documentation." ) ), - column( - width = 4, - checkboxInput( - "editImg_enhance", - "enhance: attempt to minimize noise", - value = FALSE - ), - checkboxInput( - "editImg_normalize", - "normalize: increases contrast by normalizing the pixel values to span the full range of colors", - value = FALSE - ), - hr(), - numericInput( - "editImg_contrast_sharpen", - label = "contrast (sharpen): enhance intensity differences in image", - value = NA, - min = -100, - max = 100 - ), - helpText( - "Try with 1 to start with." - ), - hr(), - numericInput( - "editImg_quantize_max", - label = "quantize (max): reduce number of colors in the image", - value = NA, - min = 1 - ), - checkboxInput( - "editImg_quantize_dither", - "quantize (dither): whether to apply Floyd/Steinberg error diffusion to the image: averages intensities of several neighboring pixels", - value = TRUE - ), - helpText( - "You could try 256 colors or a much small number like 25 or 40." - ) + helpText( + "If you want a uniform colored background, set the brightness to 0 which will make it black, then either proceeed or select the 'negate' checkbox for white, click the 'edit custom image' button, and select the input 'image name' as 'edited image'. Instead of using 'negate' you could use 'transparent (color)' and type 'black' then under 'background (color)' type a valid R color name such as 'purple' or 'lightblue' or a color HEX value such as '#e1eb34'." ), - column( - width = 4, - checkboxInput( - "editImg_equalize", - "equalize: whether to use histogram equalization", - value = FALSE - ), - hr(), - textInput( - "editImg_transparent_color", - label = "transparent (color): set pixels approximately matching given color", - value = NA - ), - numericInput( - "editImg_transparent_fuzz", - label = "transparent (fuzz): relative color distance (value between 0 and 100) to be considered similar", - value = 0, - min = 0, - max = 100 - ), - helpText( - "Type 'purple' and select a fuzz of 25 to start with." - ), - textInput( - "editImg_background_color", - label = "background (color): sets background color", - value = NA - ), - hr(), - numericInput( - "editImg_median_radius", - label = "median (radius): replace each pixel with the median color in a circular neighborhood", - value = NA, - min = 0 + hr(), + fluidRow( + column( + width = 4, + selectInput( + inputId = "editImg_channel", + label = "Select an image channel such as 'Red' or 'Blue'", + choices = c( + "", + magick::channel_types() + ), + selected = "" + ), + helpText( + "Leave this empty if you don't want to select a channel. Note that the definition of channel here is different from a multi-channel image from say VisiumIF." + ), + hr(), + numericInput( + "editImg_brightness", + label = "Image brightness level", + value = 100, + min = 0, + max = 100 + ), + numericInput( + "editImg_saturation", + label = "Image saturation level", + value = 100, + min = 0, + max = 100 + ), + numericInput( + "editImg_hue", + label = "Image hue level", + value = 100, + min = 0, + max = 200 + ), + helpText( + "Modulate the colors in the image. Brightness and saturation are in percents while hue has a range of 0 to 200. Use 100 for all 3 options for no change." + ) ), - helpText( - "Choose a small radius, like 1 or 2 to start. The higher the value, the longer this computation will take." + column( + width = 4, + checkboxInput( + "editImg_enhance", + "enhance: attempt to minimize noise", + value = FALSE + ), + checkboxInput( + "editImg_normalize", + "normalize: increases contrast by normalizing the pixel values to span the full range of colors", + value = FALSE + ), + hr(), + numericInput( + "editImg_contrast_sharpen", + label = "contrast (sharpen): enhance intensity differences in image", + value = NA, + min = -100, + max = 100 + ), + helpText( + "Try with 1 to start with." + ), + hr(), + numericInput( + "editImg_quantize_max", + label = "quantize (max): reduce number of colors in the image", + value = NA, + min = 1 + ), + checkboxInput( + "editImg_quantize_dither", + "quantize (dither): whether to apply Floyd/Steinberg error diffusion to the image: averages intensities of several neighboring pixels", + value = TRUE + ), + helpText( + "You could try 256 colors or a much small number like 25 or 40." + ) ), - hr(), - checkboxInput( - "editImg_negate", - "negate: whether to negate colors", - value = FALSE + column( + width = 4, + checkboxInput( + "editImg_equalize", + "equalize: whether to use histogram equalization", + value = FALSE + ), + hr(), + textInput( + "editImg_transparent_color", + label = "transparent (color): set pixels approximately matching given color", + value = NA + ), + numericInput( + "editImg_transparent_fuzz", + label = "transparent (fuzz): relative color distance (value between 0 and 100) to be considered similar", + value = 0, + min = 0, + max = 100 + ), + helpText( + "Type 'purple' and select a fuzz of 25 to start with." + ), + textInput( + "editImg_background_color", + label = "background (color): sets background color", + value = NA + ), + hr(), + numericInput( + "editImg_median_radius", + label = "median (radius): replace each pixel with the median color in a circular neighborhood", + value = NA, + min = 0 + ), + helpText( + "Choose a small radius, like 1 or 2 to start. The higher the value, the longer this computation will take." + ), + hr(), + checkboxInput( + "editImg_negate", + "negate: whether to negate colors", + value = FALSE + ) ) + ), + actionButton( + "editImg_reset_menus", + label = "Reset menus" + ), + helpText( + "Reset all image editing menus to their default values." + ), + hr(), + downloadButton( + "downloadPlotEditImg", + "Download PDF" + ), + plotOutput("editImg_plot"), + tags$br(), + tags$br(), + tags$br(), + tags$br(), + tags$br(), + tags$br(), + tags$br(), + tags$br(), + tags$br(), + tags$br(), + actionButton( + "editImg_update", + label = "Update custom image" + ), + helpText( + "Click the 'upgrade custom image' button above to save the custom image. You can then select 'edited_image' and use it in other parts of the web application. Note that if you had 'edited_image' already selected, you'll need to re-select or change another input to update the other plots." + ), + checkboxInput( + "editImg_overwrite", + "Whether to overwrite the 'edited_image'", + value = FALSE + ), + helpText( + "Select if you want to do sequential image manipulations when you have selected as input the 'edited_image'." ) - ), - actionButton( - "editImg_reset_menus", - label = "Reset menus" - ), - helpText( - "Reset all image editing menus to their default values." - ), - hr(), - downloadButton( - "downloadPlotEditImg", - "Download PDF" - ), - plotOutput("editImg_plot"), - tags$br(), - tags$br(), - tags$br(), - tags$br(), - tags$br(), - tags$br(), - tags$br(), - tags$br(), - tags$br(), - tags$br(), - actionButton( - "editImg_update", - label = "Update custom image" - ), - helpText( - "Click the 'upgrade custom image' button above to save the custom image. You can then select 'edited_image' and use it in other parts of the web application. Note that if you had 'edited_image' already selected, you'll need to re-select or change another input to update the other plots." - ), - checkboxInput( - "editImg_overwrite", - "Whether to overwrite the 'edited_image'", - value = FALSE - ), - helpText( - "Select if you want to do sequential image manipulations when you have selected as input the 'edited_image'." ) - ) + } ) ) ) diff --git a/R/run_app.R b/R/run_app.R index bcc8e564..b0bd391f 100644 --- a/R/run_app.R +++ b/R/run_app.R @@ -208,79 +208,91 @@ #' default_cluster = "scran_quick_cluster", #' is_stitched = TRUE #' ) -#' -#'## Example for Xenium object #' +#' ## Example for Xenium object #' if (!exists("spe_xenium")) spe_xenium <- fetch_data("spe_xenium_example") -#' -#' run_app(spe_xenium, +#' +#' run_app(spe_xenium, #' sce_layer = NULL, modeling_results = NULL, sig_genes = NULL, #' title = "spatialLIBD Xenium without layer info", -#' datatype = "Xenium" -#' ) -#' +#' spe_discrete_vars = c( +#' "SpX", +#' "ManualAnnotation" +#' ), +#' spe_continuous_vars = c( +#' "sum_gex", # "sum_umi", +#' "detected_gex" #"sum_gene", +#' ), +#' default_cluster = "SpX", +#' datatype = "Xenium" +#' ) +#' #' } -#' +#' run_app <- function( - spe = fetch_data(type = "spe"), - sce_layer = fetch_data(type = "sce_layer"), - modeling_results = fetch_data(type = "modeling_results"), - sig_genes = sig_genes_extract_all( - n = nrow(sce_layer), - modeling_results = modeling_results, - sce_layer = sce_layer - ), - docs_path = system.file("app", "www", package = "spatialLIBD"), - title = "spatialLIBD", - spe_discrete_vars = c( - "spatialLIBD", - "GraphBased", - "ManualAnnotation", - "Maynard", - "Martinowich", - paste0("SNN_k50_k", 4:28), - "SpatialDE_PCA", - "SpatialDE_pool_PCA", - "HVG_PCA", - "pseudobulk_PCA", - "markers_PCA", - "SpatialDE_UMAP", - "SpatialDE_pool_UMAP", - "HVG_UMAP", - "pseudobulk_UMAP", - "markers_UMAP", - "SpatialDE_PCA_spatial", - "SpatialDE_pool_PCA_spatial", - "HVG_PCA_spatial", - "pseudobulk_PCA_spatial", - "markers_PCA_spatial", - "SpatialDE_UMAP_spatial", - "SpatialDE_pool_UMAP_spatial", - "HVG_UMAP_spatial", - "pseudobulk_UMAP_spatial", - "markers_UMAP_spatial" - ), - spe_continuous_vars = c( - "cell_count", - "sum_umi", - "sum_gene", - "expr_chrM", - "expr_chrM_ratio" - ), - default_cluster = "spatialLIBD", - auto_crop_default = TRUE, - is_stitched = FALSE, - datatype = c("Visium", "Xenium"), - ...) { + spe = fetch_data(type = "spe"), + sce_layer = fetch_data(type = "sce_layer"), + modeling_results = fetch_data(type = "modeling_results"), + sig_genes = sig_genes_extract_all( + n = nrow(sce_layer), + modeling_results = modeling_results, + sce_layer = sce_layer + ), + docs_path = system.file("app", "www", package = "spatialLIBD"), + title = "spatialLIBD", + spe_discrete_vars = c( + "spatialLIBD", + "GraphBased", + "ManualAnnotation", + "Maynard", + "Martinowich", + paste0("SNN_k50_k", 4:28), + "SpatialDE_PCA", + "SpatialDE_pool_PCA", + "HVG_PCA", + "pseudobulk_PCA", + "markers_PCA", + "SpatialDE_UMAP", + "SpatialDE_pool_UMAP", + "HVG_UMAP", + "pseudobulk_UMAP", + "markers_UMAP", + "SpatialDE_PCA_spatial", + "SpatialDE_pool_PCA_spatial", + "HVG_PCA_spatial", + "pseudobulk_PCA_spatial", + "markers_PCA_spatial", + "SpatialDE_UMAP_spatial", + "SpatialDE_pool_UMAP_spatial", + "HVG_UMAP_spatial", + "pseudobulk_UMAP_spatial", + "markers_UMAP_spatial" + ), + spe_continuous_vars = c( + "cell_count", + "sum_umi", + "sum_gene", + "expr_chrM", + "expr_chrM_ratio" + ), + default_cluster = "spatialLIBD", + auto_crop_default = TRUE, + is_stitched = FALSE, + datatype = c("Visium", "Xenium"), + ... +) { ## Run the checks in the relevant order stopifnot(length(default_cluster) == 1) stopifnot(default_cluster %in% spe_discrete_vars) - if (is_stitched) auto_crop_default <- FALSE + if (is_stitched) { + auto_crop_default <- FALSE + } ## Check for valid datatype datatype <- match.arg(datatype) spe <- - check_spe(spe, + check_spe( + spe, variables = c(spe_discrete_vars, spe_continuous_vars), datatype = datatype ) @@ -306,7 +318,8 @@ run_app <- function( if (!is.null(sce_layer)) { ## Check required files when sce_layer is present stopifnot(file.exists(file.path( - docs_path, "documentation_sce_layer.md" + docs_path, + "documentation_sce_layer.md" ))) } @@ -322,8 +335,13 @@ run_app <- function( spe_discrete_vars = spe_discrete_vars, spe_continuous_vars = spe_continuous_vars, default_cluster = default_cluster, - auto_crop_default = auto_crop_default, + auto_crop_default = ifelse( + datatype == "Visium", + auto_crop_default, + FALSE + ), is_stitched = is_stitched, + datatype = datatype, ... ) ) diff --git a/man/run_app.Rd b/man/run_app.Rd index 710cc7b1..bb1153d3 100644 --- a/man/run_app.Rd +++ b/man/run_app.Rd @@ -276,15 +276,23 @@ run_app( is_stitched = TRUE ) -## Example for Xenium object - +## Example for Xenium object if (!exists("spe_xenium")) spe_xenium <- fetch_data("spe_xenium_example") - run_app(spe_xenium, +run_app(spe_xenium, sce_layer = NULL, modeling_results = NULL, sig_genes = NULL, title = "spatialLIBD Xenium without layer info", - datatype = "Xenium" - ) + spe_discrete_vars = c( + "SpX", + "ManualAnnotation" + ), + spe_continuous_vars = c( + "sum_gex", # "sum_umi", + "detected_gex" #"sum_gene", + ), + default_cluster = "SpX", + datatype = "Xenium" +) } From 7d08e0684f4cd08e4d17effca41af02de74952a3 Mon Sep 17 00:00:00 2001 From: lcolladotor Date: Wed, 20 May 2026 16:32:24 -0400 Subject: [PATCH 35/36] Update fetch_data() for the spe_xenium_example data --- R/fetch_data.R | 18 ++++++++---------- inst/extdata/metadata_LFF_spatial_ERC.csv | 2 +- inst/scripts/make-metadata_LFF_spatial_ERC.R | 15 ++++++++++----- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/R/fetch_data.R b/R/fetch_data.R index 83383b53..a57e02a2 100644 --- a/R/fetch_data.R +++ b/R/fetch_data.R @@ -410,16 +410,14 @@ fetch_data <- file_name <- "sce_subcluster_pseudobulk-cell_type_anno.rds" url <- "https://www.dropbox.com/scl/fi/y42pv7k02luvznwqii2rm/sce_subcluster_modeling_results-cell_type_anno.rds?rlkey=17c0ybowjpejdxc71tuxlcfna&dl=1" - } else if ( - type == "spe_xenium_example" - ) { - tag <- "LFF_spatial_ERC" - hub_title <- type - - ## While EH is not set-up - file_name <- "spe_Xenium_test.rds" - url <- - "https://www.dropbox.com/scl/fi/y42pv7k02luvznwqii2rm/sce_subcluster_modeling_results-cell_type_anno.rds?rlkey=17c0ybowjpejdxc71tuxlcfna&dl=1" + } else if (type == "spe_xenium_example") { + tag <- "LFF_spatial_ERC" + hub_title <- type + + ## While EH is not set-up + file_name <- "spe_Xenium_test.rds" + url <- + "https://www.dropbox.com/scl/fi/os4wz0kkbtmbpvnju4bxi/spe_Xenium_test.rds?rlkey=0ql1pu5d9qe448sjmkh3ja3o8&st=npwqxtdk&dl=1" } file_path <- file.path(destdir, file_name) diff --git a/inst/extdata/metadata_LFF_spatial_ERC.csv b/inst/extdata/metadata_LFF_spatial_ERC.csv index fac6d51c..2ee49fdc 100644 --- a/inst/extdata/metadata_LFF_spatial_ERC.csv +++ b/inst/extdata/metadata_LFF_spatial_ERC.csv @@ -7,4 +7,4 @@ "LFF_spatial_ERC_snRNAseq_pseudobulk_subcluster","Pseudo-bulked SingleCellExperiment object LFF_spatial_ERC human brain (ERC) snRNA-seq data (n = 31) at 38 subcluster resolution, from the Chromium platform from 10x Genomics generated by the Lieber Institute for Brain Development (LIBD) and available through the spatialLIBD Bioconductor package.","3.21","GRCh38","GTF","https://bioconductor.org/packages/spatialLIBD","Nov 20 2025","Homo sapiens",9606,TRUE,"LIBD","Leonardo Collado-Torres ","SingleCellExperiment","Rds","spatialLIBD/spatialLIBD_files/sce_subcluster_pseudobulk-cell_type_anno.rds","Visium_snRNAseq_AD_Alzheimer_Disease_APOE_ERC_spatialLIBD" "LFF_spatial_ERC_snRNAseq_modeling_results_broad","List of modeling results at the 9 broad cell type resolution for the LFF_spatial_ERC human brain (ERC) for snRNA-seq (n = 31) from the Chromium platform from 10x Genomics generated by the Lieber Institute for Brain Development (LIBD) and available through the spatialLIBD Bioconductor package.","3.21","GRCh38","GTF","https://bioconductor.org/packages/spatialLIBD","Nov 20 2025","Homo sapiens",9606,TRUE,"LIBD","Leonardo Collado-Torres ","list","Rds","spatialLIBD/spatialLIBD_files/sce_subcluster_pseudobulk-cell_type_broad.rds","Visium_snRNAseq_AD_Alzheimer_Disease_APOE_ERC_spatialLIBD" "LFF_spatial_ERC_snRNAseq_modeling_results_subcluster","List of modeling results at the 38 subcluster resolution for the LFF_spatial_ERC human brain (ERC) for snRNA-seq (n = 31) from the Chromium platform from 10x Genomics generated by the Lieber Institute for Brain Development (LIBD) and available through the spatialLIBD Bioconductor package.","3.21","GRCh38","GTF","https://bioconductor.org/packages/spatialLIBD","Nov 20 2025","Homo sapiens",9606,TRUE,"LIBD","Leonardo Collado-Torres ","list","Rds","spatialLIBD/spatialLIBD_files/sce_subcluster_modeling_results-cell_type_anno.rds","Visium_snRNAseq_AD_Alzheimer_Disease_APOE_ERC_spatialLIBD" -"spe_xenium_example","SpatialExperiment object of Xenium data for the LFF_spatial_ERC human brain (ERC) spatial transcriptomics data (n = 2) from the Xenium platform from 10x Genomics generated by the Lieber Institute for Brain Development (LIBD) and available through the spatialLIBD Bioconductor package.","3.24","GRCh38","GTF","https://bioconductor.org/packages/spatialLIBD","May 5 2026","Homo sapiens",9606,TRUE,"LIBD","Leonardo Collado-Torres ","SpatialExperiment","FilePath","spatialLIBD/spatialLIBD_files/spe_Xenium_test.rds","Visium_snRNAseq_AD_Alzheimer_Disease_APOE_ERC_spatialLIBD" \ No newline at end of file +"spe_xenium_example","SpatialExperiment object with example Xenium data (n = 2) from the LFF_spatial_ERC human brain (ERC) spatial transcriptomics data from the Xenium platform from 10x Genomics generated by the Lieber Institute for Brain Development (LIBD) and available through the spatialLIBD Bioconductor package.","3.21","GRCh38","GTF","https://bioconductor.org/packages/spatialLIBD","May 20 2026","Homo sapiens",9606,TRUE,"LIBD","Leonardo Collado-Torres ","SpatialExperiment","Rds","spatialLIBD/spatialLIBD_files/spe_Xenium_test.rds","Visium_snRNAseq_AD_Alzheimer_Disease_APOE_ERC_spatialLIBD" diff --git a/inst/scripts/make-metadata_LFF_spatial_ERC.R b/inst/scripts/make-metadata_LFF_spatial_ERC.R index 4ec828be..dc25f9d0 100644 --- a/inst/scripts/make-metadata_LFF_spatial_ERC.R +++ b/inst/scripts/make-metadata_LFF_spatial_ERC.R @@ -15,7 +15,8 @@ meta <- data.frame( "LFF_spatial_ERC_snRNAseq_pseudobulk_broad", "LFF_spatial_ERC_snRNAseq_pseudobulk_subcluster", "LFF_spatial_ERC_snRNAseq_modeling_results_broad", - "LFF_spatial_ERC_snRNAseq_modeling_results_subcluster" + "LFF_spatial_ERC_snRNAseq_modeling_results_subcluster", + "spe_xenium_example" ), Description = c( "SpatialExperiment object at the spot-level for the LFF_spatial_ERC human brain (ERC) spatial transcriptomics data (n = 31) from the Visium platform from 10x Genomics generated by the Lieber Institute for Brain Development (LIBD) and available through the spatialLIBD Bioconductor package.", @@ -25,13 +26,14 @@ meta <- data.frame( "Pseudo-bulked SingleCellExperiment object LFF_spatial_ERC human brain (ERC) snRNA-seq data (n = 31) at 9 broad cell type resolution, from the Chromium platform from 10x Genomics generated by the Lieber Institute for Brain Development (LIBD) and available through the spatialLIBD Bioconductor package.", "Pseudo-bulked SingleCellExperiment object LFF_spatial_ERC human brain (ERC) snRNA-seq data (n = 31) at 38 subcluster resolution, from the Chromium platform from 10x Genomics generated by the Lieber Institute for Brain Development (LIBD) and available through the spatialLIBD Bioconductor package.", "List of modeling results at the 9 broad cell type resolution for the LFF_spatial_ERC human brain (ERC) for snRNA-seq (n = 31) from the Chromium platform from 10x Genomics generated by the Lieber Institute for Brain Development (LIBD) and available through the spatialLIBD Bioconductor package.", - "List of modeling results at the 38 subcluster resolution for the LFF_spatial_ERC human brain (ERC) for snRNA-seq (n = 31) from the Chromium platform from 10x Genomics generated by the Lieber Institute for Brain Development (LIBD) and available through the spatialLIBD Bioconductor package." + "List of modeling results at the 38 subcluster resolution for the LFF_spatial_ERC human brain (ERC) for snRNA-seq (n = 31) from the Chromium platform from 10x Genomics generated by the Lieber Institute for Brain Development (LIBD) and available through the spatialLIBD Bioconductor package.", + "SpatialExperiment object with example Xenium data (n = 2) from the LFF_spatial_ERC human brain (ERC) spatial transcriptomics data from the Xenium platform from 10x Genomics generated by the Lieber Institute for Brain Development (LIBD) and available through the spatialLIBD Bioconductor package." ), BiocVersion = "3.21", Genome = "GRCh38", SourceType = "GTF", SourceUrl = "https://bioconductor.org/packages/spatialLIBD", - SourceVersion = "Nov 20 2025", + SourceVersion = rep(c("Nov 20 2025", "May 20 2026"), c(8, 1)), Species = "Homo sapiens", TaxonomyId = 9606, Coordinate_1_based = TRUE, @@ -45,7 +47,8 @@ meta <- data.frame( "SingleCellExperiment", "SingleCellExperiment", "list", - "list" + "list", + "SpatialExperiment" ), DispatchClass = c( "FilePath", @@ -55,6 +58,7 @@ meta <- data.frame( "Rds", "Rds", "Rds", + "Rds", "Rds" ), RDataPath = file.path( @@ -68,7 +72,8 @@ meta <- data.frame( "sce_subcluster_pseudobulk-cell_type_broad.rds", "sce_subcluster_pseudobulk-cell_type_anno.rds", "sce_subcluster_pseudobulk-cell_type_broad.rds", - "sce_subcluster_modeling_results-cell_type_anno.rds" + "sce_subcluster_modeling_results-cell_type_anno.rds", + "spe_Xenium_test.rds" ) ), Tags = "Visium_snRNAseq_AD_Alzheimer_Disease_APOE_ERC_spatialLIBD", From 046bd0e48baa56f9d6bd13ff7d443eaf0469fb63 Mon Sep 17 00:00:00 2001 From: lcolladotor Date: Wed, 20 May 2026 16:37:22 -0400 Subject: [PATCH 36/36] v1.25.1 -- `vis_*()` functions and `run_app()` now support visualizing Xenium data. `fetch_data()` now includes example Xenium data. --- NEWS.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/NEWS.md b/NEWS.md index 16ae1187..64e0d049 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,17 @@ +# spatialLIBD 1.25.1 + +NEW FEATURES + +* `fetch_data()` now has example Xenium data you can access +with `type = "spe_xenium_example"`. +* `vis_gene()`, `vis_gene_grid()`, `vis_clus()`, `vis_clus_grid()` +all now support Xenium data. This is partially done through the +new internal functions `vis_gene_c()` and `vis_clus_c()`. +* `run_app()` now supports Xenium data. So `shiny`-powered +apps can now be built for Xenium (10x Genomics) data. +* These features were added by @lahuuki and @lcolladotor +as part of . + # spatialLIBD 1.23.2 NEW FEATURES