空间数据读取:从H5到Seurat对象

这一章我们聊聊数据读取。很多新手拿到10X Visium数据时,第一反应就是直接甩一个Load10X_Spatial完事。但说实话,真到项目里往往没这么简单——数据目录结构、H5版本、图像对齐、质量控制,每一步都有可能翻车。我个人习惯是先搞清楚背后到底在干啥,再决定用哪个函数。

1. Load10X_Spatial — 一把梭还是拆步骤?

Load10X_Spatial是Seurat提供的“一站式”数据读取函数。它把你从原始Visium输出目录(包含filtered_feature_bc_matrix/、tissue_positions_list.csv、spatial/下的图像和图谱)直接打包成一个Seurat对象。

函数签名与关键参数

Load10X_Spatial(
  data.dir,            # 包含子目录的Visium输出根目录
  filename = "filtered_feature_bc_matrix.h5",  # 可选H5文件或mex目录
  assay = "Spatial",
  slice = "slice1",
  filter.matrix = TRUE,
  to.upper = FALSE,
  image = NULL,
  ...)
参数说明避坑点
data.dir指向包含spatial/和矩阵目录的文件夹路径不要带中文,我曾经被Windows反斜杠折腾过
filename默认读H5,也可改成"filtered_feature_bc_matrix"(mex格式)如果目录下有多个H5,指定清楚
filter.matrix是否只保留组织覆盖到的spots建议TRUE,否则会有一大堆背景噪声
image自定义VisiumV1对象,可跳过自动图像加载少用,除非你想手动校正图像

你想想看,如果数据目录里的tissue_positions_list.csv缺失或者行列不对,这个函数会直接报错。嗯,这里要注意:Visium v2的数据目录结构略有不同,我建议先看一眼目录内容再调用。

2. 手动拆解:H5文件 + image数据

为什么要手动拆?两个场景:一是你的数据不是标准Visium输出(比如定制芯片),二是你想单独检查H5的分子信息。

我一般分三步走:

  1. 读取H5矩阵 —— 用Read10X_H5
  2. 加载图像和组织信息 —— 用ReadVisiumImage
  3. 组装 —— 手动构造Seurat对象

读取H5文件

library(Seurat)
library(SeuratObject)

h5_path <- "filtered_feature_bc_matrix.h5"
counts <- Read10X_H5(h5_path)
# 返回一个dgCMatrix,行是基因/features,列是spots

Read10X_H5会读取基因表达、feature类型(Gene Expression / Antibody Capture等)和barcode信息。我遇到过一种情况:同一个样本跑了两次,H5里有两个barcode列名完全一样,结果Read10X_H5直接合并了……记得后续去重。

加载图像与组织覆盖

library(Seurat)
img_path <- "spatial/tissue_lowres_image.png"
# 已有ReadVisiumImage函数
image <- ReadVisiumImage(img_path, scale = "lowres", image.name = "tissue_lowres_image.png")
# 返回一个VisiumV1对象

别忘了还要加载tissue_positions_list.csv和scalefactors_json.json。这些文件会被自动封装到VisiumV1对象里。

手动拼接

spatial.data <- list(
  counts = counts,
  image = image,
  tissue.positions = read.csv("spatial/tissue_positions_list.csv", row.names = 1),
  scalefactors = jsonlite::fromJSON("spatial/scalefactors_json.json")
)
# 创建Seurat对象
so <- CreateSeuratObject(counts = spatial.data$counts, assay = "Spatial")
so[["slice1"]] <- spatial.data$image

这一步其实和Load10X_Spatial内部做的差不多,手动来能给更大的控制权,比如做图像偏移校正。

3. 创建空间对象:SpatialExperiment vs Seurat对象

现在有两个主流选择:Seurat的Assay5和Bioconductor的SpatialExperiment。说白了,两者都能存空间坐标、图像和表达矩阵,但设计哲学不一样。

对比维度Seurat对象SpatialExperiment
数据容器Assay5(支持多模态)单模态SummarizedExperiment扩展
空间坐标存储在images和meta.data专门的spatialCoords()访问器
图像处理内嵌VisiumV1/ST类通过SpatialImage对象或外部链接
下游分析Seurat全流程(降维、聚类、差异)需要配合scran/zinbwave等
个人感受上手快,集成性强更灵活,适合自定义pipeline

我在项目中曾一度想用SpatialExperiment来加速大矩阵操作,但后来发现团队的后续分析全都依赖FindSpatiallyVariableFeatures这类Seurat专用函数,最后还是切回了Seurat。建议你提前搞清楚下游要用哪些包。

如何将数据转成SpatialExperiment

library(SpatialExperiment)
spe <- SpatialExperiment(
  assays = list(counts = counts),
  spatialCoords = as.matrix(tissue.positions[, c("col", "row")]),
  colData = tissue.positions[, c("in_tissue", "barcode")],
  sample_id = "slice1"
)

不过要注意SpatialExperiment不自动加载图像,你得手动添加SpatialImage对象或者用imgData链接。

4. 实战避坑

⚠️ 常见坑位

  • 文件名大小写:tissue_positions_list.csv vs tissue_positions_list.csv,Linux下严格区分。我曾因小写l写成大写L,报错找了一下午。
  • H5版本不兼容:某些旧版10X输出是hdf5 1.8,而Read10X_H5默认用h5py 3.x,会报“Not a HDF5 file”。解决办法:用rhdf5::h5ls先检查文件是否可读。
  • 图像分辨率:tissue_lowres_image.png通常是600x600,但有些自定义数据可能不同。读取时scale参数要和scalefactors_json.json里的tissue_lowres_scalef匹配。

5. 本章核心流程

下面这张SVG图把从原始数据到Seurat对象的完整路径画出来了,建议你把它贴在笔记开头,随时回顾。

空间数据读取流程 10X Visium输出目录 Load10X_Spatial(一站式) 手动拆解:H5 + image Read10X_H5 + ReadVisiumImage CreateSeuratObject + Add Image (或手动构造Seurat对象) Seurat对象 / SpatialExperiment 下游分析 💡 每次运行前务必检查:目录结构、H5版本、tissue_positions.csv和scalefactors.json的一致性

💡 我的小习惯:每次拿到新数据先用list.files("data/spatial")扫一眼,确认有tissue_lowres_image.png和scalefactors_json.json,再决定用自动还是手动模式。如果项目中有多个切片需要整合,强烈建议先用Load10X_Spatial试跑一个切片,确认所有文件可读,再批量处理。