freeCodeCamp.org

The Hidden PHI Problem in Medical Images: Building a Synthetic Dataset for AI De-Identification

8.5内容质量
The Hidden PHI Problem in Medical Images: Building a Synthetic Dataset for AI De-Identification

TL;DR · AI 摘要

合成 PHI 数据集可解决医疗图像 AI 去标识化训练中的隐私与数据稀缺问题,提升模型性能。

核心要点

  • 使用 OpenPOCUS 提供的真实超声图像作为合成 PHI 数据的基础,确保隐私安全。
  • 合成 PHI 能够解决 HIPAA 等法规限制下的数据标注与验证难题。
  • 通过 Python 和 Faker 生成可控的合成患者身份信息,提升 AI 模型训练效率。

结构提纲

按章节快速跳转。

  1. 介绍医疗图像 AI 去标识化训练中隐私与数据稀缺的挑战。

  2. 医疗图像 AI 去标识化模型需要大量 PHI 数据,但隐私法规限制了真实数据的使用。

  3. 介绍本文将讲解的合成 PHI 数据集构建方法与应用价值。

  4. 使用 OpenPOCUS 提供的真实超声图像作为合成 PHI 数据的基础。

  5. 揭示医疗图像中隐藏的 PHI 信息,强调合成 PHI 的重要性。

思维导图

用一张图看清主题之间的关系。

查看大纲文本(无障碍 / 无 JS 友好)
  • 合成 PHI 数据集构建
    • 医疗图像 AI 去标识化挑战
      • 隐私法规限制
      • 数据稀缺
      • 标注与验证困难
    • 合成 PHI 解决方案
      • 使用 OpenPOCUS 图像
      • 生成合成患者身份
      • 注入 PHI 到图像与 DICOM 元数据

金句 / Highlights

值得收藏与分享的关键句。

  • Healthcare organizations must protect patient privacy. Regulations like HIPAA require that patient identifiers are removed before medical images can be shared for research, AI development, or external

    第 2 段

    ⬇︎ 下载 PNG𝕏 分享到 X
  • OpenPOCUS provides clinically authentic ultrasound images while avoiding patient privacy concerns.

    第 5 段

    ⬇︎ 下载 PNG𝕏 分享到 X
  • Synthetic PHI solves all three problems: privacy regulations, annotation at scale, and validation.

    第 4 段

    ⬇︎ 下载 PNG𝕏 分享到 X
#医疗AI#数据隐私#合成数据#HIPAA#DICOM
打开原文

医学图像中隐藏的 PHI 问题:构建用于 AI 去标识的合成数据集

2026年6月19日

/

#人工智能

Lakshmi Mahabaleshwara

在本文中,你将了解到我的团队如何构建一个合成 PHI 生成管道,以创建用于医学影像 AI 的隐私安全的训练和验证数据。

问题

想象一下,你正在构建一个从医学图像中移除患者信息的 AI 系统。

该模型需要数千个示例,展示受保护健康信息(PHI)出现的位置及其外观。它看到的示例越多,就越擅长发现并移除敏感信息。

但有一个问题:

你需要用来训练模型的数据,是不允许随意分享的数据。

医疗机构必须保护患者隐私。HIPAA 等法规要求,在医学图像可以用于研究、AI 开发或外部合作之前,必须移除患者标识符。

这创造了一个有趣的工程挑战:当用于训练这些系统的数据无法轻松使用时,如何构建和测试去标识系统?

一个实际的解决方案是合成 PHI。

在本文中,我将展示为什么合成 PHI 是有价值的,解释医学图像中隐藏的 PHI 问题,并逐步介绍我们团队构建的生成真实超声波数据集的管道,其中包含完全可控的合成患者信息。

在本教程中你将学到的内容

到本教程结束时,你将了解:

  • 医学影像数据中的隐藏 PHI 挑战。
  • 为什么合成 PHI 对于构建和测试医疗 AI 系统是有用的。
  • 如何使用 Python 和 Faker 生成现实的合成患者身份。
  • 如何将 PHI 注入图像像素和 DICOM 元数据中。
  • 如何为 AI 模型训练和评估创建真实标签。
  • 如何在将它们用于下游工作流程之前验证合成医学影像数据集。

