☰
骶骨腰痛脊椎分割数据集:三切面五类别标注与可视化实战
2026/10/1 3:11:12 网站建设 项目流程

简介:面向医学图像分割与深度学习研究的骶骨腰痛脊椎分割数据集,来自公开脊柱分割项目,按轴位面、冠状面、矢状面三个方向切分出二维图像,共标注五个类别。所有切片已滤除感兴趣区域占比不足百分之三的样本,并完成医学影像常用的窗宽窗位对比度增强,统一缩放至512×512像素;图像采用JPG格式存储,对应掩码为PNG格式八位灰度图,非零数值为前景、零为背景,类别编号见classes.txt。资源包共2000个文件,以1998个PNG图像与掩码文件为主,另含1个类别说明文本和1个可视化脚本show.py,压缩包总大小472.47MB;目录按x、y、z三个切面分别组织images与masks子目录,各含2113对、2272对、6404对图像和掩码,可直接用于训练UNet、DeepLab等分割网络。show.py可叠加显示分割结果,便于快速核对标注质量。目前已有224人学习使用,适合需要标准预处理脊椎CT数据的研究者、算法工程师及医学影像相关专业学生。

1. 骶骨腰痛脊椎分割的数据集到底长什么样:3个切面、5个类别的起点

医学图像分割数据集这个方向,最容易让新手失控的不是模型,而是数据本身。手头这套“骶骨腰痛脊椎分割”数据集主打的就是三样东西:3个切面划分好的图像、5类别的标签文件、以及现成的可视化代码。换句话说,它不是一个只给你一堆 PNG 和 mask 的裸数据包,而是把矢状面、冠状面、轴位都按统一逻辑切好,把骶骨和腰椎的关键结构标成 5 个类别,并且附带能让你第一时间把分割结果“叠回原图”的工具脚本。对正在做腰痛相关影像分析、脊柱外科辅助诊断或者分割模型复现的工程师来说,这套数据能帮你省掉最脏最累的格式整理和对齐工作,直接进入训练和评估环节。

2. 数据集文件拆解:三个切面与5类别标签如何对应

2.1 三切面划分逻辑:矢状面、冠状面、轴位,各自覆盖什么结构

腰痛和骶骨相关的影像评估,临床上最依赖的是矢状面,因为腰椎的生理曲度、椎间盘退变、腰骶角变化、骶骨倾斜度在矢状面上看得最清楚。这套数据把矢状面作为第一个切面目录,原因就在这里。冠状面对观察脊柱侧弯和左右对称性有帮助,轴位则是看椎间盘突出方向、椎管狭窄和关节突关节的关键视角。三个切面不是简单把同一个三维体积各切一刀,而是按照解剖结构重新组织成独立的 2D 切片序列,这一点在你后续做训练集划分时要格外留意。

我在处理这类数据集时,第一件事永远是确认目录里的切面划分是否与 DICOM 原始扫描的 orientation 一致。有的数据包表面写着“矢状位”,实际是从轴位体数据里重采样出来的,并不等价于真正的矢状扫描序列。区别在于层厚和平面内分辨率:原生矢状位的层厚通常较薄,而重采样得到的矢状位切片在 z 方向上有明显拉伸。从训练角度说,模型学习到的纹理特征会有差异,迁移到真实临床数据时表现会打折。

tree -L 2 data/
data/ ├── sagittal/ │ ├── case001/ │ │ ├── img/ │ │ ├── label/ │ │ └── info.json │ ├── case002/ │ └── ... ├── coronal/ │ ├── case001/ │ └── ... ├── axial/ │ ├── case001/ │ └── ... ├── class_names.json ├── train_val_split.txt └── visualize.py

上面这个目录结构是这套数据最常见的组织形式。sagittal、coronal、axial三个目录各放各的切片,同一个case001在三个切面下都有对应的子目录。每个 case 下的img放原始灰度图,label放同尺寸的分割标签,info.json则记录切片编号、层厚、像素间距、窗宽窗位等元信息。不要小看这个 json,它在你做切面对齐和体数据重建时是救命稻草。

2.2 5类别的索引约定与标签文件格式

5 个类别需要先搞清楚索引约定。常见做法是把 0 留作背景,然后按解剖结构排列:

类别索引结构名称说明
0background背景
1sacrum骶骨
2vertebra腰椎椎体
3disc椎间盘
4posterior棘突、椎弓根等后柱结构

