脚本更新----Xenium、CODEX、CosMx范围邻域矩阵的获得与亚群分析:用TaoToken统一Key跑通Leiden聚类
1. 从空间坐标到邻域矩阵Xenium、CODEX、CosMx 下游分析到底卡在哪如果你手上有 Xenium、CODEX 或 CosMx 的数据做完细胞分割和注释之后大概率会碰到同一个问题单细胞层面的表达矩阵已经拿到了但细胞和细胞之间的空间关系还没被利用起来。空间转录组和多重免疫荧光真正的价值不在于每个细胞单独长什么样而在于一群细胞在 200 µm 这个尺度上如何组织成 niche、如何形成亚群结构。这一步做不好后面的差异分析、细胞通讯、区域注释都会缺一块拼图。我先把问题拆清楚。Xenium 输出的是每个转录本的三维坐标加细胞边界CODEX 输出的是每个细胞的多通道蛋白强度加质心坐标CosMx 介于两者之间既有转录本也有蛋白通道。三类平台的数据结构不同但下游要做的动作高度一致提取空间坐标按欧氏距离构建邻域矩阵把邻域关系转成图用 Leiden 做图聚类再把聚类标签写回 AnnData最后用 UMAP 和空间散点图验证。这套链路的核心检索词就是空间邻域矩阵构建与 Leiden 亚群分析适合已经跑通上游 pipeline、想快速复现下游聚类的分析人员。为什么强调 200 µm这个尺度在组织学上大致对应几个细胞直径到十几个细胞直径的范围既能捕捉局部微环境又不会把整个切片混成一团。你可以把它理解成给每个细胞画一个半径 200 µm 的圆圆里所有其他细胞都是它的邻居。邻居关系确定之后Leiden 算法会在这个图上找社区社区内部的细胞连接紧密社区之间连接稀疏这些社区就是我们要的亚群。实际踩过的坑主要有三个。第一坐标字段名不统一Xenium 常用x_centroid/y_centroidCODEX 常用x/yCosMx 有时是CenterX_global_px直接写死字段名会报 KeyError。第二距离矩阵是稠密的十万级细胞直接算euclidean_distances会吃掉大量内存必须做稀疏化或分块。第三Leiden 的resolution_parameter不调默认结果要么太粗要么太碎亚群注释根本对不上。下面我把这三件事全部落到可复制的脚本里。2. TaoToken 统一 Key 前置一次配置跑通三类平台的下游脚本在进入脚本之前先把调用通道统一掉。我做空间组学分析时经常需要在不同机器、不同 notebook 之间切换如果每个环境都单独配一套鉴权脚本迁移一次就要改一次配置非常容易出错。TaoToken 的作用就是提供一个统一的 Key 通道让 Xenium、CODEX、CosMx 的下游脚本共用同一套接入参数模型对话、coding-plan、console 和 api-keys 都在同一个控制台里管理。你需要先拿到三件套Base URL、API Key、Model ID。Base URL 固定用https://taotoken.net/api注意这个地址不带任何查询参数直接作为 OpenAI 兼容接口的根路径。API Key 在控制台的 api-keys 页面生成生成后只显示一次建议立刻写进环境变量而不是硬编码进脚本。Model ID 根据你的任务选纯脚本生成和参数调优用 coding-plan 通道更划算需要边写边问的交互式调试用模型对话通道。配置方式我推荐用环境变量加配置文件双保险。环境变量负责运行时注入配置文件负责版本管理。下面这段是.env风格的写法你可以直接复制export TAOTOKEN_BASE_URLhttps://taotoken.net/api export TAOTOKEN_API_KEYsk-你的实际Key export TAOTOKEN_MODEL_ID你的ModelID如果你用的是 Claude Code 或者类似的 CLI 工具配置文件通常放在~/.claude/settings.json或项目根目录的settings.json结构如下{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的实际Key, ANTHROPIC_MODEL: 你的ModelID } }注意这里 Base URL、Key、Model ID 三件套必须同时出现缺任何一个都会在请求阶段报鉴权或模型不存在。如果你用的是 Codex 系的工具配置文件在~/.codex/auth.json字段名换成base_url、api_key、model即可值完全一致。Cline 或 MCP 场景下把同样的三件套填进 MCP server 的 env 段不要直连生产数据库只让它读写本地 h5ad 文件。配好之后先做一次最小验证确认通道是通的curl -s https://taotoken.net/api/v1/models \ -H Authorization: Bearer $TAOTOKEN_API_KEY | head -c 300返回 JSON 里能看到模型列表就说明 Key 和 Base URL 没问题。这一步不要跳过后面脚本报错时你才能确定是通道问题还是代码问题。接入文档在 doc 页面有完整的字段说明遇到 401 先回去核对 Key 有没有多余空格。3. 可复制配置邻域矩阵生成与 Leiden 聚类完整脚本现在进入核心部分。下面这个脚本我按 Xenium 的字段名写同时给出 CODEX 和 CosMx 的字段映射你只需要改COORD_FIELDS这一个字典就能切换平台。脚本包含坐标提取、稀疏邻域矩阵构建、igraph 建图、Leiden 聚类、PCA/UMAP 降维、空间可视化、结果保存七个步骤全部可复制运行。import argparse import numpy as np import pandas as pd import scanpy as sc import igraph as ig import leidenalg import matplotlib.pyplot as plt from scipy.spatial import cKDTree # 三类平台的坐标字段映射按需切换 COORD_FIELDS { xenium: (x_centroid, y_centroid), codex: (x, y), cosmx: (CenterX_global_px, CenterY_global_px), } def build_neighbor_graph(coords, distance_threshold200.0): 用 KDTree 构建稀疏邻域避免稠密距离矩阵爆内存 tree cKDTree(coords) pairs tree.query_pairs(rdistance_threshold, output_typendarray) if pairs.size 0: raise ValueError(邻域为空请检查坐标单位是否为 µm) n coords.shape[0] edges pairs.tolist() graph ig.Graph(nn, edgesedges, directedFalse) return graph def leiden_clustering( data_path, output_path, platformxenium, distance_threshold200.0, resolution1.0, n_pca_components50, n_neighbors_umap15, ): adata sc.read(data_path) print(fLoaded {adata.n_obs} cells x {adata.n_vars} features) xcol, ycol COORD_FIELDS[platform] if xcol not in adata.obs.columns: raise KeyError(f字段 {xcol} 不存在请检查 platform 参数) coords adata.obs[[xcol, ycol]].to_numpy(dtypefloat) graph build_neighbor_graph(coords, distance_threshold) print(fGraph: {graph.vcount()} nodes, {graph.ecount()} edges) partition leidenalg.find_partition( graph, leidenalg.RBConfigurationVertexPartition, resolution_parameterresolution, seed42, ) adata.obs[leiden] pd.Categorical( [str(x) for x in partition.membership] ) print(fClusters: {adata.obs[leiden].nunique()}) sc.tl.pca(adata, svd_solverarpack, n_compsn_pca_components) sc.tl.umap(adata, n_neighborsn_neighbors_umap, min_dist0.1) sc.pl.umap(adata, colorleiden, titleLeiden on UMAP, showFalse) plt.savefig(output_path.replace(.h5ad, _umap.png), dpi150, bbox_inchestight) plt.close() sc.pl.spatial(adata, colorleiden, titleLeiden on Spatial, showFalse) plt.savefig(output_path.replace(.h5ad, _spatial.png), dpi150, bbox_inchestight) plt.close() adata.write(output_path) print(fSaved to {output_path}) return adata if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(--data, requiredTrue) parser.add_argument(--output, requiredTrue) parser.add_argument(--platform, defaultxenium, choices[xenium, codex, cosmx]) parser.add_argument(--distance_threshold, typefloat, default200.0) parser.add_argument(--resolution, typefloat, default1.0) parser.add_argument(--n_pca_components, typeint, default50) parser.add_argument(--n_neighbors_umap, typeint, default15) args parser.parse_args() leiden_clustering( data_pathargs.data, output_pathargs.output, platformargs.platform, distance_thresholdargs.distance_threshold, resolutionargs.resolution, n_pca_componentsargs.n_pca_components, n_neighbors_umapargs.n_neighbors_umap, )关键改动说明。第一我用cKDTree.query_pairs替代了原来的euclidean_distances全矩阵计算十万细胞从几十 GB 内存降到几百 MB这是能跑通大切片的前提。第二Leiden 用RBConfigurationVertexPartition而不是ModularityVertexPartition因为前者支持resolution_parameter你能通过调参控制亚群粒度。第三聚类标签强制转成字符串再存进pd.Categorical避免下游画图时把类别当连续值处理。运行命令按平台切换python leiden_clustering.py \ --data xenium_sample.h5ad \ --output xenium_leiden.h5ad \ --platform xenium \ --distance_threshold 200 \ --resolution 1.0CODEX 数据把--platform换成codexCosMx 换成cosmx其余参数不变。如果你的坐标单位是像素而不是 µm需要先除以像素物理尺寸再传入否则 200 这个阈值没有意义。4. 验证请求与成功结果怎么确认聚类真的对上了脚本跑完不等于结果可信。我一般做三层验证缺一层都不敢往下做注释。第一层看日志数字。正常输出应该类似Loaded 85000 cells x 300 features、Graph: 85000 nodes, 4200000 edges、Clusters: 12。如果 edges 数量是 0说明距离阈值相对坐标单位太小如果 clusters 数量等于细胞数说明 resolution 太高或者图几乎是空的。这两个极端都要回去调参。第二层看 UMAP 图。打开生成的_umap.png健康的聚类结果应该是色块分明、边界清晰没有大量细胞混在同一个区域却分属不同颜色。如果 UMAP 上颜色完全随机打散通常是 PCA 之前没有做标准化或高变基因筛选导致降维空间没有结构。第三层看空间图。打开_spatial.png这是最关键的一步。空间转录组的聚类必须和切片上的解剖结构对应比如肿瘤区域、基质区域、免疫浸润带应该各自成块。如果空间图上颜色像撒胡椒面一样均匀分布说明邻域矩阵没有真正捕捉到空间关系大概率是坐标字段取错了比如把像素坐标当成了 µm 坐标。验证通过后用下面这段代码快速检查聚类标签的分布和保存状态import scanpy as sc adata sc.read(xenium_leiden.h5ad) print(adata.obs[leiden].value_counts()) print(adata.obs[[leiden]].head()) print(spatial in adata.obsm)value_counts()能看出每个亚群的细胞数如果某个亚群只有个位数细胞基本是噪声可以在注释阶段合并。obsm里应该包含spatial键这是sc.pl.spatial能画图的前提。如果缺这个键说明读入的 h5ad 本身没有空间坐标矩阵需要从原始数据重新导出。模型对话通道在这里的用法是把报错信息或异常分布截图贴进去让它帮你判断是参数问题还是数据问题。比如你看到 clusters 数量异常多可以直接问「Leiden resolution 1.0 在 8 万细胞上产生 60 个 cluster 正常吗」它会结合图规模和分辨率给出调参建议。这比自己翻文档快很多。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth这一节按真实报错逐条对照。我把接入阶段和运行阶段的问题分开列方便你定位。401 Unauthorized 是最常见的接入错误。表现是 curl 或脚本请求直接返回 401原因通常是 Key 没生效、Key 有多余空格、或者 Base URL 写成了带路径的形式。检查顺序先确认TAOTOKEN_API_KEY环境变量在当前 shell 里能echo出来再确认 Base URL 是https://taotoken.net/api而不是/api/v1或带 UTM 的地址。如果用的是 settings.json注意 JSON 里不能有注释和尾逗号否则解析失败会退化成空 Key。local proxy failed 通常出现在 CLI 工具或 MCP 场景。这个报错的意思是本地转发层没起来不是远端服务的问题。排查动作确认没有其他进程占用同一端口确认配置文件里的 Base URL 和 Key 三件套完整确认工具版本支持当前配置格式。如果你在 Cline 或 MCP 里看到这个错把 MCP server 的 env 段单独拎出来用 curl 手动请求一次能通就说明是工具侧配置问题。reading choices 报错一般出现在解析模型返回时。表现是脚本拿到响应但解析失败日志里出现reading choices或undefined is not an object。原因是返回结构不是标准的 OpenAI 格式或者请求被中间层拦截返回了 HTML。解决方式是先打印原始响应体确认choices字段存在。如果返回的是错误页回去检查 Base URL 是否被改写。OAuth 相关报错出现在用账号体系登录的工具里。如果你用的是 API Key 模式不应该触发 OAuth 流程。看到 OAuth 报错说明工具被配置成了账号登录模式需要切回 Key 模式把三件套填进对应字段。Claude Code 场景下确认ANTHROPIC_API_KEY和ANTHROPIC_BASE_URL同时存在Codex 场景下确认auth.json里api_key字段非空。运行阶段的错误还有两类。一是KeyError: x_centroid说明 platform 参数和实际数据不匹配对照第 3 节的COORD_FIELDS字典改。二是ValueError: 邻域为空说明距离阈值小于最近邻距离把--distance_threshold调大或者检查坐标单位。这两类错误都不会消耗模型额度属于纯本地问题改完直接重跑即可。6. 亚群注释与后续分析把 Leiden 标签变成生物学结论聚类只是中间产物真正要交付的是亚群注释。拿到adata.obs[leiden]之后我通常做三件事算每个 cluster 的标记基因或标记蛋白、对照空间位置做区域命名、把注释结果写回 obs 供下游使用。标记基因计算用 scanpy 一行搞定sc.tl.rank_genes_groups(adata, leiden, methodwilcoxon) sc.pl.rank_genes_groups(adata, n_genes10, shareyFalse, showFalse) plt.savefig(marker_genes.png, dpi150, bbox_inchestight)CODEX 数据没有基因表达改成对蛋白通道做同样的组间比较把adata.X换成蛋白强度矩阵即可。CosMx 如果同时有转录本和蛋白可以分别算一遍再交叉验证。区域命名要结合空间图。比如某个 cluster 在空间上集中在肿瘤巢内部标记基因又是上皮来源就命名为 tumor core另一个 cluster 分布在肿瘤边缘且高表达免疫检查点就命名为 immune margin。这一步没有固定公式靠标记加空间位置双重证据。注释写回 obs 的格式建议用字符串而不是数字cluster_map {0: tumor_core, 1: stroma, 2: immune_margin} adata.obs[cell_type] adata.obs[leiden].map(cluster_map).astype(category) adata.write(xenium_annotated.h5ad)后续做细胞通讯或邻域富集时直接按cell_type分组比用数字标签可读性高得多。如果你要长期迭代这套流程把脚本和参数配置一起放进版本管理每次调参记录 resolution 和 distance_threshold 的组合方便回溯哪个参数组合产出了最合理的亚群结构。Coding Plan 通道适合这种长期迭代场景把脚本骨架和参数说明交给它让它帮你生成参数扫描的批量任务比手动改命令行高效。最后提醒一点三类平台的坐标单位一定要在脚本开头统一。Xenium 默认 µmCODEX 常见像素CosMx 两种都有。单位不统一200 µm 这个阈值在三个数据集上就是三个不同的物理尺度聚类结果没法横向比较。把单位换算写成脚本里的一个函数比每次手动改数字可靠。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →