Skip to contents

Overview

In this tutorial, we will illustrate how to perform de novo discovery of spatial ecotypes 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 melanoma cells. Melanoma cells are excluded from this demonstration to reduce processing time.

All cells are grouped into four spatial regions: tumor, inner margin, outer margin, and stroma. The tumor and stroma regions are defined based on the density of cancer cells, as described in the CytoSPACE paper. The inner and outer margins are defined as regions extending 250 μm inside and outside the tumor boundaries, respectively. Furthermore, we quantified each cell’s distance to the tumor–stroma interface by calculating the shortest Euclidean distance to the nearest tumor region (for stromal cells) or stromal region (for tumor cells). A positive distance indicates cells located within the tumor region, while a negative distance indicates cells located within the stroma.

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

Quick start

## Download an example data for the analysis
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)

## Load the gene expression matrix
scdata <- fread("HumanMelanomaPatient1_subset_counts.tsv.gz", 
                sep = "\t",header = TRUE, data.table = FALSE)
rownames(scdata) <- scdata[, 1]
scdata <- as.matrix(scdata[, -1])

## Normalize the gene expression data
normdata <- NormalizeData(scdata)

## Load the spatial coordinates and cell type annotation of single cells
scmeta <- read.table("HumanMelanomaPatient1_subset_scmeta.tsv", 
                      sep = "\t",header = TRUE, row.names = 1)
scmeta <- scmeta[match(colnames(scdata), rownames(scmeta)), ]

## Discover spatial ecotypes using the SpatialEcoTyper
se_results <- SpatialEcoTyper(normdata, scmeta, 
                              outprefix = "Melanoma1_subset",
                              radius = 50, ncores = 2)

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 within the tissue. You can color the cells by cell type or predefined spatial regions.

# 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 defining spatial neighborhoods (SNs) on a regular grid and constructing cell type–specific gene expression profiles (GEPs) for each SN. For each cell type, a similarity network is then generated based on these GEPs, where nodes represent SNs and edges denote their expression similarity. To integrate these networks across cell types, SpatialEcoTyper applies the Similarity Network Fusion (SNF) method, originally developed for multi-omics data integration (Wang et al., 2014). This yields a fused similarity network that captures shared spatial transcriptomic variation across cell types, enabling the identification of SEs through clustering.

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 neighborhood.
  • grid.size Numeric specifying the grid size for spatial discretization of coordinates.
  • resolution Numeric specifying the resolution for Louvain clustering (default: 0.5).
  • nfeatures Integer specifying the number of top variable features (default: 500) used for the analysis.
  • min.cts.per.region Integer specifying the minimum number of cell types (default: 2) required for a spatial neighborhood.
  • iterations Integer specifying the number of iterations (default: 10) for SNF analysis.
  • 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 large single-cell spatial transcriptomics (ST) datasets with more than 100,000 cells, the analysis can be both time- and memory-intensive. To accelerate computation, you can increase the number of cores used (ncores), but this will also increase memory consumption.

If computational memory is limited, several strategies can help reduce usage. One option is to increase the grid.size, which decreases the number of spatial neighborhoods and can substantially decrease memory usage. Another option is to reduce the minibatch size and the number of cores (ncores). However, the minimum memory requirement may still remain high for very large datasets.

Interested in multicellular communities associated with specific cell types?

If you’re interested in investigating multicellular communities associated with specific cell types, you can use the filter.region.by.celltypes argument to limit the analysis to spatial neighborhoods that contain at least one cell of the specified type. For example, to identify SEs associated with CD8 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"))
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 restrict the analysis to spatial neighborhoods containing at least the specified minimum number of distinct cell types.

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("13T8IIbgk26sokFI8F3VqI4mvz3QIp3fT"), "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 fused network embedding of spatial neighborhoods, enabling the identification of spatial ecotypes through clustering.
  • a data frame of the single-cell metadata, which includes the original single-cell metadata with spatial ecotype cluster annotations added.
# Extract the Seurat object and updated single-cell metadata
obj <- se_results$obj # A Seurat object
scmeta <- se_results$metadata # 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 SE3
## HumanMelanomaPatient1__cell_3657 Stroma      -894.8463 SE6
## HumanMelanomaPatient1__cell_3658 Stroma      -904.1115 SE6
## HumanMelanomaPatient1__cell_3660 Stroma      -907.8909 SE6
## HumanMelanomaPatient1__cell_3661 Stroma      -874.2712 SE1
## HumanMelanomaPatient1__cell_3663 Stroma      -903.6559 SE3
table(scmeta$SE) ## The number of cells in each SE
## 
##  SE0  SE1  SE2  SE3  SE4  SE5  SE6  SE7 
## 4067 2480 6907 4297 3952 1232 1801 2244

Note: Spatial EcoTyper employs stringent quality control steps to exclude spatial neighborhoods with suboptimal gene expression profiles, such as those with insufficiently detected genes (min.feature parameter), insufficient cells (min.cells parameter), and underrepresented cell types (min.cts.per.region parameter). These low confidence regions are marked as NA by the pipeline. While regions designated as NA at one resolution (radius and grid.size parameter) may be included at a lower resolution if the larger region passes QC, we observed a negligible impact of different resolutions on overall performance. By default, NA regions are removed from the output to avoid confusion. To retain all cells in the single-cell meta data, you can set dropcell = FALSE in the SpatialEcoTyper.

Embedding of spatial architecture

The embedding of spatial neighborhoods 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 SNs to tumor/stroma interface

This plot shows the distance of each SN to the tumor/stroma interface. Here, positive distances indicate SNs located within the tumor region, while negative distances denote SNs 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. Each SE represents a distinct spatial ecosystem with unique molecular and spatial characteristics, and may also differ in cell type composition.

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

Visualizing the most abundant cell types in each SN

Here, we focus on visualizing the dominant cell types within each SN 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.

SpatialView(scmeta, by = "SE")

Visualizing the cell type composition of SEs

The bar plot below illustrates the cell type composition within each SE.

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 enrichment of SEs in pre-defined regions (e.g., tumor and stroma).

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   SE6 2.474779 2.098878   4006694 0.7945178 4.327192e-195
## 2 PLA2G2A   SE1 2.138834 1.514224   7450658 0.8350914  0.000000e+00
## 3     PKM   SE5 2.553626 1.311940   2341716 0.8605602 2.684889e-117
## 4 PLA2G2A   SE3 1.786796 1.115139   7220261 0.7530508 2.353682e-242
## 5     FN1   SE4 3.050625 1.034756   4915868 0.7688155 2.080559e-150
## 6     PKM   SE4 2.203188 1.014937   5178804 0.8099374 3.607553e-202
##            padj   pct_in  pct_out
## 1 5.942677e-193 72.88889 31.86990
## 2  0.000000e+00 91.71779 34.22976
## 3 5.530871e-115 98.56734 82.24958
## 4 4.848585e-240 80.58865 35.54100
## 5 2.142976e-148 98.40909 88.21910
## 6 1.486312e-199 97.50000 81.18635

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.6.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.1
##  [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.52                  
##  [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