我们将涵盖的内容:

  • 源图像:OpenPOCUS
  • 冰山问题:大部分 PHI 是隐藏的
  • 为什么合成 PHI 重要 挑战 1:隐私法规 挑战 2:大规模标注 挑战 3:验证
  • 合成 PHI 解决了所有三个问题
  • 构建合成 PHI 管道
  • 管道架构
  • 烧录前的安全检查 第一步:生成合成患者身份 第二步:将 PHI 烧录到图像像素中 第三步:将 PHI 添加到 DICOM 头部 第四步:身份映射:去标识的 PatientID 第五步:真实标签:结构化 CSV 输出
  • 三层 DICOM 验证
  • 一个令人惊讶的错误:MONAI 与 PIL
  • 最后的想法

源图像:OpenPOCUS

合成 PHI 生成使用了来自 OpenPOCUS 的肺部点-of-care 超声(POCUS)帧,这是一个由 POCUS 社区贡献的、具有开放许可证的真实超声图像集合。

这些图像不包含任何真实的 PHI。OpenPOCUS 提供临床真实的超声图像,同时避免患者隐私问题。这使它成为合成 PHI 生成的理想基础,因为我们可以完全专注于创建和跟踪标识符,而不会暴露真实患者信息的风险。

冰山问题:大部分 PHI 是隐藏的

当人们想到医学图像中的 PHI 时,他们通常会想到可见的文本覆盖。

这些包括:

code
患者姓名
医疗记录号(MRN)
出生日期
检查日期

这些标识符通常通过超声波、X射线、CT和MRI系统直接烧录到图像像素中。

但可见的文本只是冰山一角。剩余的大量PHI信息存储在DICOM头中,DICOM头是一组描述图像和研究的元数据字段。这些字段包含诸如PatientName(患者姓名)、PatientID(患者ID)、StudyDate(研究日期)、机构名称及其他敏感信息等标识符。

与烧录的文本不同,DICOM头中的PHI在查看图像本身时是不可见的,但它们会随文件一起传输,因此在去标识化过程中也必须将其删除。

去标识化系统必须同时处理这两种情况。

在保留DICOM元数据中的PHI信息的同时删除可见文本,仍然会带来隐私风险。同样,如果在保留患者姓名烧录到图像像素的同时删除元数据,也会带来同样严重的问题。

这种隐藏的PHI信息挑战使得测试去标识化软件比最初看起来要困难得多。

为什么合成PHI很重要

乍看之下,似乎医院已经拥有大量现成的真实世界数据。那为什么不直接使用这些数据呢?

答案归结为三个挑战。

挑战1:隐私法规

医学图像通常包含患者标识符。

在安全的临床环境之外共享这些图像会带来重大的法律和合规风险。

参与的机构越多,治理的难度就越大。

挑战2:大规模标注

现代AI系统需要带有标签的示例。

必须有人确定以下内容:

  • PHI出现在哪里
  • PHI的类型是什么
  • 哪些DICOM标签包含PHI

手动创建这些标注既昂贵又耗时。

挑战3:验证

假设你正在评估一个去标识化工具。你怎么知道它是否成功删除了所有标识符?

使用真实患者数据时,你通常无法确切知道PHI出现在哪里。没有真实数据作为基准,准确率的衡量变得困难。

合成PHI解决了所有三个问题

我们不需要从真实患者标识符开始,而是可以生成逼真的虚假身份,并有意地将它们注入医学图像中。

由于该流程本身创建了PHI,我们知道:

  • 每个标识符的值
  • 每个像素的位置
  • 每个DICOM标签
  • 每个预期的输出

这为我们提供了完美的真实数据基准。

现在,可以客观地评估去标识化系统。如果处理后患者姓名仍然存在,我们知道它失败了。如果临床内容被意外删除,我们也能知道。

合成PHI创建了一个隐私安全的数据集,可用于以下用途:

  • 训练AI模型
  • 评估去标识化软件
  • 回归测试
  • 部署前的验证

构建合成PHI流程

为了探索这个问题,我的团队构建了一个流程,用于为肺部床旁超声(POCUS)图像生成合成PHI。

