简介这份CT肝脏肿瘤像素级分割数据集面向医学影像AI研究者、计算机辅助诊断开发者及肝胆外科相关科研人员用于解决肝脏与肿瘤自动分割模型训练中高质量标注数据稀缺的问题。数据集包含1900例腹部增强CT影像覆盖门静脉期、动脉期等多期相涉及肝细胞癌、转移瘤等多种肿瘤类型与尺寸由影像科与肝胆外科专家联合完成像素级标注明确区分背景、肝脏实质与肝脏肿瘤三类目标并已完成多期相配准、体素重采样与强度归一化划分训练集与验证集。资源包共2000个文件以png、jpg图像为主另含1个txt说明与1个py分析脚本压缩包约32.06MB采用7z格式。随附脚本支持一键生成肿瘤负荷统计、多期相对比、三维可视化及分割性能评估图表可直接用于nnUNet、3D U-Net等网络的训练与验证。目前已有38人学习适合开展肝脏肿瘤AI、手术规划与疗效监测研究。1. CT影像肝脏肿瘤像素级分割从影像到手术刀的距离放射科医生在阅片工作站上滚动鼠标滚轮一层一层翻看腹部增强CT试图在肝脏实质的背景里圈出肿瘤边界。这个动作重复几十次耗时半小时以上不同医生勾出的轮廓还可能差出一圈。像素级分割要解决的就是这件事让模型对每一层、每一个像素判断它属于肝脏、肿瘤还是背景输出一张和CT切片同尺寸的掩膜。它服务的下游场景很具体——肝癌精准诊断需要肿瘤体积、形态和血供区域的量化手术规划需要知道肿瘤离门静脉、肝静脉有多远消融或切除的边界留多少安全缘。适合谁上手有Python基础、懂一点医学影像DICOM常识、手里有带标注的腹部CT数据集的工程师或研究生。这篇笔记按“数据怎么进、模型怎么选、训练怎么不翻车、结果怎么验证”的顺序拆开讲不绕弯子。2. 数据管线从DICOM到可训练张量的四个转换关口2.1 为什么不能直接把DICOM丢给分割网络CT影像的原始格式是DICOM每个文件包含像素矩阵和一堆元数据。像素值本身是整数单位是HUHounsfield Unit反映组织对X射线的衰减系数。肝脏实质平扫大约在40到60 HU增强后血管和肿瘤的强化模式不同肿瘤可能到80到120 HU甚至更高。直接把原始像素值送进网络模型会被不同扫描仪的管电压、曝光量差异带偏。常见做法是先做窗宽窗位截断把腹部常用的窗宽设为400、窗位设为40把HU值截到[-160, 240]区间再归一化到[0,1]。这一步不做后面训练loss震荡的概率极高。另一个关口是层间距。腹部CT层厚常见1mm、3mm、5mm不同医院、不同设备扫出来的体素是各向异性的。像素级分割如果按2D逐层做层间信息丢失按3D做层间距不一致会让肿瘤在Z轴被拉长或压扁。我一般会先把所有体积重采样到统一间距比如1mm×1mm×1mm用三线性插值处理图像用最近邻插值处理标签避免标签出现小数类别。2.2 用Python把DICOM转成NIfTI并统一 spacing下面这段代码做三件事读取一个病例的DICOM序列、按空间位置排序、重采样到目标间距并保存为NIfTI。依赖pydicom、nibabel、numpy、scipy。import pydicom import numpy as np import nibabel as nib from scipy.ndimage import zoom import os def load_dicom_series(series_dir): 读取一个DICOM序列并按ImagePositionPatient的Z轴排序 slices [] for fname in os.listdir(series_dir): fpath os.path.join(series_dir, fname) try: ds pydicom.dcmread(fpath) if hasattr(ds, ImagePositionPatient): slices.append(ds) except Exception: continue # 按Z轴物理坐标排序不能按文件名不同设备命名规则不同 slices.sort(keylambda s: float(s.ImagePositionPatient[2])) return slices def dicom_to_hu(slices): 把原始像素值转成HU处理RescaleSlope和RescaleIntercept volume np.stack([s.pixel_array.astype(np.float32) for s in slices], axis-1) slope float(slices[0].RescaleSlope) intercept float(slices[0].RescaleIntercept) volume volume * slope intercept return volume def resample_volume(volume, orig_spacing, target_spacing(1.0, 1.0, 1.0), is_labelFalse): 按目标spacing重采样图像用三线性标签用最近邻 zoom_factors [o / t for o, t in zip(orig_spacing, target_spacing)] order 0 if is_label else 1 return zoom(volume, zoom_factors, orderorder) # 示例读取并转换 slices load_dicom_series(/data/patient_001/CT) hu_vol dicom_to_hu(slices) orig_spacing ( float(slices[0].PixelSpacing[0]), float(slices[0].PixelSpacing[1]), float(slices[0].SliceThickness) ) resampled resample_volume(hu_vol, orig_spacing, target_spacing(1.0, 1.0, 1.0)) nib.save(nib.Nifti1Image(resampled, np.eye(4)), /data/patient_001/ct_resampled.nii.gz)逻辑说明load_dicom_series里按ImagePositionPatient[2]排序是关键很多公开数据集的文件名顺序和实际层序不一致按文件名排会得到上下颠倒的体数据。dicom_to_hu里的RescaleSlope和RescaleIntercept必须从每个序列的头部读不同扫描协议这两个值可能不同。resample_volume的order参数决定插值方式标签用0即最近邻否则会出现0.5这种无意义类别。目标spacing选1mm各向同性是腹部分割的常见起点如果显存吃紧可以放宽到1.5mm×1.5mm×1.5mm。2.3 标签处理肝脏、肿瘤、背景的三类映射与类别不平衡像素级分割的标签通常来自医生勾画可能是DICOM RTSTRUCT、NIfTI或PNG序列。不管来源如何最终要映射成整数类别0背景、1肝脏、2肿瘤。这里有个容易翻车的点——肿瘤区域在标签里应该覆盖肝脏还是和肝脏互斥如果互斥肿瘤像素只标2肝脏像素只标1如果嵌套肿瘤像素同时属于肝脏和肿瘤。多数分割框架按互斥处理输出通道数为3取argmax。我一般用互斥方案因为推理时后处理简单。类别不平衡是绕不过去的。一个腹部CT体积里背景像素可能占95%以上肝脏占3%到5%肿瘤可能不到1%。如果loss直接用交叉熵模型会倾向于全预测背景dice系数看着还行但肿瘤全漏。常见做法是加Dice Loss或Tversky Loss或者用加权交叉熵给肿瘤类更高权重。权重怎么设我一般先统计训练集里各类像素频率取频率倒数的平方根再归一化避免权重过于极端导致训练不稳定。2.4 数据增强哪些变换安全哪些会引入伪影医学影像的增强和自然图像不一样。水平翻转在肝脏分割里通常安全因为肝脏左右叶的解剖位置在翻转后仍然合理。但垂直翻转要谨慎翻转后膈肌跑到下方模型可能学到错误的空间先验。旋转角度一般控制在±15度以内大角度旋转会让肝脏和周围器官的相对位置关系失真。弹性形变可以用但形变幅度要小否则肿瘤边界被扭曲后标签不再准确。强度变换方面加高斯噪声、调整对比度、模拟不同窗宽窗位都是安全的。但不要用颜色抖动CT是灰度图颜色变换没有物理意义。Cutout或CutMix在肝脏分割里要小心如果把肿瘤区域裁掉标签还在模型会学到“这块区域是肿瘤但像素被遮挡”的矛盾信号。我一般只在背景区域做Cutout。3. 模型选型2D、2.5D还是3D以及nnU-Net为什么常被当基线3.1 2D逐层分割的适用边界与显存账2D分割把每个CT层当成一张独立图像网络输入是H×W输出同尺寸的类别概率图。优点是显存占用小256×256的输入batch size可以开到32甚至64训练速度快。缺点是层间信息完全丢失肿瘤在上下层之间的形态变化模型看不到。对于肿瘤体积较大、边界在单层内比较清晰的病例2D U-Net能跑到0.85以上的Dice。但对于小肿瘤或边界模糊的病例2D模型容易在层间出现“锯齿”状预测因为相邻层的预测没有一致性约束。显存账怎么算以U-Net为例输入256×256×1第一层特征图64通道显存占用大约几十MB整个模型前向加反向在batch size 16时大约4到6GB。如果输入512×512显存翻四倍batch size要降到4左右。实际部署时如果目标是嵌入到医院PACS里做实时推理2D模型加TensorRT优化后单层推理可以压到50ms以内。3.2 2.5D把相邻三层当三通道输入的折中方案2.5D是我在数据量不大时最常用的方案。具体做法是把当前层、上一层、下一层堆成三通道输入变成H×W×3网络还是2D U-Net但第一层卷积的输入通道改成3。这样模型能看到层间上下文又不用承担3D卷积的显存开销。相邻层怎么选如果层间距是1mm取相邻一层就够如果层间距是5mm可能要取相邻两层让感受野覆盖10mm左右。2.5D的坑在于推理时的边界层。第一层没有上一层最后一层没有下一层常见做法是复制边界层或者用镜像填充。我一般复制第一层和最后一层虽然不完美但边界层的分割精度对整体体积影响很小。另一个坑是数据增强时三通道要同步变换不能对每个通道独立做旋转否则层间空间关系被破坏。3.3 3D U-Net与nnU-Net显存、patch size和深监督3D分割直接处理D×H×W体积。3D U-Net的编码器每层下采样解码器上采样中间用跳跃连接。显存是最大瓶颈一个128×128×128的patch单通道输入第一层32通道前向加反向在batch size 2时可能就超过12GB。常见做法是减小patch size到96×96×96或64×128×128同时用梯度累积模拟大batch。nnU-Net不是某个特定网络结构而是一套自动配置流程根据数据集的spacing、图像尺寸、类别数自动决定patch size、batch size、网络深度、归一化方式。它在多个医学分割挑战赛里表现稳定所以常被当基线。用nnU-Net做肝脏肿瘤分割基本流程是把数据整理成它要求的目录格式跑nnUNet_plan_and_preprocess再跑nnUNet_train。它的默认配置对腹部CT通常够用但如果肿瘤特别小可能需要手动调patch_size和batch_size。深监督是3D U-Net里常用的技巧在解码器的每个尺度输出一个分割结果和真值算loss最后加权求和。这样做的好处是梯度能更直接地传到浅层缓解深层网络的梯度消失。权重一般从深到浅递减比如最深输出权重1.0中间0.5最浅0.25。3.4 损失函数组合Dice、交叉熵与边界Loss的配比单一Dice Loss在类别极不平衡时可能训练不稳定因为Dice对每个类别的梯度贡献和类别像素数成反比肿瘤类像素少梯度反而大容易震荡。常见组合是Dice Loss加交叉熵权重各0.5。如果肿瘤边界分割不理想可以再加边界Loss比如用形态学操作提取肿瘤边界对边界像素算二值交叉熵权重设0.1到0.2。我一般会监控两个指标整体Dice和肿瘤类Dice。如果整体Dice到0.9但肿瘤Dice只有0.6说明模型在背景和肝脏上表现好肿瘤欠拟合这时候要加大肿瘤类权重或加边界Loss。如果训练loss下降但验证loss上升先检查数据增强是否过强再检查有没有病例的标签和图像错位。4. 训练与推理从patch采样到后处理的完整链路4.1 patch采样策略前景过采样比例怎么定3D训练不能把整个体积塞进网络要采样patch。如果随机均匀采样大部分patch里没有肿瘤模型学不到肿瘤特征。常见做法是前景过采样以一定概率采样包含肿瘤的patch剩余概率采样包含肝脏的patch再剩余采样随机patch。比例我一般设肿瘤:肝脏:背景3:3:1。如果肿瘤特别小可以提到5:3:1。采样时还要注意patch不能超出体积边界。如果肿瘤靠近肝脏边缘patch可能包含肝脏外的区域这没问题但标签要正确映射。另一个细节是训练时的patch中心和验证时的滑窗推理要一致否则训练和推理的分布不匹配。4.2 学习率、优化器与早停腹部CT分割的常用参数优化器用AdamW比Adam更稳weight decay设1e-5到1e-4。初始学习率我一般设1e-3或3e-4配合余弦退火或ReduceLROnPlateau。如果batch size较小比如2学习率要相应降低否则梯度噪声大。训练轮数看数据量100到300个病例通常跑200到500个epoch。早停的patience设20到30监控验证集肿瘤Dice如果连续20轮不升就停。混合精度训练在3D分割里几乎是必选项能把显存占用降30%到40%速度提升20%以上。PyTorch里用torch.cuda.amp注意loss scaling要开否则小梯度可能下溢。4.3 滑窗推理与重叠区域融合gaussian权重还是平均推理时如果体积大于显存能容纳的patch要用滑窗。窗口按patch size滑动相邻窗口有重叠重叠区域的预测要融合。最简单的是平均但边界区域的预测通常不如中心区域可靠所以常用高斯权重窗口中心权重高边缘权重低。高斯核的sigma一般设patch size的1/8到1/4。滑窗的步长决定重叠程度。步长等于patch size时没有重叠推理最快但边界可能有拼接痕迹。步长设patch size的一半重叠50%融合后边界平滑。步长再小推理时间线性增加。我一般用50%重叠加高斯权重对大多数腹部CT够用。4.4 后处理连通域、孔洞填充与肿瘤体积计算网络输出的掩膜可能有小孔洞或孤立小连通域。后处理第一步是保留最大连通域去掉面积小于阈值的孤立区域。阈值怎么定按物理体积算小于50mm³的肿瘤区域可能是噪声。第二步是孔洞填充用形态学闭运算或scipy的binary_fill_holes。第三步是按类别分别处理肝脏掩膜填充后可以作为肿瘤掩膜的约束——肿瘤像素如果落在肝脏掩膜外可能是假阳性可以去掉。肿瘤体积计算是下游诊断和手术规划的直接输入。像素数乘以体素体积比如1mm×1mm×1mm1mm³就得到体积。如果重采样过要用重采样后的spacing算。体积的重复测量一致性可以用Bland-Altman分析看模型和医生勾画的体积差异是否在临床可接受范围内。5. 避坑与排查肝脏肿瘤分割里最容易翻车的五件事5.1 现象训练loss正常下降但验证Dice始终在0.3以下原因标签映射错误。常见情况是标签里肝脏和肿瘤的像素值不是0/1/2而是0/1/2/3或0/255模型学到的类别和评估时用的类别对不上。另一个可能是图像和标签的层序不一致比如图像按Z轴升序、标签按降序。解决训练前先可视化几个病例的图像和标签叠加确认肿瘤区域在图像上确实是低密度或强化的区域。用nibabel读标签后打印唯一值确认只有预期的类别。如果层序不一致统一按ImagePositionPatient排序后重新保存。5.2 现象肿瘤Dice在0.7左右震荡偶尔掉到0.5原因前景过采样比例不当或损失函数权重不合理。如果肿瘤patch采样太少模型看到的肿瘤样本不足如果Dice Loss权重过高训练不稳定。解决把肿瘤过采样比例从3提到5同时把Dice Loss和交叉熵的权重从1:1调到1:2让交叉熵主导初期训练Dice在后期微调。监控每个epoch的肿瘤类梯度范数如果范数忽大忽小降低学习率或加梯度裁剪。5.3 现象推理结果在肿瘤边界出现明显锯齿或阶梯原因2D逐层推理没有层间一致性约束或者滑窗步长太大导致拼接痕迹。解决如果用的是2D模型改成2.5D输入让相邻层参与预测。如果已经是3D模型检查滑窗步长改成patch size的50%并加高斯权重。另一个可能是重采样时用了三线性插值处理标签导致边界模糊检查标签是否用最近邻。5.4 现象不同扫描仪的测试数据上Dice差异超过0.15原因HU分布不一致。不同管电压下同一组织的HU值可能差10到20归一化方式如果按全局均值和方差跨设备泛化差。解决改用窗宽窗位截断加固定范围归一化比如统一截到[-160, 240]再除以400。如果训练集里有多设备数据可以做设备特定的直方图匹配把测试图像的HU直方图对齐到训练集的平均直方图。另一个做法是在训练时加更强的强度增强模拟不同设备的HU偏移。5.5 现象模型在肝脏边缘预测出肿瘤假阳性原因肝脏边缘的血管、胆囊、胃肠道壁在增强CT上可能呈现高强化和肿瘤强化模式相似。模型如果只学了强度特征容易混淆。解决在输入里加空间先验比如把肝脏掩膜作为额外通道输入让模型知道哪些区域是肝脏内部。或者在损失函数里对肝脏外区域的肿瘤预测加惩罚。后处理时用肝脏掩膜约束去掉肝脏外的肿瘤连通域。如果假阳性在胆囊附近可以考虑在训练数据里增加胆囊标注让模型学到胆囊和肿瘤的区别。6. 把分割结果用起来从Dice到手术安全缘的最后一公里Dice到0.85以上是不是就够用了不一定。手术规划关心的是肿瘤到主要血管的距离这个距离对边界像素的误差很敏感。如果肿瘤边界预测偏了2mm安全缘可能从5mm变成3mm临床决策就变了。所以验证不能只看Dice还要看边界距离误差。我一般会算两个额外指标95% Hausdorff距离和平均表面距离。95% HD对离群边界点敏感能反映最坏情况下的边界偏差ASD反映整体边界贴合程度。具体怎么算用medpy或surface-distance库。下面这段代码算95% HD和ASDimport numpy as np from medpy.metric.binary import hd95, asd def evaluate_boundary(pred_mask, gt_mask, spacing(1.0, 1.0, 1.0)): pred_mask, gt_mask: 二值numpy数组肿瘤为1背景为0 spacing: 体素物理间距单位mm if pred_mask.sum() 0 or gt_mask.sum() 0: return None, None hd hd95(pred_mask, gt_mask, voxelspacingspacing) surface_dist asd(pred_mask, gt_mask, voxelspacingspacing) return hd, surface_dist # 示例 pred np.zeros((128, 128, 64), dtypebool) gt np.zeros((128, 128, 64), dtypebool) # 假设pred和gt已经填充了肿瘤区域 hd, asd_val evaluate_boundary(pred, gt, spacing(1.0, 1.0, 1.0)) print(f95% HD: {hd:.2f} mm, ASD: {asd_val:.2f} mm)参数说明voxelspacing要和重采样后的spacing一致如果重采样到1mm各向同性就传(1,1,1)。hd95返回的是95百分位的Hausdorff距离单位是mm。临床可接受的阈值因医院而异我见过的要求是95% HD小于5mm、ASD小于1.5mm。如果超过这个范围要回头检查训练数据里边界模糊的病例是否太多或者后处理的连通域操作是否把肿瘤边缘削掉了。另一个进阶用法是把分割结果叠加到3D重建里做术前模拟。用vtk或pyvista把肝脏、肿瘤、血管分别渲染成不同颜色旋转观察肿瘤和血管的空间关系。这一步不需要深度学习但能让外科医生直观判断手术入路。我一般会导出STL文件给手术团队他们可以在自己的软件里进一步测量。最后说一个我踩过的坑早期我只看Dice模型在测试集上Dice 0.88但外科医生反馈肿瘤边界在靠近下腔静脉的地方明显偏了。后来加了边界距离评估发现那几例的95% HD到了8mm。回头查数据发现训练集里靠近大血管的肿瘤病例很少模型没学到血管对肿瘤边界的挤压效应。补了20例这类病例后HD降到4mm以内。所以Dice高不代表临床可用边界距离和血管关系才是手术规划真正在意的。希望帮到你。本文还有配套的精品资源点击获取