跳转至

749. 空间域识别SpaGCN/STAGATE

一句话概述:利用图神经网络整合基因表达和空间位置信息,自动识别组织中的"功能区域"——就像给一张组织切片自动标注"这里是肿瘤区""这里是免疫浸润区""这里是基质区"。


核心知识点速查表

概念白话解释关键工具
空间域(Spatial Domain)组织中功能相似的区域SpaGCN, STAGATE
GNN图神经网络,在图结构上做机器学习PyTorch Geometric
SpaGCN用图卷积整合表达+空间+组织学图像Python
STAGATE用图注意力网络做空间聚类Python
邻居图每个spot与其空间邻居的连接关系空间坐标计算
组织学图像H&E染色的组织形态学图片SpaGCN特色

一、原理(白话版)

1.1 传统聚类 vs 空间聚类

传统聚类(Leiden/Louvain):
  只看基因表达 → 不考虑空间位置
  → 可能把空间上很远但表达相似的spot聚在一起
  → 聚类结果在空间上可能"碎片化"

空间域识别:
  同时看基因表达 + 空间位置 → 空间上相邻且表达相似的spot优先聚在一起
  → 聚类结果在空间上更连续、更有生物学意义
  → 更像真实的组织区域

1.2 SpaGCN vs STAGATE

特性SpaGCNSTAGATE
方法图卷积网络(GCN)图注意力自编码器(GAT)
输入表达+空间+H&E图像表达+空间
特色可利用组织学图像注意力机制自动学习权重
速度较快中等
适用有H&E图像时最佳通用

二、SpaGCN分析流程

2.1 安装与运行

# ===== 安装SpaGCN =====
# pip install SpaGCN

import SpaGCN as spg  # 导入SpaGCN
import scanpy as sc   # 导入scanpy
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from PIL import Image  # 读取图像

# 读取Visium数据
adata = sc.read_visium("visium_output/")  # 读取10x Visium数据
adata.var_names_make_unique()

# 预处理
sc.pp.normalize_total(adata, target_sum=1e4)  # 归一化
sc.pp.log1p(adata)  # log转换

# 读取H&E组织学图像
img = plt.imread("visium_output/spatial/tissue_hires_image.png")

# 提取空间坐标
x_array = adata.obs["array_row"].values  # 行坐标
y_array = adata.obs["array_col"].values  # 列坐标
x_pixel = adata.obsm["spatial"][:, 0]    # 像素x坐标
y_pixel = adata.obsm["spatial"][:, 1]    # 像素y坐标

# ===== 整合组织学图像信息 =====
# SpaGCN可以从H&E图像中提取RGB值作为额外特征
adj = spg.calculate_adj_matrix(
    x=x_pixel,  # x像素坐标
    y=y_pixel,  # y像素坐标
    x_pixel=x_pixel,
    y_pixel=y_pixel,
    image=img,          # H&E图像
    beta=49,           # 组织学权重(默认49)
    alpha=1,           # 空间权重
    histology=True     # 使用组织学信息
)

# ===== 设定聚类数 =====
# 自动搜索最优分辨率
n_clusters = 7  # 预期的空间域数(可以根据组织学估计)
l = spg.search_l(
    p=0.5,    # Louvain分辨率搜索参数
    adj=adj,  # 邻接矩阵
    start=0.01,  # 搜索起始值
    end=1000,    # 搜索结束值
    tol=0.01     # 容差
)

# ===== 运行SpaGCN =====
clf = spg.SpaGCN()  # 创建SpaGCN对象
clf.set_l(l)  # 设置l值

# 训练
random_seed = 42
clf.train(
    adata,
    adj,
    num_pcs=50,       # PCA维度数
    lr=0.05,          # 学习率
    max_epochs=200,   # 最大训练轮数
    seed=random_seed
)

# 预测空间域
y_pred, prob = clf.predict()  # 预测聚类标签和概率

# 将结果添加到adata
adata.obs["SpaGCN_domain"] = y_pred.astype(str)  # 添加空间域标签

# 可选:细化边界(refine)
refined_pred = spg.refine(
    sample_id=adata.obs.index,
    pred=y_pred,
    dis=adj,
    shape="hexagon"  # Visium是六边形排列
)
adata.obs["SpaGCN_refined"] = refined_pred.astype(str)

# ===== 可视化 =====
fig, axes = plt.subplots(1, 2, figsize=(16, 6))

# 原始SpaGCN结果
sc.pl.spatial(adata, color="SpaGCN_domain", 
              title="SpaGCN Domains", ax=axes[0], show=False)

# 细化后的结果
sc.pl.spatial(adata, color="SpaGCN_refined",
              title="SpaGCN Refined", ax=axes[1], show=False)

