用 Amazon Bedrock 和 Textract 识别 DICOM 中的 PHI/PII

2026-08-19 47 预计阅读时间: 1 分钟
来源: aws.amazon.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:10 分钟

临床试验中的 DICOM 数据通常不只在元数据标签里包含敏感信息。患者姓名、检查编号、日期或机构名称,也可能已经被“烧录”到影像像素中。Clario 使用 Amazon Bedrock 和 Amazon Textract,自动检查数千张 DICOM 切片,同时覆盖 DICOM 元数据与影像内嵌文字,这为大规模医学影像脱敏提供了一条可自动化的路径。

DICOM 脱敏不能只看元数据

DICOM 文件包含结构化标签,例如患者姓名、患者 ID、出生日期、检查日期和机构信息。这些字段适合通过 DICOM 解析器直接读取和匹配规则。

但另一类信息不在标签中,而是出现在像素内容里。放射科工作站可能会把姓名、检查号或时间戳渲染到影像边缘。删除 DICOM 标签并不能清理这些文字,必须对每张切片进行 OCR。

因此,一个完整的检测流程通常包括两条路径:

  • 使用 DICOM 解析器遍历元数据标签,识别敏感字段和可疑值。
  • 将影像帧转换为 Textract 支持的格式,提取烧录文字,再使用 Bedrock 根据业务策略判断文本是否属于 PHI 或 PII。

这个划分也有助于控制成本:结构化标签可以本地快速扫描,OCR 和语义判断只对需要检查的图像执行。

Bedrock 与 Textract 如何分工

Textract 适合做文字识别。它负责回答“图像中写了什么”,输出文字内容、置信度以及文本位置。

Bedrock 则适合做后续语义判断。它可以根据组织的检测策略,对 OCR 结果进行分类,例如判断某段文本是否像患者姓名、医疗记录号、日期、地址或其他受保护信息。应用不应把模型输出当作唯一事实来源,而应保留原始 OCR 结果、模型判断、置信度和审计信息。

可以把每张切片的结果设计成类似下面的结构:

{
  "sop_instance_uid": "1.2.840.example",
  "metadata_findings": [
    {"tag": "PatientName", "value": "REDACTED", "category": "PHI"}
  ],
  "ocr_text": "Patient ID: A12345",
  "pixel_findings": [
    {"text": "A12345", "category": "PHI", "confidence": 0.96}
  ],
  "needs_review": false
}

在临床试验场景中,保留这些中间结果很重要。它们既支持人工复核,也便于解释某张切片为什么被标记为风险。

一个可改造的处理示例

下面的 Python 示例展示最小处理链路:读取 DICOM 元数据,把第一帧转换为 PNG,调用 Amazon Textract OCR,再调用 Amazon Bedrock 对 OCR 文本进行分类。示例假设 DICOM 文件能够通过 pydicom 读取像素数据,并且 AWS 凭证、区域和 IAM 权限已经配置好。

运行前安装依赖,并将 DICOM_PATH 改成实际文件路径:

python -m pip install boto3 pydicom pillow numpy
export AWS_REGION=us-east-1
export DICOM_PATH=/data/example.dcm
python detect_phi.py

将下面内容保存为 detect_phi.py。Bedrock 模型 ID 需要替换为目标区域中已启用的模型:

import io
import json
import os

import boto3
import numpy as np
import pydicom
from PIL import Image

REGION = os.getenv("AWS_REGION", "us-east-1")
DICOM_PATH = os.environ["DICOM_PATH"]
MODEL_ID = os.getenv("BEDROCK_MODEL_ID", "anthropic.claude-3-haiku-20240307-v1:0")

textract = boto3.client("textract", region_name=REGION)
bedrock = boto3.client("bedrock-runtime", region_name=REGION)


def metadata_findings(ds):
    sensitive_keywords = {
        "PatientName", "PatientID", "PatientBirthDate", "PatientAddress",
        "InstitutionName", "AccessionNumber", "StudyDate", "SeriesDate"
    }
    findings = []
    for element in ds.iterall():
        if element.keyword in sensitive_keywords and element.value not in (None, ""):
            findings.append({
                "tag": element.keyword,
                "value": str(element.value),
                "category": "PHI/PII"
            })
    return findings


