DuckDB读不了DICOM?给它写一个WASM扩展,202行Rust
DuckDB 什么都能查——CSV、Parquet、JSON、甚至直接读 PostgreSQL。但医学影像领域最通用的 DICOM 格式,它不认识。
我试了一下:用 ducklink 把 dicom-rs 塞进 DuckDB,202 行 Rust 搞定。
FROM ducklink_load('dicom_reader');
SELECT PatientName, Modality, Rows, Columns FROM read_dicom('/scans/ct.dcm');
dicom-reader architecture
● ● ●
怎么做到的
ducatlink 是 Tegmentum 开源的一个 DuckDB 扩展引擎。它在 DuckDB 里内嵌了 wasmtime 运行时,加载 WASM 组件当扩展用。
我写的 dicom-reader 组件只做了三件事:
- 01load():向 ducklink 注册
read_dicom(path)表函数,声明输出 18 列 - 02解析:读文件 →
InMemDicomObject::open_file()解析 DICOM Part 10 格式 - 03输出:把 PatientName、Modality、Rows、Columns 等标签塞进列批量缓冲区
ducklink 管剩下的——函数注册进 DuckDB catalog、列数据跨 WASM 边界搬运、NULL 传播语义。
● ● ●
输出了哪些字段
DICOM output columns
核心字段:患者姓名/ID/性别/出生日期、检查日期、设备类型(CT/MR/XA)、图像尺寸、像素间距、层厚——影像科最常用的元数据一次性全拿出来。
● ● ●
和原生扩展比
| 原生 C++/Rust 扩展 | ducklink WASM 组件 | |
|---|---|---|
| 编译 | 每个平台单独编 | 一次编译,全平台通跑 |
| 分发给用户 | 用户自己编译或等 CI | 下载 `.wasm` 就能用 |
| 文件体积 | ~几 MB(链接 DuckDB C API) | ~1.6 MB(dicom-rs 的锅) |
| 依赖 | DuckDB 开发环境 | 只需 `cargo-component` |
WASM 方案的真正好处不是省编译时间——是省分发成本。用户不需要装 Rust 工具链,不需要等对应平台的 CI 构建,下载一个 .wasm 文件就能读 DICOM。
● ● ●
为什么用 dicom-rs
Rust 生态里解析 DICOM 最成熟的选择。它完整实现了 DICOM Part 10 文件格式和 Part 5 数据编码——显式/隐式 VR、小端/大端字节序、序列化嵌套标签(SQ)、私有标签。我试过手写解析器,100 行能跑,但遇到私有标签和嵌套序列就崩。dicom-rs 把这些边界情况都处理好了。
代价是编译出来 1.6 MB——dicom-rs 的字典表和类型系统不小。如果可以接受裁剪不支持的类型(比如去掉私有标签、去掉 SQ),体积能压到几百 KB。
● ● ●
编译
cargo install cargo-component cargo component build --target wasm32-wasip2 --release # target/wasm32-wasip2/release/dicom_reader_component.wasm
然后:
DUCKLINK_COMPONENTS=dicom=dicom_reader_component.wasm \
duckdb -unsigned -c "
LOAD 'ducklink.duckdb_extension';
SELECT PatientName, Modality FROM read_dicom('scan.dcm');
"
● ● ●
代码
GitHub(MIT):
- dicom-reader: github.com/alitrack/dicom-reader-component
- 配套的 sql-eval 组件: github.com/alitrack/sql-eval-component
- ducklink 引擎: github.com/tegmentum/ducklink-extension
如果 DuckDB 读不了你的文件格式,202 行 Rust + WASM 可能就够了。