OpenMed 实验室检验表格提取实战:从 PDF/扫描件/CSV 到结构化行与 PHI 安全脱敏
【免费下载链接】openmedLocal-first healthcare AI: clinical NER & HIPAA PII de-identification that runs 100% on-device. 2,200+ medical models, 21 languages, Apple MLX + Python, no cloud, no patient data leaving your network. Apache-2.0项目地址: https://gitcode.com/GitHub_Trending/ope/openmed
实验室检验结果通常以表格形式呈现:一列检验名称、一列数值、一列单位、一列参考范围,再加上一个异常标志(H / L / Crit)。要把这些内容用于下游系统,必须先从 PDF、扫描件或电子表格中把这张网格恢复成干净的行——再用LOINC编码检验项目、用UCUM规范化单位、对异常值打标。OpenMed 将这一摄入环节封装为extracting-lab-tables技能:在设备端用openmed.multimodal完成 OCR/解析,对嵌入的 PHI 进行脱敏,并输出结构化行,作为后续parsing-lab-values(LOINC/UCUM 编码与低/正常/高/危急标志判定)的上游输入。读完本文,你将掌握 OpenMed 表格式实验室报告摄入的完整链路:OCR 引擎选择与语言映射、bbox 网格重建、CSV/TSV 列分类与脱敏、以及输出行到 FHIR Observation 的交接方式。
什么时候使用这个技能
- 你拿到的是扫描图 / 照片 / PDF 页面形式的检验报告,需要把整个检验面板变成行数据而不是像素。
- 来源是CSV/TSV 导出文件,需要自动分类各列(哪一列是数值、单位、范围、标志),并脱敏 PHI 列。
- 你需要机器可读的行数据,用于后续 LOINC 映射和构造 FHIR
Observation/DiagnosticReport。
该技能定位为摄入(intake)步骤:在设备端完成表格的 OCR/解析、对嵌入 PHI 脱敏,输出结构化行。它与 OpenMed 的临床辅助功能配对使用——LOINC/UCUM 编码与高低/危急标志判定属于下游步骤(见 skills/parsing-lab-values/SKILL.md)。
表格的五要素:提取的目标网格
无论来源是图像还是分隔文本,最终要恢复的都是同一张逻辑网格:
| 要素 | 示例 | 说明 |
|---|---|---|
| 检验名称(test name) | Hemoglobin | 后续映射为 LOINC 代码 |
| 数值(value) | 9.1 | 数值本身,与单位绑定 |
| 单位(unit) | g/dL | 用 UCUM 规范化 |
| 参考范围(reference range) | 12.0-15.5 | 供下游异常标志判定 |
| 异常标志(flag) | L / H / Crit | 可来自实验室,也可由下游推导 |
extracting-lab-tables的目标就是把这张网格恢复成{test, value, unit, ref_range, flag}结构,先完成 PHI 脱敏,再把行交给 LOINC/UCUM 编码与异常标志判定。
OpenMed 提供的摄入原语
openmed.multimodal模块内置了全部摄入原语,并且在导入时不加载任何重型依赖——OCR 后端是懒加载的(模块头部注释明确说明了这一设计,见 openmed/multimodal/ocr.py)。核心 API 包括:
openmed.multimodal.ocr.ocr(image, engine=...)→ 返回OcrResult:其.words是OcrWord(text, bbox, confidence, page),.text是所有词拼接后的字符串。OcrResult.to_document()把每个词(连同像素级 bbox)桥接成ExtractedDocument,从而让检测到的 PHI 能投影回源图位置。read_table(...)→ 返回TableView(含headers、rows、delimiter、has_header、columns),面向分隔文本;classify_columns(...)为每一列打标签;redact_table(...)→ 返回带 PHI 安全manifest的RedactedTable。
以上公开 API 全部从 openmed/multimodal/init.py 统一导出,可直接from openmed.multimodal import read_table, classify_columns, redact_table。
OCR 引擎:懒加载、可插拔、自动选择
ocr()支持多后端,实现在 openmed/multimodal/ocr.py:
- Tesseract:
pip install "openmed[multimodal]"+ 系统二进制(如apt-get install tesseract-ocr或brew install tesseract)。 - PaddleOCR:
pip install "openmed[ocr-paddle]"。 - 此外还有 docTR(
pip install "openmed[multimodal]")与 EasyOCR 两个适配器。
引擎采用注册表机制(_ENGINES字典 +register_ocr_engine),未显式指定时按_AUTO_ORDER = ("doctr", "tesseract", "easyocr", "paddleocr")自动选择第一个已安装的后端(openmed/multimodal/ocr.py)。如果没有任何后端,ocr()会抛出清晰的MissingDependencyError,错误信息中带安装指引。
语言方面,ocr(..., languages=[...])使用 OpenMed PII 语言码(en, fr, de, it, es, nl, hi, te, pt, ar, ja, tr共 12 种),由tesseract_language()/paddle_language()/easyocr_languages()分别映射为各后端标识符(如 Tesseract 的eng+fra、PaddleOCR 的japan),见 openmed/multimodal/ocr.py。注意:语言数据本身不随包分发,Tesseract 需要对应的traineddata文件,PaddleOCR/EasyOCR 首次使用时下载识别模型。
快速开始
完整的最小示例(对应 skills/extracting-lab-tables/SKILL.md 的 Quick start,并补充了参数细节):
from openmed.multimodal.ocr import ocr from openmed.multimodal import read_table, classify_columns, redact_table # A) 扫描 / 图像形式的检验报告 -> 带像素坐标的词。 result = ocr("cbc_report.png") # OcrResult for w in result.words[:5]: print(repr(w.text), w.bbox, round(w.confidence, 2), "p", w.page) doc = result.to_document() # ExtractedDocument;保留 bbox # 后续可将 doc 交给 openmed.deidentify / redact_document, # 让检测到的 PHI 通过 SourceSpan 映射回图像像素位置。 # B) 分隔文本形式的检验导出(CSV/TSV)-> 分类 + PHI 脱敏后的行。 csv_text = ( "PatientName,Test,Value,Unit,RefRange,Flag\n" "Jane Roe,Hemoglobin,9.1,g/dL,12.0-15.5,L\n" "Jane Roe,Glucose,148,mg/dL,70-99,H\n" ) view = read_table(csv_text) # TableView:自动嗅探分隔符与表头 view = classify_columns(view) # 标记 PHI 列 vs 数据列 redacted = redact_table(view) # RedactedTable:PatientName 被脱敏 for row in redacted.rows: print(row) # 姓名列被掩码;检验数据保持不变 for col in redacted.manifest: # PHI 安全的逐列审计 manifest print(col["column_name"], col["assigned_class"], col["action"])对于 OCR 出的(图像)表格,网格需要你根据词框自行重建(见下一节)——OCR 输出的是带坐标的词,而不是一张分隔好的表。
工作流:从原始文档到结构化行
- 判断来源类型。CSV/TSV →
read_table。图像/扫描件 →ocr()。PDF/DOCX不可直接解析(会抛出UnsupportedDocumentError,见 openmed/multimodal/exceptions.py);先把 PDF 页面栅格化为图片,或抽取其文本层,再做 OCR。 - 带坐标地 OCR。
ocr()返回携带bbox和page的OcrWord。务必保留坐标框——它们既能用于把词聚类成行/列,也能把 PHI 脱敏结果投影回像素。 - 重建网格。按词的
bbox的y坐标聚类成行、按x坐标聚类成列。表头行命名各列,再把表体单元格对齐到这些 x 区间。OcrWord.confidence用于标出置信度低的单元格,供人工复核。 - 识别检验列。把表头映射为角色:检验名称、数值、单位、参考范围、标志。对于分隔文本输入,
classify_columns会标记 PHI 列(姓名/MRN/出生日期),redact_table据此掩码。 - 脱敏嵌入的 PHI。患者姓名/MRN 常出现在表头或首列。用
redact_table脱敏这些列,并在行离开设备前,把自由文本单元格交给openmed.deidentify处理。 - 输出结构化行
{test, value, unit, ref_range, flag},交给 LOINC/UCUM 编码与parsing-lab-values。
从词框重建表格网格(OCR 路径)
OCR 路径与分隔文本路径的本质区别在于:图像 OCR 没有天然的行/列边界,OcrResult只给你一个平面词列表。重建的典型策略是:
- 行聚类:取每个
OcrWord.bbox的中心 y 坐标,按模板相关的容差归并为行带;跨行单元格、折行的检验名称会破坏简单的 y 分桶,需要按模板调整容差。 - 列对齐:用表头行词的 x 坐标建立列区间(x bands),再把表体词按 x 归属到对应列。
- 置信度门控:对每个单元格取所属词的
confidence,低分单元格路由到人工复核(见「边缘情况」)。
如果你需要更结构化的阅读顺序,OcrResult.to_layout()可以把带坐标的词交给布局模块重建版式感知的阅读顺序(openmed/multimodal/ocr.py);to_document(preserve_lines=True)还能在元数据携带原生行标识时,精确保留引擎输出的换行边界(openmed/multimodal/ocr.py)。
CSV/TSV 路径的列分类与脱敏机制
分隔文本路径把列作为策略单元,实现集中在 openmed/multimodal/tabular_csv.py。read_table会先做分隔符嗅探(逗号/制表符,可用delimiter覆盖)、表头嗅探(csv.Sniffer+ 内置表头启发式,可用has_header覆盖),再逐列分类。
分类逻辑:表头 + 值采样双通道
每一列的分类决策(ColumnDecision)按以下优先级产生(openmed/multimodal/tabular_csv.py):
- 表头匹配:内置
_HEADER_LABELS映射覆盖了大量常见表头(name/fullname/patient→ PERSON,mrn/patientid→ ID_NUM,dob/dateofbirth→ DATE_OF_BIRTH,ssn→ SSN,email/phone/address等),表头键会先做去非字母数字、小写归一化。你可以通过header_heuristics参数补充自定义表头 -> 规范标签映射。 - 值采样回退:表头匹配不到时,对每列采样最多
sample_size(默认 50)个非空单元格值,用正则匹配(SSN\d{3}-\d{2}-\d{4}、邮箱、电话、MRN、日期、人名模式,见 openmed/multimodal/tabular_csv.py)判定标签,命中比例需达到阈值(人名 0.8,其余 0.6)。 - 默认安全:都匹配不到时归为
SAFE;若表头属于注释类键(note/comment/free text 等)则按free_text_redact处理。
分类结果与六种动作
列最终落入三类之一(DIRECT_ID/QUASI_ID/SAFE,来自openmed.core.labels的标签元数据),并映射为六种可执行动作之一(openmed/multimodal/tabular_csv.py):
| 动作 | 常量 | 适用 | 行为 |
|---|---|---|---|
| 丢弃 | ACTION_DROP | 直接标识符 | 整列从输出中移除 |
| 哈希 | ACTION_HASH | ID_NUM / ACCOUNT_NUMBER / USERNAME | 单元格被哈希,保留记录关联 |
| 掩码 | ACTION_MASK | DIRECT_ID / QUASI_ID 默认 | 单元格内容被掩码 |
| 日期平移 | ACTION_DATE_SHIFT | DATE 列 | 准标识符日期确定性平移 |
| 自由文本脱敏 | ACTION_FREE_TEXT_REDACT | note/comment 等 | 调用openmed.core.pii.deidentify(或你传入的text_redactor) |
| 保留 | ACTION_KEEP | SAFE 数据列 | 原样保留 |
默认动作规则在_default_action()中定义(openmed/multimodal/tabular_csv.py):日期列平移、ID 类列哈希、直接/准标识符掩码、其余保留。所有规则都可通过action_overrides覆盖(按表头/规范标签/类别三个维度匹配,非法动作值会抛ValueError)。
日期平移使用确定性 per-record 偏移:derive_date_shift_days以记录字段 + 行号 + 种子做 SHA-256,得出-365 到 364 天的非零偏移(固定偏移可通过date_shift_days传入,0 会被拒绝),keep_year=True时保留原年份,lang用于本地化日期解析(openmed/multimodal/tabular_csv.py)。
manifest:PHI 安全的逐列审计
redact_table产出的RedactedTable.manifest是逐列的审计记录,每项含column_index、column_name、assigned_class、canonical_label、policy_label、action、detection_source(header_name/value_sample/default)、confidence、sampled_values、row_count、row_count_affected。设计上只含列元数据与计数,绝不包含原始单元格值(openmed/multimodal/tabular_csv.py),可直接用于合规审计。单元测试覆盖了 CSV/TSV 嗅探、DOB 列默认掩码、动作覆盖丢列、以及redact_document分发到 CSV 处理器并携带 manifest 元数据等路径,见 tests/unit/multimodal/test_tabular_csv.py。
另外,.csv/.tsv后缀文件已通过register_handler注册到多模态分发器(redact_document),因此它们可以与 PDF、图像等格式走同一套redact_document入口,处理器要求requires_multimodal=False(openmed/multimodal/tabular_csv.py)。
边缘情况与坑
- PDF/DOCX 抛
UnsupportedDocumentError。多模态分发器没有 PDF/DOCX 处理器——先把 PDF 页栅格化成 PNG(或抽取文本层)再调ocr()。图像格式(PNG/JPG/TIFF 等)与 CSV/TSV 均受支持。 - OCR 返回的是词,不是表。必须从
bbox几何重建行列。多行单元格、折行的检验名称、合并表头单元格都会破坏简单的 x/y 分桶——按模板调聚类容差。 - 参考范围容易被拆坏。"12.0-15.5"、"<5"、"70 - 99"、en/em 破折号必须作为一个单元格经受住 OCR 与分词。不要让空格或误读的破折号把范围拆碎——下游
parse_reference_range期望它保持完整。 - 单位属于数值,不属于范围。把 "9.1 g/dL" 与范围 "12.0-15.5" 放在不同字段;数值的单位必须与范围单位一致,否则异常标志会判错(标志辅助函数是单位无关的,不会帮你换算)。
- 低置信度单元格。用
OcrWord.confidence做门控;实验室表格里一个 0.4 置信度的数值是患者安全风险——路由到人工复核,不要静默接受。 - PHI 藏在表格里。患者姓名、MRN、出生日期、登记号常占据表头或首列。分类并脱敏它们,绝不要记录原始表格。
- 引擎不可用。未安装任何后端时
ocr()抛清晰的MissingDependencyError——按 extras 安装 Tesseract 或 PaddleOCR 即可。
与 OpenMed 上下游的交接
- 交给
parsing-lab-values(skills/parsing-lab-values/SKILL.md):把解析好的数值 +ref_range(+ 实验室显式标志)传给openmed.clinical.parse_reference_range与derive_abnormal_flag,得到结构化的 low/normal/high/critical 信号。注意标志解析支持闭合区间("135-145"、"0.5 - 1.2"、"135 to 145"、en/em 破折号)和单侧边界("<5"、">=10"),且显式实验室标志(H/L/C/HH)优先于推导结果。 - 交给
mapping-loinc(skills/mapping-loinc/SKILL.md):把每个检验名称编码为 LOINC 代码,用 UCUM 规范化单位。OpenMed 输出行数据,术语绑定在进程外完成。 - 交给 FHIR(skills/exporting-to-fhir/SKILL.md):每行变成一条
Observation(code=LOINC,valueQuantity带 UCUMunit,referenceRange,interpretation),分组在DiagnosticReport之下。 - 先脱敏再导出(skills/deidentifying-clinical-text/SKILL.md):用
openmed.deidentify处理自由文本单元格。OCR 词携带像素框,脱敏结果可映射回图像。 - 以上一切均在设备端运行——扫描件或行数据在未脱敏前不会离开当前进程。
标准与参考
- LOINC:通用实验室观察代码体系,用于检验名称编码。
- UCUM:度量单位统一代码,用于单位规范化。
- HL7 FHIR R4 Observation:
valueQuantity、referenceRange、interpretation字段承载检验结果。 - HL7 FHIR R4 DiagnosticReport:实验室结果的分组容器。
- Tesseract / PaddleOCR:可选 OCR 后端(也支持 docTR 与 EasyOCR)。
- OpenMed 源码:openmed/multimodal/ocr.py、openmed/multimodal/tabular_csv.py,入口导出见 openmed/multimodal/init.py,行为验证见 tests/unit/multimodal/test_tabular_csv.py。
【免费下载链接】openmedLocal-first healthcare AI: clinical NER & HIPAA PII de-identification that runs 100% on-device. 2,200+ medical models, 21 languages, Apple MLX + Python, no cloud, no patient data leaving your network. Apache-2.0项目地址: https://gitcode.com/GitHub_Trending/ope/openmed
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考