CT 真实几何架构模拟器 · Native Edition(CT6)
当前版本 v1.0 —— 变更见
CHANGELOG.md。
双源宽体 CT 的几何 / 数据 / 重建数字孪生:6 种 CT 架构(含同步辐射仿真模式)× LEAP-CT 物理重建 × 半导体探测器响应(Fermi)× 严格多材料谱分解, 并内置面向 AI Agent 的控制桥(MCP),让 Agent 能直接读写几何主参数、 切换架构、跑重建、取截图。
不是"画个好看的示意图",而是从几何恒等式一路接到真实重建算子的研发台:
左边栏改一个 alpha,扇形角、最小间距、螺旋螺距上限、探测器阵列整数化匹配、
剂量指数、时间分辨率会同时重算;点一下 FBP,走的是 LEAP-CT 的锥束/螺旋正投影与反投影。
- 程序入口:
src/simulate_ct6.py(界面标题:双源 CT 真实几何模拟器 · Native Edition) - Windows 产物:
DSW_CT.exe—— 取自 GitHub Actions 的DSW_CT_Windowsartifact - Agent 入口:
src/ct6_mcp_server.py(MCP stdio)+src/ct6_bridge.py(应用内 TCP 控制桥)
以下截图均由程序自身渲染(构造真实
MainWindow后调用win.grab(), 与 Agent 控制桥的shot能力同源),拍的是当前版本界面,不是早期 PyQt5 原型。
主界面 —— 左栏「系统几何参数 / 扫描方案 / 计算结果」(含 Bowtie 真正视野与边缘剂量比),
右栏 System Geometry 与 FOV Impact Analysis 双 3D 视口:
| FBP 重建流程(LEAP 四联图) | 光子计数架构 · Fermi 探测器响应 |
|---|---|
![]() |
![]() |
静态多源 24 源环形阵列 —— 架构切到 static_multi 时,3D 视口中可见整圈源环:
- 五种 CT 架构统一建模:双源、单源宽体、双层能谱、光子计数、静态多源阵列
- 同步辐射仿真模式:以单元光子计数架构改造,把「PCCT 能否逼近同步辐射」这条 技术线从定性讨论变成可计算的整链(源亮度 → 焦点半影 → 孔径 MTF → 电荷共享 / K 荧光 → VMI / 相衬 → 剂量与信息量)
- 几何内核:扇形角 / 最小间距 / 180° 弧差安全校验、探测器阵列整数化自动匹配、 Bowtie 滤过器、非对称准直、扫描协议闭环(剂量 / 时间分辨率 / 螺距上限)
- 真实重建:LEAP-CT 1.26 —— 扇束 FBP、锥束 FBP、螺旋(360LI / 180LI / 180MF 插值)、 模块束(静态阵列)+ SART / ASDPOCS 迭代、短扫描 Parker 加权
- 探测器物理:CZT / TlBr 等半导体响应的深度加权 CCE、能量箱匹配、双层叠加
- 谱分解:逐能量正投影 + 非线性牛顿法,输出基材料分解与有效 μ
- Agent 原生:应用内 TCP 控制桥 + MCP stdio 服务器,Agent 可脚本化一切
| 架构 key | 源数 | 能量维 | 旋转 | 说明 |
|---|---|---|---|---|
dual_source |
2 | 1 | ✅ | 双源,两套系统按 alpha 夹角同时采样 |
single_wide |
1 | 1 | ✅ | 单源宽体,靠大锥角覆盖 |
dual_layer |
1 | 2 | ✅ | 双层探测器能谱(2 层) |
pcct |
1 | 8 | ✅ | 光子计数(8 能量箱),配合 Fermi 响应 |
static_multi |
24 | 1 | ❌ | 静态多源环形阵列,每个源一个视角 |
synchrotron |
1 | 可配(默认 8) | ✅ | 同步辐射仿真模式,以单元光子计数架构改造 |
架构不只是"标签":它直接决定采样几何。旋转架构按 views_per_rot × n_src 计视角数;
静态架构不旋转,180° 对应的是一半源数(static_src_180 = round(n_src/2)),
并额外给出 static_src_short / static_src_used / static_span_deg / static_ok_180
用于判断短扫描可用性。
均为当前程序自身渲染的
SYSTEM GEOMETRY视口实拍(win.grab())。 左上角是实时参数浮层,其中System Load为机架离心力 —— 静态架构不旋转,故为0 N。
| 双源 Dual-Source(两套系统 α=95°) | 单源宽体 Single-Source Wide-Body |
|---|---|
![]() |
![]() |
| 双层能谱 Dual-Layer(2 层) | 光子计数 Photon-Counting(8 能量箱) |
|---|---|
![]() |
![]() |
静态多源 Stationary 24-Source —— 不旋转,24 个源/探测器模块均布整圈:
以单元光子计数架构改造的第 6 种架构(arch='synchrotron'),把
「无像素约束 PCD + 多次过采样能否逼近同步辐射 CT」这条技术线从定性讨论
变成可复算的整链模型。实现在 src/ct_synchrotron.py,
纯 numpy、无 Qt 依赖。
源能谱 S(E) ─▶ 焦点模糊 ─▶ 样品 μ(x,y,E) ─▶ 像素孔径 ─▶ 电荷云/串扰 ─▶ 计数分 bin
亮度 B MTF_f 投影 p(θ,t) MTF_ap MTF_ch NPS(E)
| 组 | 参数 |
|---|---|
| 源 | 类型(波荡器 / 弯铁 / 液态金属射流靶 / 微焦点 / 纳米焦点 / 旋转阳极,各带亮度档与相干性)、焦点尺寸 f |
| 几何 | 自有 sync_sod_mm / sync_odd_mm(µCT 几何 ≠ 机架几何),M = (SOD+ODD)/SOD;默认 100/300 → M = 4。留空为 0 时回退到临床的 RA / FDD−RA |
| 探测器 | 材料(CdTe / CZT / Si / GaAs,各带 K 边与 Kα)、像素 p、电荷云 σ_c、整形时间 τ、阵列尺寸 |
| 采集 | 子像素过采样帧数 K、能量箱数与阈值、标称能量、入射通量 φ |
| 环节 | 公式 / 判据 |
|---|---|
| 采样与过采样 | f₀ = 1/p(孔径零点)、f_N(K) = K/(2p);K ≥ 2 即把可恢复频带填满到 f₀,K > 2 无新增信息 |
| MTF 级联 | MTF_sys = MTF_focus(圆盘 2J₁(x)/x) · MTF_aperture(|sinc(πpν)|) · MTF_charge(高斯),输出 10% / 50% 截止 |
| 几何与最优放大 | p_eff = p/M、半影 b = f(M−1)/M、r(M) = √(f²(M−1)² + p²)/M、M* = 1+(p/f)²、r* = pf/√(p²+f²) |
| 电荷共享 | 能谱可用判据 p ≳ 2σ_c;低于阈值时给出能谱保持度的经验衰减 |
| K 荧光逃逸 | λ_K(CdTe/CZT 249 µm、Si 15 µm、GaAs 40 µm),以像素为单位给出污染范围 |
| 脉冲堆积 | 计数率对照 1/τ,给出堆积率与是否可忽略 |
| 谱与 VMI | bin 阈值/中心/宽度;VMI 权重由光电 + Klein–Nishina 两基最小二乘解出,并回传残差 |
| 相衬 | Paganin 单距离相位恢复核参数 δ/β · λR/4π;源无相干性时明确警告收益按 0 计 |
| 剂量与信息量 | 剂量幂律 N³(体素)~N⁴(角度同步加密)、源亮度差、Swank 因子、半 Nyquist 处 DQE/NEQ、香农容量 |
报告频点说明:DQE/NEQ 取半 Nyquist。有效 Nyquist 恰好落在
f₀ = 1/p上, 那里MTF_ap为首零点恒等于 0,在该处报 DQE 会得到 0 而无意义。
模块的数值与交接手册逐条对齐(_ci/verify_sync.py,24/24 通过):
| 判据来源 | 内容 | 结果 |
|---|---|---|
| 手册 §4.3 表 | M*、r* 五行(f = 10/5/5/1/0.5 µm × p = 55/55/200/55/55 µm) |
全部精确复现 |
| 手册 §5 | f₀、f_N(1/2/4)、K=2 饱和 |
全部一致 |
| 手册 §5 | 剂量幂律 N = 2/4/10 → ×8/×64/×1000 | 全部一致 |
| 手册 §4.6 | Cd Te K 荧光逃逸长度 | 一致 |
| 手册 §4.5 | p ≳ 2σ_c 能谱失效阈值 |
一致 |
| 手册 §5 手算 | 默认配置(f = 10 µm、p = 55 µm、M = 4)→ p/M = 13.75、b = 7.5、r ≈ 15.7 µm |
精确复现 |
ct_synchrotron.py 输出的是模型计算值,不是实测标定值。交接手册 §1.2 明确指出
原技术线只有可行性分析、没有仿真器实现,且 MTF / NPS / 剂量曲线原为"示意构造";
本模块把那些示意曲线换成了可复算的解析模型,但仍不等于实测,
引用时请按手册 §6.2 的口径注明来源。
前往 Releases,
下载 DSW_CT_Windows_v1.0.zip,解压后直接运行 DSW_CT.exe(_internal 需与它同级)。
若想拿最新的开发版构建,也可以走
Actions → windows-build
→ 最新一次成功 run → Artifacts → DSW_CT_Windows。
产物已内含 CUDA 运行时,且构建流程带冻结环境自检(真的 import 全部模块并构造一次 LEAP 引擎),所以不会出现"构建绿了但一跑就崩"的包。
:: 需要 Python 3.11
pip install -r requirements.txt
run.bat或
python src\simulate_ct6.pyrun.bat 会把 src/、vendor/leapct、vendor/xraylib、vendor/cuda 一起加进 PYTHONPATH。
src/
simulate_ct6.py 主程序(PySide6 + PyCt6 界面,3285 行)
ct_geometry.py 几何内核:5 架构 / 阵列整数化匹配 / Bowtie / 协议闭环
ct_leap.py LEAP-CT 引擎:扇束 / 锥束 / 模块束+迭代 / 螺旋 / 短扫描
ct_helical.py 螺旋 z 插值(360LI / 180LI / 180MF)
ct_index.py sinogram ↔ 探测器单元 ↔ 几何 解析索引 + z 重排
ct_fermi.py Fermi 半导体响应桥接(深度加权 CCE / 能量箱 / 双层)
ct_spectral.py 严格多材料谱分解(逐能量正投影 + 非线性牛顿)
ct_scene.py 3D 场景与伪影可视化
ct6_bridge.py 应用内 TCP 控制桥(Agent 入口)
ct6_mcp_server.py MCP stdio 服务器(5 个工具)
frozen_entry.py PyInstaller 打包入口 + 冻结环境自检
legacy/ 早期 PyQt5 版本(保留参考,不再维护)
scripts/
build_windows.ps1 Windows 打包脚本(workflow 只负责调用它)
collect_cuda.py 从 nvidia wheel 收集 CUDA 运行时 DLL
make_icon.py 由 assets/LOGO.jpg 生成 assets/logo.ico
assets/ logo / 图标 / 启动图
docs/ 界面截图
Fermi/ 半导体探测器响应模拟子项目(ct_fermi 依赖其 src/)
vendor/leapct/ LEAP-CT 1.26(leapctype.py + libleapct.dll 18MB)
vendor/xraylib/ xraylib 4.3.0(xraylib.py + 两个 pyd,29MB)
vendor/cuda/ 仓库只带 cudart64_12.dll,其余由构建脚本补齐
物理上通道数与排数必须是整数,但由弧长/覆盖反推往往得到小数。内核的做法是 先算出理想值,再取整,再用取整结果反解真实像素间距,保证几何自洽:
n_ch_ideal = arc / pixel_xy n_ch = round(n_ch_ideal)
n_rows = round(Z_coverage / pixel_z)
col_pitch = arc / n_ch # 反解,替代名义 pixel_xy
row_pitch_iso = Z_coverage / n_rows
row_pitch_det = row_pitch_iso × FDD / RA # 投影到探测器平面
array_total = n_ch × n_rows
默认参数(alpha=95°、RA=RB=600、FDD=1100、Z=160、pixel_z=0.625)下:
n_ch = 825 col_pitch = 1.1461 mm array_residual_px = (0.169, 0.0)
n_rows = 256 row_pitch_iso = 0.625 mm array_ok = True
array_total = 211200
array_residual_px 是取整残差(单位:像素),array_ok 是"整数化后仍满足视场要求"的判据。
也可用 n_ch_set 手动钉死通道数(n_ch_manual)。
alpha / RA / RB / FDD / SFOV_A / SFOV_B 一改,下列量全部重算:
扇形角 A/B、最小间距 min_dist(安全阈值 150 mm)、180° 弧差 arc_diff(阈值 120°)、
is_safe / is_arc_ok、几何效率 beta_A/beta_B、kappa、探测器高度 H_det_A/B、
螺距上限(双源/单源分别一个)、ctdi_rel 剂量指数、t_res_arch_ms 时间分辨率、
slices / views_total / data_cells 等。
ct_leap.py 把 LEAP-CT 1.26 的 C++ 算子接成 Python 侧的统一接口:
FanGeometry直接采用内核算出的n_ch / n_rows / col_pitch / row_pitch_det / array_total- 锥束:
set_conebeam(...)+ 曲面探测器;螺旋用set_helicalPitch() - 静态阵列:
set_modularbeam(...)(模块束)→ 解析重建或 SART / ASDPOCS - 短扫描:< 360° 时走手工 FBP 链(
preRampFiltering → rampFilterProjection → postRampFiltering → weightedBackproject)并叠 Parker 加权
⚠️ helicalPitch的单位是 mm/弧度(LEAP 约定),不是"每圈进给比"。 代码内已按pitch × Z_cov / (2π)换算;这是最容易静默出错的一处。
Fermi/ 是一个独立的半导体探测器响应模拟器(CZT / TlBr / CdTe / Si / GaAs …)。
ct_fermi.py 负责桥接,向主程序提供:
- 材料参数与载流子输运剖面(
get_transport_profile()) - 深度加权 CCE(电荷收集效率)与权重场
- 能量箱响应矩阵 / 源谱 / 箱权重 → 光子计数与双层架构的分箱展开
- 电场剖面(
get_electric_field_profile())
Fermi 的 src/ 内部使用顶层导入(from src.material import ...),因此
ct_fermi 需要把 Fermi/ 与 Fermi/src/ 都加进 sys.path —— 打包时也必须把
Fermi/ 作为真实目录带上,不能只丢进 PYZ。
ct_spectral.py 做严格多材料分解:
- μ 表优先用 xraylib;水等材料改用元素加权构造(pip wheel 常缺化合物表,
CS_Total_CP('Water', E)会直接报错) per_energy_sinograms逐能量正投影,bin_from_energy_maps按箱聚合decompose(线性化)与decompose_newton(逐射线 2 参数牛顿,显式 2×2 正规方程, 回传残差)- 谱与响应在能量网格上做单元积分(
spectrum_on_grid/response_on_grid)
精度演进(碘浓度回收,真值 10 mg/mL 量级):
| 方法 | 相对误差 |
|---|---|
| 线性化分解 | 57.5% |
| 非线性牛顿 | 10.4%(残差 4.9e-07) |
| 牛顿 + 逐像素 Lᵢ/L_w 比值标定 | 6.1% |
flowchart LR
A["AI Agent<br/>(Claude / DSH / …)"] -->|"MCP · stdio · JSON-RPC 2.0"| B["ct6_mcp_server.py"]
B -->|"TCP · 换行分隔 JSON"| C["ct6_bridge<br/>应用内 TCP 服务"]
C -->|"QObject 队列信号<br/>(跨线程安全)"| D["PySide6 主线程 / GUI"]
D --- E["ct_geometry<br/>几何内核"]
D --- F["ct_leap<br/>LEAP-CT"]
D --- G["ct_fermi<br/>ct_spectral"]
程序启动时把监听端口写进发现文件 ct6_bridge.json,Agent 侧读它即可连上 ——
所以 Agent 不需要知道端口号,只要程序在跑。
| 工具 | 作用 |
|---|---|
ct6_call(op, params) |
通用调用,op ∈ {ping, list, get, set, arch, scan, fbp, fermi, shot, view} |
ct6_set_geometry(**params) |
写几何/扫描方案参数(alpha, RA, RB, FDD, SFOV_A/B, Z_coverage, rotation_time, sampling_rate, pixel_xy, pixel_z, n_ch_set, bowtie_sfov, bowtie_edge, pitch, scan_length, slice_thickness, slice_interval, shots_per_source, nch_auto_sw, asymmetric_cb, scan_mode) |
ct6_switch_arch(arch) |
切换架构:dual_source | single_wide | dual_layer | pcct | static_multi |
ct6_run_fbp(settle_ms) |
执行 FBP 重建并等待落定 |
ct6_screenshot() |
回传窗口截图(MCP image 内容) |
list 列出全部可写输入变量,get 额外回传上百项派生结果(数量随架构与页签可见性变化)。
写变量没有白名单——包括 alpha / RA / RB / FDD 这些几何主参数,Agent 都能直接改。
{
"mcpServers": {
"ct6": {
"command": "D:\\python\\envs\\mar\\python.exe",
"args": ["I:\\dsw\\digital_ct_simulate\\src\\ct6_mcp_server.py"]
}
}
}前提:仿真程序已启动(否则工具会返回"未连接:仿真程序未运行或发现文件缺失")。
echo {"jsonrpc":"2.0","id":1,"method":"tools/list"} | python src\ct6_mcp_server.pyCI 只做三件事:装依赖 → 调 scripts/build_windows.ps1 → 上传产物。
所有构建参数都在仓库脚本里(不是 workflow 里),改构建不用动 workflow。
:: 本地复现同一套构建 + 自检
pwsh -File scripts\build_windows.ps1脚本会依次:
scripts/collect_cuda.py—— 从 nvidia wheel 收集 CUDA 运行时到vendor/cuda/scripts/make_icon.py—— 由assets/LOGO.jpg现生成assets/logo.ico- PyInstaller
--onedir打包(含--icon、--copy-metadata、--add-data vendor/*) - 冻结环境自检:在产物内部 import 全部关键模块并构造一次 LEAP 引擎,失败即构建失败
本仓库的
.ps1必须保存为 UTF-8 with BOM:Windows PowerShell 5.1 会把无 BOM 文件按 ANSI(GBK) 解码,中文注释会变成乱码并抛ParserError。
- Python 3.11(
libleapct/xraylib的扩展都是cp311) pip install -r requirements.txt- LEAP-CT / xraylib 不走 pip:已在
vendor/内附带,启动脚本会加进sys.path - CUDA:
libleapct.dll是 CUDA 版。按 PE 导入表实测,它直接依赖cufft64_11.dll(后者又依赖nvJitLink_120_0.dll/nvrtc64_120_0.dll) 与显卡驱动自带的nvcuda.dll;并不需要 cublas / cusparse。 这些库体积过大(cufft 单个约 274 MB,超过 GitHub 单文件 100 MB 限制), 故仓库只带一个cudart64_12.dll:- 源码运行:安装 CUDA 12 运行时并加入
PATH,或在程序中切set_GPU(-1)走 CPU - 打包:
scripts/collect_cuda.py会从 nvidia wheel 自动取齐,本机无需装 CUDA
- 源码运行:安装 CUDA 12 运行时并加入
| 症状 | 原因 / 处理 |
|---|---|
ModuleNotFoundError: No module named 'PyQt5' |
你拿到的是旧原型产物。本程序的界面是 PySide6 + PyCt6;PyQt5 版本已移到 src/legacy/ 且不再构建 |
| 程序能启动,但显示 "LEAP-CT 不可用" | 打包时漏了 --copy-metadata imageio --copy-metadata lazy_loader。imageio/__init__.py 在 import 期读自己的 dist-info 取版本号,PyInstaller 默认不打 .dist-info,于是 import leapctype 抛 PackageNotFoundError |
| exe 图标是 PyInstaller 的默认图标 | 打包时没传 --icon。见 scripts/make_icon.py |
CI:ParserError: Missing expression after unary operator '--' |
在 GitHub Windows runner 的 run:(默认 PowerShell)里用了 cmd 的 ^ 续行符。参数应写成数组或整行 |
CI:NativeCommandError 紧跟 PyInstaller 第一行 INFO |
PowerShell 5.1 在 $ErrorActionPreference='Stop' 下会把原生命令的 stderr 当终止错误。见脚本里的 Invoke-Native |
本地跑 .ps1:Unexpected token '}' |
.ps1 丢了 UTF-8 BOM,被按 GBK 解码 |
import leapctype 失败但文件都在 |
检查 vendor/leapct 是否作为真实目录打进了产物(leapctype 靠 __file__ 定位 libleapct.dll,不能只在 PYZ 里) |
| Fermi 相关功能静默失效 | Fermi/ 需作为真实目录随产物发布;ct_fermi 要把 Fermi/ 与 Fermi/src/ 加进 sys.path |
- 螺旋
helicalPitch为 mm/rad(LEAP 约定),代码内已按每圈进 mm / 2π换算 - 谱分解的碘浓度误差约 6%(逐像素比值标定后);主要残差来自 FBP 域边缘伪影
- 静态多源提供"等效旋转锥束"与"模块束 + SART"两条路径;xraylib 化合物表缺失, 改用元素加权构造 μ
src/legacy/的 PyQt5 版本仅作历史参考,不再构建、不再修复
本项目自有代码以 MIT 发布,Copyright (c) 2026 hg3992260。
但本程序分发了若干第三方二进制,因此还需履行它们的许可义务,完整声明见
THIRD_PARTY_NOTICES.md:
| 组件 | 许可 | 关键义务 |
|---|---|---|
LEAP-CT 1.26(vendor/leapct/) |
MIT | 附版权与许可声明 |
xraylib 4.3.0(vendor/xraylib/) |
BSD-3-Clause | 附声明;不得以作者名义背书 |
| Qt / PySide6 | LGPL-3.0 | 附声明;允许替换 / 重新链接 —— 本项目用 --onedir 打包以满足 |
| CUDA 运行时 | NVIDIA EULA | 构建时从官方 wheel 取,仓库不含该二进制(超过 GitHub 单文件 100 MB 限制) |
| 其余 Python 依赖 | MIT / BSD / PSF 系 | 附各自声明 |
⚠️ 因此不要把打包方式改成--onefile:--onedir让 Qt 的*.dll/*.pyd保持为_internal/下的独立文件,接收者可直接替换 —— 这是 LGPL-3.0 合规的关键。
打包产物内会一并附带 LICENSE、THIRD_PARTY_NOTICES.md 与 licenses/ 目录
(各依赖自带的许可原文,由 scripts/collect_licenses.py 从已安装分发的元数据收集,
不靠手抄,保证与实际打包版本对应)。
- LEAP-CT(LLNL,MIT)—— 锥束 / 螺旋 / 模块束投影与重建算子
- xraylib(BSD-3-Clause)—— 元素与化合物的 X 射线衰减截面
- PyCt6(MIT)—— 主题化 PySide6 组件库
- Qt for Python (PySide6)(LGPL-3.0)—— 界面与 OpenGL 视口运行时








