Skip to contents

Overview

In this tutorial, we will illustrate how to perform de novo discovery of spatially distinct cellular ecosystems (SEs) from single-cell spatial transcriptomics dataset using SpatialEcoTyper.

We will be analyzing single-cell spatial transcriptomics data from a melanoma sample (raw data available in Vizgen’s MERSCOPE FFPE Human Immuno-oncology). To quickly demonstrate how SpatialEcoTyper works, we’ve selected a subset of the sample, which can be downloaded from here.

This demo data comprises the spatial expression of 500 genes across 27,907 cells, which are categorized into ten distinct cell types: B cells, CD4+ T cells, CD8+ T cells, NK cells, plasma cells, macrophages, dendritic cells (DC), fibroblasts, endothelial cells, and the cancer cell of origin (CCOs). Given the large proportion of CCOs, and in order to reduce running time, CCOs were excluded from this demonstration.

Besides, all cells were grouped into four regions, including tumor, inner margin, outer margin, and stroma. The tumor and stroma regions were defined based on the density of CCOs. For detailed information about the tumor and stroma annotations, please refer to the CytoSPACE paper. The inner and outer margins were delineated as regions extending 250 μm inside and outside the tumor boundaries, respectively. In addition, we assessed distance of each single cell to tumor/stroma interface by computing the shortest Euclidean distance to tumor regions (or stromal regions) for cells localized in stromal region (or tumor regions). A positive distance is used for cells localized in tumor region, and a negative distance is used for cells localized in stroma region.

SpatialEcoTyper analysis requires two input data:

  • gene expression matrix: rows represent gene names and columns represent cell IDs
  • meta data: a data frame with at least three columns, including “X” (X-coordinate), “Y” (Y-coordinate), and “CellType” (the cell type annotation). The row names of the meta data should match the column names (cell IDs) in the expression matrix.

First load required packages for this vignette

Loading data

Text files as input

Download the data from Google Drive

drive_deauth() # Disable Google sign-in requirement
drive_download(as_id("13Rc5Rsu8jbnEYYfUse-xQ7ges51LcI7n"), "HumanMelanomaPatient1_subset_counts.tsv.gz", overwrite = TRUE)
drive_download(as_id("12xcZNhpT-xbhcG8kX1QAdTeM9TKeFAUW"), "HumanMelanomaPatient1_subset_scmeta.tsv", overwrite = TRUE)

Large text files can be loaded into R using the fread function from the data.table package.

# Load single-cell gene expression matrix. Rows represent gene names and columns represent cell IDs
scdata <- fread("HumanMelanomaPatient1_subset_counts.tsv.gz", 
                sep = "\t",header = TRUE, data.table = FALSE)
rownames(scdata) <- scdata[, 1]  # Setting the first column as row names
scdata <- as.matrix(scdata[, -1]) # Dropping first column
head(scdata[, 1:5])
##          HumanMelanomaPatient1__cell_3655 HumanMelanomaPatient1__cell_3657
## PDK4                                    0                                1
## TNFRSF17                                0                                0
## ICAM3                                   0                                0
## FAP                                     1                                0
## GZMB                                    0                                0
## TSC2                                    0                                0
##          HumanMelanomaPatient1__cell_3658 HumanMelanomaPatient1__cell_3660
## PDK4                                    1                                0
## TNFRSF17                                0                                0
## ICAM3                                   0                                0
## FAP                                     0                                0
## GZMB                                    0                                0
## TSC2                                    0                                0
##          HumanMelanomaPatient1__cell_3661
## PDK4                                    0
## TNFRSF17                                0
## ICAM3                                   0
## FAP                                     0
## GZMB                                    0
## TSC2                                    0
# Load single-cell metadata
# Three columns are required, including "X", "Y", and "CellType"
# The row names should match the cell ids in the expression matrix
scmeta <- read.table("HumanMelanomaPatient1_subset_scmeta.tsv", 
                      sep = "\t",header = TRUE, row.names = 1)
scmeta <- scmeta[match(colnames(scdata), rownames(scmeta)), ] # match the cell ids in scdata and scmeta
head(scmeta[, c("X", "Y", "CellType")])
##                                         X         Y   CellType
## HumanMelanomaPatient1__cell_3655 1894.706 -6367.766 Fibroblast
## HumanMelanomaPatient1__cell_3657 1942.480 -6369.602 Fibroblast
## HumanMelanomaPatient1__cell_3658 1963.007 -6374.026 Fibroblast
## HumanMelanomaPatient1__cell_3660 1981.600 -6372.266 Fibroblast
## HumanMelanomaPatient1__cell_3661 1742.939 -6374.851 Fibroblast
## HumanMelanomaPatient1__cell_3663 1921.683 -6383.309 Fibroblast
Sparse matrix as input