为什么这样分?腰痛场景里椎间盘是最常见的病变位置,骶骨是承重和力线传导的关键,椎体骨折和滑脱也要看,后柱结构则关系到手术入路和内固定规划。五类刚好覆盖了腰痛影像评估的主要解剖对象,但又不会细到每个椎体单独编号,否则标注成本会暴涨。

标签文件的格式通常是 PNG、NIfTI 或 NRRD 三者之一。2D 切片数据多用 PNG 保存,优点是体积小且查看方便,缺点是容易丢失原始像素间距等空间信息。3D 体数据则常用 NIfTI 格式,头文件里保留了 direction、spacing、origin 三个关键矩阵。如果你的数据包同时包含 NIfTI 体数据和 2D 切片 PNG,建议以 NIfTI 为准,因为从三维信息可以随时重新切片,反过来却不行。

import json, cv2 import numpy as np with open('data/class_names.json', 'r', encoding='utf-8') as f: class_names = json.load(f) # 检查一张标签图的类别值分布 label_path = 'data/sagittal/case001/label/slice_023.png' label = cv2.imread(label_path, cv2.IMREAD_UNCHANGED) # 有些标签PNG是16位或带调色板的,转成灰度索引图再统计 if label.ndim == 3: label = label[:, :, 0] unique_vals = np.unique(label) print('class mapping:', class_names) print('unique values in label:', unique_vals)

这段代码做的事情是:读取类名映射 JSON,加载一张标签 PNG,并统计图像里面出现了哪些像素值。如果标签文件保存成 RGB 三通道 PNG,代码里会取第一个通道来获得索引值。统计结果能快速帮你确认这张图上到底标了哪几个类别,以及是否存在超出 0~4 范围的异常像素值。常见的一个坑是某些标注工具会把背景保存成 255 而不是 0,如果发现 unique values 里出现 255,记得做一次重映射,否则训练时背景类会直接少掉一大块。

2.3 用一套小脚本核对文件完整性与切面属性

拿到数据集后的第一件事不是训练,而是做一次完整性检查。三个切面目录下的切片数量是否一致,每张图像和标签的尺寸是否匹配,标签类别是否都在约定范围内,这些都是必须先验的。写过分割训练的人都知道,模型跑起来之后报“size mismatch”已经是比较友好的错误了,最恶心的是图像和标签尺寸一致但内容对不上,那才叫半夜加班。

import os import cv2 def verify_dataset(base_dir): for plane in ['sagittal', 'coronal', 'axial']: case_root = os.path.join(base_dir, plane) for case_name in os.listdir(case_root): case_dir = os.path.join(case_root, case_name) img_dir = os.path.join(case_dir, 'img') lbl_dir = os.path.join(case_dir, 'label') img_files = sorted(os.listdir(img_dir)) lbl_files = sorted(os.listdir(lbl_dir)) if len(img_files) != len(lbl_files): print(f'[MISMATCH] {plane}/{case_name}: img={len(img_files)} lbl={len(lbl_files)}') continue for img_f, lbl_f in zip(img_files, lbl_files): img = cv2.imread(os.path.join(img_dir, img_f), cv2.IMREAD_GRAYSCALE) lbl = cv2.imread(os.path.join(lbl_dir, lbl_f), cv2.IMREAD_UNCHANGED) if lbl.ndim == 3: lbl = lbl[:, :, 0] if img.shape != lbl.shape: print(f'[SIZE] {plane}/{case_name}/{img_f}: img={img.shape} lbl={lbl.shape}') break else: print(f'[OK] {plane}/{case_name}: {len(img_files)} slices') verify_dataset('data')

这段脚本会对三个切面目录逐一遍历,检查图像与标签的文件数量是否一致、每张图的尺寸是否相同。代码里的for...else表示循环正常结束时执行else分支,一旦发现尺寸不一致就会break跳出,避免后续无用检查。三类问题中,“数量不一致”最常见于标注过程中有人删了某几张质量差的切片但忘了同步删除对应图像,“尺寸不一致”则通常源于某次预处理时用了不同的裁剪参数。建议把脚本输出重定向到日志文件里,方便和原始数据描述逐条比对。

3. 用可视化代码把标签叠回原图:两步走,别让黑匣子骗你

3.1 读取 NIfTI 体数据并抽取一个切面

如果你的数据包不是纯 2D 切片,而是以三维体形式提供的,那么需要先知道怎么从体数据里抽出指定切面。用 SimpleITK 读取 NIfTI 时,拿到的顺序是(x, y, z),注意不是(z, y, x),这一点经常让人翻车。抽取矢状面时固定 x 坐标,抽取冠状面固定 y 坐标,抽取轴位固定 z 坐标,具体取哪个索引要看标记在哪个结构附近。

