引言

本系列讲解 空间转录组学 (Spatial Transcriptomics) 相关基础知识与数据分析教程,持续更新,欢迎关注,转发,文末有交流群!
简介
本章演示的分析用到两份数据:一份是 10x Genomics 的 313-plex Xenium 平台生成的人乳腺癌活检切片数据;另一份是 Bruker 的 1k-plex CosMx 平台生成的小鼠脑切片数据。
接下来,我们会在 Xenium(简称 xen)和 CosMx(简称 cos)两份数据之间来回切换,既用 scuttle 展示 scRNA-seq 通用的质控方法,也用 SpaceTrooper 展示成像类空间转录组特有的质控技巧。最后,把质控合格的 Xenium 数据存盘,供后续继续分析。
依赖
library(arrow)
library(dplyr)
library(tidyr)
library(scater)
library(ggplot2)
library(ggspavis)
library(patchwork)
library(OSTA.data)
library(SpaceTrooper)
library(STexampleData)
library(SpatialExperiment)
library(SpatialExperimentIO)
数据导入
SpatialExperimentIO 帮你一键把 CosMx(Bruker)、Xenium(10x Genomics)和 MERSCOPE(Vizgen)这三家厂商的输出文件读成 SingleCellExperiment 或 SpatialExperiment 对象。
我们从 STexampleData 加载 Xenium 数据集
(xen <- Janesick_breastCancer_Xenium_rep1())
# required for visuals
xen$in_tissue <- TRUE
xen$sample_id <- "Xenium"

为了把 CosMx 数据加载成 SpatialExperiment 对象,我们使用 SpatialExperimentIO 的 readCosmxSXE 函数。为了避免 RNA 靶标计数与阴性探针或系统对照产生的计数混淆,后者默认被移入 altExps。我们还会额外添加 ggspavis(用于可视化)和 SpaceTrooper(用于分析)所需的 colData 和 metadata 槽。SpaceTrooper 的 updateCosmxSPE 函数会负责将对象的 colData 标准化,以便与 SpaceTrooper 的函数配套使用。
# retrieve CosMx dataset from OSF repo
id <- "CosMx1k_MouseBrain2"
pa <- OSTA.data_load(id, mol=FALSE)
dir.create(td <- tempfile())
unzip(pa, exdir=td)
cos <- readCosmxSXE(td, addTx=FALSE)
# prepare data for 'SpaceTrooper'
cos <- updateCosmxSPE(cos, td, sampleName="CosMx")
cos <- readAndAddPolygonsToSPE(cos)
cos$in_tissue <- TRUE
cos

可视化
基于成像的空间转录组(Imaging-based ST)数据依赖于对 2D 组织切片的成像。因此,与 scRNA-seq 相比,一个优势在于我们可以轻松地将细胞的 2D 图谱可视化,并直接把细胞特征绘制在该图上。最简单的可视化方式是把细胞表示为质心(centroids),这有助于获得组织的全局鸟瞰图。
# spatial plots for datasets acquired through
# Xenium & CosMx (points = cell centroids)
plotCoords(xen, point_size=0, y_reverse = FALSE) + ggtitle(xen$sample_id) +
plotCoords(cos, point_size=0, y_reverse = FALSE) + ggtitle(cos$sample_id)

通用 QC 指标
与 scRNA-seq 类似,质量控制(QC)是剔除低质量细胞、并在样本、视野(FOV)或玻片区域层面揭示特定偏差的关键步骤。然而,与 scRNA-seq 和基于测序的空间转录组不同,大多数成像类 ST 平台并不测量整个转录组,也不测量其“无偏”样本,而是依赖预先设计的基因 panel。
根据平台和应用场景,这些 gene panel 可能是泛组织的,也可能针对特定组织或细胞类型;其规模从几百个基因(如首批 Xenium 的 313 个)到几千个(如 Xenium 5k panel 或 CosMx 的 6k、18k panel)不等。因此,与测序类平台不同,我们可能没有足够的核糖体或线粒体转录本来计算 QC 指标。不过,成像平台提供了一组 negative probes,可用于检测非特异性 RNA 捕获。
最后,成像类平台除转录本数据外,还附带形态学信息(如分割细胞的面积与偏心率)以及抗体染色信息(如靶向细胞核或膜表面的染色)。
scRNA-seq
我们首先用 scuttle 包计算 scRNA-seq 标准 QC 指标。此处设置 use.altexps=TRUE,可在对主对象中 RNA 靶标定量的同时,也对 altExps 里的负探针等附加特征进行定量。
# compute standard scRNA-seq QC metrics for the Xenium dataset (e.g.,
# total counts & unique features, for RNA targets & negative probes)
xen <- addPerCellQCMetrics(xen, use.altexps=TRUE)
IF 染色
CosMx 输出通常包含一组免疫荧光(IF)标记的最小/最大/平均强度,例如用于分割的核和细胞表面标记,以及实验相关的额外标记。在此,我们展示 CosMx 样本中平均 IF 强度的分布。
flu <- grepv("^Mean", names(colData(cos)))
lapply(flu, \(.) {
cos[[.]] <- asinh(cos[[.]]/10)
plotCoords(cos, annotate=., point_size=0, y_reverse = FALSE)
}) |>
wrap_plots(nrow=1) &
theme(
legend.position="bottom",
legend.key.height=unit(0.4, "lines")) &
scale_color_gradientn(colors=pals::jet())