目标是:

  • 从不包含任何患者信息的超声图像开始。
  • 生成逼真的合成患者身份。
  • 将PHI烧录到图像像素中。
  • 将匹配的PHI插入DICOM元数据中。
  • 自动生成真实数据标签。
  • 验证生成的DICOM文件。

从去标识化系统的角度来看,输出看起来是真实的,但不包含任何真实患者信息。

流程架构

工作流程如下(下面将详细讲解每个步骤):

每个阶段都会生成供下一阶段使用的产物。失败的情况会被隔离,而不是被静默忽略。

烧录前的安全检查

在将合成的 PHI 写入图像之前,流程会执行安全检查,以确保所选插入 PHI 的区域位于超声扇区之外。

肺部 POCUS 图像的左上角通常位于成像扇区之外,是一条暗边,可以安全地写入 PHI 而不会遮挡临床内容。

为了确保该区域适用于每张图像,流程会对每张图像执行两次检查:

  • 亮度检查:如果配置的烧录区域的平均强度超过阈值,该区域可能与超声扇区重叠,而不是暗边。
  • 边界检查:流程验证配置的烧录区域是否完全位于图像内部。如果图像尺寸小于预期的烧录区域,则图像会被隔离。

在任何一种情况下,图像都会被隔离,并将原因记录到清单中。不会出现部分烧录、不会覆盖临床内容,也不会对测试数据造成静默损坏。

这可以防止合成标识符意外遮挡解剖结构。

python
def burn_region_is_safe(arr):
    """检查烧录区域是否足够暗,以确保其位于扇区之外。"""
    h, w = arr.shape
    y2 = min(BURN_REGION_Y + BURN_REGION_H, h)
    x2 = min(BURN_REGION_X + BURN_REGION_W, w)
    region = arr[BURN_REGION_Y:y2, BURN_REGION_X:x2]
    if region.size == 0:
        return False, float("nan")
    mean = float(region.mean())
    return mean <= BRIGHTNESS_SKIP_THRESHOLD, mean

该函数提取配置的烧录区域并计算其平均亮度。如果该区域太亮,可能与超声扇区重叠,而不是边框。

第一步:生成合成患者身份

合成身份由 Faker 生成,并根据每个案例进行种子设置,因此相同的图像始终生成相同的假患者信息。

确定性很重要,因为:

  • 重现测试结果需要重现测试数据。
  • 在输入不随运行而变化时,调试下游工具会更容易。
  • 公平比较两个去标识化工具需要两者看到相同的植入 PHI。
python
def case_seed(global_seed: int, source_id: str) -> int:
    """从全局种子和源路径派生的每张图像的确定性种子。"""
    h = hashlib.sha256(f"{global_seed}|{source_id}".encode()).hexdigest()
    return int(h[:8], 16)

def generate_phi(seed: int) -> dict:
    fake = Faker()
    Faker.seed(seed)
    rng = random.Random(seed)

    last = fake.last_name()
    first = fake.first_name()
    middle = fake.random_letter().upper()
    mrn = f"{rng.randint(1000000, 9999999)}"
    dob = fake.date_of_birth(minimum_age=18, maximum_age=95)
    study_date = fake.date_time_this_decade()
    institution = rng.choice(INSTITUTION_POOL)

    return {
        "case_uuid": f"SYNTH-{uuid.UUID(int=rng.getrandbits(128))}",
        "patient_name_display": f"{last}, {first} {middle}.",
        "patient_name_dicom": f"{last}^{first}^{middle}",   # DICOM PN VR 格式
        "patient_id": mrn,
        "dob": dob,
        "study_date": study_date,
        "institution_name": institution,
    }

case_seed() 函数从源图像路径生成一个确定性种子。该种子随后由 Faker 用于创建合成身份。

由于种子是可重复的,相同的输入图像始终会接收到相同的合成患者信息。这使得调试和基准测试可以重现。

将文本渲染到图像上相对而言成本较高。对于包含 30 多帧的单个区域,每帧重复执行此操作会浪费资源。

相反,管道将 PHI 覆盖层一次性渲染到每个区域的透明画布上。这与许多超声系统在实际操作中的方式类似,其中患者信息保持固定,而底层图像内容则在帧与帧之间发生变化。

