alitrack

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

dicom-reader architecture

● ● ●

怎么做到的

ducatlink 是 Tegmentum 开源的一个 DuckDB 扩展引擎。它在 DuckDB 里内嵌了 wasmtime 运行时,加载 WASM 组件当扩展用。

我写的 dicom-reader 组件只做了三件事:

  1. 01load():向 ducklink 注册 read_dicom(path) 表函数,声明输出 18 列
  2. 02解析:读文件 → InMemDicomObject::open_file() 解析 DICOM Part 10 格式
  3. 03输出:把 PatientName、Modality、Rows、Columns 等标签塞进列批量缓冲区

ducklink 管剩下的——函数注册进 DuckDB catalog、列数据跨 WASM 边界搬运、NULL 传播语义。

● ● ●

输出了哪些字段

DICOM output columns

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 可能就够了。