SpatialEcoTyper supports sparse matrix as input. Mtx files can be loaded into R using the ReadMtx function from the Seurat package.

drive_download(as_id("13M3xhRxp0xK9gf5F4DE9idSBFqVQIXDT"), "HumanMelanomaPatient1_subset_counts.mtx.gz", overwrite = TRUE)
drive_download(as_id("136feRaFjMtNvduLTm5xqa3WhyyoG4Xzo"), "HumanMelanomaPatient1_subset_cells.tsv.gz", overwrite = TRUE)
drive_download(as_id("13QprWzJhzzUy_w3XSrjlt9pjf2n-G7HV"), "HumanMelanomaPatient1_subset_genes.tsv.gz", overwrite = TRUE)

scdata <- ReadMtx(mtx = "HumanMelanomaPatient1_subset_counts.mtx.gz", 
                  cells = "HumanMelanomaPatient1_subset_cells.tsv.gz", 
                  features = "HumanMelanomaPatient1_subset_genes.tsv.gz",
                  feature.column = 1, cell.column = 1)

Data normalization

The gene expression data should be normalized before SpatialEcoTyper analysis. The data can be normalized using NormalizeData or SCTransform.

Here, we are normalizing using SCTransform normalization. We recommend to install the glmGamPoi package for faster computation.

if(!"glmGamPoi" %in% installed.packages()){
  BiocManager::install("glmGamPoi")
}
tmpobj <- CreateSeuratObject(scdata) %>%
      SCTransform(clip.range = c(-10, 10), verbose = FALSE)

seurat_version = as.integer(gsub("\\..*", "", as.character(packageVersion("SeuratObject"))))
if(seurat_version<5){
  normdata <- GetAssayData(tmpobj, "data")
}else{
  normdata <- tmpobj[["SCT"]]$data
}
Using NormalizeData for the normalization:
normdata <- NormalizeData(scdata)

Preview of the sample

The SpatialView function can be used to visualize single cells in spatial space, by cell type or by region.

# Visualize the cell type annotations in the tissue
SpatialView(scmeta, by = "CellType") + scale_color_manual(values = pals::cols25())

# Visualize the regions in the tissue
SpatialView(scmeta, by = "Region") + scale_color_brewer(type = "qual", palette = "Set1")

The SpatialView function can also be used to visualize continuous characteristics, such as the minimum distance of each single cell to tumor/stroma margin. Here, positive distances indicate cells located within the tumor region, while negative distances denote cells within the stroma.

# Visualize the distance to tumor margin
SpatialView(scmeta, by = "Dist2Interface") + 
  scale_colour_gradient2(low = "#5e3c99", high = "#e66101", 
                         mid = "#d9d9d9", midpoint = 0) + 
  labs(color = "Distance to\ntumor margin")

SE discovery using SpatialEcoTyper

The SpatialEcoTyper function is designed to identify spatial ecotypes (SEs) from single-cell spatial transcriptomics data. The workflow begins by grouping spatially co-localized single cells into microregions, where cell-type-specific gene expression profiles (GEPs) are constructed for each microregion. Spatial networks of these microregions are constructed based on their cell type-specific GEPs, merged, and clustered for identification of SEs.

Key arguments for SpatialEcoTyper
  • normdata A matrix representing normalized gene expression data, where rows correspond to genes and columns correspond to cells.
  • metadata A data frame containing metadata associated with each cell. Must include spatial coordinates (e.g., X and Y) as well as cell type annotations. The row names must match the column names of the normdata.
  • outprefix Character string specifying the prefix for output file names.
  • radius Numeric specifying the radius (default: 50 µm) for defining spatial microregion.
  • minibatch Integer specifying the number of columns to process in each minibatch in the SNF analysis. Default is 5000. This option splits the matrix into smaller chunks (minibatch), thus reducing memory usage.
  • ncores Integer specifying the number of CPU cores to use for parallel processing.

You can type ?SpatialEcoTyper to visualize the full manual.

## This step takes ~2 minutes to complete on macOS with an Apple M1 Pro chip and 16 GB memory.
se_results <- SpatialEcoTyper(normdata, scmeta,
                              outprefix = "Melanoma1_subset",
                              radius = 50, ncores = 2)
