
1. 从一张 CODEX 切片说起为什么“邻居”比“细胞本身”更能说明问题如果你做过结直肠癌的免疫微环境分析大概率遇到过这种困惑单细胞测序告诉你 CD8 T 细胞不少CD68 巨噬细胞也很多但患者的预后差异依然很大。问题往往不在“有多少细胞”而在“这些细胞挨着谁、被谁包围”。CODEX 多重成像的价值就在这里——它把 40 多种蛋白标志物同时染在一张 FFPE 切片上给出每个细胞的空间坐标和身份标签让“细胞邻域Cellular Neighborhood, CN”这种更高层次的组织结构变得可计算。这篇顶刊工作研究的是结直肠癌CRC侵袭前沿的 iTME把细胞按标志物聚成 28 个细胞类型CT再用滑动窗口把空间上反复共现的 CT 组合定义成 CN然后比较不同患者组之间 CN 的组成、功能状态和通讯网络。核心观察是在 DII 组患者中肿瘤边界和 bulk 肿瘤里的 T 细胞与巨噬细胞邻域出现“耦合与碎片化”CN-1富含 T 细胞和 CN-4富含巨噬细胞之间可能存在免疫抑制性通讯而富含粒细胞的 CN 中某些 T 细胞亚群活性反而与阳性免疫相关。适合谁读做肿瘤免疫的研究者、想复现空间邻域分析的生信读者、以及手里有 CODEX/mIHC 数据但不知道从哪下手配置的人。下面我按“可复制配置骨架 公开数据验证动作”来写重点放在邻域定义和空间统计的工程实现上而不是复述论文结论。2. 前置准备TaoToken 接入与运行环境2.1 为什么这里会用到 TaoTokenCODEX 邻域分析本身是本地 Python/R 计算但整个流程里有两类调用会频繁用到模型能力一是让模型帮你把论文方法段落翻译成可执行的参数配置比如窗口半径、最小细胞数、聚类分辨率怎么定二是在排障时把报错日志丢给模型做归因。TaoToken 提供统一的 API 入口兼容主流模型调用格式适合把这类“读方法、写配置、查报错”的零散需求集中处理。官网入口https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址https://taotoken.net/api2.2 拿 Key 与最小验证先到控制台创建 API Key建议单独建一个项目 Key方便后面按用量排查。控制台https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewriteAPI Keys 管理https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite拿到 Key 后先做一次最小请求确认网络和鉴权都通curl https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: claude-sonnet-4-20250514, messages: [ {role: user, content: 把CODEX邻域分析的窗口半径选择原则用三句话讲清楚} ] }返回里有choices[0].message.content就说明通了。这一步别跳过后面配置里模型名写错、Key 权限不对都会在这里先暴露。2.3 本地环境邻域分析主力是 Python建议 conda 建独立环境conda create -n codex_cn python3.10 -y conda activate codex_cn pip install numpy pandas scipy scikit-learn matplotlib seaborn anndata pip install squidpy spatialdatasquidpy负责空间邻域富集和邻接图spatialdata用来统一管理坐标和分割结果。如果你的分割结果来自 QuPath 或 HALO导出成 CSV含cell_id, x, y, ct_label就能直接读。3. 可复制配置邻域定义与空间统计骨架3.1 目录结构codex_cn/ ├── config.toml ├── data/ │ ├── cells.csv # cell_id,x,y,ct_label │ └── sample_meta.csv # sample_id,group ├── scripts/ │ ├── build_neighborhood.py │ └── contact_stats.py └── out/3.2 config.toml 示例这份配置对应论文里的“windows 策略”以每个细胞为中心取固定半径窗口统计窗口内 CT 组成再对组成向量聚类得到 CN。[input] cells data/cells.csv meta data/sample_meta.csv coord_unit micron # 若坐标是像素改成 pixel 并设 pixel_size [preprocess] pixel_size 0.377 # 像素转微米按你的扫描倍率改 min_cells_per_sample 500 # 少于这个数不参与邻域聚类 [neighborhood] method window # window | knn | delaunay radius_um 30.0 # 窗口半径论文量级在 20-50um 之间 min_cells_in_window 5 # 窗口内细胞太少则跳过 composition fraction # fraction | count [clustering] algo leiden resolution 0.8 # 邻域聚类分辨率0.5-1.2 之间调 n_neighbors 15 random_state 42 [contact] normalize true # 计算归一化接触似然比 permutation_n 1000 # 置换检验次数 fdr_alpha 0.05 [output] dir out save_adjacency true几个参数的经验值radius_um决定邻域尺度太小则每个窗口只有两三种细胞太大则所有窗口组成趋同、CN 分不开resolution控制 CN 数量论文里最终得到若干稳定 CN你可以从 0.5 开始逐步加到 CN 数量稳定为止。3.3 邻域构建脚本import tomllib import numpy as np import pandas as pd from scipy.spatial import cKDTree from sklearn.preprocessing import normalize import leidenalg as la import igraph as ig with open(config.toml, rb) as f: cfg tomllib.load(f) cells pd.read_csv(cfg[input][cells]) if cfg[input][coord_unit] pixel: cells[[x, y]] * cfg[preprocess][pixel_size] radius cfg[neighborhood][radius_um] min_in cfg[neighborhood][min_cells_in_window] ct_list sorted(cells[ct_label].unique()) ct_index {c: i for i, c in enumerate(ct_list)} coords cells[[x, y]].to_numpy() tree cKDTree(coords) comps, keep_ids [], [] for i, (x, y) in enumerate(coords): idx tree.query_ball_point([x, y], rradius) if len(idx) min_in: continue vec np.zeros(len(ct_list)) for j in idx: vec[ct_index[cells.iloc[j][ct_label]]] 1 if cfg[neighborhood][composition] fraction: vec vec / vec.sum() comps.append(vec) keep_ids.append(cells.iloc[i][cell_id]) X normalize(np.array(comps)) np.save(out/neighborhood_composition.npy, X) pd.Series(keep_ids).to_csv(out/neighborhood_cells.csv, indexFalse, header[cell_id])3.4 邻域聚类from sklearn.neighbors import kneighbors_graph n_neighbors cfg[clustering][n_neighbors] res cfg[clustering][resolution] A kneighbors_graph(X, n_neighborsn_neighbors, modeconnectivity) g ig.Graph.Adjacency((A 0).todense().tolist()) part la.find_partition( g, la.RBConfigurationVertexPartition, resolution_parameterres, seedcfg[clustering][random_state] ) cn_labels np.array(part.membership) np.save(out/cn_labels.npy, cn_labels) print(CN 数量:, len(set(cn_labels)))跑完先看 CN 数量是否落在合理区间论文里是若干个稳定 CN不是几十个碎片。如果 CN 数量过多把resolution降到 0.5过少则升到 1.0 以上。3.5 接触似然比论文里计算了成对细胞接触频率和归一化接触似然比用来判断哪些 CT 对是“同型偏好”还是“异型偏好”。def contact_likelihood(cells, radius, ct_list, perm_n1000): tree cKDTree(cells[[x, y]].to_numpy()) pairs tree.query_pairs(rradius, output_typendarray) obs pd.crosstab( cells.iloc[pairs[:, 0]][ct_label].values, cells.iloc[pairs[:, 1]][ct_label].values ).reindex(indexct_list, columnsct_list, fill_value0) freq obs.values / obs.values.sum() marg freq.sum(1, keepdimsTrue) freq.sum(0, keepdimsTrue) llr np.log2((freq 1e-9) / (marg 1e-9)) return pd.DataFrame(llr, indexct_list, columnsct_list) llr contact_likelihood(cells, radius, ct_list) llr.to_csv(out/contact_llr.csv)对角线为正说明同型接触偏好非对角线为正说明这两类细胞倾向相邻。论文里最主要的成对接触是同型的你可以用这张表先验证自己的数据是否符合这个基线。4. 验证请求与成功结果4.1 用公开 CODEX 数据跑通公开 CODEX CRC 数据可以从 Zenodo 或论文补充材料下载通常包含cells.csv坐标 标签和一张或多张组织图像。把cells.csv放进data/改好config.toml里的路径依次执行python scripts/build_neighborhood.py python scripts/contact_stats.py成功时你会看到out/neighborhood_composition.npy每个保留细胞的 CT 组成向量out/cn_labels.npy每个细胞的 CN 标签out/contact_llr.csvCT 对接触似然比矩阵4.2 结果自检先看 CN 数量是否稳定再看 CN 组成是否符合生物学直觉。比如论文里 CN-2 主要由肿瘤细胞组成CN-6 含肿瘤细胞加 CD11c DC、CD68 巨噬细胞和 T 细胞亚群对应“肿瘤边界”。如果你的 CN 里出现一个几乎全是肿瘤细胞的簇和一个肿瘤免疫混合簇说明邻域尺度选得基本合理。再看接触似然比矩阵的对角线是否普遍为正。如果对角线大量为负说明半径太小、窗口内细胞太少或者 CT 标签本身有问题。4.3 用模型辅助解读把contact_llr.csv里几个显著的正负值贴给模型让它帮你判断哪些 CT 对可能对应免疫抑制或免疫激活程序curl https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: claude-sonnet-4-20250514, messages: [ {role: user, content: 以下是结直肠癌CODEX数据的CT接触似然比矩阵片段请判断哪些细胞对可能对应免疫抑制性通讯CD8_T vs CD68_Mac: 0.42; CD8_T vs CD4_T: 0.88; CD68_Mac vs Treg: 0.61} ] }模型对话入口https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite5. 本篇常见错排查5.1 坐标单位没统一最常见的问题分割软件导出的是像素坐标config.toml里却按微米设了radius_um 30结果窗口半径实际只有 30 像素窗口内细胞数普遍低于min_cells_in_window最后 CN 数量极少或直接报空。排查方法先算一下坐标的数值范围如果 x 最大值在几千到几万基本是像素如果只有几百可能是微米。改coord_unit和pixel_size后重跑。5.2 邻域聚类分辨率不当resolution太低会把所有窗口聚成一两个 CN太高会碎成几十个。判断标准CN 数量应该和论文量级接近且每个 CN 有明确的 CT 组成特征。如果 CN 数量超过 20 且多数 CN 只有几十个细胞降分辨率如果只有 2-3 个 CN 且组成混杂升分辨率。5.3 接触似然比全为负除了半径问题还可能是 CT 标签里混入了“未知”或“混合”群导致边际分布被拉偏。先把unknown和mixed类细胞过滤掉再算一次。另外置换检验次数permutation_n太低时 FDR 校正不稳定建议至少 1000 次。5.4 API 调用报 401 或 404401 通常是 Key 没带上或已失效检查Authorization头格式是否为Bearer key。404 多半是模型名写错先到模型列表页确认可用模型名。如果请求超时检查是否把https://taotoken.net/api误写成了带路径的地址。5.5 内存不够CODEX 切片动辄几十万细胞query_ball_point在半径较大时会返回大量索引内存容易爆。可以按样本分批处理每个样本单独构建邻域后再合并或者把radius_um先设小一点做冒烟测试确认流程通了再放大。6. 继续往下走从邻域到通讯网络跑通上面的骨架后下一步是把 CN 之间的功能状态相关性算出来推断 CN 间通讯网络。论文里观察到 CN-1富含 T 细胞和 CN-4富含巨噬细胞之间可能存在免疫抑制程序且这种通讯网络在患者组之间发生改变。你可以按同样的思路把每个 CN 内 T 细胞亚群的功能标志物表达均值算出来再做 CN 之间的相关性分析看哪些 CN 对在 DII 组里相关性显著增强。如果你要长期跑这类空间分析、写脚本、调参数建议用 Coding Plan 把模型调用额度固定下来避免每次排障都临时找 KeyCoding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewriteClaude Code 用户可以直接参考 Anthropic 兼容配置https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude-code-anthropicutm_campaignrewrite最后留一个实操建议邻域分析里最容易被忽略的不是聚类算法而是分割质量。如果细胞分割把两个相邻细胞合并成一个或者把一个大细胞切成两半坐标和标签都会偏后面所有统计都跟着偏。跑完 CN 后回头看一眼原始图像上 CN 的空间分布如果 CN 边界和组织形态对不上先回去修分割比调聚类参数有用得多。