LIDC-IDRI肺结节CT数据集详解:从DICOM解析到pylidc与预处理实战
2026/9/7 2:39:31 网站建设 项目流程

简介:这一工具包专为LIDC-IDRI肺结节CT数据集设计,面向医学影像研究人员与算法开发者,用于高效提取、转换和分析肺结节病灶的CT图像及专家标注信息。压缩包共24个文件,以MATLAB脚本(8个m)为主,附带文本说明、Perl脚本与Shell脚本等辅助文件,整体仅182KB。脚本覆盖XML注解解析、掩模与真实图像生成、像素映射转换、大数据拆分、文件批量查找及路径修正等关键环节,可显著降低处理LIDC数据集的入门门槛。资源已有2940人学习,适合从事肺结节检测、分割、特征提取及机器学习算法评估的中高级研究人员参考使用。通过该工具包,用户能直接复用现成的MATLAB函数完成从原始CT到可分析标注的完整流水线,节省自行编写解析与预处理代码的时间,并借助清晰的许可与说明文档快速上手。 第一次解压完从TCIA下载的LIDC-IDRI数据集,我盯着满屏幕的DICOM文件夹和XML文件愣了很久。这个被无数肺结节深度学习论文引用过的公开数据集,原始形态远没有论文里写得那么友好——一堆编号杂乱的子目录、以二进制结构存储的CT影像序列,以及一套完全独立的XML标注体系,三者之间的对应关系全靠自己摸索。如果不借助工具包,光是"读懂标注"这一关就能劝退一大半新手。这篇文章把我这两年用LIDC数据集做肺结节检测和分割的经验整理出来,核心回答两个问题:数据集本身该怎么理解,以及有哪些工具包能把这套流程从"地狱难度"降到"普通人可操作"。文中涉及的代码和踩坑记录,都是我在实际项目中验证过的。

1. 拿到LIDC压缩包后,第一步该做什么

1.1 先搞清楚数据集的原始形态

LIDC-IDRI全称是Lung Image Database Consortium and Image Database Resource Initiative,是目前公开可获取的规模最大、标注最完整的肺结节CT数据集之一。它包含约1018个病例的CT扫描,累计影像大概在24万张以上。每个病例除了原始的DICOM影像外,还会附带一个XML标注文件——这才是这个数据集最有价值也最难啃的部分。

从数据组织看,TCIA下载回来的是一个很大的manifest目录,里面有LIDC-IDRI-0001到LIDC-IDRI-1018这样的子目录,每个子目录里又按检查日期和CT序列拆成更小的文件夹。很多第一次接触的人在这里就绕晕了:一个病例可能会包含多个CT序列,比如薄层1mm扫描和厚层3mm扫描会同时存在;如果不看Series Description或Series Instance UID,很容易把不同序列当成同一个数据。建议下载后第一时间给每个病例建立索引表,记录patient_id、序列UID、层厚、像素间距等信息,这会在后面对齐标注时省下大量时间。

1.2 标注文件里到底写了什么

XML标注不是简单的一个"结节列表",而是四位经验丰富的胸部放射科医生各自独立标注的结果。每一位医生的反馈都完整保存:他画了哪些结节、每个结节用什么轮廓表示、钙化程度怎么样、毛刺和分叶特征如何打分、恶性程度评几分,全部一一对应放好。

具体到结节定义,LIDC把标注对象分成三类:直径≥3mm的结节,标注者要给出完整的边界轮廓,这类结节构成绝大多数深度学习实验的ground truth;直径<3mm的小结节,只记录一个粗略的质心位置,没有完整轮廓,一般当作定位信息使用;还有一类是"非结节",指疑似结节但医生最后判断不是结节的区域。很多论文在做检测训练时,会把这第三类作为难例负样本纳入训练,因为和真实结节在形态上非常接近,能有效降低误检率。

这里要特别提醒:同一个结节的标注可能多达四份,四份轮廓往往不完全重合。这是由标注者的主观判断差异造成的,也是后续使用pylidc工具包时不可避免的问题。

2. pylidc:把LIDC的标注变成代码的工具

2.1 安装、初始化与目录关联

pylidc是专门为LIDC-IDRI标注数据编写的Python库,作用可以理解为"XML翻译官"——它会扫描你下载好的原始目录,把XML里的所有标注信息解析出来,存成一个轻量级查询接口,然后你就能用Python对象的方式直接操作。安装非常直接:

pip install pylidc

注意,pylidc只是解析工具,不包含原始CT数据,所以TCIA上下载的DICOM文件仍然需要提前就位。第一次使用前要做目录关联,把数据目录指给pylidc,最简单的方式是把下载并解压后的manifest文件夹放到pylidc默认的数据路径下,或者在代码里通过环境变量、全局变量指向数据位置。由于不同版本的pylidc对这个细节的处理略有差异,建议用的时候先看一下当前版本的说明文档,配置完成后执行一次查询,能正常返回结果再开始下一步。