code
def make_phi_overlay(shape, phi):
    """将 PHI 一次性渲染到画布上。返回 (overlay_array, overlays_meta)。"""
    h, w = shape
    canvas = Image.new("L", (w, h), 0)  # 空白画布
    draw = ImageDraw.Draw(canvas)

    overlays, x, y = [], BURN_REGION_X, BURN_REGION_Y
    for entry in _phi_text_block(phi):
        x0, y0, x1, y1 = draw.textbbox((x, y), entry["line"], font=FONT)
        tw, th = x1 - x0, y1 - y0

        if x + tw > w or y + th > h:
            raise ValueError(
                f"渲染的 PHI 超出图像范围: '{entry['line']}' "
                f"在 ({x},{y}) 大小为 ({tw}x{th}), 图像 {w}x{h}"
            )

        draw.text((x, y), entry["line"], font=FONT, fill=TEXT_COLOR)
        overlays.append({
            "phi_category": entry["phi_category"],
            "rendered_text": entry["line"],
            "phi_value": entry["value"],
            "bbox": [x, y, tw, th],
            "dicom_tag": entry["dicom_tag"],
        })
        y += th + LINE_GAP
    return np.array(canvas), overlays

make_phi_overlay() 函数创建一个空白画布,并将每条 PHI 文本渲染到其上。同时,它记录了诸如渲染文本、边界框坐标和对应的 DICOM 标签等元数据。

该函数返回图像覆盖层和注释元数据,确保真实值始终与实际绘制的像素相匹配。

一次性渲染并重复使用覆盖层带来了以下优势:

  • 处理速度更快
  • 所有帧上的 PHI 位置保持一致
  • 简化真实值生成
  • 行为更贴近真实超声设备

另一个好处是,管道会自动记录每个被烧录标识符的位置。

第三步:将 PHI 添加到 DICOM 标头

DICOM 标准支持两种表示连续超声循环的方式:作为共享系列 UID 的单帧 DICOM 序列,或作为包含所有帧堆叠在一起的多帧 DICOM。

管道使用多帧方法,因为:

  • 它与真实超声设备写入连续循环的方式一致。
  • 一个标头适用于所有帧 —— 患者元数据无需重复。
  • 存储和传输更加高效。
code
ds.PatientName = phi["patient_name_dicom"]
ds.PatientID = deid_patient_id
ds.PatientBirthDate = phi["dob"].strftime("%Y%m%d")

ds.StudyInstanceUID = study_uid
ds.StudyDate = phi["study_date"].strftime("%Y%m%d")
ds.InstitutionName = phi["institution_name"]

这些字段将与图像覆盖层中使用的相同合成身份填充到 DICOM 标头中。这确保了可见的 PHI 和隐藏的元数据保持一致,生成真实的测试数据。

DICOM 标准强制要求一些细节,但规范中并未明确说明:

  • StudyID 是必需的,必须是一个短字符串,且与 StudyInstanceUID 不同。很容易忘记这一点。
  • ImageType 必须存在。["DERIVED", "SECONDARY"] 是合成数据的诚实值,因为它不是由设备获取的。
  • 制造商是通用设备 IOD 模块的一部分,即使数据是合成的,也必须设置。将其设置为一个明显合成的值(SYNTHETIC-DEID-TUTORIAL)可以使数据来源变得明确无误。

第 4 步:身份映射:去标识化患者 ID

为了支持后续的评估,每个源患者都会被分配一个稳定的标识符,例如 DEID-0001。一个映射文件将源患者、合成研究和生成的 DICOM 对象联系起来。这使得评估人员可以将去标识化工具的输出与原始真实数据进行比较。

code
source_patient,deid_patient_id,study_instance_uid
patient_001,DEID-0001,1.2.826.0.1.3680043.8.498.1234...
patient_002,DEID-0002,1.2.826.0.1.3680043.8.498.5678...

第 5 步:真实数据:结构化 CSV 输出

合成 PHI 的一个主要优势是能够自动生成标签。由于流程创建了每一个标识符,它已经知道文本值、边界框坐标和对应的 DICOM 标签。

这些注释以结构化 CSV 文件的形式导出,并成为用于训练和评估的真值数据。