Optimizing memory usage

For real-world single-cell ST datasets with over 100,000 cells, the analysis can be both time- and memory-intensive. To speed up the process, you can increase the number of cores used (ncores), but this will also increase memory consumption.

If you have limited computational memory, there are several strategies to reduce usage. One approach is to decrease the minibatch size and reduce the number of cores (ncores) allocated. However, for larger datasets, the minimum memory requirement may remain high. Another option is to increase the grid.size, which reduces the number of spatial microregions. This can significantly decrease memory usage by downsampling the ST data, potentially excluding many cells from the analysis though.

Interested in multicellular communities associated with specific cell types?

If you’re interested in studying multicellular communities associated with specific cell types, you can use the filter.region.by.celltypes argument to focus on microregions containing at least one cell from the specified cell types. For example, to identify multicellular communities related to T cells, you can use the following command.

se_results <- SpatialEcoTyper(normdata, scmeta,
                              outprefix = "Melanoma1_subset",
                              radius = 50, ncores = 2,
                              filter.region.by.celltypes = c("CD8T", "CD4T"))
Interested in regions with multiple cell types?

If you’re interested in regions composed of multiple cell types, you can use the min.cts.per.region argument to specify the minimum number of distinct cell types required for each microregion to be included in the analysis.

se_results <- SpatialEcoTyper(normdata, scmeta,
                              outprefix = "Melanoma1_subset",
                              radius = 50, ncores = 2,
                              min.cts.per.region = 2)

SpatialEcoTyper result

When the outprefix is specified, the SpatialEcoTyper result will be saved as a RDS file named outprefix_SpatialEcoTyper_results.rds. The result can be loaded into R using readRDS.

## drive_download(as_id("18AzHCtwwXoOM9_yRCjyfzcLQaWiQo1au"), "Melanoma1_subset_SpatialEcoTyper_results.rds", overwrite = TRUE)
se_results <- readRDS("Melanoma1_subset_SpatialEcoTyper_results.rds")

The SpatialEcoTyper result is a list that includes two key components:

  • a Seurat object constructed from the integrated embedding of spatial microregions, allowing for detailed analysis and visualization of spatial architecture;
  • a data frame of the single-cell metadata, which includes annotations for discovered spatial ecotypes. These annotations can be used to explore the spatial distribution and characteristics of SEs within the tissue.
# Extract the Seurat object and updated single-cell metadata
obj <- se_results$obj # A Seurat object
scmeta <- se_results$metadata # Updated single-cell meta data, with SE annotation added
head(scmeta)
##                                         X         Y   CellType CellTypeName
## HumanMelanomaPatient1__cell_3655 1894.706 -6367.766 Fibroblast  Fibroblasts
## HumanMelanomaPatient1__cell_3657 1942.480 -6369.602 Fibroblast  Fibroblasts
## HumanMelanomaPatient1__cell_3658 1963.007 -6374.026 Fibroblast  Fibroblasts
## HumanMelanomaPatient1__cell_3660 1981.600 -6372.266 Fibroblast  Fibroblasts
## HumanMelanomaPatient1__cell_3661 1742.939 -6374.851 Fibroblast  Fibroblasts
## HumanMelanomaPatient1__cell_3663 1921.683 -6383.309 Fibroblast  Fibroblasts
##                                  Region Dist2Interface  SE
## HumanMelanomaPatient1__cell_3655 Stroma      -883.1752 SE6
## HumanMelanomaPatient1__cell_3657 Stroma      -894.8463 SE7
## HumanMelanomaPatient1__cell_3658 Stroma      -904.1115 SE7
## HumanMelanomaPatient1__cell_3660 Stroma      -907.8909 SE7
## HumanMelanomaPatient1__cell_3661 Stroma      -874.2712 SE0
## HumanMelanomaPatient1__cell_3663 Stroma      -903.6559 SE6
table(scmeta$SE) ## The number of cells in each SE
## 
##  SE0  SE1  SE2  SE3  SE4  SE5  SE6  SE7 
## 2847 6724 2773 5324 1857 3285 2856 2124

Embedding of spatial architecture

The spatial microregions’ embedding can be visualized using standard Seurat functions such as DimPlot and FeaturePlot. These visualizations help to explore the spatial organization and heterogeneity within the tissue.