2.2 核心API:从Scan到Nodule再到轮廓

pylidc的使用逻辑很清晰,一句话总结就是:Scan代表一个CT序列,Annotation代表一位医生的一次标注,Nodule代表多个医生标注聚类后的一个结节对象。我用得最多的组合是这样:

import numpy as np import pylidc from pylidc.utils import query scans = query(patient_id="LIDC-IDRI-0001") scan = scans[0] # 一个Scan对象 vol, seg = scan.load_to_volume() # 体数据 + 肺实质分割结果 for nodule in scan.cluster_annotations(): if len(nodule) < 2: continue # 过滤掉只有一个医生标注的弱标签 print("结节体积:", nodule.volume, "mm^3") for ann in nodule: for contour in ann.contours: # 取出轮廓点的x/y/z坐标,实际属性名以当前版本文档为准 xs = np.array(contour.x) ys = np.array(contour.y) zs = np.array(contour.z) # 用这些坐标去画切片掩码

scan.load_to_volume()返回一个元组,第一个是CT体数据,通常shape为(切片数, 行, 列);第二个是同样尺寸的肺实质分割结果。pylidc会把DICOM的原始像素值转换成适合显示的HU数值,这一点对不熟悉医学影像读数的开发者非常友好。

2.3 版本兼容性这个真实的坑

pylidc是典型的老牌工具库,功能强但更新慢,最大的隐患是和最新科学计算库的兼容性。我初学时在numpy 1.21环境下直接调用load_to_volume,遇到np.double被移除导致的报错。这类错误网上讨论很多,解决办法也简单:要么把numpy降级到1.19以下,要么手动把pylidc源码中涉及np.double、np.float的位置改成np.float64。我偏向后者,因为项目里其它依赖通常不允许随意降级numpy。

另外一个常见问题是,pylidc默认会把解析出来的标注缓存到本地数据库中。如果数据目录很大,首次构建索引会非常慢,看起来像卡死;你只需要耐心等它跑完,后续查询就会很快。这一步也建议在训练集准备之前一次性完成,避免中途反复扫描。

3. 从DICOM到训练样本:CT预处理流水线搭建

3.1 HU值转换与窗宽窗位

PACS系统里存下来的DICOM像素值基本都不是真实的CT值。读取时需要使用标签中的RescaleSlope和RescaleIntercept,按公式HU = pixel × slope + intercept做转换,得到的才是一张CT影像里真正有物理意义的密度值。肺结节的密度分布和骨头、软组织差异很大,统一转成HU后,模型才能学到可迁移的特征。

同样重要的是窗宽窗位。肺结节的AI任务几乎都会用肺窗来观察,常规设置是窗宽1500HU、窗位-500HU到-550HU。简单解释就是窗宽决定对比度范围,窗位决定显示的中心值,通过WindowLevel和WindowWidth对HU值做截断映射,能把肺实质里细微的结节病变从背景里"拉"出来。pylidc的load_to_volume默认已经做了一部分类似处理,但如果你用pydicom自己写读取管线,这一步就不能省。

3.2 体素重采样和坐标体系

不同医院的CT扫描层厚不一,即便LIDC内部也存在1mm、1.5mm、3mm等多种层厚。如果直接把这些数据丢进3D神经网络,不同体素间距会让模型看到的病灶形状严重失真。我习惯用SimpleITK把扫描统一重采样到1mm×1mm×1mm的各向同性体素:

import SimpleITK as sitk image = sitk.ReadImage("dicom文件夹路径") image = sitk.Cast(image, sitk.sitkFloat32) spacing = image.GetSpacing() size = image.GetSize() new_spacing = [1.0, 1.0, 1.0] new_size = [ int(round(size[0] * spacing[0] / new_spacing[0])), int(round(size[1] * spacing[1] / new_spacing[1])), int(round(size[2] * spacing[2] / new_spacing[2])), ] resampler = sitk.ResampleImageFilter() resampler.SetOutputSpacing(new_spacing) resampler.SetSize(new_size) resampler.SetInterpolator(sitk.sitkLinear) resampled = resampler.Execute(image)

重采样之后最重要的事,是把标注坐标一起变换过去。DICOM的坐标系是LPS(左右、前后、上下),而numpy数组的index顺序通常和DICOM的轴向并不一致,从pylidc拿到的轮廓坐标必须按相同的重采样比例进行缩放,同时在轴向映射上做对应翻转或换轴。坐标对不上是所有预处理bug里最隐性的一种,模型训练不会报错,但所有预测结果在临床解释上全是错的。

3.3 生成结节掩码与2D切片导出

