第2章 原始数据下载与格式解析
空间转录组的第一道坎,就是把原始数据拿到手,并且搞懂每个文件到底在说什么。我当年第一次接触Visium数据时,对着文件夹里一堆.h5、.csv、.json和图片,愣是看了半天才理清关系。这一章我们就把这个过程拆开揉碎,你跟着走一遍,后面分析就顺了。
2.1 去哪里下载Visium数据
10x Genomics官方提供了不少示例数据集。我个人最常用的是正式公开的Visium Spatial Gene Expression数据集,比如Human Breast Cancer(FFPE)、Mouse Brain(Fresh Frozen)等。下载地址一般在10x的官方数据集页面(注意,这里只是为了说明来源,我们不在正文中放链接,只是提一下)。
另外,GEO(Gene Expression Omnibus)上也存了大量Visium数据。搜索“Visium”加上你想研究的组织,就能找到很多。
下载时注意区分两种输出:Space Ranger 输出(通常是一个tar.gz压缩包,解压后包含spatial文件夹和filtered_feature_bc_matrix.h5等)和原始FASTQ文件。我们这个教程主要用Space Ranger处理后的结果,也就是下游分析的标准输入。
ls -lh看一眼文件大小。有些数据集动辄几个GB,先确认下载完整,免得后面解压报错。
解压命令很简单:
tar -xzvf Visium_Mouse_Brain.tar.gz
ls -R
2.2 标准文件夹结构长啥样
解压后你会看到类似这样的目录结构(以Space Ranger v2.0.0输出为例):
.
├── filtered_feature_bc_matrix.h5
├── raw_feature_bc_matrix.h5
├── filtered_feature_bc_matrix
│ ├── barcodes.tsv
│ ├── features.tsv
│ └── matrix.mtx
├── raw_feature_bc_matrix
│ ├── barcodes.tsv
│ ├── features.tsv
│ └── matrix.mtx
├── spatial
│ ├── tissue_hires_image.png
│ ├── tissue_lowres_image.png
│ ├── scalefactors_json.json
│ ├── tissue_positions_list.csv
│ └── aligned_tissue_image.jpg (可选)
└── analysis (可选,包含聚类等)
很多人一开始会被两个.h5文件弄晕。我解释一下它们的区别:
- filtered_feature_bc_matrix.h5 —— 只保留检测到的有效spots(通常是组织覆盖区域)。我们大部分分析都用这个。
- raw_feature_bc_matrix.h5 —— 包含所有barcodes(包括背景区域),数据未经滤除。做质量控制时可能需要它来对比。
2.3 核心文件逐个解析
我们重点聊spatial文件夹里的内容,因为它们是空间信息的核心。
2.3.1 组织图片:tissue_hires_image.png / tissue_lowres_image.png
这两张图是染色后的组织切片扫描图。high-resolution (hires) 是完整分辨率,低分辨率 (lowres) 则是缩略图。算法在匹配spots坐标时,会用这些图做基准。
你可以直接用看图软件打开,但注意它们的大小——hires图可能高达20000×20000像素,加载慢。代码里我用matplotlib.image.imread读取后通常会先缩放。
2.3.2 尺度因子:scalefactors_json.json
这个JSON文件包含了将原始像素坐标转换为分析坐标的缩放参数。里边的字段如下:
| 字段名 | 含义 | 典型值(示例) |
|---|---|---|
tissue_hires_scalef |
高分辨率图缩放因子 (实际像素 / 原始像素) | 0.1 |
tissue_lowres_scalef |
低分辨率图缩放因子 | 0.025 |
spot_diameter_fullres |
全分辨率下每个spot的直径(像素) | 89.0 |
fiducial_diameter_fullres |
全分辨率下fiducial点直径(像素) | 79.0 |
举个例子:如果你的hots图实际宽度是20000像素,但tissue_hires_scalef=0.1,那么代码里用的时缩放到2000像素。我们需要这个值把spot坐标正确映射到图片上。
2.3.3 位置信息:tissue_positions_list.csv
这个文件是空间转录组里最“灵魂”的数据。每一行代表一个barcode(spot),记录了它在组织上的位置以及是否属于组织区域。列结构:
| 列名 | 说明 |
|---|---|
| barcode | spot的唯一标识符,例如 AAACCCAAGTATTGAG-1 |
| in_tissue | 0/1标记,1表示该spot在组织覆盖区域(有用),0代表背景 |
| row | spot在网格中的行坐标 |
| col | spot在网格中的列坐标 |
| imagerow | 对应组织图像上的像素行坐标(全分辨率下) |
| imagecol | 对应组织图像上的像素列坐标(全分辨率下) |
你可以用Python的pandas快速查看前几行:
import pandas as pd
pos = pd.read_csv('spatial/tissue_positions_list.csv', index_col=0)
print(pos.head())
输出会像这样:
in_tissue row col imagerow imagecol
barcode
AAACCCAAGTATTGAG-1 1 20 42 538.194 9103.821
AAACCCAACACTGGCT-1 1 50 23 1345.670 5307.450
...
2.3.4 表达矩阵:filtered_feature_bc_matrix.h5
这是标准的10x HDF5格式,可以通过scanpy.read_10x_h5直接读入。如果你喜欢用R,Seurat也有Read10X_h5函数。里面包含三个核心元素:barcodes、features(基因)和counts矩阵。
读入后检查一下基因数和细胞数:
import scanpy as sc
adata = sc.read_10x_h5('filtered_feature_bc_matrix.h5')
print(adata.shape)
# 输出示例: (2000, 34523) 表示2000个spots和34523个基因
注意:这里的“细胞”实际上是spots——每个spot包含多个细胞,但分析时通常当成一个“样本”。
2.4 数据结构的整体关系
为了让这些文件之间的关系更直观,我画了一张示意图。你看完就明白了:
图2-1: Visium原始数据下载、解压、文件组成及核心解析流程
从图中你可以看到,核心任务就是把spatial文件夹里的位置信息、图像缩放因子和.h5表达矩阵整合到一起,得到一个AnnData对象(scanpy标准格式),后续所有分析都基于它。
2.5 实际代码整合示例
我来写一段最常用的读取流程,你照着做就能把数据读干净:
import scanpy as sc
import pandas as pd
import json
# 1. 读取表达矩阵
adata = sc.read_10x_h5('filtered_feature_bc_matrix.h5')
# 2. 读取位置信息
pos = pd.read_csv('spatial/tissue_positions_list.csv', index_col=0)
# 3. 仅保留组织上的spots
pos_in_tissue = pos[pos['in_tissue'] == 1]
# 注意:.h5里的barcodes可能比pos少,我们取交集
common_bc = adata.obs.index.intersection(pos_in_tissue.index)
adata = adata[common_bc] # 过滤
# 4. 添加空间坐标
adata.obs['imagerow'] = pos_in_tissue.loc[common_bc, 'imagerow']
adata.obs['imagecol'] = pos_in_tissue.loc[common_bc, 'imagecol']
# 5. 读取缩放因子
with open('spatial/scalefactors_json.json') as f:
sf = json.load(f)
print('Hires scale factor:', sf['tissue_hires_scalef'])
这样,你的adata就包含了表达矩阵和空间坐标,下一步就能画组织切图了。
adata.obs.index[:5]和pos.index[:5],如果发现不匹配,用pos.rename(index=lambda x: x + '-1')手动补上。
2.6 小结
这一章我们干了三件事:下载数据、搞懂文件夹里每个文件的用途、用代码把它们组装起来。你看,其实没有多玄乎,就是几个文件之间的对应关系。下结论前我多说一句——学会一口气把位置和表达矩阵对齐,是后续所有空间分析的基础。很多人卡在格式不匹配上,你用我上面那段代码,基本能一次性搞定。