2. 数据下载与预处理:从官方资源获取原始数据

做空间转录组分析,第一步就是拿到对的数据。说白了,数据质量直接决定最终结论靠不靠谱。我刚开始接触Visium时,图省事从第三方平台拷了一份数据,结果跑出来的聚类分布死活不对劲——最后发现是下载版本不对,图像和基因矩阵没对齐。从那以后,我老老实实只从官方渠道拿原始数据。

下面我就带你走一遍标准流程:从10x Genomics官网下载Visium原始数据,做基本的预处理,为后续参数调试打好底子。

官方数据源简介

10x Genomics官方提供了大量Visium公开数据集,涵盖多种组织类型和物种。这些数据以Space Ranger输出格式发布,包含三大类文件:

  • 空间基因表达矩阵(barcodes.tsv.gz、features.tsv.gz、matrix.mtx.gz)
  • 组织图像文件(tissue_hires_image.png、tissue_lowres_image.png)
  • 图像对齐信息(scalefactors_json.json、tissue_positions_list.csv)

官方下载链接通常长这样:

https://www.10xgenomics.com/resources/datasets/
或直接通过wget获取示例数据集:
wget https://cf.10xgenomics.com/samples/spatial-exp/1.2.0/Visium_Mouse_Olfactory_Bulb/Visium_Mouse_Olfactory_Bulb_raw_feature_bc_matrix.h5
wget https://cf.10xgenomics.com/samples/spatial-exp/1.2.0/Visium_Mouse_Olfactory_Bulb/Visium_Mouse_Olfactory_Bulb_spatial.tar.gz

下载命令与目录结构

我个人习惯用wget批量拉取,再统一解压。注意,不同版本的数据集文件夹命名可能有细微差别,最好先看一眼官方README。

# 创建项目目录
mkdir -p visium_raw_data && cd visium_raw_data

# 下载小鼠嗅球示例数据(约300MB)
wget https://cf.10xgenomics.com/samples/spatial-exp/1.2.0/Visium_Mouse_Olfactory_Bulb/Visium_Mouse_Olfactory_Bulb_raw_feature_bc_matrix.h5
wget https://cf.10xgenomics.com/samples/spatial-exp/1.2.0/Visium_Mouse_Olfactory_Bulb/Visium_Mouse_Olfactory_Bulb_spatial.tar.gz

# 解压图像与对齐文件
tar -xzf Visium_Mouse_Olfactory_Bulb_spatial.tar.gz

# 查看目录结构
tree

解压后的`spatial/`文件夹里通常会包含以下文件:

spatial/
├── tissue_hires_image.png         # 高分辨率染色图(约2000x2000)
├── tissue_lowres_image.png        # 低分辨率预览图
├── scalefactors_json.json         # 图像缩放因子
├── tissue_positions_list.csv      # 每个位置对应的barcode与像素坐标
└── aligned_tissue_image.jpg       # 可选对齐后图像

核心文件说明

文件格式内容
barcodes.tsv.gz / .h5文本/HDF5所有捕获点的barcode序列
features.tsv.gz / .h5文本/HDF5基因名称与ID
matrix.mtx.gz / .h5MTX/HDF5稀疏表达矩阵(UMI counts)
tissue_positions_list.csvCSVbarcode → 像素坐标 + 组织内/外标记
scalefactors_json.jsonJSON高/低分辨率缩放因子、spot直径(像素)等

注意: 有些数据集用.h5格式打包了全部内容(包括barcode、feature、matrix),读起来更省事。如果你用scanpy,我建议优先选.h5版本,少一重解压步骤。

用Python读取数据

这里我推荐使用scanpy的read_visium函数,它一步到位,自动关联基因矩阵和空间坐标。为什么不自己写循环?因为官方API已经封装好,而且社区验证过很多遍了,你想想看,何必给自己挖坑呢?

import scanpy as sc

# 从h5文件读取(推荐)
adata = sc.read_visium("Visium_Mouse_Olfactory_Bulb_raw_feature_bc_matrix.h5", 
                       source_image_path="spatial/")