有了轮廓坐标,生成掩码就简单了。对每个二维切片,用skimage.draw.polygon把多边形内部填成1,再放到3D矩阵对应的Z层上即可。如果目标是训练2D检测器,比如用yolov8做切片级别的结节检测,还需要把3D掩码映射到每一张2D切片上,导出的同时记录边界框。

我一般的流程是:先对每个结节生成一个固定尺寸的3D patch,例如32×32×32像素,中心对齐结节质心;训练3D网络时使用的是这些patch而不是整张CT图,既能控制显存占用,也能让模型聚焦病灶区域。这些patch和对应的3D掩码可以保存成npy或NIfTI格式,后续训练3D分割网络时直接使用。

4. 用工具包之前就要想清楚的几个事

4.1 数据划分必须以患者为单位

LIDC的1018个病例里,很多病人不止一个CT序列。如果你简单按"序列"或"扫描文件"去划分训练集和测试集,同一个患者的薄层扫描很可能同时出现在两边,模型表面验证指标虚高,一到外部数据就崩。正确做法是先提取全部patient_id去重,再以患者为最小单位划分训练、验证、测试集。从query中拿到全部scan对象后,可以用set收集唯一的patient_id,然后做随机划分。

这一点看似简单,但在实际项目里仍然是最容易被忽视的问题。我在陪跑多个新手项目时,发现不少同学是先看到数据集目录结构,自然而然按文件夹划分了,最后结果还很漂亮,实际上全是过拟合的假象。

4.2 标注者一致性阈值怎么选才算严谨

四位医生的标注各不相同,pylidc的cluster_annotations会把空间上重合的标注自动聚合成一个Nodule对象,但具体要用"至少几位医生同意"的结节作为正样本,需要你自己定。下表是我常用的一致性阈值参考:

一致性阈值正样本数量趋势标签质量适用任务
至少1名医生最多含单医生主观噪声通用预训练、样本量优先
至少2名医生较多噪声明显下降检测与初筛
至少3名医生中等较稳定分割与多中心迁移
4名医生全部一致很少最干净但样本稀缺小样本高精度评估

官方口径下至少一名医生标注为≥3mm的结节大约有2669个,如果收敛到至少3名医生一致,数量会明显下降;但很多论文宁可牺牲数量也要换标注质量,尤其是做分割任务时。我的建议是:检测任务可以先从"至少2名医生一致"起步,把数据准备成多份版本,训练后对比不同阈值下的指标波动。这个波动本身就能反映标注噪声对任务的影响程度。

4.3 恶性程度评分怎么用

XML里每个标注还带有恶性程度评分,范围1到5,分数越高恶性可能性越大。pylidc对单个Nodule对象暴露了malignancy属性,取到的是几位医生评分的一个列表。如果你做分类任务,常见的两种做法是把评分映射成二分类(1-2为良性、4-5为恶性、3剔除),或者直接在1到5的整数上做回归。

我的经验是,不要轻易把评分3的样本强行归到良性或者恶性,因为医生在犹豫不决的病灶上给的边界评分,恰恰包含了对临床诊断最有价值的模糊信息。把评分3单独作为一个轻度可疑类别,或者做排序任务,常常比硬编码二分类得到更稳定的模型。

5. 把工具包组合起来才是完整方案

5.1 肺实质分割工具的联动

pylidc自带的load_to_volume虽然会返回一个肺实质分割结果,但这个分割质量比较基础,复杂病例容易出现漏分割。我在实际项目中会再叠加一个专门的肺实质分割模型,比如基于nnU-Net训练的开源工具,或者TotalSegmentator。先切出肺实质区域,再在这个区域内做结节检测,能去掉一大片胸壁、肋骨、血管带来的误检干扰,模型收敛速度和最终精度都会有可感知的提升。

5.2 从LIDC到外部数据集的迁移

LIDC确实很适合做预训练和baseline,但要清醒地看到它的局限:数据来源集中、标注者人数有限、设备型号相对固定。我在把LIDC训练的模型迁移到自己合作医院的CT数据时,发现直接fine-tune的效果一般,需要重新做窗宽窗位和数据分布对齐。另一个技巧是,在LIDC上训练阶段就刻意保留一部分层厚不均的样本,让模型在体素间距变化的输入上更鲁棒,这比只喂各向同性重采样数据效果好。

5.3 一点个人经验总结

最后说点真正的经验教训。工具包本质上只能帮你读取和组织数据,不能替你去理解数据里的医学语义。pylidc用熟之后,建议找几个病例把轮廓坐标画在DICOM切片上肉眼检查一遍,感受一下不同医生画法的差异,这会直接影响你怎么设阈值、怎么定loss、怎么做数据增强。我后来做肺结节AI项目的绝大部分方法论调整,都不是从哪篇SOTA论文里抄来的,而是在这种最朴素的"看数据"过程里产生的。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询