形态学
SpaceTrooper 的 spatialPerCellQC 函数可计算一组成像类 ST 数据特有的指标;示例见下文。此外,该函数内部还会调用 scuttle 包的 perCellQCMetrics,对 altExp 中的负探针计算若干标准 scRNA-seq 质控指标。
# compute 'SpaceTrooper' QC metrics
cos <- spatialPerCellQC(cos, use_altexps="NegPrb")
# the following cell metadata are newly available:

具体而言,下列指标已被追加到 colData 中:
- Area_um:细胞面积,单位为微米²
- CountArea:计数密度 = 每微米² 的转录本数量
- log2AspectRatio:细胞长宽比(x 轴 / y 轴)的 log₂ 值
- ctrl_total_ratio:负探针计数占总计数的比例
计数与面积
基于成像的空间转录组数据依赖对二维组织切片的成像。因此,细胞只是三维体系中的一层切片,我们预期细胞的转录本计数与其截面面积存在紧密关联。当我们分别检视总计数与细胞面积这两个单变量分布时,二者均近似呈对数正态分布。
.p <- \(df, xs) {
fd <- pivot_longer(df, all_of(xs))
mu <- summarise_at(group_by(fd, name), "value", median)
ggplot(fd, aes(value)) + facet_grid(~ name) +
geom_histogram(bins=50, linewidth=0.1, fill="gray") +
geom_vline(data=mu, aes(xintercept=value), col="blue") +
geom_text(
hjust=-0.1, size=3, col="blue",
data=mu, aes(value, 0, label=round(value))) +
scale_x_continuous(NULL, trans="log10") + ylab("# cells") +
theme_minimal() + theme(panel.grid.minor=element_blank())
}
df_cos <- data.frame(colData(cos), spatialCoords(cos))
df_xen <- data.frame(colData(xen), spatialCoords(xen))
p2 <- .p(df_cos, c("Area_um", "total"))
p1 <- .p(df_xen, c("cell_area", "total_counts"))
p1 + ggtitle("CosMx") | p2 + ggtitle("Xenium")

同样,我们可以把这些指标在组织里的分布画出来:用细胞质心做底,分别按总转录本数和细胞面积上色,一目了然。
lapply(c("cell_area", "total_counts"), \(.) {
# crop very low/high values for clearer visualization
val <- xen[[.]]
qs <- quantile(val, c(0.01, 0.99))
val <- ifelse(val < qs[1], qs[1], ifelse(val > qs[2], qs[2], val))
xen[[.]] <- val
plotCoords(xen, annotate=., point_size=0, y_reverse = FALSE) +
theme(legend.key.width=unit(0.5, "lines")) +
scale_color_gradientn(colors=unname(pals::jet()), trans="log10")
}) |> wrap_plots(nrow=1)

放到双变量图里,计数和面积果然呈正相关,但关系并不总是线性,有时还会出现双峰。
.p <- \(df, x, y) {
ggplot(df, aes(.data[[x]], .data[[y]])) +
geom_point(shape=16, size=0, alpha=0.1) +
geom_smooth(method="lm", col="blue") +
scale_x_log10() + scale_y_log10() +
theme_minimal() + theme(
aspect.ratio=1,
panel.grid.minor=element_blank())
}
.p(df_cos, "Area_um", "total") + ggtitle("CosMx") +
.p(df_xen, "cell_area", "total_counts") + ggtitle("Xenium")

某些异常的“计数-面积”分布可能暗示分割出了问题。欠分割(把一群细胞误当成一个巨细胞)时,面积虽大,计数却偏低——比如细胞其实离得老远,却把空白也圈了进来。