LiTS

LiTS — 肝脏与肝脏肿瘤分割 AI-Ready Wikipedia | 千方病案医数集

来源 TUM (Bilic/Christ/Menze) & 7 clinical partner institutions url: https://competitions.codalab.org/competitions/17094发布时间: 2026-08-10最后更新: 2026-08-10 阅读 6

信息速览

数据集名称LiTS
数据类型201 例 CT(131 train + 70 test),7 中心多国家采集,门静脉期增强 CT,4 名放射科医生标注流程
规模201 例 CT 体数据(131 训练 + 70 测试)
接入方式TUM (Bilic/Christ/Menze) & 7 clinical partner institutions url: https://competitions.codalab.org/competitions/17094
AI 就绪度

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 &amp; 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 &amp; 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 &amp; ~np.pad(pred, 1, mode=''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''constant'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''')[1:-1, 1:-1, 1:-1]
    gt_surface = gt &amp; ~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 &amp; ~np.pad(pred, 1, mode=''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''constant'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''')[1:-1, 1:-1, 1:-1]
    gt_surface = gt &amp; ~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 &amp; 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 &amp; 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 关键洞察

  1. 肝脏分割已接近饱和:Liver Dice 0.963+ 已接近人类 IAA (0.952),进一步提升困难
  2. 肿瘤分割仍有显著空间:最佳 Tumor Dice 0.763(per case)远低于人类 IAA (0.952)
  3. 检测是更大挑战:最佳 lesion-wise recall 仅 0.554,小肿瘤(< 5mm)几乎无法检出
  4. 3D 方法优势明显:从 ISBI 2017 的 2D/2.5D 到 MSD 2018 的 3D,性能提升显著
  5. nnU-Net 持续领先:自配置 3D 架构至今仍是最强基线
  6. 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 含动脉期)。

返回 AI Ready 数据集