plt.tight_layout()
plt.savefig("spagcn_domains.png", dpi=300)
plt.show()

三、STAGATE分析流程

# ===== STAGATE分析 =====
# pip install STAGATE_pyG  # PyTorch Geometric版本

import STAGATE_pyG as STAGATE  # 导入STAGATE
import scanpy as sc
import torch

# 读取数据
adata = sc.read_visium("visium_output/")
adata.var_names_make_unique()

# 预处理
sc.pp.highly_variable_genes(adata, flavor="seurat_v3", n_top_genes=3000)
sc.pp.normalize_total(adata, target_sum=1e4)
sc.pp.log1p(adata)

# ===== 构建空间邻居图 =====
STAGATE.Cal_Spatial_Net(
    adata,
    rad_cutoff=150  # 邻居距离阈值(像素单位)
    # 或用 k_cutoff=6 指定每个spot的邻居数
)

# 查看空间网络统计
print(f"平均邻居数: {adata.uns['Spatial_Net']['Cell1'].value_counts().mean():.1f}")

# ===== 训练STAGATE =====
adata = STAGATE.train_STAGATE(
    adata,
    alpha=0,        # KL散度权重
    n_epochs=1000,  # 训练轮数
    hidden_dims=[512, 30],  # 隐藏层维度
    random_seed=42
)

# 结果存储在 adata.obsm["STAGATE"]
# 这是STAGATE学习到的潜在表示

# ===== 聚类 =====
sc.pp.neighbors(adata, use_rep="STAGATE")  # 用STAGATE表示构建邻居图
sc.tl.umap(adata)  # UMAP可视化

# mclust聚类(STAGATE推荐)
import rpy2.robjects as ro
from rpy2.robjects import pandas2ri
pandas2ri.activate()

# 在R中运行mclust
ro.r('library(mclust)')
embedding = adata.obsm["STAGATE"]
ro.globalenv['embedding'] = embedding
ro.r('result <- Mclust(embedding, G=7)')  # G=预期聚类数
labels = np.array(ro.r('result$classification'))
adata.obs["STAGATE_domain"] = labels.astype(str)

# 或用Leiden替代
sc.tl.leiden(adata, resolution=0.5)
adata.obs["STAGATE_domain"] = adata.obs["leiden"]

# ===== 可视化 =====
sc.pl.spatial(adata, color="STAGATE_domain",
              title="STAGATE Spatial Domains")

四、常见报错与解决

报错信息原因解决方案
SpaGCN: adj matrix error坐标格式不对确保x_pixel/y_pixel是数值数组
STAGATE: CUDA errorGPU显存不足用CPU: device=torch.device('cpu')
Too many/few clusters聚类数设置不合适调整n_clusters或resolution
Spatial_Net: no edgesrad_cutoff太小增大rad_cutoff或用k_cutoff
mclust: R not found未安装R或rpy2改用Leiden聚类替代
Image not foundH&E图像路径错误检查spatial/目录结构

五、面试高频问题

Q1: 空间域识别和普通聚类有什么本质区别?

A: 普通聚类只考虑基因表达相似性,空间域识别同时考虑基因表达和空间位置。实际效果:空间域在组织切片上更连续,更接近真实的组织解剖学区域。

Q2: SpaGCN如何利用H&E图像?

A: 从H&E图像中提取每个spot位置的RGB值作为额外特征。这样空间邻接矩阵不仅考虑物理距离,还考虑组织形态学相似性。组织学外观相似的区域会被更强地连接。

Q3: 如何确定最佳聚类数?

A: ①参考组织学解剖结构(如大脑皮层有6层);②尝试多个聚类数,用ARI对比参考注释;③使用mclust的BIC准则自动选择;④结合生物学知识判断。


六、速查表

# ===== 空间域识别速查 =====

# SpaGCN
pip install SpaGCN
adj = spg.calculate_adj_matrix(x, y, image=img, histology=True)
clf = spg.SpaGCN()
clf.train(adata, adj, num_pcs=50)
y_pred, prob = clf.predict()

# STAGATE
pip install STAGATE_pyG
STAGATE.Cal_Spatial_Net(adata, rad_cutoff=150)
adata = STAGATE.train_STAGATE(adata, n_epochs=1000)
sc.pp.neighbors(adata, use_rep="STAGATE")
sc.tl.leiden(adata, resolution=0.5)

# 方法选择:
# 有H&E图像 → SpaGCN(利用组织学信息)
# 无H&E图像 → STAGATE(纯表达+空间)
# 追求速度 → SpaGCN
# 追求灵活性 → STAGATE