Note: The embedding and clustering results differ slightly between Seurat v4 and v5. However, the overall clustering patterns remain largely consistent, with an Adjusted Rand Index (ARI) of 0.7 for the demonstration dataset. This consistency ensures that, despite minor variations, the key biological insights are preserved across versions.

Visualizing tumor/stroma regions in the embedding

DimPlot(obj, group.by = "Region") + scale_color_brewer(type = "qual", palette = "Set1")

Visualizing the distance of microregions to tumor/stroma interface

This plot highlights the distance of each spatial microregion to the tumor/stroma interface, revealing the spatial continuum learnt by SpatialEcoTyper. Here, positive distances indicate microregions located within the tumor region, while negative distances denote microregions within the stroma.

FeaturePlot(obj, "Dist2Interface", min.cutoff = -600, max.cutoff = 600) + 
  scale_colour_gradient2(low = "#5e3c99", high = "#e66101", mid = "#d9d9d9", midpoint = 0)

Visualizing spatial ecotypes in the embedding

This plot visualizes the SEs within the spatial embedding. The SEs represent distinct spatial microenvironments, each with unique cellular compositions and spatial characteristics.

DimPlot(obj, group.by = "SE") + scale_color_manual(values = pals::kelly()[-1])

Visualizing the most abundant cell types in each spatial microregion

Here, we focus on visualizing the dominant cell types within each spatial microregion to highlight the characteristic cellular composition of different SEs.

DimPlot(obj, group.by = "CellType") +
  scale_color_manual(values = pals::cols25())

SE characteristics

Visualizing SEs in the tissue

The spatial distribution of SEs within the tissue can be visualized using the SpatialView function. This visualization provides insights into the localization and spatial organization of different SEs across the tissue section.

SpatialView(scmeta, by = "SE")

Note: NA values in the SE annotations denote cells excluded from this analysis, due to absence of neighboring cells or an insufficient number of detected genes.

Visualizing the cell type composition of SEs

The bar plot below illustrates the cell type composition within each SE. This visualization helps to identify the dominant cell types in each SE and compare the cellular makeup across different SEs.

gg <- scmeta %>% filter(!is.na(SE)) %>% count(SE, CellType)
ggplot(gg, aes(SE, n, fill = CellType)) + 
  geom_bar(stat = "identity", position = "fill") +
  scale_fill_manual(values = pals::cols25()) +
  theme_bw(base_size = 14) + coord_flip() + 
  labs(y = "Cell type abundance")

Visualizing the association between SEs and pre-annotated regions

This bar plot shows the relationship between SEs and pre-annotated regions, such as tumor and stroma. The proportion of each SE associated with these regions is displayed, providing insights into how SEs correspond to different tissue regions.

gg <- scmeta %>% filter(!is.na(SE)) %>% count(SE, Region)
ggplot(gg, aes(SE, n, fill = Region)) + 
  geom_bar(stat = "identity", position = "fill") + 
  scale_fill_brewer(type = "qual", palette = "Set1") +
  theme_bw(base_size = 14) + coord_flip() + 
  labs(y = "Fraction")

Visualizing the distance of SEs to tumor/stroma interface

This box plot visualizes the distribution of distances of SEs to the tumor/stroma interface. Positive distances indicate cells located within the tumor region, while negative distances denote cells within the stroma. The SEs are ordered by their median distance, highlighting their spatial localization relative to the tumor/stroma interface.

gg <- scmeta %>% filter(!is.na(SE))
## Order SEs by their distance to tumor/stroma interface
tmp <-  gg %>% group_by(SE) %>% summarise(Mid = median(Dist2Interface)) %>% arrange(Mid) %>% pull(SE)
gg$SE = factor(gg$SE, levels = tmp)
ggplot(gg, aes(SE, Dist2Interface)) + 
  geom_boxplot() + theme_bw() + labs(y = "Distance to tumor/stroma interface (μm)")

Identification of cell-type-specific SE markers

Differential expression analysis

To identify cell-type-specific SE markers, we can perform differential expression analysis using the presto package. Below is an example of how to identify fibroblast-specific markers for each SE.

if(!"presto" %in% installed.packages()){ 
  BiocManager::install("devtools")
  devtools::install_github("immunogenomics/presto")
}
library("presto")

# Ensure the normalized data is aligned with the single-cell metadata
normdata = normdata[, rownames(scmeta)]

# Perform differential expression analysis for fibroblasts across SEs
idx = which(scmeta$CellType=="Fibroblast" & !is.na(scmeta$SE))
degs = wilcoxauc(normdata[, idx], scmeta$SE[idx])

