信息速览
INFOBOX
| 数据集名称 | LiTS (Liver Tumor Segmentation Benchmark) |
| 英文全称 | Liver Tumor Segmentation Benchmark |
| 别名 / 简称 | LiTS17、LiTS Challenge、Liver Tumor Segmentation、MSD Task03 Liver |
| 疾病分类 | 肝脏肿瘤(KK73 恶性肿瘤 / 2C12 肝细胞癌 / 2C14 肝内胆管癌 / 2B72 继发性肝恶性肿瘤) |
| SNOMED CT | 254837009 Liver tumor / 82711006 Malignant tumor of liver / 372031002 Hepatocellular carcinoma / 93761008 Liver metastasis |
| 数据模态 | 影像(对比增强腹部 CT,门静脉期,3D 体数据) |
| 标注类型 | 语义分割(3 类:背景 / 肝脏 / 肿瘤),逐像素标注 |
| 标注格式 | NIfTI (.nii),0=背景, 1=肝脏, 2=肿瘤 |
| 总样本数 | 201 例 CT 体数据(131 训练 + 70 测试) |
| 切片总数 | 约 58,638 张轴向切片(训练集) |
| 多中心来源 | 7 家临床机构(德国 / 荷兰 / 加拿大 / 法国 / 以色列) |
| 图像尺寸 | ~512×512×(42~1026)轴向切片,中位数 512×512×432 |
| 体素间距 | 面内 0.56~1.0 mm,层厚 0.45~6.0 mm |
| 肿瘤数量/例 | 0~12 个 |
| 肿瘤体积范围 | 中位数 16.11×10³ mm³(训练集),34.78×10³ mm³(测试集) |
| 标注者 | 4 名放射科医生(R1 原始共识标注 → R2 重新标注 15 例 → R3 委员会认证审核 → R4 最终校正) |
| 标注者间一致性 | 肿瘤 Dice 70.2%(R1 vs R2 新标注)/ 95.2%(R1 vs R3/R4 委员会认证) |
| 许可证 | CC-BY-NC-SA 4.0 |
| 数据集版本 | v1 (ISBI 2017) / v2 (MICCAI 2017, 新增肝脏分割任务) / v3 (MICCAI-MSD 2018) |
| 下载大小 | ~15 GB 压缩 / ~53 GB 解压 |
| 评测平台 | CodaLab 在线评测(3414+ 次提交,900+ 注册用户) |
| 核心论文 | Bilic et al., Medical Image Analysis 84:102680, 2023 |
| DOI | 10.1016/j.media.2022.102680 |
| GitHub | github.com/PatrickChrist/LiTS-CHALLENGE |
§0 E-E-A-T 信任声明
本条目由千方病案医学编辑部依据以下权威来源编写:
- 原始论文:Bilic et al., “The Liver Tumor Segmentation Benchmark (LiTS)”, Medical Image Analysis, Vol. 84, Article 102680, 2023. DOI: 10.1016/j.media.2022.102680
- 官方挑战赛:CodaLab 在线评测平台 competitions.codalab.org/competitions/17094
- 数据来源:7 家临床机构跨 4 国联合提供(TUM 德国 / Radboud 荷兰 / Polytechnique Montreal & CHUM 加拿大 / IRCAD 法国 / Sheba & Hebrew University & Hadassah 以色列)
- 后续基准:Medical Segmentation Decathlon (MSD) Task03 Liver 直接使用 LiTS 数据
本条目所有数据指标均引用自上述原始论文和官方平台,SOTA 排行榜数据截至 2026 年 8 月。
§1 数据集概览
§1.1 基本信息
LiTS(Liver Tumor Segmentation Benchmark)是医学影像分割领域最经典的肝脏及肝脏肿瘤 CT 分割基准数据集。该数据集于 2016 年 12 月首次发布训练数据,先后在 IEEE International Symposium on Biomedical Imaging (ISBI) 2017、International Conferences on Medical Image Computing and Computer-Assisted Intervention (MICCAI) 2017 和 MICCAI 2018(作为 Medical Segmentation Decathlon 的一部分)三届挑战赛中使用。
数据集包含 201 例对比增强腹部 CT 扫描(131 例训练 + 70 例测试),来自全球 7 家临床机构、跨 4 个国家(德国、荷兰、加拿大、法国、以色列)。所有 CT 均为门静脉期(portal venous phase)增强扫描,涵盖原发性肝肿瘤(肝细胞癌 HCC、胆管癌 cholangiocarcinoma)和继发性肝转移瘤(结直肠癌、乳腺癌、肺癌来源)。
标注为 3 类语义分割(背景=0、肝脏=1、肿瘤=2),由 4 名放射科医生经多轮审核流程完成。测试集 70 例的标注不公开,需通过 CodaLab 平台在线提交预测结果进行评测。
截至 2022 年,CodaLab 平台累计接收 3414+ 次有效提交(238,980 个体积分割),注册用户 900+ 人,最佳肿瘤 Dice 达 82.5%。
§1.2 历史背景与定位
LiTS 在医学影像分割领域具有独特的里程碑地位:
| 维度 | 定位 |
|---|---|
| 历史意义 | 首个大规模多中心肝脏肿瘤 CT 分割基准,推动了级联 U-Net 架构的普及 |
| 方法学影响 | 75 个提交算法中,级联 coarse-to-fine 策略成为主流范式 |
| 3D 转折点 | ISBI 2017(以 2D/2.5D 为主)→ MICCAI 2018(3D 方法主导),LiTS 见证了 3D 深度学习在医学分割中的崛起 |
| MSD 贡献 | LiTS 数据集构成 Medical Segmentation Decathlon 的肝脏分割任务(Task03 Liver) |
| 持续活跃 | CodaLab 平台至今仍接受在线提交,2022 年结果显著优于 2021 年 |
| 与 3D-IRCADb 关系 | LiTS 训练集包含 3D-IRCADb 全部 20 例数据(来自 IRCAD France) |
| 与 SLIVER07 关系 | SLIVER07 是更早期的肝脏分割数据集(30 例 CT,仅肝脏标注,无肿瘤),二者可合并使用 |
§1.3 核心统计
| 统计项 | 训练集 | 测试集 |
|---|---|---|
| CT 扫描数 | 131 | 70 |
| 含肿瘤病例数 | 124(94.7%) | 70(100%) |
| 肿瘤数中位数(IQR) | 3(1, 9) | 5(2, 12) |
| 肿瘤体积中位数(mm³) | 16.11×10³(3.40×10³, 107.77×10³) | 34.78×10³(10.41×10³, 98.90×10³) |
| 肝脏体积(mm³,均值±SD) | 1586.48×10³ ± 447.14×10³ | 1622.64×10³ ± 546.48×10³ |
| 面内分辨率中位数(mm) | 0.76(0.7, 0.85) | 0.74(0.69, 0.8) |
| 层厚中位数(mm) | 1.0(0.8, 1.5) | 1.5(0.8, 4.0) |
| 轴向切片数范围 | 42~1026 | — |
| 平均肿瘤 HU 值 | 65 | 59 |
| 训练/测试差异 p 值 | — | 肿瘤数 p=0.016, 层厚 p=0.004, 肝脏体积 p=0.60 |
§2 医学背景
§2.1 肝脏肿瘤的医学概述
肝脏是原发性肿瘤(起源于肝脏本身)和继发性肿瘤(从其他器官转移至肝脏)的高发部位。
原发性肝肿瘤:
- 肝细胞癌(HCC):最常见的原发性肝脏恶性肿瘤,全球癌症死亡率第三位。HCC 通常在慢性肝病(乙肝、丙肝、酒精性肝硬化)基础上发生。CT 门静脉期表现为低密度病灶(相对于肝实质),动脉期可能显示强化("快进快出"模式)。
- 肝内胆管癌(ICC):第二大原发性肝恶性肿瘤,起源于肝内胆管上皮。CT 表现为延迟期渐进性强化的肿块。
继发性肝肿瘤(肝转移瘤):
- 结直肠癌肝转移:最常见,约 50% 结直肠癌患者会发生肝转移。CT 表现为低密度环形强化病灶。
- 乳腺癌肝转移:多发性、较小病灶,预后较差。
- 肺癌肝转移:常为多发性、快速生长。
§2.2 CT 门静脉期的临床意义
LiTS 数据集采集的均为**门静脉期(portal venous phase)**增强 CT,这是肝脏影像评估的标准时相:
| 时相 | 时间(注射后) | 肝实质强化 | 临床用途 |
|---|---|---|---|
| 平扫期 | 0 | 无强化 | 基线、钙化检测 |
| 动脉期 | 25-35s | 主动脉高密度,肝实质轻微强化 | HCC 检测(富血供肿瘤强化) |
| 门静脉期 | 60-70s | 肝实质均匀强化(峰值) | 肝脏实质评估、肿瘤检出(低密度对比) |
| 延迟期 | 3-5min | 强化消退 | ICC 检测(延迟强化)、血管瘤鉴别 |
门静脉期是肝脏肿瘤检测和分割的首选时相,因为正常肝实质在此期达到最大强化(密度最高),而大多数肿瘤(尤其是 HCC 和转移瘤)在此期表现为低密度,形成最佳的肿瘤-肝实质对比度。
§2.3 自动分割的临床需求
-
手术规划:精确的肝脏和肿瘤三维重建是肝段切除(hepatectomy)术前规划的核心。自动分割可显著缩短规划时间(从数小时降至分钟级)。
-
肿瘤负荷评估:通过自动分割计算肿瘤总体积占肝脏体积的比例(tumor burden),用于疗效监测和预后评估。
-
介入治疗导航:TACE(经动脉化疗栓塞)和 RFA(射频消融)需要精确定位肿瘤位置和边界。
-
筛查与随访:慢性肝病患者需定期 CT 随访,自动分割可提高效率和一致性。
§3 数据规格
§3.1 影像参数
| 参数 | 值 |
|---|---|
| 模态 | 对比增强腹部 CT(门静脉期) |
| 文件格式 | NIfTI (.nii) |
| 图像位深 | 16-bit integer(Hounsfield Unit) |
| 图像矩阵 | ~512×512 像素(面内) |
| 面内分辨率 | 0.56~1.0 mm |
| 层厚 | 0.45~6.0 mm |
| 轴向切片数 | 42~1026 层/例 |
| CT 体素矩阵中位数 | 512×512×432(训练集)/ 512×512×270(测试集) |
| HU 值范围 | -1024~3071(标准 CT) |
| 常用 HU 裁剪窗 | [-200, 200] 或 [-160, 240] 或 [-100, 400] |
§3.2 标注规范
| 项目 | 规范 |
|---|---|
| 标注类别 | 3 类:0=背景 (Background), 1=肝脏 (Liver), 2=肿瘤 (Tumor) |
| 标注格式 | NIfTI (.nii),与 CT 体数据一一对应 |
| 标注密度 | 逐像素(全切片标注,非稀疏标注) |
| 标注流程 | R1 原始共识标注 → R3 委员会认证放射科医生审核修正 → R4 委员会认证放射科医生最终校正 |
| IAA 评估 | R2 从零重新标注 15 例,计算 R1 vs R2 的 Dice |
| 肿瘤标注标准 | 包括原发性和继发性肿瘤,不区分肿瘤亚型(HCC/ICC/转移瘤均为 label=2) |
§3.3 各机构数据贡献
| 机构 | 国家 | 训练集 | 测试集 |
|---|---|---|---|
| Rechts der Isar Hospital, TUM | 德国 | 28 | 28 |
| Radboud University Medical Center | 荷兰 | 48 | 12 |
| Polytechnique Montreal & CHUM Research Center | 加拿大 | 30 | 25 |
| IRCAD | 法国 | 20 | 0 |
| Sheba Medical Center / Hebrew University / Hadassah | 以色列 | 5 | 5 |
| 合计 | — | 131 | 70 |
注意:IRCAD 贡献的 20 例训练数据即为 3D-IRCADb 数据集的全部病例。合并使用时须注意避免数据泄漏。
§3.4 数据多样性
LiTS 数据集在设计上充分考虑了临床多样性:
-
肿瘤类型多样性:HCC、胆管癌、结直肠癌/乳腺癌/肺癌肝转移
-
病变密度多样性:高密度(hyperdense)和低密度(hypodense)肿瘤
-
治疗状态多样性:治疗前和治疗后 CT 混合
-
图像质量多样性:不同 CT 扫描仪、不同采集协议、金属伪影等真实临床常见问题
-
分辨率多样性:面内 0.56-1.0 mm,层厚 0.45-6.0 mm(跨数量级差异)
-
肿瘤大小多样性:38 mm³ 至 1231×10³ mm³
§4 数据结构
§4.1 文件组织
LiTS/
├── Training_Batch1/
│ ├── volume-0.nii # CT 体数据 (case 0)
│ ├── segmentation-0.nii # 分割标注 (case 0)
│ ├── volume-1.nii
│ ├── segmentation-1.nii
│ └── ... (volume-0 to volume-27, 28 cases)
│
├── Training_Batch2/
│ ├── volume-28.nii
│ ├── segmentation-28.nii
│ └── ... (volume-28 to volume-130, 103 cases)
│
├── Test_Batch/
│ ├── test-volume-0.nii # 测试集 CT (无标注)
│ └── ... (70 cases)
│
└── evaluation/
└── lits_evaluation.py # 官方评测代码
§4.2 标签编码
| 像素值 | 类别 | 描述 |
|---|---|---|
| 0 | Background | 非肝脏区域 |
| 1 | Liver | 肝脏实质(含肿瘤区域) |
| 2 | Tumor | 肝内肿瘤(叠加在肝脏区域内) |
重要说明:肿瘤标签 (2) 叠加在肝脏标签 (1) 上方。读取时,
mask == 1获取整个肝脏(含肿瘤),mask == 2仅获取肿瘤,mask >= 1获取所有前景。这种设计允许同时训练肝脏分割和肿瘤分割任务。
§4.3 与相关数据集的关系
| 数据集 | 关系 | 注意事项 |
|---|---|---|
| 3D-IRCADb | LiTS 训练集包含其全部 20 例 | ⚠️ 禁止合并使用,会导致数据泄漏 |
| MSD Task03 Liver | 直接使用 LiTS 数据 | ⚠️ 禁止合并使用,同一数据 |
| SLIVER07 | 独立数据集(30 例,仅肝脏标注) | ✅ 可合并使用,补充肝脏分割训练数据 |
| CHAOS | 独立数据集(40 CT + 120 MRI) | ✅ 可合并使用,但模态和标注协议不同 |
| LiVs | 独立数据集(532 例,肝血管标注) | ✅ 可合并使用,但标注目标不同(血管 vs 肿瘤) |
§5 数据划分与使用
§5.1 官方划分
| 划分 | 数量 | 标注 | 用途 |
|---|---|---|---|
| 训练集 | 131 例 | ✅ 公开 | 模型训练 |
| 测试集 | 70 例 | ❌ 不公开 | CodaLab 在线评测 |
训练集与测试集的机构分布大致保持 2:1 比例。统计学检验显示训练集和测试集的肝脏体积无显著差异(p=0.60),但测试集的肿瘤数更多(p=0.016)、层厚更厚(p=0.004)。
§5.2 自定义划分建议
由于测试集标注不公开,实际开发中通常将 131 例训练集进一步划分:
| 策略 | 划分 | 适用场景 |
|---|---|---|
| 官方提交 | 131 train → CodaLab test (70) | 最终评测 |
| 5 折交叉验证 | 131 → 5 folds (各 ~26 例) | 模型选择、超参调优 |
| 7:2:2 | 91 train / 20 val / 20 test | 快速原型 |
| 8:1:1 | 105 train / 13 val / 13 test | 标准划分 |
推荐:使用 5 折交叉验证进行模型选择,最终在 CodaLab 提交测试集结果。注意 7 例训练集无肿瘤标注(正常肝脏),训练肿瘤分割时需排除。
§5.3 数据预处理
HU 值裁剪
不同研究团队使用的 HU 裁剪窗口不同,直接影响模型性能:
| 裁剪范围 | 使用者 | 适用场景 |
|---|---|---|
| [-200, 200] | 3DUNet-Pytorch (lee-zq) | 软组织对比,抑制骨骼和空气 |
| [-160, 240] | Cascaded ResNet (Bi et al.) | 平衡肝脏和肿瘤对比度 |
| [-100, 400] | 部分方法 | 扩展范围,保留更多组织信息 |
| [-150, 250] | Bellver et al. (MICCAI 2017) | 轻度裁剪 |
| [-21, 189] | Diff4MMLiTS | 肝脏特异性窗口 |
推荐:使用 [-100, 400] 或 [-200, 200],前者保留更多组织细节,后者更聚焦软组织。裁剪后归一化至 [0, 1]。
重采样
由于各机构采集参数差异巨大(层厚 0.45-6.0 mm),重采样至统一间距是必要的:
- nnU-Net 默认:自动重采样至中位数间距(面内 ~0.8 mm, 层间 ~1.5 mm)
- 手动重采样:常重采样至 1.0×1.0×1.0 mm 或 0.8×0.8×1.5 mm
ROI 裁剪
为减少背景计算量,通常裁剪至肝脏区域:
-
沿 Z 轴:仅保留含肝脏的切片(扩展 ±20 层余量)
-
沿 X/Y 轴:裁剪至肝脏边界外扩 ~20 像素
§6 AI 就绪指南
§6.1 PyTorch DataLoader
import os
import numpy as np
import torch
from torch.utils.data import Dataset
import nibabel as nib
class LiTSDataset(Dataset):
"""LiTS 肝脏肿瘤分割数据集加载器
支持:
- 前景优先 patch 采样 (oversample tumor regions)
- HU 裁剪与归一化
- 2D/3D patch 提取
"""
def __init__(self, data_dir, split=''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''train'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''', patch_size=(48, 128, 128),
hu_clip=(-100, 400), oversample_foreground=0.33):
self.data_dir = data_dir
self.patch_size = patch_size
self.hu_clip = hu_clip
self.oversample_foreground = oversample_foreground
# 加载文件列表
vol_dir = os.path.join(data_dir, ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''ct'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''')
seg_dir = os.path.join(data_dir, ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''label'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''')
case_ids = sorted([f.replace(''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''volume-'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''', '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''').replace(''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''.nii'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''', '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''')
for f in os.listdir(vol_dir) if f.endswith(''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''.nii'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''')])
# 按划分选择
if split == ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''train'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''':
self.case_ids = case_ids[:111]
elif split == ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''val'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''':
self.case_ids = case_ids[111:]
else:
self.case_ids = case_ids
self.vol_paths = [os.path.join(vol_dir, f''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''volume-{cid}.nii'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''')
for cid in self.case_ids]
self.seg_paths = [os.path.join(seg_dir, f''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''segmentation-{cid}.nii'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''')
for cid in self.case_ids]
# 缓存(可选)
self._cache = {}
def __len__(self):
return len(self.case_ids)
def __getitem__(self, idx):
if idx in self._cache:
vol, seg = self._cache[idx]
else:
vol = nib.load(self.vol_paths[idx]).get_fdata().astype(np.float32)
seg = nib.load(self.seg_paths[idx]).get_fdata().astype(np.int8)
# 转置为 (Z, Y, X) 以便于 patch 提取
vol = vol.transpose(2, 0, 1)
seg = seg.transpose(2, 0, 1)
self._cache[idx] = (vol, seg)
# HU 裁剪与归一化
vol = np.clip(vol, self.hu_clip[0], self.hu_clip[1])
vol = (vol - self.hu_clip[0]) / (self.hu_clip[1] - self.hu_clip[0])
# Patch 采样
d, h, w = vol.shape
pd, ph, pw = self.patch_size
# 前景优先采样
if np.random.random() < self.oversample_foreground:
# 在肿瘤区域内采样
tumor_coords = np.argwhere(seg == 2)
if len(tumor_coords) > 0:
center = tumor_coords[np.random.choice(len(tumor_coords))]
z = max(0, min(center[0] - pd//2, d - pd))
y = max(0, min(center[1] - ph//2, h - ph))
x = max(0, min(center[2] - pw//2, w - pw))
else:
z = np.random.randint(0, max(1, d - pd))
y = np.random.randint(0, max(1, h - ph))
x = np.random.randint(0, max(1, w - pw))
else:
z = np.random.randint(0, max(1, d - pd))
y = np.random.randint(0, max(1, h - ph))
x = np.random.randint(0, max(1, w - pw))
vol_patch = vol[z:z+pd, y:y+ph, x:x+pw]
seg_patch = seg[z:z+pd, y:y+ph, x:x+pw]
# 数据增强
if np.random.random() < 0.5:
vol_patch = np.flip(vol_patch, axis=2).copy()
seg_patch = np.flip(seg_patch, axis=2).copy()
if np.random.random() < 0.5:
vol_patch = np.flip(vol_patch, axis=1).copy()
seg_patch = np.flip(seg_patch, axis=1).copy()
vol_patch = torch.from_numpy(vol_patch).float().unsqueeze(0)
seg_patch = torch.from_numpy(seg_patch).long()
return vol_patch, seg_patch
# 使用示例
if __name__ == ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''__main__'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''':
dataset = LiTSDataset(
data_dir=''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''/path/to/LiTS/fixed_data'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''',
split=''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''train'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''',
patch_size=(48, 128, 128),
hu_clip=(-100, 400),
oversample_foreground=0.33
)
print(f"Dataset size: {len(dataset)}")
from torch.utils.data import DataLoader
loader = DataLoader(dataset, batch_size=2, shuffle=True, num_workers=4)
for vol, seg in loader:
print(f"Volume: {vol.shape}, Segmentation: {seg.shape}")
print(f" HU range: [{vol.min():.3f}, {vol.max():.3f}]")
print(f" Labels: bg={int((seg==0).sum())}, liver={int((seg==1).sum())}, "
f"tumor={int((seg==2).sum())}")
break
§6.2 预处理 Pipeline
import SimpleITK as sitk
import numpy as np
import os
from tqdm import tqdm
class LiTSPreprocessor:
"""LiTS 数据预处理器
流程:
1. HU 裁剪 [-100, 400]
2. Z-score 归一化
3. 重采样至统一间距
4. ROI 裁剪 (肝脏区域)
"""
def __init__(self, hu_clip=(-100, 400), target_spacing=(0.8, 0.8, 1.5),
roi_expand_slices=20, min_slices=48):
self.hu_clip = hu_clip
self.target_spacing = target_spacing
self.roi_expand_slices = roi_expand_slices
self.min_slices = min_slices
def process(self, vol_path, seg_path, output_dir, case_name):
# 读取 NIfTI
vol_sitk = sitk.ReadImage(vol_path)
seg_sitk = sitk.ReadImage(seg_path)
vol = sitk.GetArrayFromImage(vol_sitk).astype(np.float32) # (Z, Y, X)
seg = sitk.GetArrayFromImage(seg_sitk).astype(np.int8)
original_spacing = vol_sitk.GetSpacing() # (X, Y, Z)
# 1. HU 裁剪
vol = np.clip(vol, self.hu_clip[0], self.hu_clip[1])
# 2. Z-score 归一化 (基于前景区域)
foreground_mask = vol > self.hu_clip[0]
if foreground_mask.sum() > 0:
mean_val = vol[foreground_mask].mean()
std_val = vol[foreground_mask].std()
vol = (vol - mean_val) / (std_val + 1e-8)
# 3. 重采样至目标间距
zoom_factors = [
original_spacing[2] / self.target_spacing[2], # Z
original_spacing[1] / self.target_spacing[1], # Y
original_spacing[0] / self.target_spacing[0], # X
]
from scipy.ndimage import zoom
vol_resampled = zoom(vol, zoom_factors, order=3) # 三次插值
seg_resampled = zoom(seg, zoom_factors, order=0) # 最近邻插值
# 4. ROI 裁剪 (沿 Z 轴提取肝脏区域)
liver_slices = np.any(seg_resampled > 0, axis=(1, 2))
liver_indices = np.where(liver_slices)[0]
if len(liver_indices) >= self.min_slices:
z_start = max(0, liver_indices[0] - self.roi_expand_slices)
z_end = min(vol_resampled.shape[0],
liver_indices[-1] + 1 + self.roi_expand_slices)
vol_roi = vol_resampled[z_start:z_end]
seg_roi = seg_resampled[z_start:z_end]
else:
vol_roi = vol_resampled
seg_roi = seg_resampled
# 保存
os.makedirs(os.path.join(output_dir, ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''ct''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''), exist_ok=True)
os.makedirs(os.path.join(output_dir, ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''label''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''), exist_ok=True)
out_vol = sitk.GetImageFromArray(vol_roi)
out_vol.SetSpacing(self.target_spacing[::-1])
sitk.WriteImage(out_vol, os.path.join(output_dir, ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''ct'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''', f''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''{case_name}.nii.gz''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''))
out_seg = sitk.GetImageFromArray(seg_roi)
out_seg.SetSpacing(self.target_spacing[::-1])
sitk.WriteImage(out_seg, os.path.join(output_dir, ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''label'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''', f''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''{case_name}.nii.gz''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''))
return vol_roi.shape, seg_roi.shape
# 批量预处理
if __name__ == ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''__main__'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''':
preprocessor = LiTSPreprocessor(
hu_clip=(-100, 400),
target_spacing=(0.8, 0.8, 1.5),
roi_expand_slices=20,
min_slices=48
)
raw_dir = ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''/path/to/LiTS/raw''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
output_dir = ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''/path/to/LiTS/fixed_data''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
for i in tqdm(range(131), desc=''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''Preprocessing LiTS''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''):
vol_path = os.path.join(raw_dir, f''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''volume-{i}.nii'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''')
seg_path = os.path.join(raw_dir, f''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''segmentation-{i}.nii'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''')
if os.path.exists(vol_path) and os.path.exists(seg_path):
shape, _ = preprocessor.process(vol_path, seg_path, output_dir, f''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''case_{i:03d}'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''')
print(f"Case {i}: {shape}")
§6.3 TensorFlow / Keras DataLoader
import tensorflow as tf
import nibabel as nib
import numpy as np
import os
def create_lits_tf_dataset(data_dir, batch_size=2, patch_size=(48, 128, 128),
hu_clip=(-100, 400), augment=True):
"""创建 LiTS TensorFlow 数据集"""
vol_dir = os.path.join(data_dir, ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''ct'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''')
seg_dir = os.path.join(data_dir, ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''label'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''')
case_files = sorted([f for f in os.listdir(vol_dir) if f.endswith(''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''.nii'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''')])
vol_paths = [os.path.join(vol_dir, f) for f in case_files]
seg_paths = [os.path.join(seg_dir, f.replace(''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''volume'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''', ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''segmentation''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''))
for f in case_files]
def load_and_augment(vol_path, seg_path):
vol = nib.load(vol_path.numpy().decode()).get_fdata().astype(np.float32)
seg = nib.load(seg_path.numpy().decode()).get_fdata().astype(np.int32)
vol = vol.transpose(2, 0, 1) # (Z, Y, X)
seg = seg.transpose(2, 0, 1)
# HU 裁剪与归一化
vol = np.clip(vol, hu_clip[0], hu_clip[1])
vol = (vol - hu_clip[0]) / (hu_clip[1] - hu_clip[0])
# 随机 patch
d, h, w = vol.shape
pd, ph, pw = patch_size
z = np.random.randint(0, max(1, d - pd))
y = np.random.randint(0, max(1, h - ph))
x = np.random.randint(0, max(1, w - pw))
vol_patch = vol[z:z+pd, y:y+ph, x:x+pw]
seg_patch = seg[z:z+pd, y:y+ph, x:x+pw]
if augment:
if np.random.random() < 0.5:
vol_patch = np.flip(vol_patch, axis=2).copy()
seg_patch = np.flip(seg_patch, axis=2).copy()
if np.random.random() < 0.5:
vol_patch = np.flip(vol_patch, axis=1).copy()
seg_patch = np.flip(seg_patch, axis=1).copy()
vol_patch = vol_patch[..., np.newaxis] # 添加通道维度
return vol_patch, seg_patch
def tf_wrapper(vol_path, seg_path):
vol, seg = tf.py_function(load_and_augment, [vol_path, seg_path],
[tf.float32, tf.int32])
vol.set_shape(patch_size + (1,))
seg.set_shape(patch_size)
return vol, seg
dataset = tf.data.Dataset.from_tensor_slices((vol_paths, seg_paths))
dataset = dataset.shuffle(len(vol_paths))
dataset = dataset.map(tf_wrapper, num_parallel_calls=tf.data.AUTOTUNE)
dataset = dataset.batch(batch_size)
dataset = dataset.prefetch(tf.data.AUTOTUNE)
return dataset
§6.4 评估指标
import numpy as np
from scipy.ndimage import distance_transform_edt
def dice_score(pred, gt, smooth=1e-5):
"""计算 Dice 系数"""
pred = pred.astype(np.bool_)
gt = gt.astype(np.bool_)
intersection = (pred & gt).sum()
return (2.0 * intersection + smooth) / (pred.sum() + gt.sum() + smooth)
def jaccard_index(pred, gt, smooth=1e-5):
"""计算 Jaccard (IoU)"""
pred = pred.astype(np.bool_)
gt = gt.astype(np.bool_)
intersection = (pred & gt).sum()
union = (pred | gt).sum()
return (intersection + smooth) / (union + smooth)
def volume_overlap_error(pred, gt):
"""计算 VOE (1 - Jaccard)"""
return 1.0 - jaccard_index(pred, gt)
def relative_volume_difference(pred, gt):
"""计算 RVD"""
pred = pred.astype(np.bool_)
gt = gt.astype(np.bool_)
return (pred.sum() - gt.sum()) / (gt.sum() + 1e-5)
def average_symmetric_surface_distance(pred, gt, spacing=(1.0, 1.0, 1.0)):
"""计算 ASSD (平均对称表面距离)"""
pred = pred.astype(np.bool_)
gt = gt.astype(np.bool_)
pred_surface = pred & ~np.pad(pred, 1, mode=''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''constant'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''')[1:-1, 1:-1, 1:-1]
gt_surface = gt & ~np.pad(gt, 1, mode=''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''constant'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''')[1:-1, 1:-1, 1:-1]
if pred_surface.sum() == 0 or gt_surface.sum() == 0:
return float(''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''inf'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''')
# 计算从预测表面到 GT 表面的距离
dt_gt = distance_transform_edt(~gt, sampling=spacing)
dt_pred = distance_transform_edt(~pred, sampling=spacing)
assd = (dt_pred[pred_surface].sum() + dt_gt[gt_surface].sum()) / \
(pred_surface.sum() + gt_surface.sum())
return assd
def max_symmetric_surface_distance(pred, gt, spacing=(1.0, 1.0, 1.0)):
"""计算 MSSD (最大对称表面距离, 即对称 Hausdorff)"""
pred = pred.astype(np.bool_)
gt = gt.astype(np.bool_)
if pred.sum() == 0 or gt.sum() == 0:
return float(''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''inf'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''')
pred_surface = pred & ~np.pad(pred, 1, mode=''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''constant'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''')[1:-1, 1:-1, 1:-1]
gt_surface = gt & ~np.pad(gt, 1, mode=''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''constant'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''')[1:-1, 1:-1, 1:-1]
dt_gt = distance_transform_edt(~gt, sampling=spacing)
dt_pred = distance_transform_edt(~pred, sampling=spacing)
mssd = max(dt_pred[pred_surface].max(), dt_gt[gt_surface].max())
return mssd
def evaluate_lits(predictions, ground_truths, spacing=(0.8, 0.8, 1.5)):
"""
LiTS 官方评估指标
参数:
predictions: list of np.ndarray (预测分割)
ground_truths: list of np.ndarray (真实标注)
spacing: 体素间距 (mm)
返回:
dict: liver 和 tumor 的各项指标
"""
results = {
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''liver'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''': {''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''dice'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''': [], ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''jaccard'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''': [], ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''voe'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''': [], ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''rvd'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''': [],
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''assd'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''': [], ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''mssd'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''': []},
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''tumor'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''': {''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''dice'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''': [], ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''jaccard'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''': [], ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''voe'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''': [], ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''rvd'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''': [],
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''assd'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''': [], ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''mssd'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''': []}
}
for pred, gt in zip(predictions, ground_truths):
for organ, label in [(''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''liver'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''', 1), (''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''tumor'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''', 2)]:
pred_mask = (pred == label)
gt_mask = (gt == label)
if gt_mask.sum() == 0 and pred_mask.sum() == 0:
continue # 双方均无该器官
if gt_mask.sum() == 0:
results[organ][''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''dice''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''].append(0.0)
continue
results[organ][''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''dice''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''].append(dice_score(pred_mask, gt_mask))
results[organ][''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''jaccard''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''].append(jaccard_index(pred_mask, gt_mask))
results[organ][''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''voe''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''].append(volume_overlap_error(pred_mask, gt_mask))
results[organ][''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''rvd''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''].append(relative_volume_difference(pred_mask, gt_mask))
results[organ][''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''assd''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''].append(
average_symmetric_surface_distance(pred_mask, gt_mask, spacing))
results[organ][''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''mssd''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''].append(
max_symmetric_surface_distance(pred_mask, gt_mask, spacing))
# 汇总
summary = {}
for organ in [''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''liver'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''', ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''tumor'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''']:
summary[organ] = {
metric: {
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''mean'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''': float(np.mean(values)),
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''median'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''': float(np.median(values)),
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''std'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''': float(np.std(values))
}
for metric, values in results[organ].items() if len(values) > 0
}
return summary
# 检测指标 (lesion-wise)
def evaluate_detection(predictions, ground_truths, iou_threshold=0.5):
"""
LiTS lesion-wise 检测评估
返回: precision, recall, F1
"""
from scipy.ndimage import label
tp = fp = fn = 0
for pred, gt in zip(predictions, ground_truths):
pred_tumor = (pred == 2)
gt_tumor = (gt == 2)
pred_labels, n_pred = label(pred_tumor)
gt_labels, n_gt = label(gt_tumor)
for i in range(1, n_gt + 1):
gt_region = (gt_labels == i)
best_iou = 0
for j in range(1, n_pred + 1):
pred_region = (pred_labels == j)
intersection = (gt_region & pred_region).sum()
union = (gt_region | pred_region).sum()
iou = intersection / (union + 1e-5)
if iou > best_iou:
best_iou = iou
if best_iou >= iou_threshold:
tp += 1
else:
fn += 1
for j in range(1, n_pred + 1):
pred_region = (pred_labels == j)
best_iou = 0
for i in range(1, n_gt + 1):
gt_region = (gt_labels == i)
intersection = (gt_region & pred_region).sum()
union = (gt_region | pred_region).sum()
iou = intersection / (union + 1e-5)
if iou > best_iou:
best_iou = iou
if best_iou < iou_threshold:
fp += 1
precision = tp / (tp + fp) if (tp + fp) > 0 else 0
recall = tp / (tp + fn) if (tp + fn) > 0 else 0
f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0
return {''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''precision'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''': precision, ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''recall'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''': recall, ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''f1'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''': f1,
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''tp'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''': tp, ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''fp'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''': fp, ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''fn'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''': fn}
§6.5 数据增强安全表
| 增强方法 | 安全性 | 推荐参数 | 注意事项 |
|---|---|---|---|
| 水平翻转 (L-R) | ✅ 安全 | p=0.5 | 肝脏解剖左右不对称,但 CT 不含方向标识 |
| 垂直翻转 (A-P) | ✅ 安全 | p=0.5 | 不改变解剖关系 |
| Z 轴翻转 | ⚠️ 谨慎 | p=0.1 | 头尾翻转可能影响解剖合理性 |
| 随机旋转 (面内) | ✅ 安全 | ±15° | 小角度旋转,避免极端角度 |
| 随机缩放 | ✅ 安全 | 0.9-1.1 | 保持器官形态 |
| 弹性形变 | ⚠️ 谨慎 | σ=5-10, α=100-200 | 增强肿瘤形态多样性,但过度形变破坏解剖 |
| 随机对比度 | ✅ 安全 | ±0.1 | 模拟不同 CT 扫描仪对比度 |
| 随机亮度 | ✅ 安全 | ±0.1 | 模拟不同 HU 偏移 |
| 高斯噪声 | ✅ 安全 | σ=0.01-0.05 | 模拟采集噪声 |
| 高斯模糊 | ⚠️ 谨慎 | σ=0.5-1.0 | 模拟厚层重建效应 |
| Mixup | ❌ 不推荐 | — | 不同患者的肝脏解剖混合无意义 |
§6.6 推荐模型配置
| 模型 | 架构 | Liver Dice | Tumor Dice | 训练时间 | 推荐场景 |
|---|---|---|---|---|---|
| nnU-Net 3D | 3D U-Net (自配置) | 96.3% | 76.3% | ~3d (1×RTX 3090) | 基线首选 |
| nnU-Net 3D (1000ep) | 3D U-Net (长训练) | 96.3% | 79.6% | ~5d | 追求最高 Tumor Dice |
| SAMed-H | SAM (ViT-H) | 95.6% | 78.6% | ~2d (1×A100) | SAM 微调 |
| MedNeXt-L | 3D ConvNeXt | 94.6% | 77.9% | ~3d (1×RTX 3090) | 现代 CNN 架构 |
| LightM-UNet | Mamba + UNet | 96.3% | 72.9% | ~1d (1×RTX 8000) | 轻量化部署 |
| H-DenseUNet | 2D+3D Hybrid | 96.1% | 72.2% | ~2d | 经典级联架构 |
| SwinUNETR | Swin Transformer | 86.7% | 74.2% | ~3d (1×A100) | Transformer 基线 |
注:以上数据来自 TriALS benchmark(5 折交叉验证,100/1000 epochs),非官方 CodaLab 提交结果。nnU-Net 在官方 CodaLab 排行榜上的 Tumor Global Dice 为 0.858。
§6.7 硬件配置建议
| 场景 | GPU | 显存 | batch_size | patch_size | 训练时间 |
|---|---|---|---|---|---|
| 快速原型 | RTX 3060 (12GB) | 12 GB | 1 | 32×96×96 | ~5d |
| 标准训练 | RTX 3090 (24GB) | 24 GB | 2 | 48×128×128 | ~3d |
| 高效训练 | A100 (40GB) | 40 GB | 4 | 96×192×192 | ~2d |
| 极致性能 | A100 ×4 (160GB) | 160 GB | 8 | 128×256×256 | ~1d |
| 推理部署 | RTX 4060 (8GB) | 8 GB | 1 | full volume (sliding) | <1min/case |
§7 DAIMS 评估
§7.1 24 项标准化评估
| # | 评估项 | 状态 | 说明 |
|---|---|---|---|
| 1 | 数据集公开可获取 | ✅ | CodaLab 平台免费下载 |
| 2 | 标准化文件格式 | ✅ | NIfTI (.nii),医学影像标准格式 |
| 3 | 多中心数据 | ✅ | 7 家机构跨 4 国(德国/荷兰/加拿大/法国/以色列) |
| 4 | 训练/测试划分明确 | ✅ | 131 train + 70 test,机构分布大致 2:1 |
| 5 | 标注者间一致性报告 | ⚠️ | 仅 15 例重新标注,R1 vs R2 肿瘤 Dice 70.2%,R1 vs R3/R4 肿瘤 Dice 95.2% |
| 6 | 基准论文发表 | ✅ | Bilic et al., Medical Image Analysis 84:102680, 2023 |
| 7 | 评估指标明确 | ✅ | Dice (global + per case), Jaccard, VOE, RVD, ASSD, MSSD, 检测指标 |
| 8 | 开放排行榜 | ✅ | CodaLab 在线评测,3414+ 次提交 |
| 9 | 肿瘤类型多样性 | ✅ | HCC, 胆管癌, 结直肠癌/乳腺癌/肺癌肝转移 |
| 10 | 图像质量多样性 | ✅ | 层厚 0.45-6.0mm,7 种不同扫描仪,含金属伪影 |
| 11 | 标准化采集协议 | ⚠️ | 7 家机构各自采集,无统一协议(但均为门静脉期) |
| 12 | 患者人口统计 | ❌ | 无年龄/性别/种族信息 |
| 13 | 造影时相明确 | ✅ | 门静脉期(portal venous phase) |
| 14 | 纵向随访数据 | ❌ | 横截面数据,无随访 |
| 15 | 专家标注 | ✅ | 4 名放射科医生,含 2 名委员会认证 |
| 16 | 开放许可证 | ✅ | CC-BY-NC-SA 4.0 |
| 17 | 商业使用许可 | ⚠️ | NonCommercial 限制 |
| 18 | 评测代码开源 | ✅ | GitHub: PatrickChrist/LiTS-CHALLENGE |
| 19 | 活跃基准 | ✅ | CodaLab 持续接受提交,2022 年仍有显著改进 |
| 20 | 广泛采用 | ✅ | MSD Task03 Liver 直接使用 LiTS 数据 |
| 21 | 公平性评估 | ❌ | 无跨人群(年龄/性别/种族)公平性分析 |
| 22 | 可复现性 | ✅ | 数据 + 评测代码 + 论文公开,nnU-Net 可复现 |
| 23 | 长期维护承诺 | ⚠️ | TUM 维护,但无明确长期支持承诺 |
| 24 | 商业部署就绪 | ❌ | NC 限制 + 无 FDA/CE 认证 |
§7.2 总评分
DAIMS 评分:18/24(⭐⭐⭐⭐ 4/5)
扣分项:
- #12 患者人口统计完全未公开 ❌
- #14 无纵向随访数据 ❌
- #21 无公平性评估 ❌
- #24 商业部署不就绪(NC 限制) ❌
- #5 IAA 仅 15 例 ⚠️
- #11 采集协议跨机构不统一 ⚠️
加分项:
- 多中心(7 机构 4 国)数据多样性出色
- 4 名标注者审核流程(含 2 名委员会认证放射科医生)
- CodaLab 活跃基准(3414+ 提交)
- 已成为 MSD 核心任务
§7.3 外部验证数据集
| 数据集 | 规模 | 模态 | 标注 | 用途 |
|---|---|---|---|---|
| 3D-IRCADb | 20 例 | CT (门静脉期) | 肝脏+肿瘤 | ⚠️ 已包含在 LiTS 中,不可用于外部验证 |
| MSD Task03 Liver | 443 例 | CT (门静脉期) | 肝脏+肿瘤 | ⚠️ 与 LiTS 数据重叠 |
| CHAOS | 40 CT + 120 MRI | CT+MRI | 肝/肾/脾 | ✅ 外部验证 |
| SLIVER07 | 30 例 | CT | 仅肝脏 | ✅ 肝脏分割外部验证 |
| HCC-TACE-Seg | 115 例 | CT (动脉期) | HCC | ✅ HCC 专项外部验证 |
重要:由于 3D-IRCADb 和 MSD Task03 Liver 均与 LiTS 数据重叠,使用这些数据集进行外部验证会导致数据泄漏。推荐使用 CHAOS 或 HCC-TACE-Seg 进行真正的外部验证。
§8 排行榜与生态
§8.1 官方挑战赛排行榜
LiTS-ISBI 2017 — 肿瘤分割
| 排名 | 团队 | 方法 | Tumor Dice (per case) |
|---|---|---|---|
| 1 | X. Han et al. | 2.5D Residual U-Net (5-slice stack) | 0.674 |
| 2 | E. Vorontsov et al. | Cascaded FCN (liver→tumor) | 0.652 |
| 3 | G. Chlebus et al. | 3×2D U-Net (orthogonal) + RF filter | 0.645 |
- 最佳 lesion-wise recall: 0.458
- 前三名之间无统计显著差异
LiTS-MICCAI 2017 — 肝脏分割
| 排名 | 团队 | 方法 | Liver Dice | ASD (mm) |
|---|---|---|---|---|
| 1 | Y. Yuan et al. | Hierarchical 2.5D FCN + 5-fold ensemble | 0.963 | 1.104 |
| 2 | A. Ben-Cohen et al. | VGG-16 backbone + 3-channel | 0.962 | 1.130 |
| 3 | J. Zou et al. | Ensemble + residual + RF post-proc | 0.961 | 1.268 |
| 4 | X. Li et al. | H-DenseUNet (2D+3D hybrid) | 0.961 | 1.692 |
| 5 | L. Zhang et al. | — | — | 1.510 |
LiTS-MICCAI 2017 — 肿瘤分割
| 排名 | 团队 | 方法 | Tumor Dice (per case) |
|---|---|---|---|
| 1 | J. Zou et al. | Ensemble + residual + sophisticated post-proc | 0.702 |
| 2 | X. Li et al. | H-DenseUNet (2D+3D hybrid) | 0.722* |
*H-DenseUNet 的 0.722 是后续论文报告值,非比赛当时排名。
LiTS-MICCAI-MSD 2018 — 肿瘤分割
| 排名 | 团队 | 方法 | Tumor Dice (per case) | Tumor ASD (mm) |
|---|---|---|---|---|
| 1 | F. Isensee et al. | nnU-Net (3D, self-configured) | 0.739 | 0.903 |
| 2 | — | — | 0.721 | — |
- MSD 2018 相比 ISBI 2017,Dice 提升达统计显著(p=0.015)
- 3D 方法在 MSD 2018 中占主导
§8.2 后续 SOTA 排行榜(非官方提交)
| 方法 | 年份 | Liver Dice | Tumor Dice (per case) | Tumor Global Dice | 来源 |
|---|---|---|---|---|---|
| nnU-Net | 2020 | 0.967 | 0.763 | 0.858 | arXiv:2004.02002 |
| J. Zhang (Light Hybrid CNN) | 2019 | 0.965 | 0.730 | 0.820 | IJCAI 2019 |
| Y. Tang (E²Net) | 2020 | 0.966 | 0.724 | 0.829 | arXiv:2011.03095 |
| X. Li (H-DenseUNet) | 2018 | 0.961 | 0.722 | 0.824 | TMI 2018 |
| X. Wang (Volumetric Attention) | 2019 | — | 0.741 | — | MICCAI 2019 |
| CodaLab 最佳 (2022.04) | 2022 | — | ~0.825 | — | CodaLab |
§8.3 现代基准排行榜(TriALS 5 折交叉验证)
| 模型 | Epochs | Liver Dice (%) | Tumor/Lesion Dice (%) | Tumor Jaccard (%) | 参数量 (M) |
|---|---|---|---|---|---|
| nnU-Net 3D | 1000 | 96.28 | 79.57 | 67.06 | 88.6 |
| SAMed-H | 100 | 95.58 | 78.60 | 65.77 | — |
| MedNeXt-L | 100 | 94.55 | 77.85 | 65.02 | — |
| nnU-Net 2D | 100 | 95.30 | 77.14 | 64.05 | 88.6 |
| MedNeXt-B | 100 | 93.60 | 77.44 | 64.57 | — |
| UMambaBot | 100 | 92.30 | 77.31 | 64.41 | 173.5 |
| LightM-UNet | 100 | 96.31 | 72.86 | 66.67 | 1.87 |
| SegResNet | 100 | 90.99 | 76.15 | 63.18 | 18.8 |
| SwinUNETR | 100 | 86.74 | 74.21 | 60.74 | 62.0 |
注:以上结果来自 xmed-lab/TriALS 仓库,使用 MSD Liver 数据集(与 LiTS 相同数据),5 折交叉验证。Dice 系数按 lesion 平均计算。nnU-Net 3D 在 1000 epochs 训练后达到最优。
§8.4 关键洞察
- 肝脏分割已接近饱和:Liver Dice 0.963+ 已接近人类 IAA (0.952),进一步提升困难
- 肿瘤分割仍有显著空间:最佳 Tumor Dice 0.763(per case)远低于人类 IAA (0.952)
- 检测是更大挑战:最佳 lesion-wise recall 仅 0.554,小肿瘤(< 5mm)几乎无法检出
- 3D 方法优势明显:从 ISBI 2017 的 2D/2.5D 到 MSD 2018 的 3D,性能提升显著
- nnU-Net 持续领先:自配置 3D 架构至今仍是最强基线
- SAM 系列潜力初显:SAMed-H 在 100 epochs 即达到 78.6% Tumor Dice
§9 资源与引用
§9.1 数据集下载
| 资源 | 链接 | 说明 |
|---|---|---|
| 官方挑战赛 | https://competitions.codalab.org/competitions/17094 | CodaLab 平台,数据下载 + 在线评测 |
| GitHub | https://github.com/PatrickChrist/LiTS-CHALLENGE | 评测代码、数据格式说明 |
| MSD Task03 Liver | https://medicaldecathlon.com/ | LiTS 数据的 MSD 版本 |
| Kaggle 镜像 | https://www.kaggle.com/datasets/andrewmvd/liver-tumor-segmentation | 社区镜像 |
| Aistudio | https://aistudio.baidu.com/ | 国内下载(飞桨 AI Studio) |
§9.2 核心论文
| # | 论文 | 引用 | 关键贡献 |
|---|---|---|---|
| 1 | Bilic et al., “The Liver Tumor Segmentation Benchmark (LiTS)” | Medical Image Analysis 84:102680, 2023 | 基准论文,75 个算法综合分析 |
| 2 | Isensee et al., “nnU-Net: a self-configuring method for deep learning-based biomedical image analysis” | Nature Methods 18:203-211, 2021 | MSD 冠军,自配置 3D U-Net |
| 3 | Li et al., “H-DenseUNet: Hybrid Densely Connected UNet for Liver and Tumor Segmentation from CT Volumes” | IEEE TMI 37(12):2663-2674, 2018 | 2D+3D 混合架构,经典级联方法 |
| 4 | Han et al., “Automatic Liver and Lesion Segmentation in CT Using Cascaded Fully Convolutional Neural Networks and 3D Conditional Random Fields” | MICCAI 2017 | ISBI 2017 冠军,2.5D Residual U-Net |
| 5 | Yuan et al., “Hierarchical 2.5D FCN for Liver Lesion Segmentation” | MICCAI 2017 | MICCAI 2017 肝脏分割冠军 |
| 6 | Vorontsov et al., “Liver lesion segmentation based on FCN with two-pathway ensemble” | ISBI 2017 | 级联 FCN + 随机森林后处理 |
| 7 | Chlebus et al., “Neural network-based automatic liver segmentation in CT” | ISBI 2017 | 3 方向 2D U-Net 集成 |
| 8 | Roth et al., “Spatial aggregation of holistically-nested convolutional neural networks for automated pancreatic lesion detection and segmentation” | Medical Image Analysis 2018 | 2.5D 级联架构 |
| 9 | Roy et al., “Recalibrating Fully Convolutional Networks with X-Ray Images” | MICCAI 2020 | 不确定性校准 |
| 10 | Hatamizadeh et al., “UNETR: Transformers for 3D Medical Image Segmentation” | WACV 2022 | Transformer 架构在 LiTS 上的应用 |
§9.3 生态系统快照
| 组件 | 状态 | 说明 |
|---|---|---|
| CodaLab 在线评测 | ✅ 活跃 | 3414+ 次提交,持续接受 |
| nnU-Net 预训练模型 | ✅ 可用 | nnU-Net v2 支持 LiTS/MSD Liver |
| MONAI LiTS 配方 | ✅ 可用 | MONAI 框架内置 LiTS 数据处理 |
| TriALS Benchmark | ✅ 活跃 | 2024-2025 基准,7 种模型对比 |
| 3D-IRCADb | ⚠️ 子集 | 20 例已包含在 LiTS 中 |
| MSD Task03 Liver | ⚠️ 同源 | 使用 LiTS 数据,443 例(含扩展) |
| SLIVER07 | ✅ 互补 | 30 例肝脏分割(无肿瘤),可合并使用 |
§9.4 6 大坑点详解
坑点 1:3D-IRCADb 数据重叠
问题:LiTS 训练集包含 3D-IRCADb 全部 20 例数据(来自 IRCAD France)。许多研究者在 LiTS 训练后使用 3D-IRCADb 作为"外部验证集",实际上是在训练集上评估,导致数据泄漏。
影响:外部验证 Dice 被高估 5-10%。
解决方案:
- 使用 3D-IRCADb 时,从 LiTS 训练集中排除 IRCAD 贡献的 20 例(volume-28 到 volume-47 中部分)
- 或使用完全不重叠的数据集(如 CHAOS、HCC-TACE-Seg)进行外部验证
坑点 2:MSD Task03 Liver = LiTS
问题:Medical Segmentation Decathlon 的 Task03 Liver 直接使用 LiTS 数据。许多研究者在 LiTS 上训练后在 MSD Liver 上"验证",实际上是在相同数据上评估。
影响:性能被严重高估,无法反映真实泛化能力。
解决方案:
- 明确区分 LiTS 和 MSD Liver 的训练/测试划分
- 不要混合使用两个数据集
- 使用 CHAOS 或 SLIVER07 作为独立外部验证
坑点 3:测试集标注不公开
问题:70 例测试集的标注仅由 TUM 组织者持有,不对外公开。无法进行本地测试集评估。
影响:
- 无法快速迭代(每次需提交 CodaLab)
- 无法计算除官方指标外的其他指标(如 surface Dice)
- 无法进行误差分析(无法查看哪些 case 失败)
解决方案:
- 将 131 例训练集进一步划分为 train/val/test
- 使用 5 折交叉验证进行模型选择
- 最终在 CodaLab 提交一次
坑点 4:HU 裁剪窗口不一致
问题:不同研究团队使用的 HU 裁剪窗口差异巨大([-200,200] 到 [-100,400]),直接影响模型性能和结果可比性。
影响:相同模型在不同 HU 窗口下 Tumor Dice 可差 2-5%。论文之间的性能对比可能不公平。
解决方案:
- 统一使用 [-100, 400] 或 nnU-Net 自动确定的百分位裁剪
- 在论文中明确报告 HU 裁剪范围
- 使用 nnU-Net 默认预处理作为标准化基线
坑点 5:肿瘤分割性能远低于人类
问题:最佳算法的 Tumor Dice(per case)为 0.763,而人类 IAA(R1 vs R3/R4)为 0.952。即使是 R1 vs R2(新标注 vs 原始)也仅 70.2%。
影响:
- 0.763 vs 0.952 的差距说明模型远未达到专家水平
- 小肿瘤(< 5mm)几乎无法检出(recall < 0.554)
- 模型在高密度(hyperdense)和低对比度肿瘤上表现差异显著
解决方案:
- 关注 lesion-wise detection(recall/precision)而非仅 Dice
- 使用级联架构(先肝脏 ROI,再肿瘤分割)
- 引入注意力机制和多尺度特征融合
坑点 6:检测与分割性能不对齐
问题:LiTS 挑战赛以 Dice 排名,但 Dice 高的方法不一定检测性能好。MICCAI 2017 冠军 J. Zou 的方法在 Dice 上排名第一(0.702),但 precision 仅 0.148(大量假阳性),说明通过高召回率"刷"Dice。
影响:仅看 Dice 会误导对临床实用性的评估。临床更关心是否检出所有肿瘤(recall),而非分割精度。
解决方案:
- 同时报告 Dice 和检测指标(recall, precision, F1)
- 关注 lesion-wise recall 而非仅 global Dice
- 使用 per-case Dice 而非 global Dice(后者可能被大肿瘤主导)
§9.5 版本抉择矩阵
| 需求 | 推荐版本 | 理由 |
|---|---|---|
| 标准分割训练 | LiTS 训练集 (131 例) | 数据量适中,标注质量高 |
| 在线评测 | LiTS 测试集 (70 例, CodaLab) | 唯一可获取官方排名的方式 |
| 与 MSD 方法对比 | MSD Task03 Liver | 同源数据,但划分可能不同 |
| 肝脏分割(无肿瘤) | LiTS + SLIVER07 | 合并使用增加数据量 |
| 外部验证 | CHAOS / HCC-TACE-Seg | 不与 LiTS 数据重叠 |
| 轻量化部署 | LightM-UNet (1.87M params) | 参数量极小,性能可接受 |
§9.6 常见问题
Q: LiTS 和 MSD Task03 Liver 是同一个数据集吗?
A: 是的。MSD Task03 Liver 直接使用 LiTS 数据。两者不应合并使用。如果需要区分,LiTS 原始划分是 131 train + 70 test,而 MSD 划分是 303 train + 140 test(MSD 可能包含额外扩展数据)。
Q: 3D-IRCADb 可以与 LiTS 合并使用吗?
A: 不可以。LiTS 训练集已经包含 3D-IRCADb 的全部 20 例数据(来自 IRCAD France 贡献)。合并会导致这 20 例同时在训练集和验证集中,造成数据泄漏。
Q: 为什么测试集的标注不公开?
A: LiTS 采用"在线评测"模式,测试集标注由 TUM 组织者持有。参与者将预测结果提交至 CodaLab 平台进行评估。这确保了测试集不被用于训练(防止过拟合),但限制了本地评估的灵活性。
Q: LiTS 的标注者间一致性如何?
A: 论文报告了两种 IAA:(1) R1(原始标注)vs R2(新标注放射科医生)的肿瘤 Dice 中位数为 70.2%,表明不同标注者对肿瘤边界存在较大分歧;(2) R1 vs R3/R4(委员会认证放射科医生)的肿瘤 Dice 中位数为 95.2%,表明经过专家审核后一致性很高。最佳算法的 Tumor Dice 为 0.763,仍远低于专家审核后的 IAA (0.952)。
Q: 应该使用哪种 HU 裁剪窗口?
A: 没有统一答案,但推荐使用 [-100, 400] 或 nnU-Net 自动确定的数据驱动百分位裁剪。[-200, 200] 更聚焦软组织但可能丢失肿瘤信息,[-100, 400] 保留更广范围。关键是保持一致并在论文中报告。
Q: LiTS 是否包含非门静脉期 CT?
A: 不包含。LiTS 所有 201 例 CT 均为门静脉期(portal venous phase)增强扫描。如果需要动脉期或延迟期数据,需要使用其他数据集(如 HCC-TACE-Seg 含动脉期)。
