4、Seurat空间对象结构:spatial assay、image、坐标与metadata
好,咱们进入第四讲。前几章我们把空间转录组的基本概念和Seurat的安装跑通了,现在该动真格的了——你得搞明白Seurat里那个叫 SeuratObject 的东西,到底长什么样。
说白了,空间数据跟单细胞数据最大的区别在哪?多了一张图像,多了一套坐标。你不能光知道哪个细胞表达了什么基因,你还得知道它在组织切片上的哪个位置。Seurat为了把这个事儿包装得规规矩矩,设计了一套专门的对象结构。我带你看一遍。
核心概念一句话:Seurat的空间对象 = 单细胞assay + 空间影像 + 物理坐标 + 元数据。
我个人习惯把空间对象想象成一个“俄罗斯套娃”——最外面是Seurat对象,里面装着assay,assay旁边挂着一个叫 image 的槽位,这个槽位里又藏着低分辨图、坐标数据、缩放因子这些东西。咱一层层拆开看。
4.1 spatial assay:数据的主心骨
你创建一个空间对象时,第一个接触到的就是 SpatialAssay。这个assay和单细胞里的 RNAAssay 很相似,都存着表达矩阵、特征元数据、细胞元数据。但它多了点私货:spatial 这个slot,专门放每个spot的坐标。
# 看看assay里都有什么
pbmc_spatial[["Spatial"]]
# 如果你用Read10X_Spatial读入数据
# 默认会生成一个 SpatialAssay,名字叫 "Spatial"
关键区别在这:
| 属性 | 单细胞 RNAAssay | 空间 SpatialAssay |
|---|---|---|
| 表达矩阵 | 细胞 × 基因 | spot × 基因 |
| 坐标信息 | 无 | 有(spatial slot) |
| 图像关联 | 无 | 有(绑定image对象) |
| 缩放因子 | 无 | 有(spot直径、图像缩放等) |
我在项目里遇到过一个情况:读入来的数据,GetAssayData 取出来的矩阵是空值。后来发现是因为读数据时没指定 assay 参数。嗯,这种小坑,你只要记住 spatial 参数一定要对得上就行。
4.2 image系统:不仅仅是张图
空间转录组最让人兴奋的就是那张组织切片图。Seurat里,图像不是随便丢进去的JPG,它被封装成一个叫 VisiumV1(或 STARmap、SlideSeq 等)的S4类对象。
这个image对象里装了什么?
image:真实的像素图,通常是低分辨率的PNG(你文件夹里那个tissue_lowres_image.png)scale.factors:缩放因子,告诉你怎么从像素坐标映射回实际物理坐标coordinates:这个比较绕——它存的是原始图像上的坐标,不是那些经过去偏转、对齐后的坐标spot.radius:每个spot在图像上占据的像素半径,用于后面的可视化
# 看看image对象的结构
image_obj <- pbmc_spatial[["slice1"]]
# 这个 "slice1" 是默认的图像名称
# 提取低分辨图
img <- image_obj@image
# 看缩放因子
image_obj@scale.factors
# 输出的东西长这样:
# lowres: 0.052
# hires : 0.17
# spot : 13.8
我记得刚用10x Visium数据时,拿到h5文件和space Ranger输出的 tissue_lowres_image.png,还以为低分辨率图只是个预览,不能用于分析。其实Seurat内部做可视化时,依赖的就是这张低分辨图叠加spot。你把它删了,SpatialDimPlot 直接就报错。
注意:不同平台(Visium、Slide-seq、MERFISH)的图像对象类名不一样。Visium是 VisiumV1,Slide-seq可能是 SlideSeq。写代码时别直接写死类名,用 class() 先看一眼。
4.3 坐标系统:像素与物理空间的桥梁
坐标这东西,说简单也简单,说坑也坑。Seurat里至少有两套坐标在打架:
- 图像像素坐标(
tissue_lowres_image上的x,y) - 实际物理坐标(经过缩放、对齐后的x,y)
你最常用的是 GetTissueCoordinates() 这个函数。它会默认帮你取出转换后的坐标。
# 获取坐标
coords <- GetTissueCoordinates(pbmc_spatial)
# 前几行长这样
# x y
# AAACAAGTATCTCC-1 5300.93 5234.19
# AAACACCAATAACTG-1 3733.29 5933.85
# 如果想看原始图像坐标,加个参数
raw_coords <- GetTissueCoordinates(pbmc_spatial, which = "slice1",
use.image = TRUE)
为什么会搞两套?你想想看,组织切片在显微镜下拍的时候,可能位置是歪的,或者做了旋转。Space Ranger在跑流程时已经帮你做了一次对齐。Seurat保存的就是对齐后的坐标,方便你直接画图。
但有一个地方很容易踩坑——坐标的方向。有时候你会看到y轴是反的,这是因为图像坐标系的原点在左上角,而R的默认坐标系原点在左下角。Seurat内部做了处理,但如果你自己去 @coordinates 里取原始值,就可能拿到反向的y。
我建议:任何时候取坐标,都用 GetTissueCoordinates() 这个接口函数,别直接去slots里掏。接口函数会帮你处理好一切潜在的转换问题。
4.4 metadata:你以为只是细胞注释?
单细胞里的 meta.data 是按细胞(barcode)为行存的。空间数据里,这个原则没变——每个spot就是一行。你可以往里面随便加东西:
- 聚类结果(
seurat_clusters) - 基因模块分数(
AddModuleScore()的结果) - 手动注释的组织区域(比如“皮层”、“髓质”)
- 质控指标(
nCount_Spatial、nFeature_Spatial)
# 加一列自定义信息
pbmc_spatial$tissue_region <- ifelse(
pbmc_spatial$nCount_Spatial > 5000,
"high_density",
"low_density"
)
# 查看
head(pbmc_spatial[[]])
但是有一个区别你要注意:空间数据的 meta.data 里,行名是spot的barcode,而barcode的命名是有规律的——它包含了组织切片的坐标信息编码。不同平台barcode的格式差别很大:
| 平台 | barcode示例 | 说明 |
|---|---|---|
| 10x Visium | AAACAAGTATCTCC-1 | 33个字符 + 编号 |
| Slide-seq | ACGTACGTACGT | 纯序列,无分隔符 |
| STARmap | x_y | 直接用物理坐标 |
4.5 misc:杂物箱,但不要小看
最后这个 misc slot,其实就是个列表,什么都能往里装。Seurat官方文档里对它的定义很模糊——“杂项数据”。但实际用起来,它挺关键的。
比如,你跑 SpatialDimPlot() 时,图例的颜色映射是从哪来的?很多时候就是存在 misc 里。还有像SCT变换的模型参数、自定义的配色方案,都会被塞进去。
# 看看misc里有啥
pbmc_spatial@misc
# 你自己也可以往里存东西
pbmc_spatial@misc$my_custom_colors <- c("red", "blue", "green")
# 或者存一个处理日志
pbmc_spatial@misc$processing_log <- list(
date = Sys.time(),
steps = c("filtering", "normalization", "clustering")
)
我曾经在跟同事协作时,把空间坐标的自定义校正信息存在 misc 里,结果对方用另一个版本的Seurat读入,misc 里的格式不兼容,导致分析流程中断。从此我养成了一个习惯:关键数据绝不放在 misc 里,要么另存一个Rds文件,要么写成独立的CSV。
4.6 一张图看懂完整结构
好,到这你可能有点晕——又是assay又是image又是坐标的。我画了张图,把Seurat空间对象的内部结构给串起来。
从上往下看就清楚了——Seurat对象像一棵树,根节点是主对象,四个分支分别是 SpatialAssay、Image、meta.data 和 misc。每个分支负责一类数据,彼此有交叉引用但职责分离。
嗯,这一章的信息量不小。你如果能自己写段代码把新建一个空的空间对象、往里塞一张低分辨图和几个模拟坐标跑通,那这套结构基本就刻在你脑子里了。下次拿到真实的10x Visium数据时,你会发现——熟悉的感觉,全在这里面了。