code
def build_overlay_rows(*, case_uuid, sop_instance_uid, source_id, source_relpath, output_dicom_relpath, overlays,
                      image_shape):
    h, w = image_shape
    rows = []
    for ov in overlays:
        x, y, ow, oh = ov["bbox"]
        rows.append({
            "case_uuid": case_uuid,
            "sop_instance_uid": sop_instance_uid,
            "source_id": source_id,
            "source_relpath": source_relpath,
            "output_dicom_relpath": output_dicom_relpath,
            "image_h": h,
            "image_w": w,
            "region": "top_left_banner",
            "phi_category": ov["phi_category"],
            "phi_value": ov["phi_value"],
            "rendered_text": ov["rendered_text"],
            "bbox_x": x, "bbox_y": y,
            "bbox_w": ow, "bbox_h": oh,
            "dicom_tag": ov["dicom_tag"],
            "seed": SEED,
            "pipeline_version": PIPELINE_VERSION,
            "run_id": RUN_ID,
        })
    return rows

build_overlay_rows 函数将每个覆盖层转换为一行结构化元数据。除了文本和边界框坐标外,它还记录了标识符和可重复性信息,例如流程版本和随机种子。

这些 CSV 文件成为用于训练和评估去标识化系统的真值数据。

在运行结束时,累积的行会根据去标识化患者 ID 进行分组,并写入每个患者的 CSV 文件中。每个患者文件夹都会收到一个 phi_overlays.csv 文件,涵盖该患者的全部区域,同时还有一个 run_manifest.csv 文件,总结区域级别的状态(已处理、已隔离、失败)和路径。

三层 DICOM 验证

只有当合成的 DICOM 文件实际上符合 DICOM 标准时,它才有用。否则,使用它的下游工具可能会失败,甚至更糟的是,它们可能会静默地错误处理它。

该流程使用一个三层验证链,根据环境中可用的内容进行优雅降级:

  • dciodvfy(来自 dicom3tools):这是最严格的符合标准的验证器,由 David Clunie 编写。它无法通过 pip 安装。它会检查完整的 DICOM IOD 定义。如果它在 PATH 中可用,这是首选的检查方式。
  • dicom-validator CLI:这是可以通过 pip 安装的。它在首次运行时会下载 DICOM 标准定义,然后验证 IOD 合规性。当 dciodvfy 不可用时使用它。
  • pydicom 重新读取:最基本的回退方法。它确认每个文件都可以重新打开、解码,并且像素数据可以正确往返传输。它不检查标准合规性,但可以捕捉到严重的损坏问题。

意外的错误:MONAI 与 PIL

最初,我计划使用 MONAI 来加载图像,因为它在医学影像工作流程中被广泛使用。

在测试过程中,我发现了一个问题:MONAI 的图像加载惯例导致非正方形图像在下游代码假设传统图像布局时出现旋转。

同时,许多超声图像包含需要更正的 EXIF 方向元数据。

切换到 PIL 解决了这两个问题。

code
from PIL import Image, ImageOps

img = Image.open(path)
img = ImageOps.exif_transpose(img)

最后的想法

合成的 PHI 并不能替代实际测试,但它为医疗 AI 团队提供了一种他们很少拥有的东西:一个安全、可共享且完全标注的数据集,其中包含已知的答案。

通过生成逼真的标识符并将其嵌入到图像像素和 DICOM 元数据中,我们可以在不暴露真实患者数据的情况下构建可重复的去标识化系统基准。

随着 AI 系统在处理敏感医疗信息方面变得越来越重要,合成的 PHI 可能会成为构建可信赖的医疗 AI 工作流程最重要的工具之一。

完整的实现可以在 MONAI 超声波工作组的仓库中作为 Jupyter 笔记本提供。您可以探索该笔记本并亲自尝试该流程。

有时,测试一个系统是否能够移除 PHI 最安全的方法就是自己创建 PHI。

专注于医疗 AI、医学影像和可扩展 ML 系统的数据工程师和 AI 开发者。正在构建用于医学数据去标识化、隐私保护 AI 和医疗分析的开源工具。

如果这篇文章对你有帮助,请分享它。

免费学习编程。freeCodeCamp 的开源课程已帮助超过 40,000 人成为开发人员。立即开始

ADVERTISEMENT