数据获取与格式介绍:Visium的HDF5、图像文件与Slide-seq Bead-level数据

做空间转录组整合,第一步不是跑代码,而是搞清楚你手里拿到的到底是什么。Visium和Slide-seq虽然都是空间转录组技术,但底层数据格式完全两码事。搞混了,后面所有流程都得崩。

我自己刚入行时,就吃过这个亏——拿到Visium数据,以为是普通单细胞表达矩阵,结果死活跑不通。后来才发现,人家是用HDF5存的,而且图像文件才是空间坐标的真正来源。今天咱们就把这两大技术的数据“解剖”清楚。

一句话总结:Visium用HDF5存表达+图像辅助定位;Slide-seq用Bead-level格式,每个珠子一个坐标。理解这俩差异,整合才能顺。

2.1 Visium数据:藏在HDF5里的秘密

Visium官方输出是一个文件夹,里面通常包含:一个HDF5文件(比如filtered_feature_bc_matrix.h5),一个图像文件夹(含高分辨率组织切片图),以及一个spatial目录(放坐标和缩放信息)。你如果用10x Genomics的Space Ranger跑完,会得到这些。

HDF5是什么?说白了,它是一种层级式数据结构,类似一个压缩的“文件柜”。里面可以嵌套多个数据集(datasets),还自带索引。你想想看,一个10x Visium样本,可能有5000个spots,每个spot测到2000个基因,如果用CSV存,不仅体积大,读取也慢。HDF5天生适合这类稀疏数据。

HDF5内部结构说明
matrix表达矩阵主体(稀疏格式)
barcodes每个spot的barcode序列
features基因名或基因ID
feature_type是“Gene Expression”还是“Antibody Capture”

读取HDF5,我习惯用h5py或scanpy自带的read_10x_h5。下面是示例代码,你可以直接跑跑看:

import h5py
import scanpy as sc

# 方法一:直接用scanpy(推荐)
adata = sc.read_10x_h5("filtered_feature_bc_matrix.h5")
adata.var_names_make_unique()
print(adata)

# 方法二:手动探索HDF5结构
with h5py.File("filtered_feature_bc_matrix.h5", "r") as f:
    print(f.keys())                # 查看顶层group
    print(f["matrix"].keys())      # 查看matrix下的dataset
避坑提醒:HDF5里的barcode和feature是索引对应的,直接读成DataFrame容易搞乱顺序。我一般用scanpy一步到位,省心。

图像文件呢?Visium的高分辨率图像(通常是.tif或.png)用作空间对齐的底图。Space Ranger会利用图像和tissue_position.csv把spots映射到组织轮廓上。你拿到原始数据后,别只盯着表达矩阵,图像里的空间信息才是关键。

2.2 Slide-seq Bead-level数据:极简但高效

Slide-seq跟Visium最大的不同,是它没有固定阵列,而是用随机排列的微珠(beads)捕获RNA。每个珠子直径约10微米,位置靠化学印迹和测序来确定。所以它的数据格式是“一珠一行”——每个珠子有一个唯一的barcode和一个空间坐标(x, y, z)。

官方提供的数据通常是一个数字表达矩阵(.csv或.tsv),配一个坐标文件(.csv)。但也有标准化格式叫Bead-level H5,里面包含barcode、bead类型、UMI矩阵等。不过我自己用过最多的,还是最原始的“三联文件”:

  1. expression_matrix.txt:行为beads,列为基因
  2. bead_barcodes.txt:每行一个barcode
  3. bead_coordinates.txt:每行对应barcode的x,y坐标(有时也有z)

你想想看,这种格式有多直接——没有层级,没有压缩。但坏处是文件体积可能很大,一个10万beads × 2万基因的矩阵,存成txt要几百兆,读取慢得想哭。

我曾经遇到过:从GEO下载的Slide-seq数据,expression_matrix.txt是稀疏矩阵格式(每行三个值:行索引、列索引、UMI计数),如果不先转换,直接读成稠密矩阵,内存直接爆掉。建议用scipy.sparse的coo_matrix加载。

下面是读取Slide-seq典型数据的示例:

import numpy as np
import pandas as pd
from scipy.sparse import coo_matrix, csr_matrix

# 以稀疏格式读入表达矩阵
expr = pd.read_csv("expression_matrix.txt", sep="\t", header=None)
# 假设expr三列:bead_idx, gene_idx, count
bead_idx = expr.iloc[:, 0].values
gene_idx = expr.iloc[:, 1].values
counts = expr.iloc[:, 2].values

# 构建稀疏矩阵
sparse_mat = coo_matrix((counts, (bead_idx, gene_idx))).tocsr()

# 读取barcode和坐标
barcodes = pd.read_csv("bead_barcodes.txt", header=None)[0].tolist()
coords = pd.read_csv("bead_coordinates.txt", sep="\t", header=None)
coords.columns = ["x", "y"]

2.3 两大格式对比:一张表看明白

为了让你更直观,我把核心差异列了个表。记牢这些,后面整合时心里有数。

对比维度Visium (10x)Slide-seq
数据存储方式HDF5 + 图像文本矩阵 + 坐标文件
空间分辨率~55 μm/spot~10 μm/bead
坐标来源图像对齐 + 预设阵列测序后的化学标记位置
表达矩阵结构稀疏HDF5,内含barcode/feature稀疏或稠密文本,需手动组装
典型文件大小几MB到几十MB几百MB到GB级
示例读取工具scanpy、Seurat、h5pypandas、scipy、Scanpy(需转换)

2.4 数据获取渠道与合规

数据从哪里搞?Visium数据:官方10x Genomics官网有公共数据集,也可以从GEO/SRA搜10x Visium关键词。Slide-seq数据:哈佛的Feinberg实验室网站、Broad Institute的Single Cell Portal,以及GEO都能找到。记得看使用条款。

我个人习惯是先下载README文件,很多时候格式说明就写在里面。有一次我偷懒没看,结果发现下载的Visium HDF5里用了版本2的格式,scanpy旧版本读不出来,折腾了半天。所以,一定先看文档。

一个小技巧:很多Slide-seq数据集提供了预处理的“Bead-level H5”版本,用h5py读就行,省去了拼坐标的麻烦。优先找这种。

2.5 本章知识结构图

下面这张图梳理了Visium和Slide-seq数据从获取到格式解析的关键路径,你看一眼就能形成整体印象。

数据获取与格式全景 Visium 原始数据 HDF5 (表达) 图像文件 (.tif/.png) spatial 坐标 Slide-seq 原始数据 表达矩阵(CSV/TSV) bead_barcodes.txt bead_coordinates.txt 格式统一化 整合分析 注意:Visium需要图像对齐获取spot坐标;Slide-seq坐标直接来自bead标记

2.6 格式转换实用备忘录

做整合时,你需要把Visium的HDF5和Slide-seq的文本转换成统一的AnnData对象。这里给出我常用的两个函数骨架:

# 将Slide-seq bead-level数据转成AnnData
def slide_seq_to_anndata(expr_file, coord_file, barcode_file=None):
    import scanpy as sc
    import pandas as pd
    from scipy.sparse import coo_matrix
    # ... 组装代码(参考前文)...
    adata = sc.AnnData(X=sparse_mat, obs=obs, var=var)
    adata.obsm["spatial"] = coords[["x", "y"]].values
    return adata
# 读取Visium HDF5到AnnData(scanpy已封装)
def visium_to_anndata(h5_path, image_dir=None):
    adata = sc.read_10x_h5(h5_path)
    # 添加空间坐标(需额外读tissue_positions.csv)
    positions = pd.read_csv("spatial/tissue_positions.csv", index_col=0)
    adata.obs["tissue"] = positions.loc[adata.obs_names, "in_tissue"]
    adata.obsm["spatial"] = positions.loc[adata.obs_names, ["x", "y"]].values
    return adata

注意:Visium的tissue_positions.csv默认使用像素坐标,而Slide-seq坐标是物理微米。整合前一定要先统一单位。我一般把Visium坐标乘以图像的分辨率(如0.219 μm/pixel),再跟Slide-seq对齐。