def dicom_frame_as_png(ds):
    pixels = ds.pixel_array
    if pixels.ndim > 2:
        pixels = pixels[0]
    pixels = pixels.astype(np.float32)
    pixels -= pixels.min()
    maximum = pixels.max()
    if maximum:
        pixels = pixels / maximum * 255.0
    image = Image.fromarray(pixels.astype(np.uint8)).convert("L")
    output = io.BytesIO()
    image.save(output, format="PNG")
    return output.getvalue()


def ocr(image_bytes):
    response = textract.detect_document_text(Document={"Bytes": image_bytes})
    return "\n".join(
        block["Text"]
        for block in response["Blocks"]
        if block["BlockType"] == "LINE"
    )


def classify_with_bedrock(text):
    if not text.strip():
        return {"contains_sensitive_data": False, "findings": []}

    prompt = (
        "Classify possible PHI or PII in the following OCR text. "
        "Return JSON only with keys contains_sensitive_data and findings. "
        "Each finding must contain text, category, and reason.\n\n"
        f"OCR text:\n{text}"
    )
    response = bedrock.converse(
        modelId=MODEL_ID,
        messages=[{"role": "user", "content": [{"text": prompt}]}],
        inferenceConfig={"temperature": 0}
    )
    answer = response["output"]["message"]["content"][0]["text"]
    return json.loads(answer)


def main():
    ds = pydicom.dcmread(DICOM_PATH, force=True)
    metadata = metadata_findings(ds)
    text = ocr(dicom_frame_as_png(ds))
    pixel = classify_with_bedrock(text)
    result = {
        "sop_instance_uid": str(getattr(ds, "SOPInstanceUID", "")),
        "metadata_findings": metadata,
        "ocr_text": text,
        "pixel_findings": pixel,
    }
    print(json.dumps(result, ensure_ascii=False, indent=2))


if __name__ == "__main__":
    main()

这是一个验证思路的最小实现,不是直接用于生产临床数据的脱敏程序。生产环境还需要处理多帧 DICOM、压缩传输语法、窗宽窗位、图像方向、OCR 误识别,以及 Bedrock 返回非严格 JSON 的情况。对于多切片研究,应按 Study、Series 和 SOP Instance 建立任务状态,避免重复处理并支持失败重试。

生产化时要重点控制什么

准确率与人工复核。 OCR 可能把字符 0 识别成 O,也可能漏掉低对比度文字。可以结合 Textract 置信度、Bedrock 分类结果和规则引擎设置复核阈值。高风险数据宁可进入人工队列,也不要只依赖一次模型调用。

数据最小化。 发送到云服务前应明确数据流边界、加密方式、区域选择、访问控制和日志策略。不要把完整患者信息写入普通应用日志。S3 对象、Textract 响应、Bedrock 请求和人工复核记录都应按照组织的保留策略管理。

可审计性。 记录输入文件哈希、DICOM UID、处理版本、模型 ID、OCR 输出、分类结果和最终处置。模型或规则升级后,可以用固定样本集重新评估,而不必凭感觉判断效果是否变好。

性能与成本。 元数据扫描可以批量执行,像素 OCR 则按切片并发,但并发度需要受到服务配额、网络带宽和预算约束。对没有像素文字风险的图像,可以依据设备类型、Series 描述或历史评估结果设计受控跳过策略,但这种优化必须有验证数据支撑。

落地检查清单

  • 明确需要检测的 PHI/PII 类别和组织内部规则。
  • 分别覆盖 DICOM 标签与像素烧录文字。
  • 用固定的已标注样本评估 OCR、分类和脱敏效果。
  • 为低置信度、模型解析失败和服务调用失败设计人工或自动重试路径。
  • 保存可审计的处理记录,但避免在日志中扩散敏感原文。
  • 在正式处理临床试验数据前完成区域、权限、加密和合规评审。

Amazon Bedrock 和 Amazon Textract 的价值不在于替代整个脱敏流程,而在于把文字提取和语义判断接入一个可扩展的流水线。真正可靠的方案,仍然需要 DICOM 规则、图像处理、人工复核、审计记录和合规控制共同构成闭环。


相关推荐