同一块结肠腺体,同一个杯状细胞标记基因 FCGBP。用 2 µm 网格画图,只有 14.2% 的格子能检出信号。换成 16 µm,这个比例升到 84.7%。两张图里,FCGBP 的去重分子计数都是 1,973 个 UMI。那条看起来更连贯的边界,有多少来自组织本身,又有多少是网格变大的结果?
我们用 10x Genomics 的一张公开人乙状结肠癌切片做了比较。对于这张片,整片分析可从 8 µm 开始,辨认腺体交界时回到 2 µm 和组织图。 如果只想先找出大片组织,可以从 16 µm 起步。10x 结直肠癌公开数据
同一块腺体,三张表达图分别能看见什么?
这张切片来自福尔马林固定、石蜡包埋(FFPE)的乙状结肠癌标本,切片厚 5 µm,公开结果由 Space Ranger 3.0.0 生成。癌组织切片里也保留着黏膜腺体。10x 的配套教程用 FCGBP 和 MUC2 辨认富含杯状细胞的腺体。我们据此挑了一块腺体形态可辨、FCGBP 信号较丰富的视野,可以把表达图直接和显微图对照。10x 对同一张切片的组织标记解读

图 1 四幅图范围和方向一致。表达图的颜色表示每平方微米的 FCGBP UMI 数,三图共用色标,达到 0.30 的位置使用同一最深色,不做空间平滑。“检出”指该格至少有 1 个 FCGBP UMI。数据来自 10x Genomics,由云生信重新分箱、计算并绘图。原始 2 µm 网格的行号为 576–671,列号为 2352–2447,均从 0 开始。 原始数据与许可说明 · CC BY 4.0
Visium HD 的 bin 是正方形网格。8 µm 的一格合并了 4 × 4 个原始 2 µm 小格,16 µm 的一格合并了 8 × 8 个。把同一批 UMI 分进更大的格子,格内只要有一个小格检出 FCGBP,整个大格就会着色。把这块 192 × 192 µm 的视野逐格数一遍,就能看到区别。Space Ranger 分箱原理
| bin 边长 | 视野内检出 FCGBP 的格数 | 读这张图时要看哪里 |
|---|---|---|
| 2 µm | 1,313 / 9,216(14.2%) | 看上方腺体外缘,散点仍能勾出弧线。回到组织图,可以查看信号落在边界哪一侧。单个空格只表示该处未检出 FCGBP。 |
| 8 µm | 360 / 576(62.5%) | 同一弧线容易辨认,中间较淡的区域也保留下来。适合先沿整张切片寻找相似腺体,再挑局部复查。 |
| 16 µm | 122 / 144(84.7%) | 还能找到信号集中在哪里,但一块较深的方格就可能横跨组织交界。若要把这段信号归到某侧上皮,需要回看细网格。 |
这组百分比只描述图中这个特意选出的视野。三种分辨率里的 FCGBP 总计数相等,84.7% 指的是检出该基因的网格占比。要回答“有多少杯状细胞”,还需要细胞边界、分子归属和多个标记。要判断肿瘤浸润前沿,则要换到相应病理区域,同时查看肿瘤和间质标记。10x 对 bin 与细胞的说明
换成自己的切片,先看研究问题有多大
看全片分区,从 8 µm 开始。 先画总 UMI、一个上皮标记和一个间质标记,再叠回 H&E(苏木精–伊红染色)组织图。到腺体密集处看一眼,确认热图还能区分相邻结构,然后再做聚类。8 µm 也是 10x 和 Seurat 教程采用的常用分析起点。Seurat 的 Visium HD 分析教程
看窄边界,回到 2 µm 的局部。 比如想问“腺体外面这排点也有 FCGBP 吗”,可以从 8 µm 图上圈出边缘,提取同一视野的 2 µm 原始计数,叠加 H&E,再用 MUC2 复核。对一个或几个基因,Space Ranger 的 feature slice 文件支持按基因读取,可以跳过载入整套表达矩阵。10x 按基因提取空间信号的文件说明
显示阈值也会改变边界。在 Loupe 里把显示下限从 1 调到 2 个 UMI,可以隐去一部分孤立点。但同样的“至少 2 个 UMI”,在 2 µm 小格里相当于每平方微米至少 0.5 个,在 16 µm 大格里只需约 0.008 个。做跨尺度对照时,可以保留原始 UMI 数作为检出依据,另外用单位面积计数统一色标。若要比较不同基因或不同切片的表达,还要检查文库深度和归一化方式。图 1 只比较同一张片的同一个基因。10x 对该样本 FCGBP 显示阈值的演示
看大片组织或初筛多张片,用 16 µm 快速定位。 如果问题只是哪些区域整体富含上皮或间质信号,16 µm 可以先给出候选区域。定下来的区域再用 8 µm 对照,特别留意在粗网格下看似相连的两块组织,细看时中间是否有缝隙。
自己复算图中的 1,973 个 UMI
从数据页下载文件名以 _feature_slice.h5 结尾的空间表达文件,放在当前目录,在装有 NumPy 和 h5py 的 Python 环境中运行。先按原始 2 µm 网格截取固定视野,再对齐边界合并计数。这样做可以保证三次比较覆盖完全相同的组织面积。原始 feature slice 文件,约 788 MB
import h5py
import numpy as np
with h5py.File("Visium_HD_Human_Colon_Cancer_feature_slice.h5", "r") as f:
names = f["features/name"].asstr()[:].tolist()
gene = f[f"feature_slices/{names.index('FCGBP')}"]
rows, cols, umi = (gene[key][:] for key in ("row", "col", "data"))
# 行列从 0 开始。两个区间均含起点、不含终点。
row0, col0, side = 576, 2352, 96
keep = ((rows >= row0) & (rows < row0 + side)
& (cols >= col0) & (cols < col0 + side))
roi = np.zeros((side, side), dtype=np.uint32)
np.add.at(roi, (rows[keep] - row0, cols[keep] - col0), umi[keep])
for factor in (1, 4, 8):
n = side // factor
binned = roi.reshape(n, factor, n, factor).sum(axis=(1, 3))
detected = np.count_nonzero(binned)
print(f"{2 * factor} µm {detected}/{binned.size} 总 UMI {binned.sum()}")
可以先做两个自检。第一,三行的总 UMI 都应为 1,973,否则检查选区和网格起点。第二,算检出率时把零计数的格子放回分母。公开文件按稀疏格式存储,直接数该基因出现了多少行,会把许多零格漏掉。上面这块视野全部落在组织掩膜内。如果换到切片外缘,还要剔除组织外的格子,并说明如何处理只覆盖一部分组织的大格。
从一个视野扩到整张切片,需要多少内存?
先看这张片真正有多大。10x 的统计只计算组织覆盖的网格。分到 2 µm 时,有 873.14 万个位置需要处理,每个位置平均只有约 31 个 UMI。合到 8 µm,位置减到约 54.59 万个,每格的平均 UMI 升到约 488 个。前一项决定要处理多少个位置,后一项影响每个位置能用多少信号做分析。本样本的 Space Ranger 指标原表
| bin 边长 | 组织覆盖的网格数 | 每格平均 UMI | 每格平均检出基因 | 假设 2,000 个基因全部展开为 float32 稠密矩阵 |
|---|---|---|---|---|
| 2 µm | 8,731,400 | 30.5 | 28.7 | 65.1 GiB |
| 8 µm | 545,913 | 488.4 | 393.2 | 4.1 GiB |
| 16 µm | 137,051 | 1,945.9 | 1,274.8 | 1.0 GiB |
最后一列是按“组织内网格数 × 2,000 个基因 × 每个数 4 字节”算出的一个数组的大小。换成双精度,数值占用翻倍。原始计数可以稀疏存储,但若某一步把所有位置的标准化值转成稠密矩阵,内存还要容纳已有对象、中间副本和后续的邻接图。若只看少数标记,所需的对象又可以小很多。
我们从同一份 feature slice 单独提取 FCGBP,应用该尺度的组织掩膜,绘制整张捕获区域的单基因灰度 PNG。每档用独立的 Windows 64 位 Python 进程运行三次,记录进程峰值工作集,中位数约为 158 MiB(2 µm)、66 MiB(8 µm)、66 MiB(16 µm)。在这个只读一个基因的脚本里,8 和 16 µm 两档的内存峰值接近。做全基因聚类时,应拿自己的读取、缩放、降维和建图流程重新试跑。
如果手里是 FASTQ 和组织图,要从头跑 Space Ranger。 10x 为 Linux 主机列出的资源是至少 8 核 CPU、64 GB 内存、1 TB 可用磁盘,推荐 32 核、128 GB 内存。1 TB 指磁盘空间。已有公开矩阵、准备直接在 Seurat 或 Python 做下游的读者,可以从实际要跑的分析步骤估算资源。10x Space Ranger 系统要求
如果准备做多张片,先让一张代表切片跑完最吃内存的步骤。 例如要做 8 µm 全片聚类,就一直测到建邻接图,记下峰值内存、工作盘最高占用和耗时,再用数据量最大的一张复核。多张片分开跑时,用各任务峰值和预计并发数初估资源,再留出系统与读写余量。若要把多张片合并成一个对象,则用两张片先试一次真实的合并流程。
在装有 GNU Time 的 Linux 服务器上,可以用下面的命令给自己的单进程 Python 脚本留一份记录。将示例脚本名换成实际文件,运行结束后,在 8um-memory.txt 中查找 Maximum resident set size。Linux 常见输出以 kbytes 标注,除以 1,048,576 可换算为 GiB。若使用多个独立进程并发,要同时查看作业或容器的总内存峰值,单个进程的记录覆盖不了所有进程同时占用的内存。GNU Time 内存指标
/usr/bin/time -v -o 8um-memory.txt python run_8um.py
准备跑全片时,把这几个数带来
从公开数据看一处腺体,用现有电脑按基因取数就能先做起来。当任务扩到多张 8 µm 全片聚类,或需要从 FASTQ 重跑时,服务器应该按那一步的实际负载来选。想自己用 R 或 Python 继续分析,可以把切片数、打算用的 bin、要跑的步骤发给云生信。有单片试跑记录,再附峰值内存或报错位置,我们可以据此和你核对内存、CPU、工作盘及租期。查看云生信科研服务器租赁与配置咨询