import SimpleITK as sitk import numpy as np def load_and_extract_plane(image_path, label_path, plane='sagittal', index=None): img = sitk.ReadImage(image_path) lbl = sitk.ReadImage(label_path) arr_img = sitk.GetArrayFromImage(img) # shape: (z, y, x) arr_lbl = sitk.GetArrayFromImage(lbl) if plane == 'sagittal': # 固定x方向索引,保留(z, y) idx = index if index is not None else arr_img.shape[2] // 2 slice_img = arr_img[:, :, idx] slice_lbl = arr_lbl[:, :, idx] elif plane == 'coronal': idx = index if index is not None else arr_img.shape[1] // 2 slice_img = arr_img[:, idx, :] slice_lbl = arr_lbl[:, idx, :] else: # axial idx = index if index is not None else arr_img.shape[0] // 2 slice_img = arr_img[idx, :, :] slice_lbl = arr_lbl[idx, :, :] return slice_img, slice_lbl, img.GetSpacing(), idx

GetArrayFromImage返回的数组维度顺序是 z、y、x,和 ITK 内部的 x、y、z 顺序相反。所以矢状面切片取arr[:, :, idx],其中idx对应着体数据在 x 方向上的索引。这里把索引写成可传参数而不是硬编码,是因为在实际操作中你想看的往往不是正中间那张,而是腰骶角最明显的旁正中矢状位切片。抽取结果返回的同时带上spacing信息,后面如果要做像素级统计或者可视化时按实际物理尺寸标注,会方便很多。

3.2 彩色叠加与窗宽窗位对照

有了单个切片之后,叠加显示是最直观的验证手段。灰度图作为底图,标签用半透明的彩色蒙版覆盖在上面。关键参数是透明度和配色方案:透明度太低标注重叠不明显,透明度太高又看不清底下的解剖结构。0.35~0.5 是一个相对可靠的区间。配色建议每种类别分配一个固定颜色,尤其是椎间盘这类小结构,用亮色系,不然叠上去很容易被背景吞掉。