# 或者从传统MTX格式读取
# adata = sc.read_visium("raw_feature_bc_matrix/", 
#                        source_image_path="spatial/")

print(adata)
# 输出:AnnData object with n_obs × n_vars = 4500 × 21000
#     obs: 'in_tissue', 'array_row', 'array_col'
#     var: 'gene_ids', 'feature_types'
#     obsm: 'spatial'
#     uns: 'spatial_crambin' (图像对象)

嗯,这里有个坑——source_image_path参数要指向包含tissue_hires_image.png的文件夹。我遇到过路径写错导致图像无法嵌入的情况,后来老老实实打印adata.uns['spatial']检查了才定位到问题。

质量控制与预处理

拿到原始数据后,别急着冲进聚类调试。先做几道基础过滤,把明显不合格的spot和基因筛掉。我个人的经验是:

  1. 过滤低质量spot:UMI数量过少(<200)或线粒体基因占比过高(>20%)的spot,多半是破损细胞或背景噪音。
  2. 过滤低表达基因:在少于3个spot中表达的基因,统计意义不大,留着只会增加计算量。
  3. 归一化与对数化:让spot间的表达量可比,常用sc.pp.normalize_total和sc.pp.log1p。
  4. 识别高变基因:用于后续降维和聚类,sc.pp.highly_variable_genes。
# 基础QC
sc.pp.filter_cells(adata, min_genes=200)
sc.pp.filter_genes(adata, min_cells=3)

# 计算线粒体占比
adata.var['mt'] = adata.var_names.str.startswith('MT-')
sc.pp.calculate_qc_metrics(adata, qc_vars=['mt'], percent_top=None, log1p=False, inplace=True)
# 过滤高线粒体spot
adata = adata[adata.obs.pct_counts_mt < 20, :].copy()

# 归一化+对数化
sc.pp.normalize_total(adata, target_sum=1e4)
sc.pp.log1p(adata)

# 筛选高变基因(默认5000个)
sc.pp.highly_variable_genes(adata, n_top_genes=5000)
adata = adata[:, adata.var.highly_variable].copy()

print(adata)
# 输出:AnnData object with n_obs × n_vars = 4100 × 5000
提示:线粒体基因的参考名称因物种而异——人类是MT-前缀,小鼠是mt-。我曾经在分析小鼠数据时忘了改前缀,结果线粒体占比全为0,让我以为数据特别干净…后来检查才发现是前缀写错了。
警告:不要轻易删除原始文件!建议将下载的压缩包和原始.h5保留一份未处理的副本。我见过同事手滑把原始数据覆盖了,结果要重新下载3GB数据,浪费半天时间。

预处理流程总览

下图把“下载→解压→读入→过滤→归一化→保存”串起来,方便你对照执行:

数据下载与预处理流程图 10x官网下载 wget/curl 解压.spatial tar -xzf read_visium() scanpy 过滤低质量spot/基因 min_genes, min_cells, MT% 归一化 + 对数化 normalize_total, log1p 保存 .h5ad adata.write() ⚑ 预处理核心要点 • 保留原始文件副本 • 确认物种线粒体前缀 • 高变基因数不宜过多

保存中间结果

预处理完成后,我习惯把处理过的数据存成.h5ad文件,方便后续章节反复加载,不用每次从头跑过滤。

adata.write("Data/visium_processed.h5ad")
# 下次恢复
adata = sc.read("Data/visium_processed.h5ad")

你可能会问:为什么不直接在原始数据上做所有操作?说白了,预处理过程本身就有不少试错空间。保存一份中间态,万一参数调坏了,还能从干净的过滤后数据重新开始,不用再下载一遍。

好了,到此为止,原始数据已经下载、解压、读入、过滤、归一化,并保存成标准格式。接下来你就可以拿着这份干净的AnnData对象,去探索空间基因表达模式、做聚类参数调试了。