# Filter for significant markers with positive log-fold change
degs = degs %>% filter(logFC>0 & pval<0.05) %>% arrange(desc(logFC))
head(degs)
##   feature group  avgExpr     logFC statistic       auc          pval
## 1   ACTA2   SE7 2.269714 1.9200869   4826706 0.7719666 6.157041e-196
## 2 PLA2G2A   SE0 2.076013 1.4444331   9636530 0.8226676  0.000000e+00
## 3 PLA2G2A   SE6 2.177647 1.4145934   5418244 0.7996960 2.595786e-219
## 4     PKM   SE4 2.592488 1.3997410   3527900 0.8820820 1.875924e-178
## 5     FN1   SE3 3.124745 1.1463924   5771112 0.7979963 4.778744e-193
## 6 HLA-DRA   SE3 1.711258 0.9756877   5434388 0.7514360 8.958194e-160
##            padj   pct_in  pct_out
## 1 8.455670e-194 69.35897 30.37675
## 2  0.000000e+00 90.22005 34.38547
## 3 1.069464e-216 88.39390 40.08561
## 4 3.864404e-176 98.75260 81.20265
## 5 4.922107e-191 98.03922 88.25844
## 6 6.151293e-158 86.05664 43.91978

Note: The MERSCOPE data used for SE discovery includes a limited number of genes. To enhance the analysis, you can extend the differential expression analysis to the whole transcriptome by aligning scRNA-seq data to the spatial transcriptomics data via CytoSPACE.

Visualizing the expression of cell state markers

Once you’ve identified potential markers, you can visualize their expression across SEs. Below is an example using the ACTA2 gene.

## Gene expression across all SEs
gg <- scmeta[idx, ]
gg$Expression <- normdata["ACTA2", idx]
ggplot(gg, aes(SE, Expression)) + geom_boxplot() + 
  theme_classic(base_size = 14) + ylab("ACTA2 expression")

You can also visualize the expression of a marker (e.g., ACTA2) in the tissue.

## Visualize gene expression in spatial
gg <- scmeta
gg$Expression <- normdata["ACTA2", ]
SpatialView(gg, by = "Expression") + scale_color_viridis_c() + 
  labs(color = "ACTA2\nexpression")

Session info

The session info allows users to replicate the exact environment and identify potential discrepancies in package versions or configurations that might be causing problems.

