资讯动态

LIDC/IDRI肺结节数据集解析:从DICOM到XML标注实践

发布时间:2026/9/8 2:05:01 来源:尧图企业网站定制
简介面向Python医学影像分析初学者这份资源为使用LIDC-IDRI肺结节CT数据集做检测与特征分析提供了完整的数据说明与标签文件省去了逐份下载和整理标注的时间。压缩包共1323个文件以1319个XML结节标注为主体另含CSV元数据、Excel结节统计表和TXT下载说明整体仅6.44MB结构清晰尤其适合初次接触医学影像数据的开发者快速上手。XML中详细记录了每个结节的坐标、直径、形状等放射科医生标注信息CSV与Excel则汇总了病人ID、扫描序列、结节数量等统计维度这些数据可配合pydicom读取DICOM原始图像利用pandas和lxml解析表格与标注直接用于肺结节检测、分割、特征提取以及机器学习模型训练。已有280人学习下载资源体量轻但信息密度高对正在做医学影像入门或毕业设计的Python开发者很实用。1. LIDC/IDRI数据集到底是什么为什么绕不开它做肺部影像AI、肺结节检测相关工作的朋友对这个数据集应该都不陌生。LIDC全称Lung Image Database Consortium中文一般叫肺部图像数据库联盟由美国国家癌症研究所牵头联合多家医学机构共同构建。后来在IDRIImage Database Resource Initiative项目的推动下补充了完整的标注信息和配套文档所以现在大家习惯上说的LIDC/IDRI数据集指的就是这个包含1018例肺部CT检查的公开资源。这个数据集在肺结节AI领域的位置基本相当于ImageNet在通用视觉领域的地位。你去看任何一篇做肺结节检测、分类、良恶性判断的论文十篇里有八篇的训练或验证环节都离不开它。原因很简单医疗影像数据涉及患者隐私能公开且标注完善的肺部CT数据集太少了而LIDC恰好把最稀缺的资深放射科医生标注这件事做到了足够规模。它到底有多大价值1018例病例每例包含一次完整的胸部CT扫描以及最多四位胸片放射科医生独立完成的标注结果。这些标注不是简单画个框而是覆盖了结节的轮廓勾画、边缘毛刺程度、内部钙化形态、分叶征、恶性程度评分等一整套语义信息。换句话说你用这个数据集不仅能训练结节在哪的目标检测模型还能训练结节长什么样、危险程度如何的属性分类模型。需要提醒一点很多人下载完数据集就急着开始跑模型结果发现标注文件是一堆XML影像数据是DICOM格式光数据预处理就能卡一两个星期。这篇文章就把数据说明和标签体系从头到尾拆一遍包括每个文件是什么、每段XML节点什么意思、DICOM怎么读、标签怎么转换成常见训练格式以及我实际跑项目时踩过的那些坑。2. 下载下来的文件夹里到底装了什么元数据构成详解2.1 目录结构与两种核心文件LIDC/IDRI数据集的原始发布形式是从TCIAThe Cancer Imaging Archive官网申请下载的。整个数据集解压后是一个很规整的目录树每个病例一个文件夹命名格式类似LIDC-IDRI-0001到LIDC-IDRI-1018。没下载全的话实际拿到手的可能是子集。每个病例文件夹下包含两类核心内容DICOM影像文件也就是CT扫描的原始医学图像。一例CT检查由几百张横断面切片图组成每张切片就是一个独立的DICOM文件文件后缀通常为.dcm。这些文件记录了扫描参数、患者位置、像素间距、切片厚度等完整元信息。XML标注文件文件名通常是{患者ID}.xml存放的是放射科医生对该病例所有结节的详细标注。这才是LIDC最有价值的部分。把DICOM理解成照片XML理解成照片上医生画的标记和写的诊断意见两者通过患者ID一一关联这样想就清楚了。2.2 为什么用XML而不是直接给标注图片不少第一次用LIDC的人会问为什么要用XML存标注直接给一张画好掩膜的PNG多省事这个问题其实问到了设计思路上。LIDC的标注主体是四位独立工作的放射科医生他们各自审阅CT影像后在独立会话中勾画结节轮廓并评分。每个人的勾画结果都要单独保留不能合并成一张掩膜图否则就丢失了医生间差异这个重要信息。XML天生适合存这类层级化、多实体的复杂数据。你在XML里可以轻松表达这是第几位医生标注的他画的边界由哪些坐标构成他给的恶性程度打了几分而且结构一目了然修改和扩展都方便。后续做模型训练时再按照自己的规则把XML转换成目标检测的边界框、分割任务的掩膜或分类任务的标签文件这样灵活度最高。2.3 数据集里的三个关键文档除了影像和XML下载目录里通常还有几个配套文档这几个容易被忽略但其实很关键LICENSE与数据使用协议使用前认真读做学术用途和商业用途的限制不一样。README或者数据集说明PDF里面有扫描设备的参数统计、标注协议全文、字段取值含义后面讲标签细节会反复用到。元数据CSV部分第三方整理版提供包含每例患者的吸烟史、癌症分期等附加信息。分词提取一下如果是做纯视觉模型元数据CSV的用处不大但如果你做的是多模态融合或临床辅助诊断方向这些信息就是额外的监督信号别丢掉。3. 标签体系拆解四位医生如何投票出一份高质量标注3.1 从Response到ReadingSession的三级结构打开任意一份XML标注文件比如LIDC-IDRI-0001.xml第一眼会看到根节点下挂了个大块头名叫ResponseHeader。这里面记录的是标准版本号、CTA算法版本、数据创建时间等管理信息对训练没有直接用处看一眼知道存在即可。真正核心的标注内容全部在ReadingSession节点里。理解这个节点的关键在于Reading Session这个命名本身就有深意每个 Reading Session 相当于一位放射科医生在某一次独立阅片会话中的全部记录。所以正常来说一份XML里会有多个ReadingSession节点最多四个对应四位医生。每个ReadingSession有一个servicerad属性取值是1到4的编号代表第几位医生。这个编号很重要后面做多标注融合时会用到。Session节点内部是若干unblindedReadNodule节点代表医生发现并勾画的每个结节大家可以把它理解为医生在CT片上圈出来的每个可疑位置。3.2 结节三大字段体系坐标、影像特征、恶性程度每个unblindedReadNodule里真正会被模型用到的信息分为三大块第一块是noduleID和勾画坐标。noduleID是这个结节的唯一标识符同一医生标注的多个结节用不同ID区分。勾画坐标存在edgeMap节点下每个edgeMap对应一个CT切片z属性是切片在序列中的位置x和y坐标则记录了医生在该切片上勾画轮廓各点的空间坐标。把所有切片的轮廓点连起来就还原出了结节在三维空间里的完整形状。必须注意坐标系的问题LIDC的坐标是DICOM病人坐标系单位是毫米不是像素。直接拿去做图像处理前需要先用DICOM头信息里的ImagePositionPatient和PixelSpacing换算成像素坐标这个细节很多人第一次都会搞岔后文专门说。第二块是影像特征评分。这一部分包括subtlety、internalStructure、calcification、sphericity、margin、lobulation、spiculation、texture等字段。每个字段的取值范围通常是1到5或1到6分别代表不同含义。比如malignancy恶性程度评分1代表高度良性5代表高度恶性margin边界清晰度1是边界非常模糊5是非常清晰。这套评分体系本质上是把放射科医生阅片时的经验判断量化成数字方便做计算机辅助诊断模型。第三个需要重点强调的是可以出现部分读取结节non-nodule在XML里对应nonNodule节点代表医生认为可疑但没有达到肺结节诊断标准的病灶或血管断面。这类不要丢很多研究中的难负样本挖掘就靠它们。3.3 多医生标注冲突时怎么处理三种常见策略四位医生标注结果完全一致的情况在实际数据里很少见。结节边界有细微差别、恶性程度评分不同甚至某个结节只有位医生勾了另一位没勾都不稀奇。这时候就需要定一个融合策略学术界常用的有三种策略一多数投票Majority Voting。对于分类属性比如恶性程度统计四位医生评分的众数或平均值取整作为该结节的最终标签。边界的分割掩膜则对所有医生的标注区域做像素级的交集或并集。做分割任务时我遇到的团队大多使用至少三位医生标注重叠区域作为金标准这能有效降低个别医生的勾画噪声。策略二按需选用单医生标注。有些研究聚焦于早期肺癌识别场景会刻意选取恶性评分最高医生的标注作为最终标签用来模拟更激进的筛查标准。策略三不确定性建模。把四位医生的标注作为独立分支同时训练让模型学习标注的分布而不仅仅是单一结果。这种方法论文效果好但实现复杂度高、调试成本大工程落地周期短的团队不建议一上来就这样干。4. 从DICOM到可训练数据CT值、窗宽窗位与像素数组处理4.1 打开DICOM文件的两个常见技术路线处理LIDC的DICOM影像主流技术路线有两条各有适用场景。路线一使用pydicom库直接读取。pydicom是Python生态下最成熟的DICOM解析库适合需要精细控制每一步读取细节的场景。核心代码不复杂import pydicom ds pydicom.dcmread(LIDC-IDRI-0001/000001.dcm) pixel_array ds.pixel_array # 原始像素数组 # 常用元数据 print(ds.PatientID) print(ds.SliceThickness) print(ds.PixelSpacing) # 像素间距单位mm print(ds.ImagePositionPatient)读取后拿到的是像素数组但注意这还不是物理意义上可以直接喂给模型的CT值DICOM里存的通常是设备原始值需要通过RescaleSlope和RescaleIntercept做线性变换才能得到标准化的亨氏单位HU值。路线二使用SimpleITK一站式处理。SimpleITK读DICOM序列非常方便可以自动把同一个病例的多张切片组合成三维体数据代码更简洁import SimpleITK as sitk reader sitk.ImageSeriesReader() dicom_names reader.GetGDCMSeriesFileNames(LIDC-IDRI-0001) reader.SetFileNames(dicom_names) image reader.Execute() # 返回三维体数据 array sitk.GetArrayFromImage(image) # shape为(z, y, x)个人建议如果你只是做纯训练、不打算在预处理环节做太多自定义操作直接上SimpleITK如果你想深入理解数据和坐标转换先用pydicom把流程走通后续再迁移到SimpleITK理解到位之后会更顺手。4.2 四个必调的DICOM参数少一个模型效果都可能打折第一是Rescale Slope和Rescale Intercept。Siemens、GE、Philips不同品牌的扫描设备原始存储数值的偏移量不一样。必须执行hu_value raw_value * slope intercept把像素值转为标准HU。肺部CT中空气大约是-1000HU软组织在-100到100之间骨头能到上千。转换完成后建议把HU值裁剪到[-1000, 400]区间这个范围基本覆盖了肺部软组织和病灶的信息做归一化送到网络前也更稳定。第二是PixelSpacing。也就是每个像素代表的物理尺寸。不同CT机型、不同扫描协议像素间距可能不同常见的是0.5-0.8毫米之间。做多病例混合训练时如果不把不同间距的图像重采样到统一分辨率模型的检测框尺寸计算就会失真分割掩膜也会出现缩放不一致。第三是SliceThickness切片厚度。LIDC里不少早期病例的切片厚度在1.5-3毫米和现在0.5毫米薄层扫描没法比。切片越厚结节在z轴方向上的分辨率越低小尺寸结节的检测难度明显上升。根据我的经验数据预处理阶段最好设一个统一的重采样层厚比如1毫米用线性插值或三次样条插值把厚层数据在z方向插密后续模型训练更稳。第四是Window Center和Window Width窗宽窗位。这部分对训练的影响容易被低估。肺窗、纵隔窗、骨窗各有不同的显示范围但网络训练并不需要严格模拟医生的显示设置。实际项目中大多数情况下在对HU值做裁剪归一化后窗宽窗位的影响会被大幅稀释。4.3 CT坐标对齐毫米坐标、像素坐标与矩阵索引的三层换算这一节是坐标体系的关键。LIDC的XML里存的坐标是毫米为单位的世界坐标而DICOM像素数组的索引是整数坐标中间需要两步换算。第一步读取DICOM头中的ImagePositionPatient假设为(sx, sy, sz)。这代表该切片图像左上角点的世界坐标。第二步对第i张切片上某一点世界坐标(wx, wy, wz)换算成像素坐标(px, py)的公式是px int(round((wx - sx) / spacing_x)) py int(round((wy - sy) / spacing_y))其中spacing_x、spacing_y来自PixelSpacing字段。第三步如果做三维体数据处理还需要将切片序号i和世界坐标z对应起来使用ImagePositionPatient里的z值。很多人在这一步栽跟头因为不同切片的ImagePositionPatient的z值并不一定是均匀递增的决定切片顺序时不能只看文件名一定要按z坐标排序。我给个最稳妥的处理方式解析XML时顺便把每张切片的z值建立映射表然后根据XML里edgeMap的z属性精确找到对应的切片索引。5. 把XML标注变成模型能用的标签完整实现流程5.1 自己写解析器还是用现成工具我的选择GitHub上有多个开源LIDC解析库比较知名的有pylidc和lidc-idri相关的各种工具包。pylidc封装了DICOM读取、XML解析、结节掩膜生成等全套功能接口友好支持把标注转成numpy数组。我第一次跑LIDC就是直接用pylidc确实省事。但要注意两个问题一是它内部封装了大量逻辑一旦遇到坏数据不好定位原因二是坐标处理细节被隐藏了如果将来要换数据集格式或做自定义增强可能还得回头补原理。所以我的建议是快速验证用pylidc正式项目则花半天时间把解析逻辑自己实现一遍整体心里会更有底。5.2 手工解析XML的完整流程与示例代码手工解析的核心分四步第一步定位所有结节节点。用Python的xml.etree.ElementTree遍历所有unblindedReadNodule同时记录所属的ReadingSession的servicerad编号。第二步读取每个结节的属性字段。包括malignancy、margin等字段缺失的做默认值填充。第三步重建结节掩膜。对每个edgeMap读取z值和一组坐标点用skimage.draw.polygon在多张空白切片上把多边形内部填充为1from skimage.draw import polygon mask np.zeros((num_slices, height, width), dtypenp.uint8) for edge in edgeMaps: z_idx z_to_index[float(edge.get(z))] x_coords [float(p.get(x)) for p in edge.findall(x)] y_coords [float(p.get(y)) for p in edge.findall(y)] px ((x_coords - origin_x) / spacing_x).astype(int) py ((y_coords - origin_y) / spacing_y).astype(int) rr, cc polygon(py, px) mask[z_idx, rr, cc] 1注意这里polygon函数接收的是(行、列)索引也就是(y, x)顺序新手容易写反。第四步多医生标注融合。按前面提到的多数据合并策略比如取至少三位医生标注的重叠区域作为金标准# 假设masks是所有医生对该结节的掩膜集合 stacked np.stack(masks, axis0) final_mask (stacked.sum(axis0) 3).astype(np.uint8)5.3 目标检测格式转换与难样本筛选如果你做的是目标检测而不是分割坐标信息还需要进一步转成边界框。三维结节的检测框大多按切片级二维检测来做也有一部分直接对三维体积做anchor-based检测两种都依赖XML的坐标信息。我实际项目里比较常用的一种转换是三维边界框取所有医生标注轮廓点的x、y、z最小值作为框的起点最大值作为终点就得到包含空间位置的检测框信息量比二维框更大all_x [p_x for edge in edgeMaps for p_x in x_list] all_y [p_y for edge in edgeMaps for p_y in y_list] all_z [z for edge in edgeMaps] xmin, xmax min(all_x), max(all_x) ymin, ymax min(all_y), max(all_y) zmin, zmax int(min(all_z)), int(max(all_z)) box_3d (xmin, ymin, xmax, ymax, zmin, zmax)筛选结节时还要过滤掉明显异常的数据。LIDC里有的结节标注区域面积特别小可能是血管断面误标或噪声有的结节跨切片数量极少甚至只有一两张。在实际训练中这些噪声样本会造成检测框不稳定的情况建议设置一个最小面积或最小切片数阈值比如掩膜像素数大于一个固定下限才保留。6. 真正让人头秃的几个坑我踩过的和你可能会踩的6.1 单位不统一引发的坐标漂移我最早做肺结节检测时直接从XML里拿坐标画框结果框在图上怎么都对不齐边界偏移得厉害。排查后定位到原因XML里坐标是毫米DICOM像素坐标需要乘上PixelSpacing换算而当时代码里有个分支搞混了不同切片的spacing。这类问题很隐蔽因为视觉上偏移不是特别大没有经验的可能会当作标注误差忽略掉但模型指标会一直上不去。建议做法是写完坐标换算函数后把XML里的坐标和解析出的像素数组叠加画几个病例的切片可视化一眼就能看出来对不对。6.2 切片顺序不按文件名走有些散装版本的LIDC数据DICOM文件名可能是1.dcm、2.dcm这样递增但不代表切片的物理顺序就是递增的多序列扫描时尤其容易出问题。正确做法始终是先读取ImagePositionPatient的z值按z值排序后再索引。6.3 部分病例可能缺失标注文件1018例不是每一例都有对应的完整XML下载不完整或传输出错都可能缺文件。代码里读取XML时一定要做存在性校验缺XML的病例直接跳过否则后期训练崩溃排查起来费时又费力。遇到过有人没有跳过缺失文件导致索引错位几百个样本的标签全部错乱重跑又花了一周。6.4 数据泄露问题如果按病例划分训练集、验证集、测试集问题不大。但如果按切片级别划分或者按结节ID划分模型可能发生数据泄露。同一病人的多个结节来自同一个CT扫描信息高度相关切片级别划分会让模型见过同一病人的邻近切片评估结果虚高。正确的划分只按照患者ID做分层采样。6.5 结节中心切片的选取很多二维检测方案训练时只取结节中心附近的切片这样可以减少无效计算。但要注意结节的中心切片不要简单取掩膜z轴的中点很多时候标注轮廓在某几张切片上并不规则中间切片未必在三维掩膜的中心。稳妥的办法是逐切片计算掩膜面积取面积最大的切片作为代表切片。7. 延伸一步除了检测LIDC还能做些什么讲完标签和解析最后聊点更有意思的。LIDC的价值从来不只是检测肺结节在哪。那套完整的评分体系可以直接转换为多个辅助任务。比如用malignancy做良恶性分类是临床场景里最直接的需求用texture、margin、spiculation分别做纹理分类、边界分类、毛刺分类这些都是很有应用价值的细分方向。我做过的另外一个玩法是同一结节利用多医生标注做分歧学习。四位医生评分不一致的样本往往处于诊断模糊地带这类样本对模型的不确定性感知训练非常有价值。后来还在这类样本上做了置信度校准把模型输出的概率和医生分歧程度做相关性分析发现两者确实有相关性整个过程挺有启发的。还有一个更偏科研前沿的方向是用LIDC做弱监督语义分割的基准。有些模型设计上不依赖细粒度轮廓标注只给边界框或大概位置训练后对比掩膜重建的效果LIDC丰富的标注信息正好可以当评测基准来用。最后想说一下数据规模的心态问题。1018例看着不多但这是医学影像领域难得的带细粒度标注的大规模公开集了。合理用好它做出有说服力的结果完全够用。关键是前期的数据预处理和标签工程要足够扎实这部分工作做扎实了后面的模型训练会非常顺这也是我写这篇分享的原因。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价