Visium HD is a next-generation spatial transcriptomics technology developed by 10x Genomics, designed to provide single-cell resolution spatial gene expression data across entire tissue sections. Commercially available since 2024, Visium HD advanced the platform’s resolution from 55 µm spots to bins of near subcellular resolution (2 µm). In this workflow, we will demonstrate common analysis steps with a Visium HD dataset on colorectal cancer (Oliveira et al. 2024).
In this demo, we perform deconvolution on 16 µm bins and compare the concordance with results on 8 µm bins, provided by 10x Genomics.
Here, we only demonstrate the analysis for patient 2 (P2). Other patients, such as P1 and P5, also have public Chromium, Visium HD, and Xenium data available for the CRC group (see Chapter 6). It would be interesting to investigate the differences in malignant cells/bins (e.g., DEGs, pathways) between patients across different platforms.
Code
# retrieve dataset from OSF repoid<-"VisiumHD_HumanColon_Oliveira"pa<-OSTA.data_load(id)dir.create(td<-tempfile())unzip(pa, exdir=td)# read 8um bins into 'SpatialExperiment'vhd8<-TENxVisiumHD( spacerangerOut=td, processing="filtered", format="h5", images="lowres", bin_size="008")|>import()# use gene symbols as feature namesgs<-rowData(vhd8)$Symbolrownames(vhd8)<-make.unique(gs)colnames(vhd8)<-vhd8$barcode# TODO: VisiumIO bug?vhd8
These data come with bin-level annotations from deconvolution estimates by RCTD, implemented in spacexr. Specifically, two sets of labels are available that correspond to the most and second most frequent of 38 cell types per bin, respectively:
To keep runtimes low, our downstream analysis will be performed on a subset area of 16 µm bins (1/64 of the data coverage area), which we read in here; there are no annotations available for this resolution in the public domain.
Note that 8 and 16 µm binned Visium HD data share the same full-resolution H&E image and scaling factor.
Code
# read 16um bins into 'SpatialExperiment'vhd16<-TENxVisiumHD( spacerangerOut=td, processing="filtered", format="h5", images="lowres", bin_size="016")|>import()# use symbols as feature namesgs<-rowData(vhd16)$Symbolrownames(vhd16)<-make.unique(gs)colnames(vhd16)<-vhd16$barcode# TODO: VisiumIO bug?vhd16
As detailed in Chapter 26, Banksy utilizes a pair of spatial kernels to capture gene expression variation, followed by dimension reduction and graph-based clustering to identify spatial domains.
Code
# set seed for random number generation# in order to make results reproducibleset.seed(112358)# 'Banksy' parameter settingsk<-8# consider first order neighborsl<-0.2# use little spatial informationa<-"logcounts"xy<-c("array_row", "array_col")# restrict to selected featurestmp<-.vhd16[hvg, ]# compute spatially aware 'Banksy' PCstmp<-computeBanksy(tmp, assay_name=a, coord_names=xy, k_geom=k)tmp<-runBanksyPCA(tmp, lambda=l, npcs=20)reducedDim(.vhd16, "PCA")<-reducedDim(tmp)# run UMAP (for visualization purposes only).vhd16<-runUMAP(.vhd16, dimred="PCA")# build cellular shared nearest-neighbor (SNN) graphg<-buildSNNGraph(.vhd16, use.dimred="PCA", type="jaccard", k=20)# cluster using Leiden community detection algorithmk<-cluster_leiden(g, objective_function="modularity", resolution=1.2)table(.vhd16$Banksy<-factor(k$membership))
Next, we can perform differential gene expresison (DGE) analysis to identify markers for each cluster; c.f. Chapter 27. We also compute the cluster-wise mean expression of selected markers:
Code
# differential gene expression analysismgs<-findMarkers(.vhd16, groups=.vhd16$Banksy, direction="up")# select for a few markers per clustertop<-lapply(mgs, \(df)rownames(df)[df$Top<=3])top<-unique(unlist(top))# average expression by clusterspbs<-aggregateAcrossCells(.vhd16, ids=.vhd16$Banksy, subset.row=top, use.assay.type="logcounts", statistics="mean")
The marker genes in each cluster can be visualized with a heatmap:
We will now perform deconvolution on the 16 µm binned Visium HD data. First, we retrieve matching (Chromium) scRNA-seq data, which includes low- (Level1) and high-resolution (Level2) annotations of cells into 9 respectively 31 clusters. In order to ensure similar transcriptional profile across technologies, we filter the reference population to patient 2 only.
Code
# retrieve dataset from OSF repositoryid<-"Chromium_HumanColon_Oliveira"pa<-OSTA.data_load(id)dir.create(td<-tempfile())unzip(pa, exdir=td)# read into `SingleCellExperiment`fs<-list.files(td, full.names=TRUE)h5<-grep("h5$", fs, value=TRUE)sce<-read10xCounts(h5, col.names=TRUE)# add cell metadatacsv<-grep("csv$", fs, value=TRUE)cd<-read.csv(csv, row.names=1)colData(sce)[names(cd)]<-cd[colnames(sce), ]# use gene symbols as feature namesgs<-rowData(sce)$Symbolrownames(sce)<-make.unique(gs)# exclude cells deemed to be of low-qualitysce<-sce[, sce$QCFilter=="Keep"]# subset cells from same patientsce<-sce[, grepl("P2", sce$Patient)]sce
Note that "Proliferating Immune II" spans evenly between "B cells" and "Tumor", so we will make it a separate class, "Prolif. Immune" in the low-resolution annotations:
Below, these extra classes in the Visium HD 8 μm bin annotations will be ignored.
13.5.2 Simplify 8 µm annotations
We can merge the provided deconvolution annotation "DeconLabel1" (most likely cell type for singlets, most dominant type for doublets) into a lower resolution using the single-cell reference annotations:
Let us check the top-4 common doublet pairs according to the "doublet_certain" class:
Code
dbl<-vhd8$DeconClass=="doublet_certain"lab<-c(".DeconLabel1", ".DeconLabel2")df<-data.frame(colData(vhd8)[dbl, lab])# sort as to ignore orderdf<-apply(df, 1, sort)df<-do.call(rbind, df)names(df)<-lab# count unique pairsij<-paste(df[, 1], df[, 2], sep=";")head(sort(table(ij), decreasing=TRUE))
## ij
## Tumor;Tumor Myeloid;Tumor B cells;Fibroblast
## 3984 3799 3670
## Fibroblast;Myeloid Endothelial;Endothelial Fibroblast;Fibroblast
## 3453 3073 2627
We can see that myeloid are commonly co-localized with fibroblasts and tumor cells; most doublets are homotypic, i.e., they are (mostly) composed of one low-resolution type.
Subset 8um bins within black bounding box
We can perform the same subsetting procedure to 8 µm bins and compare the difference in the number of bins in the zoomed (black box) area.
Code
# subset zoomed (black box) area.vhd8<-.sub(spe=vhd8, rng=vhd8r)# compare number of bins between resolutions(n<-ncol(.vhd8))
As expected, the number of 8 µm bins is about 4 times the number of 16 µm bins. We will visualize the same subset for both bin sizes in any downstream analyses.
13.5.3 Run RCTD on 16 µm bins
We also assume there are at most two cell types in a 16 µm bin as in 8 µm, and thus we would expect a smaller proportion of singlets.
Code
# downsample to at most 4,000 cells per cluster# (this is done only to keep runtime/memory low)cs<-split(seq_len(ncol(sce)), sce$Level1)cs<-lapply(cs, \(.)sample(., min(length(.), 4e3)))ncol(.sce<-sce[, unlist(cs)])obj<-createRctd(.vhd16, .sce, cell_type_col="Level1", max_cores=5)(res<-runRctd(obj, rctd_mode="doublet"))
Weights inferred by RCTD correspond to proportions of cell types, such that they sum to 1 for a given observation (expect for rejected ones, which sum to 0):
From the above heatmap, cluster 1 is mostly immune cells with endothelial; cluster 9 matches B cell bins; cluster 7 and 8 are likely intestinal epithelial and smooth muscle, respectively; fibroblast is common in cluster 4; tumor spans cluster 2, 5, and 6.
13.7 Neighborhood analysis
Here, we want to quantify the abundance between T cells, B cells, or Myeloid bins to Fibroblast or Tumor bins in 16 µm zoomed region. We use getAbundances() from Statial to calculate, for each bin, the abundance of other cell types within a radius of 200 px; the resulting (bins \(\times\) types) matrix is stored as reducedDim slot "abundances".
Now, we filter to only T cells, B cells and Myeloid bins, and investigate – within their neighborhood – the spatial proximity differences to Fibroblast or Tumor bins in terms of abundance.
In this region, we observe more tumor than fibroblast bins around each of the immune bins. However, note that in this zoomed region, we have nearly 3 times more tumor bins compared to fibroblast bins. Thus, we need to scale abundance values by the number of target cell type bins observed in the region.
After correction, we see that the abundance of fibroblast bins does not differ much from the abundance of tumor bins around each immune cell type in the zoomed region.
The same analysis steps could be applied to the (unfiltered) 8 μm resolution data with the corresponding RCTD results provided by the authors.
13.8 Conclusion
In this workflow, we demonstrated how to perform a standard analysis pipeline on a subset of Visium HD data binned at 16 µm. Most methods and visualization techqniues developed for Visium can be applied to Visium HD. However, more sparsity is expected, as each data point (e.g. 2, 8, or 16 µm) has smaller area compared to Visium (55 µm). Each bin cannot be interpreted as a cell, but rather, a segmentation-free approach is applied here. Below are some resources and news for Visium HD to consider and incorporate into your pipeline:
Analysis pipeline by 10x Genomics (April 2024). The analysis consists of three steps: run StarDist nuclei segmentation in Python, sum gene expression into nuclei based on segmentation mask, and downstream analysis with scanpy and anndata.
10x Genomics news on morphology-driven segmentation from H&E enabled in CytAssist for Visium HD, by assigning transcripts to individual cells.
Pipeline on cell typing for Visium HD in Python by Sanofi.
References
Oliveira, Michelli F., Juan P. Romero, Meii Chung, Stephen Williams, Andrew D. Gottscho, Anushka Gupta, Susan E. Pilipauskas, et al. 2024. “Characterization of Immune Cell Populations in the Tumor Microenvironment of Colorectal Cancer Using High Definition Spatial Profiling.”bioRxiv. https://doi.org/10.1101/2024.06.04.597233.