DRUID 上手指南
从零跑通第一批 LA-ICP-MS 锆石 U-Pb 数据 —— 不需要任何编程经验
这份文档要解决的事:你手上有一台 iCAP 仪器导出的 Qtegra 数据,想知道它给出的年龄能不能信、怎么写进文章。
读完你会拿到:① 一个装好的环境;② 一条命令(或一次双击)跑完全流程;③ 知道先看哪三个数字判断数据可用;④ 出问题时知道该看哪一行报错。
一、三分钟最短路径
如果只想尽快看到"这东西在我机器上能跑",按下面六步走。每一步都写了「怎么算成功」,对不上就别往下走,直接翻 第八节。
- 确认有 Python
python --version看到Python 3.9以上即可(3.12 / 3.13 更好)。没有就装一个,见 第三节。 - 建一个隔离环境
python -m venv .venv只是为了让依赖不污染系统 Python。不建也能跑,但以后换版本会乱。 - 装五个依赖
.venv\Scripts\pip install numpy pandas matplotlib openpyxl xlrd就这五个。不需要 scipy、不需要任何 Web 框架,也不需要联网跑程序。 - 拿到代码 把仓库放到一个不含中文和空格的路径下,例如
D:\tools\DRUID路径里有空格时命令行要加引号;有中文时个别老工具会踩编码问题,能避就避。 - 跑一遍示例批次
python tests\run_all.py,再跑python tests\check_example_batch.py第二条会用自带的 85 个测点真算一遍,最后一行是✓ 全部与基线一致就说明环境完全就绪。 - 换成你自己的数据 按 第四节把数据摆成文件夹,然后双击
启动数据处理工具.bat(macOS 用启动数据处理工具.command)网页界面里选文件夹 → 看检查报告 → 开始处理 → 下载结果。
二、它做什么、你要做什么
DRUID 把仪器的原始计数率时间序列变成年龄。中间那七步(死时间、204Hg 干扰、 普通铅显著性检验与扣除、标样夹逼归一化、F(τ) 深度分馏校正、jackknife 不确定度)是它的全部工作, 也正是不要让下游工具重复做的事情。
| 角色 | 谁来做 | 产物 |
|---|---|---|
| 原始信号 → 带不确定度的年龄 | DRUID(本工具) | 结果.xlsx(六张表) + 结果.handoff.json(给程序读) |
| 在同一条剖面上挑出坪、判 MSWD | ADEPT(R 包) | 坪年龄 ± 2σ、MSWD、MSWD 概率 |
| 判断这批数据能不能用 | 你(看三个数字,见第六节) | 一句话结论 + 证据 |
装环境(Windows,从零开始)
python 换成 python3,.venv\Scripts\pip 换成 .venv/bin/pip;
② 最后不是双击 .bat,而是双击 启动数据处理工具.command(首次被系统拦下时:右键 → 打开 → 再点一次「打开」);
③ 不需要另配中文字体,会自动用 Mac 自带的苹方。
另外 DRUID.exe 是 Windows 专用的,拷到 Mac 上跑不了 —— 要在 Mac 上也要单文件程序,
只能在 Mac 本机重新冻结一次(冻结工具不支持跨系统打包)。
完整的对照见落地页 「苹果电脑上怎么用」,仓库根目录的 使用说明.txt 里也有。
目标:把 Python 和五个依赖装好,并用仓库自带的示例批次确认一切正常。全程只需要复制粘贴四条命令。
pip 有可能装到系统 Python 里去,以后排查会多一层干扰。3.1 没有 Python 怎么办
去 python.org 下载 Windows 安装包(3.9 以上任意版本,3.12 或 3.13 最省事),安装时务必勾上
「Add python.exe to PATH」——这是唯一一个装完必须检查的选项。装完新开一个命令行窗口再敲
python --version:旧窗口的 PATH 不会更新,这是最常见的"明明装了却说找不到"。
.venv 文件夹,从第 2 步重来即可 ——
它只包含依赖,不含你的数据,也不含这个仓库的代码。
3.2 依赖为什么只有五个
| 包 | 干什么用 |
|---|---|
| numpy | 数组与数值计算 |
| pandas | 序列表与结果表的读写 |
| matplotlib | 逐点深度剖面图(--plot 时才用到) |
| openpyxl | 写 .xlsx 结果表 |
| xlrd | 读仪器工作站导出的老式 .xls 序列表 |
没有 scipy,这是刻意的:年龄方程的不动点迭代、二分法、卡方上尾概率都是自己实现的。 少了这个依赖,就意味着数值行为不会被第三方库的版本更新悄悄改掉——这对要写进文章的软件很重要。 网页界面也只用 Python 标准库,不需要装任何 Web 框架、不需要 npm、没有构建步骤。
3.3 怎么确认装对了(两步)
:: 第一条:不需要 pytest 的自检,覆盖统计内核、输入边界、打包完整性
python tests\run_all.py
:: 第二条:拿仓库自带的示例批次真算一遍(约 1 分钟)
python tests\check_example_batch.py
第二条最后会打印 ✓ 全部与基线一致,并逐项列出标样 QC 与样品年龄分布。
这一步的价值在于:它把"环境装错"和"数据有问题"分开了。示例批次的数字是钉死的,只要一致,
就说明工具本身没坏,接下来出任何问题都在你的数据或参数上。
准备你自己的数据
规则只有三条,但每一条错了都会在处理前就被报出来(网页界面里会立刻提示,不用等跑完)。
4.1 样品名决定角色
| 样品名里出现 | 判定为 | 在流程里的作用 |
|---|---|---|
| 91500(默认主标) | 主标 | 算归一化因子,决定绝对年龄是否成立 |
| Ple / Plešovice(默认监控标样) | 监控标样 | 估外部重现性、做质控与二次校正 |
| SRM / NIST / 612 / 610 | 玻璃标样 | 不参与 U-Pb(给它定年没有意义) |
| 其余任何名字 | 未知样品 | 真正要定年的对象 |
--primary / --secondary 指定(网页界面上是输入框),
比如本实验室若用 Temora 做主标,就填 --primary Temora。可用标样清单见 druid/core/constants.py 里的标准物质库。
参考值来源也是可选项。同一支标样的推荐值有好几套来源(Horstwood et al. (2016) 表 S2 的 CA-ID-TIMS 值、 Wiedenbeck et al. (1995)、由标样年龄反算的自洽值、Isoclock2.0 等),用
--ref-preset 指定
(网页界面上是参考值来源下拉),默认 horstwood2016。
档与档之间是整体平移而不是重算:示例批次上未校正列最大差 0.83 Ma、带监控标样的最终列只差 0.031 Ma,
行数、深度结构与 MSWD 一律不变。
4.2 从仪器导出时的三个注意
- 导出的是 cps(计数率),不是原始计数。Qtegra 已经按驻留时间换算过了,DRUID 也按这个前提处理;混用会出现一个固定的比例错误。
- 每个测点单独一个 CSV,文件名与 LIST 里登记的字符串一一对应(不带扩展名)。
- 别手工改 CSV 的内容。表头前十几行是仪器元数据、中间夹着一行单位行,DRUID 靠自己找 "Time" 表头来解析,改过就未必认得。
跑自己的批次
两种用法选一种。日常用网页界面,要批量化或脚本化时用命令行。
5.1 命令行版本
:: 在仓库根目录执行(含 druid 子目录的那一层)
python -m druid.cli.reduce_batch ^
--dir "D:\data\EX2022A" ^
--out "D:\data\EX2022A_U-Pb结果.xlsx" ^
--plot
:: 看全部参数
python -m druid.cli.reduce_batch -h
换成自己的主标 / 监控标样时加 --primary 91500 --secondary Ple。
需要死时间校正时加 --deadtime-ns 14.7645——默认是 0(不校正),
这个值是拿实测死时间扫描标定出来的,请不要凭经验填(见 第九节的说明)。
5.2 结果文件放在哪
命令行由 --out 指定;网页界面默认写在批次目录旁边的输出位置,完成后在页面上给出下载链接。
另外界面里的运行日志(那个滚动的框)建议在报错时一起留档——它包含每个测点的处理进度与跳过原因。
看懂结果:先看这三个数
结果 Excel 有六张表(结果 / 标样QC / 深度剖面域 / 不分域年龄 / 剖面窗口 / 运行参数);
同一目录下还会多出一份 .handoff.json(见 6.3)。
先不要看年龄。先用下面三个数字判断这批数据到底能不能用——它们衡量的是"仪器的状态",不是"岩石的年龄"。
s68_1sig 与 s68_2sig 分别是 1σ 与 2σ ——
下游算 MSWD 必须用 1σ,误用 2σ 会把 MSWD 缩小到四分之一,于是"本该不自洽的数据看起来完美"。
② 摘要里的"样品年龄中位"只统计样品,不含标样;91500 约 1060 Ma、Ple 约 343 Ma 混进来会把中位数整体抬高。
本工具的 CLI 与网页界面都已按此处理,但你自己写脚本从结果表里取数时要注意筛选 类型 = 样品。
6.1 MSWD 怎么读
MSWD ≈ 1 该域用一个年龄就能解释
MSWD 远大于 1 要么真的混了多个年龄,要么 σ 被低估了(最常见:只算计数统计,没算外部重现性)
MSWD 远小于 1 σ 被高估了
MSWD_概率 该域只有单一真实年龄的前提下,MSWD 至少这么大的概率;小于约 0.05 是常规警戒线
"深度剖面域""不分域年龄"与"剖面窗口"三张表里的年龄都是反比方差加权平均(不是算术平均), MSWD 与它的卡方概率都在同一套算法下算,与 R 端 ADEPT 完全一致,两边数字可以直接并列对照。
6.2 「不分域年龄」表怎么读 —— 它对比的是什么
这张表回答一个很具体的问题:如果我不做任何分域,会得到一个什么年龄? 它把整个剥蚀段当作唯一一个域来算,用的窗口、F(τ) 校正与加权方式与「深度剖面域」表完全一样—— 只有"分不分域"这一个变量不同。所以两种算法的差额,恰好就是"分域"这一步带来的差别。
| 列 | 它是什么 |
|---|---|
| 年龄_Ma / s2_Ma | 整段窗口的加权平均年龄与 2σ。它不是整段 cps 积分 |
| MSWD / 相容上限 | 整段能不能用一个年龄解释。MSWD ≤ 相容上限才算能 |
| 判定 | 整段常数 或 整段非常数。只有前者可以直接用 |
| 漂移_Ma | 最后一个窗口减第一个窗口的年龄差。一眼看出剖面斜不斜 |
| 主域 / 主域年龄_Ma | 分域结果里窗口数最多的那个真年龄域 |
| Δ年龄_pct | (整段 − 主域) / 主域。分域这一步对年龄的影响:示例批次 33 个多域点里从 −16.2% 到 +36.6% |
| 整段积分年龄_Ma / Δ整段_pct | 结果表里另一种算法(整段 cps 积分)的值,用来提醒你别把两者混成一个数 |
还有一件容易搞混的事:"整段非常数"不等于"有两个年龄域"。 前者只是统计判定;要算一个真年龄域,流程还额外要求域之间年龄差达到 5% 以上("地质显著")。 示例批次里 15 个"分域后仍判均一"的测点中,有 10 个整段其实非常数(多半是没校正干净的倾斜)—— 这些点在「深度剖面域」表里根本没有行,只有这张表能把它们列出来。
6.3 同一个批次还有一份给程序读的文件
跑完一个批次,结果目录里除了 Excel 还会多出一个同名文件:
EX2022A_U-Pb结果.xlsx ← 给人看(六张表)
EX2022A_U-Pb结果.handoff.json ← 给程序看(质控结论 + 交接约定)
它把这一批数据的判断写成了机器能读的形式:26 条带稳定 key 的检查项、每条一个结论
(fail / warn / info / pass),以及"这批数据交给下游时会掉哪些测点、为什么"。
平时用不到它,可以不管;但如果你要写脚本批量汇总多个批次,或者以后接一个自动解释工具,
读这个文件比解析 Excel 稳得多——Excel 的列名是给人看的,随时可能微调,而这个文件里的 key 是对外契约。
不想要它就加 --no-json;想换路径用 --json 自定义名.json(两个参数互斥)。
接到 ADEPT 做坪年龄
把结果 Excel 直接喂给 ADEPT 即可:它读"剖面窗口"表,忽略同一工作簿里其它不含 Age68 的表。
但四个参数必须显式给,因为 ADEPT 不去猜数据的预处理状态——
adept("EX2022A_U-Pb结果.xlsx",
lower_ablation_time = 0, # DRUID 已经切好了自己的剥蚀窗口
upper_ablation_time = 1e6, # 上界放到最后一个窗口之后
smooth = "none", # 数据已做过 F(τ) 校正,剖面本就是平的
calibration_uncertainty = 0) # σ 里已含标样外部重现性
| 参数 | 为什么要显式给 |
|---|---|
| lower/upper_ablation_time | 一个 _1s 列无法说明剥蚀窗口在哪里结束。猜错会静默改变参与分段的数据点数,年龄直接跑偏。 |
| smooth = "none" | 剖面已经做过逐深度分馏校正;再平滑会把真实的年龄域边界一起抹平。 |
| calibration_uncertainty = 0 | σ 里已经含了标样外部重现性;再按默认乘 3% 就是把它算两遍。 |
出错了怎么办
先记住一条:报错信息里的最后三行通常就够定位。看下面这个表,找到你的现象。
| 现象 | 多半是什么 | 怎么办 |
|---|---|---|
| 双击 bat 一闪而过 | 窗口里打印了报错但没停住 | 改从命令行跑 python -m druid.cli.serve_ui,报错就会留在屏幕上;或者先跑 python tests\run_all.py 看环境是否就绪 |
| ModuleNotFoundError: No module named 'pandas' | 用的是系统 Python,不是装了依赖的那个 | 用 .venv\Scripts\python.exe 显式跑;或先执行第 3 步的 pip 命令 |
| 'python' 不是内部或外部命令 | 装 Python 时没勾 "Add python.exe to PATH",或者没重新开窗口 | 重装并勾上,然后新开命令行窗口再试 |
| pip 装不上 / 一直卡住 | 网络到 pypi 不稳 | 加国内镜像:pip install numpy pandas matplotlib openpyxl xlrd -i https://pypi.tuna.tsinghua.edu.cn/simple |
| KeyError: 缺少通道: [207] | 某个测点的 CSV 没导出该质量数 | 回仪器重新导出那个测点;报错里会写是哪个文件 |
| 找不到序列表 | 文件没与文件夹同名,或 _LIST 写成了别的 |
改名成 <文件夹名>_LIST.csv(大小写不敏感) |
| PermissionError: ...xlsx | 结果文件正被 Excel 打开着 | 关掉 Excel 再重跑 |
| 网页打不开 | 端口被占用(会自动顺延) | 以黑窗口里打印的地址为准;确认服务打印了"界面地址 http://127.0.0.1:…" |
| 处理到一半关了网页 | 不是故障 | 服务端任务会继续跑完并写盘;重开网页看不到那次日志,但结果文件已经在了 |
python tests\run_all.py(环境或代码坏没坏);
② 跑 python tests\check_example_batch.py(数值是否仍与基线一致);
③ 若是自己的数据,把批次里最小的一个 CSV 单独跑一次,缩小到具体测点。
这三步能把问题定位到"环境 / 工具 / 你的数据"三类中的一类,比逐行读代码快得多。
想改代码 / 长期维护
这个仓库是给人改的。分层只朝一个方向依赖,所以"改算法"和"改流程"各有一个明确去处。
core/constants.py 一次——
同一个衰变常数在两处各写一遍,改了一处忘了另一处不会报错,只会悄悄给出错误的年龄。9.1 改完必须做的三件事
1) python -m pyflakes druid\ :: 必须 0 项
2) python tests\run_all.py :: 必须全过
3) python tests\check_example_batch.py :: 与基线逐项一致
9.2 几条不要轻易碰的地方
| 位置 | 为什么 |
|---|---|
| core/constants.py | 全部物理常量与标准物质库的唯一出处 |
| --deadtime-ns 默认 0 | --deadtime-ns 默认 0 是刻意的:一旦默认开启,历史上所有未校正的结果就不再可比。要改默认值请先与人商量 |
| 204 显著性检验 | 204Pb 只有几~几十 cps,204Hg 本底可达数百 cps。不做显著性检验就扣普通铅 = 把噪声当铅扣掉,对 207Pb 是灾难性的 |
| 207Pb/206Pb 不做 F(τ) | 它是 Pb 同位素内部比值,不随坑深分馏;窗口级 R76 的计数统计已在 1% 量级(4 s 窗口 207Pb 净计数中位约 9×10³),套 F(τ) 只会注入噪声 |
| *.bat 文件 | 必须保持纯 ASCII + CRLF。cmd.exe 按代码页解析,中文注释会变乱码;而解释器路径里含中文用户名,所以含中文的路径只能在运行时用变量拼出来 |
附录:命令速查与名词表
10.1 命令速查
| 想干什么 | 命令 |
|---|---|
| 看版本 | python -c "import druid; print(druid.__version__)" |
| 跑一个批次 | python -m druid.cli.reduce_batch --dir "批次目录" --out "结果.xlsx" --plot |
| 看参数 | python -m druid.cli.reduce_batch -h |
| 开网页界面 | python -m druid.cli.serve_ui (或双击 启动数据处理工具.bat / .command) |
| 自检 | python tests\run_all.py |
| 数值回归 | python tests\check_example_batch.py |
| 静态检查 | python -m pyflakes druid\ |
| 注册为命令行工具 | pip install -e . → 之后可用 druid-reduce / druid-gui |
10.2 名词表
| 名词 | 一句话解释 |
|---|---|
| cps | counts per second,计数率。Qtegra 已经按驻留时间换算好,不是原始计数 |
| 剥蚀区间 | 激光真正在烧样品的那个时间段;两端各有几秒的启动爬升与 washout 拖尾必须切掉 |
| 气体空白 | 没开激光时测到的本底信号,用于扣除载气与样品池的贡献 |
| 死时间 | 探测器两次计数之间的最小分辨时间。高计数率下漏计会低估信号,需要校正 |
| 分馏 F(τ) | 随坑深变化的元素分馏(本项目 ²⁰⁶Pb/²³⁸U 从坑口到坑底漂移约 20%),F(τ) = 标样参考值 / 实测值,逐深度校正 |
| 归一化 | 用主标把仪器测到的比值为真值,F = 真值 / 实测值。它决定绝对年龄是否成立 |
| 普通铅 | 矿物形成时混入的非放射成因铅,用 204Pb 指示,需显著性检验后再扣 |
| jackknife | 刀切法:逐点剔除重算,得到 σ 与相关系数 ρ(画协和椭圆必需) |
| 协和度 | ²⁰⁶Pb/²³⁸U 年龄与 ²⁰⁷Pb/²⁰⁶Pb 年龄的一致程度(%)。接近 100% 说明未发生明显的铅丢失或铀富集 |
| 坪年龄 | 剖面上一段年龄彼此一致的区间的加权平均年龄;由 ADEPT 判定 |
| MSWD | 加权偏差平方和除以自由度:约 1 表示散布与各自不确定度相符;远大于 1 说明有额外散布没被解释 |
| 整段积分 | 把一个测点的全部计数一次性合成比值再算年龄(结果表的 年龄206_238)。与"逐窗口算年龄再加权平均"是两种不同算法,在有结构的测点上可以差 20% 以上 |
| 不分域年龄 | 把所有窗口当成一个域做加权平均。与分域结果用的是同一套算法,所以两级之差正好等于"分域"这一步的贡献 |
| 交接文件 | *.handoff.json。质控结论与接口约定的机读形式,给程序(及将来的自动解释工具)读 |
10.3 这份文档里的数字从哪来
第 六节的三个判据数值(−0.31% / +1.85% / 93.8%)与
6.2 的"48 个测点里 5 个整段相容、MSWD 中位 4.44",以及第 五节的
"85 个测点 → 样品 48 / 主标 21 / 监控 14 / 玻璃 2",全部来自仓库自带的示例批次
examples/EX2022A,由 python tests\check_example_batch.py 输出
(六张表的行数也一并钉着:结果 83 / 标样QC 2 / 深度剖面域 91 / 不分域年龄 48 / 剖面窗口 1677)。
它们的判据与容差写在 AGENTS.md 的基线表里,改动算法时那张表会一起被检查。