## R version 4.4.1 (2024-06-14)
## Platform: aarch64-apple-darwin20
## Running under: macOS 15.1
## 
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib 
## LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0
## 
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
## 
## time zone: America/Los_Angeles
## tzcode source: internal
## 
## attached base packages:
## [1] parallel  stats     graphics  grDevices utils     datasets  methods  
## [8] base     
## 
## other attached packages:
##  [1] presto_1.0.0          Rcpp_1.0.13           SpatialEcoTyper_1.0.0
##  [4] NMF_0.28              Biobase_2.64.0        BiocGenerics_0.50.0  
##  [7] cluster_2.1.6         rngtools_1.5.2        registry_0.5-1       
## [10] RANN_2.6.2            Matrix_1.7-0          R.utils_2.12.3       
## [13] R.oo_1.26.0           R.methodsS3_1.8.2     googledrive_2.1.1    
## [16] data.table_1.16.0     Seurat_5.1.0          SeuratObject_5.0.2   
## [19] sp_2.1-4              ggplot2_3.5.1         dplyr_1.1.4          
## 
## loaded via a namespace (and not attached):
##   [1] RcppAnnoy_0.0.22            splines_4.4.1              
##   [3] later_1.3.2                 tibble_3.2.1               
##   [5] polyclip_1.10-7             fastDummies_1.7.4          
##   [7] lifecycle_1.0.4             doParallel_1.0.17          
##   [9] pals_1.9                    globals_0.16.3             
##  [11] lattice_0.22-6              MASS_7.3-60.2              
##  [13] magrittr_2.0.3              plotly_4.10.4              
##  [15] sass_0.4.9                  rmarkdown_2.28             
##  [17] jquerylib_0.1.4             yaml_2.3.10                
##  [19] httpuv_1.6.15               glmGamPoi_1.16.0           
##  [21] sctransform_0.4.1           spam_2.10-0                
##  [23] spatstat.sparse_3.1-0       reticulate_1.39.0          
##  [25] mapproj_1.2.11              cowplot_1.1.3              
##  [27] pbapply_1.7-2               RColorBrewer_1.1-3         
##  [29] maps_3.4.2                  zlibbioc_1.50.0            
##  [31] abind_1.4-5                 GenomicRanges_1.56.1       
##  [33] Rtsne_0.17                  purrr_1.0.2                
##  [35] GenomeInfoDbData_1.2.12     circlize_0.4.16            
##  [37] IRanges_2.38.1              S4Vectors_0.42.1           
##  [39] ggrepel_0.9.6               irlba_2.3.5.1              
##  [41] listenv_0.9.1               spatstat.utils_3.1-0       
##  [43] goftest_1.2-3               RSpectra_0.16-2            
##  [45] spatstat.random_3.3-1       fitdistrplus_1.2-1         
##  [47] parallelly_1.38.0           DelayedMatrixStats_1.26.0  
##  [49] pkgdown_2.1.0               DelayedArray_0.30.1        
##  [51] leiden_0.4.3.1              codetools_0.2-20           
##  [53] tidyselect_1.2.1            shape_1.4.6.1              
##  [55] farver_2.1.2                UCSC.utils_1.0.0           
##  [57] matrixStats_1.4.1           stats4_4.4.1               
##  [59] spatstat.explore_3.3-2      jsonlite_1.8.8             
##  [61] GetoptLong_1.0.5            progressr_0.14.0           
##  [63] ggridges_0.5.6              survival_3.6-4             
##  [65] iterators_1.0.14            systemfonts_1.1.0          
##  [67] foreach_1.5.2               tools_4.4.1                
##  [69] ragg_1.3.2                  ica_1.0-3                  
##  [71] glue_1.7.0                  SparseArray_1.4.8          
##  [73] gridExtra_2.3               xfun_0.47                  
##  [75] MatrixGenerics_1.16.0       GenomeInfoDb_1.40.1        
##  [77] withr_3.0.1                 BiocManager_1.30.25        
##  [79] fastmap_1.2.0               fansi_1.0.6                
##  [81] digest_0.6.37               R6_2.5.1                   
##  [83] mime_0.12                   textshaping_0.4.0          
##  [85] colorspace_2.1-1            scattermore_1.2            
##  [87] tensor_1.5                  dichromat_2.0-0.1          
##  [89] spatstat.data_3.1-2         utf8_1.2.4                 
##  [91] tidyr_1.3.1                 generics_0.1.3             
##  [93] S4Arrays_1.4.1              httr_1.4.7                 
##  [95] htmlwidgets_1.6.4           uwot_0.2.2                 
##  [97] pkgconfig_2.0.3             gtable_0.3.5               
##  [99] ComplexHeatmap_2.20.0       lmtest_0.9-40              
## [101] XVector_0.44.0              htmltools_0.5.8.1          
## [103] dotCall64_1.1-1             clue_0.3-65                
## [105] scales_1.3.0                png_0.1-8                  
## [107] spatstat.univar_3.0-1       knitr_1.48                 
## [109] rstudioapi_0.16.0           reshape2_1.4.4             
## [111] rjson_0.2.22                nlme_3.1-164               
## [113] curl_5.2.2                  cachem_1.1.0               
## [115] zoo_1.8-12                  GlobalOptions_0.1.2        
## [117] stringr_1.5.1               KernSmooth_2.23-24         
## [119] miniUI_0.1.1.1              desc_1.4.3                 
## [121] pillar_1.9.0                grid_4.4.1                 
## [123] vctrs_0.6.5                 promises_1.3.0             
## [125] xtable_1.8-4                evaluate_0.24.0            
## [127] cli_3.6.3                   compiler_4.4.1             
## [129] rlang_1.1.4                 crayon_1.5.3               
## [131] future.apply_1.11.2         labeling_0.4.3             
## [133] plyr_1.8.9                  fs_1.6.4                   
## [135] stringi_1.8.4               viridisLite_0.4.2          
## [137] deldir_2.0-4                gridBase_0.4-7             
## [139] munsell_0.5.1               lazyeval_0.2.2             
## [141] spatstat.geom_3.3-2         RcppHNSW_0.6.0             
## [143] patchwork_1.2.0             sparseMatrixStats_1.16.0   
## [145] future_1.34.0               shiny_1.9.1                
## [147] highr_0.11                  SummarizedExperiment_1.34.0
## [149] ROCR_1.0-11                 gargle_1.5.2               
## [151] igraph_2.0.3                bslib_0.8.0