数据预处理(QC):质量控制指标、过滤与组织提取
各位,正式上机分析之前,最磨人的一步来了——数据预处理。说白了就是给你的空间转录组数据“体检”。我做了这么多年项目,可以很负责任地告诉你:这一步省掉的时间,最后都会变成修 bug 的时间。咱们直接开整。
质量控制指标:先了解你的数据“体质”
不管是用 10x Visium、Slide-seq 还是 MERFISH,拿到手的 Raw Data 都会包含几个核心指标。我个人习惯用一个矩阵一张表来快速扫一眼所有 spots 的分布。
| 指标 | 含义 | 典型异常 | 我见过的极端案例 |
|---|---|---|---|
| UMI 计数 (nCount_RNA) | 每个 spot 捕获到的转录本总数 | 过低(< 500)或过高(> 10000) | 某样本有 30% spots UMI 数 < 100,后来发现是组织切片贴歪了 |
| 检测基因数 (nFeature_RNA) | 每个 spot 检测到的不同基因种类 | 过低(< 200)或过高(> 8000) | |
| 线粒体基因比例 (percent.mt) | 线粒体基因 UMI 占总 UMI 的比例 | 过高(> 20%)说明细胞破损或死细胞多 | 海马体样本线粒体比例普遍在 35% 以上,并非异常,而是神经元能量需求高 |
| 核糖体基因比例 (percent.ribo) | 核糖体基因占比 | 过高可能污染 |
VlnPlot 和 FeatureScatter 够用了。
过滤低质量 spots:该删就删,别手软
拿到上面的指标分布后,阈值怎么定?我早期吃过亏,直接套用 scRNA-seq 的阈值,结果把组织边缘的稀疏区域全干掉了——后面批次校正时模型学不到局部结构。血的教训。
正确的打开方式:先看数据分布,再定阈值。
# R 语言示例(Seurat)
library(Seurat)
spatial_obj <- CreateSeuratObject(counts = raw_counts, assay = "Spatial")
spatial_obj[["percent.mt"]] <- PercentageFeatureSet(spatial_obj, pattern = "^MT-")
# 我习惯用分位数来定阈值,不拍脑袋
mt_low <- quantile(spatial_obj$percent.mt, 0.02) # 2% 分位
mt_high <- quantile(spatial_obj$percent.mt, 0.98) # 98% 分位
nCount_low <- quantile(spatial_obj$nCount_Spatial, 0.01)
spatial_obj <- subset(spatial_obj,
subset = nCount_Spatial > nCount_low &
percent.mt > mt_low & percent.mt < mt_high)
注意!线粒体比例 过低 也要警惕。我碰到过一次:某个 spot 的 perecent.mt = 0%,查了半天发现是那个位置的 barcode 根本没捕获到真细胞,就是背景噪声。所以最好设一个下限(比如 > 1%),把一杆空枪都过滤掉。
图像配准与组织区域提取:让 spot 和像素对上号
空间转录组的特点就是位置信息。10x Visium 的输出里有个 spatial/tissue_lowres_image.png 和 tissue_positions_list.csv。但配准不一定完美,尤其是当组织切片有褶皱、气泡或者染色不均的时候。
我的标准流程分三步:
- 读取原始图像与坐标文件。手动检查组织区域是否被正确识别(通常用
H&E或IF染色背景来判定组织边界)。 - 手动或自动对齐。Seurat 提供了
Read10X_Image函数,但只适用于标准配准。当用户自定义实验(比如多张切片拼图)时,我建议用EBImage或OpenCV做仿射变换,把 spots 坐标映射到图像像素上。 - 提取组织区域 mask。可以用图像分割(如 Otsu 阈值法、分水岭)自动去掉背景区域,只保留组织内部 spots。这个步骤能显著减少下游计算量,也避免了背景 spots 对批次校正的干扰。
# Python 示例(OpenCV 做简单组织区域提取)
import cv2
import numpy as np
img = cv2.imread("tissue_lowres_image.png", cv2.IMREAD_GRAYSCALE)
_, mask = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# 再做形态学闭合操作,填掉小空洞
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (15,15))
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
# 然后根据 mask 过滤 spots
嗯,这里要注意,配准的精度决定了后面所有空间分析的准确性。我曾在项目中遇到两个批次的组织切片旋转角度差了 30°,直接用默认配准函数跑出来一团糟。后来手动加上旋转矩阵和像素重采样才搞定。
下面我画了一张图,把本章的 QC 流程串起来。你跟着它走,基本不会漏掉关键节点。
这张图你应该能看懂了:从原始矩阵和图像出发,先算三个核心指标,再基于分布定阈值过滤,之后做图像配准让 spots 位置与像素对齐,最后提取组织区域、去掉背景。这四步做完,你的数据才算是“洗白白”,可以放心喂给下游的批次校正模型了。