1. VS Code 里跑 MONAI 心脏分割,先把 Base URL 写对
费城儿童医院(CHOP)开源的儿童心脏建模平台最近在医学影像圈被反复提起:它基于 NVIDIA MONAI,把 CT、MRI 和超声转成儿童心脏的 3D 模型,让医生在术前研究解剖结构、评估器械适配,甚至在模型上尝试模拟不同支架的表现;每个产出的模型仍然要由心脏科或放射科医生审核签署。对工程师而言,真正值得抄的不是“AI 提速”这个结论,而是它把 MONAI 这条链路跑通了:数据读取、重采样、分割、后处理、导出、审核,一个环节都不能缺。
但如果你是在 VS Code 里用代码助手写这套流程,最先卡住的通常不是网络结构,而是助手本身连不上:Key 放在哪、Base URL 填什么、上下文怎么贴、报错怎么追问。所以这篇从一个很具体的地方开始——先到 TaoToken 官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=vscode_monai_intro 拿到 API Key,再把 Base URL 设为 https://taotoken.net/api,然后让 VS Code 里的代码助手围绕 MONAI 分割脚本干活。整篇文章的目标只有一个:给你一套能复现的配置 + 能跑通的 MONAI 分割代码 + 能自查的运行命令,而不是停在“这个新闻很厉害”。
先明确本文的工作假设,避免你照着抄错:
- 任务:把儿童心脏 CT/MRI 体数据(NIfTI 为例)分割成心脏结构掩膜,再导出成可供 3D 查看的格式。
- 工具:VS Code + 代码助手(Claude Code CLI、Codex CLI、CC Switch 切换器三类配置都给)。
- 模型侧:TaoToken 提供的统一入口,Base URL 固定为
https://taotoken.net/api,Key 使用YOUR_API_KEY占位。 - 边界:所有命令都在你本地终端执行,不涉及任何生产库直连;医学数据请遵循你所在机构的伦理与数据合规要求,本文代码只做工程演示。
2. 拿 Key 与 TaoToken 接入坐标:三处配置别写混
很多人第一次接第三方模型入口,失败原因就一个:把「网页端地址」和「API Base URL」当成同一个东西。这两者必须分开记。
- 官网入口(拿 Key、看模型、看套餐):https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=vscode_monai_key
- API Base URL(写进工具配置的):
https://taotoken.net/api - Key 占位符:
YOUR_API_KEY,替换成你在控制台创建的真实值。
创建 Key 的路径直接走这个 deep link,登录后在控制台里生成,复制出来只显示一次,建议先落到本地的密钥管理工具里,再写进配置文件:
- 创建 API Key:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=vscode_monai_apikey
这里有个容易被忽略的工程习惯:不要把 Key 直接硬编码进 Git 仓库里的脚本。VS Code 里写 MONAI 代码时,推荐用环境变量注入,脚本只读os.environ。这样你在本地能跑,在别人机器上换一个 Key 也不用改代码。
# macOS / Linux:写入当前 shell 会话(不要提交到仓库) export TAOTOKEN_API_KEY="YOUR_API_KEY" export TAOTOKEN_BASE_URL="https://taotoken.net/api"# Windows PowerShell $env:TAOTOKEN_API_KEY = "YOUR_API_KEY" $env:TAOTOKEN_BASE_URL = "https://taotoken.net/api"确认 Key 是否可用的最小验证,不是去跑模型,而是先用一条对话请求打穿链路。你可以直接在模型对话页做连通性确认,省掉本地 curl 的麻烦:
- 模型对话入口:https://taotoken.net/models/detail/chat?utm_source=taotoken_aicg_blog_end&utm_content=vscode_monai_chat
如果这一步能正常返回,说明 Key 没问题,接下来所有失败都可以归因到「工具配置」而不是「账号状态」。这个排查顺序能帮你省掉大量来回试错的时间。
3. Claude Code / Codex / CC Switch 三件套:VS Code 侧配置全量对照
这一节是全文最需要照抄的部分。请注意一个硬性区别:Claude Code 用ANTHROPIC_*环境变量或settings.json,Codex 用config.toml,两者的字段体系不通用。把ANTHROPIC_BASE_URL写进 Codex 的配置里,是新手最常踩的坑,一定不要混。
3.1 Claude Code:settings.json 与 ANTHROPIC_* 两种写法
Claude Code 在 VS Code 里通常以集成终端的形式使用,配置文件放在用户目录下的.claude/settings.json(项目级也可以放.claude/settings.json)。推荐写法如下:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "YOUR_API_KEY" } }如果你更习惯环境变量,等价的 shell 写法是:
export ANTHROPIC_BASE_URL="https://taotoken.net/api" export ANTHROPIC_AUTH_TOKEN="YOUR_API_KEY"两个注意点:
ANTHROPIC_AUTH_TOKEN填的是你的 TaoToken Key,不要保留YOUR_API_KEY字面量。- 模型 ID 以你在模型列表页看到的实际名称为准,不要凭记忆写。需要切换模型时,优先在会话内切换或用官方文档给的字段,不要自己造参数名。
完整的 Claude Code 接入说明在文档里,配置项含义和边界写得比博客清楚,建议对照看一遍:
- Claude Code 文档:https://taotoken.net/doc/ClaudeCodeAnthropic?utm_source=taotoken_aicg_blog_end&utm_content=vscode_monai_claudecode
3.2 Codex:config.toml 独立配置,别套 ANTHROPIC_*
Codex 走的是 TOML 配置,字段和 Claude Code 完全不同。典型的~/.codex/config.toml长这样:
model = "<在你的模型列表页选择的模型 ID>" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY"对应的环境变量在 shell 里设置:
export TAOTOKEN_API_KEY="YOUR_API_KEY"这里再次强调:Codex 配置里不要出现ANTHROPIC_*,Claude Code 配置里也不要出现model_providers。两套配置各管各的,混写的结果通常是工具静默回退到默认端点,然后你在 VS Code 里看到一堆莫名其妙的超时。
3.3 CC Switch 三件套:切供应商时到底在切什么
如果你用 CC Switch 这类切换器在多个供应商之间来回切,它本质在维护的是三件套:
- Base URL:
https://taotoken.net/api - API Key:
YOUR_API_KEY(建议从环境变量读,或由切换器自身的安全存储管理) - 默认模型:填你在模型列表页选定的模型 ID
有的实现里还会多一个 provider 名称字段,用来在 UI 上区分条目,但真正影响请求能不能打通的就是上面三项。切换器的价值在于:你不用每次手改settings.json,只要切 profile。代价是切换后一定要重启 VS Code 的集成终端,否则旧的环境变量还在进程里,你会以为切换没生效。
一个可操作的验证顺序:切完 profile → 新开一个终端 →echo $ANTHROPIC_BASE_URL确认输出是https://taotoken.net/api→ 再启动 Claude Code。这三步做完,90% 的「切了没反应」问题都会暴露出来。
4. MONAI 分割脚本:从 NIfTI 到心脏 3D 掩膜的完整链条
配置通了,接下来是正题。下面这份脚本是一个可运行的最小推理骨架,重点是展示 MONAI 的标准流程:LoadImaged → Spacingd → Orientationd → ScaleIntensityRanged → sliding_window_inference → Invertd → SaveImaged。你可以把它当成自己项目的起点,替换掉模型权重和类别数即可。
# monai_heart_infer.py import argparse from pathlib import Path import torch from monai.data import DataLoader, Dataset, decollate_batch from monai.inferers import sliding_window_inference from monai.networks.nets import UNet from monai.transforms import ( Activationsd, AsDiscreted, Compose, EnsureChannelFirstd, EnsureTyped, Invertd, LoadImaged, Orientationd, SaveImaged, ScaleIntensityRanged, Spacingd, ) from monai.utils import set_determinism def build_pre_transforms(pixdim=(1.0, 1.0, 1.0)): return Compose( [ LoadImaged(keys=["image"]), EnsureChannelFirstd(keys=["image"]), Orientationd(keys=["image"], axcodes="RAS"), Spacingd(keys=["image"], pixdim=pixdim, mode=("bilinear",)), ScaleIntensityRanged( keys=["image"], a_min=-200, a_max=800, b_min=0.0, b_max=1.0, clip=True, ), EnsureTyped(keys=["image"]), ] ) def build_post_transforms(pre_trans, out_dir): return Compose( [ Activationsd(keys="pred", sigmoid=True), AsDiscreted(keys="pred", threshold=0.5), Invertd( keys="pred", transform=pre_trans, orig_keys="image", nearest_interp=True, to_tensor=True, ), SaveImaged( keys="pred", output_dir=str(out_dir), output_postfix="seg", resample=False, separate_folder=False, ), ] ) def main(): parser = argparse.ArgumentParser() parser.add_argument("--input", required=True, help="输入 NIfTI 路径") parser.add_argument("--weights", required=True, help="分割权重 .pt 路径") parser.add_argument("--out", default="outputs", help="输出目录") parser.add_argument("--roi", type=int, default=96, help="滑窗边长") parser.add_argument("--overlap", type=float, default=0.25) parser.add_argument("--cpu", action="store_true") args = parser.parse_args() set_determinism(seed=42) device = torch.device("cpu" if args.cpu or not torch.cuda.is_available() else "cuda") out_dir = Path(args.out) out_dir.mkdir(parents=True, exist_ok=True) pre_trans = build_pre_transforms() files = [{"image": args.input}] ds = Dataset(data=files, transform=pre_trans) loader = DataLoader(ds, batch_size=1, num_workers=0) model = UNet( spatial_dims=3, in_channels=1, out_channels=1, channels=(16, 32, 64, 128, 256), strides=(2, 2, 2, 2), num_res_units=2, ).to(device) model.load_state_dict(torch.load(args.weights, map_location=device)) model.eval() post_trans = build_post_transforms(pre_trans, out_dir) with torch.no_grad(): for batch in loader: images = batch["image"].to(device) outputs = sliding_window_inference( inputs=images, roi_size=(args.roi, args.roi, args.roi), sw_batch_size=2, predictor=model, overlap=args.overlap, ) batch["pred"] = outputs for item in decollate_batch(batch): post_trans(item) print(f"[done] segmentation saved to: {out_dir.resolve()}") if __name__ == "__main__": main()这份脚本有意做了三个工程化的选择,值得你在自己项目里延续:
第一,空间标准化放在预处理里。心脏 CT/MRI 的层厚和体素间距差异很大,不统一pixdim,滑窗推理出来的结果在 Z 轴上会明显失真。Orientationd(axcodes="RAS")则保证左右方向一致,否则你导出的 3D 模型可能出现镜像,医生一眼就能看出不对。
第二,归一化范围显式写死。a_min=-200 / a_max=800是 CT 心血管窗附近的常见范围,但真正该用多少取决于你的数据来源,做 MRI 时这套窗宽窗位就不适用了,要换成基于百分位的强度归一化。这点必须按你自己的数据集调,不要直接抄。
第三,后处理用Invertd把结果映射回原始空间。很多教程只做推理不做逆变换,导致掩膜留在了重采样后的网格上,拿去和原始影像叠加时对不上。Invertd配合SaveImaged才能保证输出的掩膜和输入影像在同一坐标系里。
5. 让代码助手看懂 MONAI 报错:上下文投喂与常见坑位
配置对了、脚本有了,接下来决定效率的是你怎么用代码助手。MONAI 的报错信息经常很长,直接整段贴给助手,回复质量往往一般。更有效的做法是给结构化上下文,让助手知道"我在哪一步、输入是什么形状、期望是什么"。
推荐的提问模板(可直接在 VS Code 的助手会话里用):
环境:MONAI 版本、PyTorch 版本、CUDA 是否可用 任务:3D 心脏分割推理,输入 NIfTI,单通道 代码:粘贴 build_pre_transforms / sliding_window_inference 调用处 报错:完整 traceback 最后 15 行 期望:输出与输入同形状的掩膜并保存为 NIfTI 约束:不要改动我已有的 transform 顺序,只指出问题行和修法几个在 MONAI 分割任务里高频出现的坑,先自查一遍再问助手,效率更高:
| 现象 | 常见原因 | 处理方向 |
|---|---|---|
| 推理时显存爆掉 | roi_size或sw_batch_size过大 | 先把 roi 降到 64/96,sw_batch_size 设 1 或 2 |
| 输出掩膜与影像错位 | 缺少Invertd或 spacing 不一致 | 补逆变换,检查pixdim是否与训练时一致 |
| 左右方向反了 | 未做Orientationd统一 | 固定 axcodes,训练和推理保持一致 |
| 输出的 seg 文件是空的 | 阈值不合适或 sigmoid 未加 | 检查Activationsd与AsDiscreted阈值 |
| DataLoader 卡住不动 | num_workers过大 + 共享内存不足 | 推理阶段先设num_workers=0 |
用助手改代码时,有个小技巧:让它只输出 diff 级别的修改,而不是重写整个文件。MONAI 的 transform 链是有顺序依赖的,整文件重写很容易把EnsureChannelFirstd的位置挪错,导致后面全部报形状错误。你可以直接说"只给我需要替换的那几行,并说明为什么"。
如果你在修 bug 的过程中需要一个干净的对话环境来对比两种实现,可以用模型对话页做 A/B 对照,把两种写法的输出贴进去让它判断差异:
- 模型对话:https://taotoken.net/models/detail/chat?utm_source=taotoken_aicg_blog_end&utm_content=vscode_monai_debug
6. 运行命令与验收清单:一次可复现的端到端演练
把上面的脚本落地,完整流程如下。建议全部在 VS Code 的集成终端里执行,方便边跑边改。
# 1. 建环境 python -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate # 2. 装依赖(MONAI + PyTorch + 医学影像 IO) pip install --upgrade pip pip install "monai[nibabel]" torch # 3. 注入 TaoToken 配置 export TAOTOKEN_API_KEY="YOUR_API_KEY" export TAOTOKEN_BASE_URL="https://taotoken.net/api" # 4. 确认 CUDA 是否可用(不可用就走 --cpu) python -c "import torch; print(torch.cuda.is_available())" # 5. 跑分割推理 python monai_heart_infer.py \ --input data/case_001.nii.gz \ --weights weights/heart_seg.pt \ --out outputs \ --roi 96 \ --overlap 0.25跑完之后,验收不要只看"有没有报错",按下面这份清单逐条确认:
outputs/下生成了*_seg.nii.gz,文件大小不为 0。- 用查看器打开掩膜,与原始影像叠加后位置对齐,没有整体偏移。
- 左右方向正确,心脏结构没有镜像。
- 掩膜在 Z 轴连续,没有因层厚导致的分层断裂。
- 记录本次运行的
roi、overlap、spacing 参数,方便下次复现。 - 如果显存紧张,回看第 5 节的排查表,先降
roi再动模型。
这套流程跑通之后,你手里就有了一个可迭代的基线。后面无论是换更强的权重、加多类别输出(把out_channels从 1 改成 N,并把AsDiscreted改成 argmax 语义),还是接入批量推理,都只是在这个骨架上改参数,不用推倒重来。
7. 从分割掩膜到可审核的 3D 模型:别跳过医生签署环节
CHOP 那套平台的完整闭环里,有一个工程上很容易被忽略、但临床上不能少的步骤:每个模型都要由心脏科或放射科医生审核签署。这给我们的启示不是"加个按钮",而是流程设计上必须给人工复核留位置。
技术侧可以这样落地:
- 分割结果保存为 NIfTI,同时导出 STL/OBJ 供 3D 查看,两份文件用同一个 case ID 关联。
- 在输出目录旁生成一份
case_001.json,记录模型权重版本、spacing、roi、时间戳、执行命令,作为可追溯的元数据。 - 把"AI 生成"和"医生已审核"做成两个独立状态字段,不要用同一个字段表示,避免审核状态被自动流程覆盖。
- 如果要做支架模拟这类下游应用,输入必须是已签署的掩膜,而不是刚推理出来的原始输出。
# 生成可追溯元数据的最小示例 import json import datetime from pathlib import Path meta = { "case_id": "case_001", "source_image": "data/case_001.nii.gz", "weights": "weights/heart_seg.pt", "spacing": [1.0, 1.0, 1.0], "roi_size": 96, "overlap": 0.25, "generated_at": datetime.datetime.now().isoformat(timespec="seconds"), "status": "ai_generated", "review_status": "pending", } Path("outputs/case_001.json").write_text( json.dumps(meta, ensure_ascii=False, indent=2), encoding="utf-8" )这份元数据的价值在于:当医生复核发现某处分割不合理时,你能快速定位是权重问题、预处理问题,还是原始影像质量本身就有限。没有元数据的 AI 输出,在临床复核环节基本等于不可用。
另外要注意,医学影像数据涉及患者隐私,本文所有示例都应替换成公开数据集或脱敏数据后再运行;不要用真实患者数据去测试第三方服务,也不要把影像内容贴进任何对话请求里。工具配置和代码逻辑可以让助手帮你改,数据本身不要外传。
8. 下一步:把对话、Coding Plan 和 Key 管理串成一条线
如果你已经把第 6 节的命令跑通,说明 VS Code 侧的接入和 MONAI 的推理骨架都到位了。接下来通常是三种需求之一,对应三条不同的路径:
需求一:先把对话链路用起来,做代码评审和报错分析。适合还在搭环境阶段的人,先用对话验证 Key 和 Base URL 是否可用,再决定要不要动 CLI 配置。
- 模型对话:https://taotoken.net/models/detail/chat?utm_source=taotoken_aicg_blog_end&utm_content=vscode_monai_cta_chat
需求二:把代码助手当成日常开发工具,长期在 VS Code 里用。这种情况更适合直接上 Coding Plan,把 Claude Code / Codex 这类 CLI 工具接进工作流,日常改 MONAI 脚本、写 transform、查 traceback 都在终端里完成。
- Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=vscode_monai_cta_plan
需求三:Key 要分环境管理,团队里多人共用。建议每个环境(本地 / 测试 / 共享)单独建 Key,出问题可以单独吊销,不要所有人共用一个。
- 创建 API Key:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=vscode_monai_cta_key
最后再回到开头那份配置,确认三件事没有写错:ANTHROPIC_BASE_URL或base_url指向https://taotoken.net/api,Key 使用真实值而不是YOUR_API_KEY,Claude Code 与 Codex 的配置字段没有互相串用。Claude Code 的完整字段说明在这里,配置改完记得新开终端再验证:
- Claude Code 文档:https://taotoken.net/doc/ClaudeCodeAnthropic?utm_source=taotoken_aicg_blog_end&utm_content=vscode_monai_cta_doc
把「拿 Key → 配 Base URL → 跑通 MONAI 分割 → 保留元数据 → 医生审核」这条链子走完一遍,你得到的不只是一个能跑的脚本,而是一套能被人接手、能被复核、能持续迭代的工程流程。