import matplotlib.pyplot as plt import numpy as np COLORS = { 0: [0, 0, 0], # 背景 1: [255, 0, 0], # 骶骨 红 2: [0, 255, 0], # 椎体 绿 3: [255, 255, 0], # 椎间盘 黄 4: [0, 255, 255], # 后柱 青 } def overlay_label(image, label, alpha=0.4, save_path=None): rgb = np.stack([image] * 3, axis=-1) label_rgb = np.zeros_like(rgb) for cls, color in COLORS.items(): mask = label == cls label_rgb[mask] = color result = rgb * (1 - alpha) + label_rgb * alpha result = result.astype(np.uint8) plt.figure(figsize=(8, 8)) plt.imshow(result) plt.axis('off') if save_path: plt.savefig(save_path, dpi=150, bbox_inches='tight') plt.show()

这段代码直接对 NumPy 数组做混合,像素值会被等比例拉伸到 0~255 的显示范围。注意plt.imshow处理灰度图时默认用 colormap 映射,叠加时如果底图是 float 类型且取值范围不在 0~1,会显示成一片白或者一片黑。所以实际使用时要先检查输入 image 的数据范围。这里选择给每一类单独设置 RGB 颜色,而不是用matplotlib的jetcolormap,原因在于直接看颜色就知道是哪一类结构,不需要额外猜索引,也比图例更直观。

3.3 可视化在标注质量检查里的用法

可视化代码的价值不只是“看一眼结果”,更在于校验标签边界是否贴合解剖轮廓。我一般会生成“左一右三”的对比图:左边是原图,中间是标签叠加,右边只显示标签。这样能同时看清原图结构、标注覆盖位置、以及有没有明显偏离。生成全部切片的对比图之后,按目录顺序快速翻过去,能在几分钟之内发现绝大多数质量问题。

def save_grid_case(img_slices, lbl_slices, case_name, output_dir): os.makedirs(output_dir, exist_ok=True) for i, (img, lbl) in enumerate(zip(img_slices, lbl_slices)): fig, axes = plt.subplots(1, 2, figsize=(10, 5)) axes[0].imshow(img, cmap='gray') axes[0].set_title('original') axes[1].imshow(img, cmap='gray') axes[1].imshow(lbl, cmap='jet', alpha=0.4) axes[1].set_title('overlay') plt.savefig(os.path.join(output_dir, f'{case_name}_{i:03d}.png')) plt.close(fig)

这里把cmap='jet'用于标签图,而不是手工定义每个类别的颜色,优点是代码简洁、不需要维护颜色映射表;缺点则是相邻类别在视觉上可能分不清。所以这条代码只适合快速筛,不适合做正式结果的报告图。正式报告请回到 3.2 中固定颜色的方案。保存到输出目录时用case名+序号命名,这样翻图时能快速定位到具体哪个案例和哪一层出了问题。

4. 从数据集到训练集:预处理、归一化与三切面训练策略

4.1 CT HU 值裁剪与 MRI 归一化,别用一套参数打天下

腰痛脊椎分割的数据来源可能同时包含 CT 和 MRI。CT 图像是标准的 HU 值单位,正常范围是 -1024 到 3071,但有用的信息集中在骨组织和软组织之间。骶骨和腰椎椎体在 CT 上表现为高亮度的皮质骨边缘和相对低密度的松质骨内部,如果直接做全局 min-max 归一化,软组织对比度会被压缩到几乎看不见,分割效果必然崩。我通常对 CT 做窗宽窗位裁剪:骨窗用窗宽 1500、窗位 500,软组织窗用窗宽 400、窗位 50。

def ct_window_clip(image, window_width=1500, window_level=500): lower = window_level - window_width / 2 upper = window_level + window_width / 2 return np.clip(image, lower, upper).astype(np.float32) def normalize(image, mode='ct_bone'): if mode == 'ct_bone': image = ct_window_clip(image, 1500, 500) return (image - (-250)) / (1250 - (-250)) elif mode == 'ct_soft': image = ct_window_clip(image, 400, 50) return (image - (-150)) / (250 - (-150)) elif mode == 'mri': mean = image.mean() std = image.std() return (image - mean) / (std + 1e-8)

窗口裁剪之后的归一化不是把整个数组线性缩放到 0~1,而是基于窗宽的下界和上界做映射。窗口下界是level - width/2,在这个例子里是 500 - 750 = -250,上界是 1250。这样裁剪后小于下界的像素统一变 0,大于上界的统一变 1,中间的组织细节得到最大保留。MRI 没有统一的物理单位,直接 z-score 归一化即可。关键在于qcut那张图的顺序,如果你把 CT 和 MRI 放在同一个训练集里却用同一套归一化参数,模型会学到“亮度不一致”这种完全没有意义的特征匹配。

4.2 类别不平衡与损失函数选择

骶骨和椎体在整张切片里占的面积不小,但椎间盘是典型的小目标,几个像素宽的结构夹在两个椎体之间,后柱结构也是细条状分布。这种情况下如果直接用 softmax cross-entropy,模型大概率会“学会”把所有像素都预测成背景和前两大类别,椎间盘直接消失。标准做法是给每个类别算权重,权重和像素频率成反比,同时配合 DICE Loss。

from collections import Counter def compute_class_weights(label_paths, num_classes=5): counts = Counter() for lp in label_paths: lbl = cv2.imread(lp, cv2.IMREAD_UNCHANGED) if lbl.ndim == 3: lbl = lbl[:, :, 0] vals, cnts = np.unique(lbl, return_counts=True) for v, c in zip(vals, cnts): counts[int(v)] += c total = sum(counts.values()) class_weights = [] for i in range(num_classes): freq = counts.get(i, 0) / total class_weights.append(1.0 / (freq + 1e-6)) # 归一化,让背景权重为1,其他类别是相对值 class_weights = np.array(class_weights) class_weights /= class_weights[0] return class_weights

这套计算前需要先跑一遍完整的标签统计,默认假设标签 PNG 是灰度索引图。代码里对三通道 PNG 的处理是取第一个通道,如果标签恰好是调色板 PNG,第一个通道可能不是索引,排查的方法很简单——直接打印np.unique(lbl)看值域是否在 0~4。权重计算出来后,在损失函数里这样用:loss = F.cross_entropy(logits, target, weight=torch.from_numpy(class_weights).float())。背景权重被归一化成 1,椎间盘的权重可能到 10 甚至更高,模型才会被迫认真学习小结构的边缘。

4.3 数据增强和控制输入尺寸的实操值

三切面各自的图像长宽比例不一样。矢状面通常细长,轴位接近方形,训练时如果直接 resize 到统一尺寸,椎间盘那种窄长结构会被拉伸到失真。一个常见做法是限制短边再随机裁剪,比如把输入固定为 256×256,但矢状面先 resize 成 192×320 再随机裁剪。这个操作在验证时也要同步,否则推理时的输入分布和训练时不一致,边界预测会变得飘忽。

def train_augmentation(img, label, crop_size=(256, 256), p=0.5): h, w = img.shape[:2] # 先做短边缩放,长边等比缩放 scale = max(crop_size) / min(h, w) new_h, new_w = int(round(h * scale)), int(round(w * scale)) img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LINEAR) label = cv2.resize(label, (new_w, new_h), interpolation=cv2.INTER_NEAREST) # 随机裁剪到目标尺寸 if new_h > crop_size[0] and new_w > crop_size[1]: top = np.random.randint(0, new_h - crop_size[0]) left = np.random.randint(0, new_w - crop_size[1]) img = img[top:top + crop_size[0], left:left + crop_size[1]] label = label[top:top + crop_size[0], left:left + crop_size[1]] else: img = cv2.resize(img, crop_size, interpolation=cv2.INTER_LINEAR) label = cv2.resize(label, crop_size, interpolation=cv2.INTER_NEAREST) if np.random.rand() < p: img = np.fliplr(img).copy() label = np.fliplr(label).copy() return img, label

代码里两个关键点先说清:一是标签的 resize 插值方式必须是INTER_NEAREST,用线性插值会让类别边界出现小数,产生非法类别值;二是随机翻转时标签和图像必须使用相同的轴,并且要copy(),因为np.fliplr返回的是只读视图,后续转 tensor 会报错。旋转和弹性形变我一般不在第一版训练里加,先跑基线,后面看模型的过拟合情况再逐步加。

5. 避坑指南:三切面数据集的5个常见踩坑记录

5.1 现象:标签和图像明明同尺寸,错位却有一层——方向矩阵没有对齐

某个案例的矢状面切片序列,前期看单个切片图像和标签看不出异常,但把所有切片叠成体积后,发现标签整体向右偏移了一个像素。单个切面上这种偏移完全不可见,因为边界本身就模糊。

原因:读取 NIfTI 时用了sitk.GetArrayFromImage得到的是 z、y、x 顺序的数组,但可视化时用了plt.imshow的默认坐标轴。ITK 的物理坐标系原点在图像左下角,而 NumPy 数组的索引原点在左上角,两者在 y 轴方向上天然差了一个翻转。如果标签是从 ITK 读取的,而图像是从 nibabel 读取的,两个库对数组维度的处理方式不同,就会悄悄让 mask 翻转或者错位。

解决:统一使用同一个库读图像和标签。如果图像来自nibabel,标签也要用nibabel读,然后再做np.flip对齐。在训练代码里,推荐在数据加载类中固定direction方向,统一输出为“矢状面右前后”朝向,不要每个切片单独处理。

5.2 现象:Dice 很高,可医生看了说“骶骨磨没了”——边界与临床标准不一致

自动分割的 Dice 在验证集上到了 0.9 甚至 0.93,但把结果交给临床合作医生看,反馈是骶骨边缘少了一圈、椎间盘有假阳性。原因在于训练时默认把所有类别的像素一视同仁,而临床关注的是骶骨终板和椎间盘边缘的完整性。这个例子说明“指标好”和“可用”是两码事。

解决:对骶骨和椎间盘这两个类别单独算边界 Dice,也就是只评估距离边界若干个像素范围内的预测正确率。同时把损失函数换成soft-dice + boundary loss的组合,让模型在边界区域更谨慎。另外一个更朴素的办法,是可以考虑用可视化代码生成局部放大图,把骶髂关节附近单独截出来一张大图写进测试报告里,不只看全局指标。

5.3 现象:椎间盘和椎体边界黏在一起——类间距过近导致混淆

矢状面上椎间盘夹在上下椎体之间,灰度值和邻近骨组织在 CT 上有重叠,尤其在椎间盘退变脱水后,其密度接近软组织。模型经常把椎间盘区域并进椎体类,单看数值叫类混,但视觉上就是“没看到椎间盘”。

原因:类别之间在低分辨率特征图上相互干扰,加上椎间盘像素占比小,特征提取阶段就丢失了。

解决:训练时不再从全图随机裁剪,而是以椎间盘中心为锚点做局部裁剪,提高小类别在 batch 里的出现频率。推理后处理上加一条先验约束:椎间盘必须出现在两个椎体标签之间,且形态偏向扁椭圆形。如果分割结果不具备这个特征,大概率是预测错了,需要回炉。后处理的做法帮你把“模型学不到”的部分用解剖学知识兜住。

5.4 现象:验证集预测稳定但新样本翻车——把三个切面当独立数据集的问题

同一套数据里,矢状面、冠状面、轴位三个切面单独训练效果都不差,但把三个切面的推理结果融合到同一个三维体积里时,互相矛盾,比如轴位预测说这里有个椎间盘,矢状位预测却把它标成椎体。

原因:把一个病例的三个切面当三个独立数据集用,切分训练集时没有按病例锁定。同一个病人的三组切片被拆进了 train 和 val,验证集形同虚设。

解决:划分时必须保证同一人的三切面数据要么全在训练集,要么全在验证集,不能混。一个可靠策略是用train_val_split.txt里的病例 ID 列表来划分,而不是按文件路径随机分割。这在三切面数据集中是高概率踩中的坑,处理不干净时所有后续对比都不成立。

5.5 现象:读取标签时出现小数 mask——归一化放错了阶段

训练中途报错,target里出现非整数像素值,损失函数直接崩。查了半天发现是数据加载时先做了归一化再调到了 float32。

原因:标签图像在预处理流水线里被当成了普通图像处理,在归一化公共函数里被整体缩放到 0~1。分割标签本质上是一个离散索引图,不是灰度图像,任何对它的数值变换都会破坏类别语义。

解决:图像和标签永远走两条独立的预处理通道。图像可以归一化、裁剪、增强,标签只允许最近邻插值、翻转和裁剪,不能做任何数值缩放。建议在 DataLoader 的__getitem__里直接将标签astype(np.int64),一旦出现 float 型就抛异常,从源头拦住这类低级错误。

6. 进阶:三切面推理融合与质控报告,让分割结果可复用

6.1 把三个切面的概率图融合成一个体积

当三个切面各有模型时,融合是一件值得认真对待的事。常见做法不是做“投票”,而是将每个切面模型输出的概率图重采样到同一三维空间,然后逐体素取平均。矢状面对椎间盘敏感,轴位对椎管形态和关节突更准,简单平均会导致分辨率高的细节被分辨率低的切面拉平。更稳的方案是按结构加权:骶骨与椎体用矢状面权重高一些,椎间盘用轴位权重高一些,后柱结构用冠状面权重高一些。三个权重参数不需要精调,按 0.5/0.3/0.2 起步,在验证集上微调即可。重采样时用sitk.Resample的线性插值处理概率图,最终取 argmax 得到硬标签,再套一层连通域过滤掉孤立小斑块。

6.2 生成可视化质控页,用“不看指标看切片”做最终把关

自动化指标永远不能代替目检,我用一个简单的脚本把验证集每例生成一页质控报告:上半部分放三张有代表性的切面叠加图,下半部分放每类别的 Dice、IoU、表面距离。打印之前先快速翻一遍叠加图,哪一例的边缘不对就回到那一例单独查,不进入发布流程。做这一步的价值在于防止测试集过拟合,也防止“指标没崩但结构位置错”这种隐蔽翻车被发出去。

def generate_quality_report(case_name, slices, labels, preds, metric_fn, out_pdf): from matplotlib.backends.backend_pdf import PdfPages with PdfPages(out_pdf) as pdf: for i, (sl, lb, pr) in enumerate(zip(slices, labels, preds)): if i % 10 != 0: continue fig, axes = plt.subplots(1, 3, figsize=(15, 5)) axes[0].imshow(sl, cmap='gray') axes[0].set_title('input') axes[1].imshow(lb, cmap='jet', alpha=0.5) axes[1].set_title('ground truth') axes[2].imshow(pr, cmap='jet', alpha=0.5) axes[2].set_title('prediction') fig.suptitle(f'{case_name} slice {i}') pdf.savefig(fig) plt.close(fig)

每十层输出一页,既保证覆盖度又不至于生成几百页 PDF。PdfPages会把多页拼成一个文件,方便邮件发送和存档。这里的metric_fn传入每层的指标字典,按你的需求计算即可。我自己一直坚持先看切片再谈优化,因为分割模型的翻车往往不在整体数值上,而在个别病例的解剖变异里。这类数据集的正确打开方式就是三切面互相验证、可视化全程兜底。希望这套经验能让你少走几步弯路,直接看到真实问题所在。

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

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

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

立即咨询