1. WSI基础与格式解析

全切片图像(Whole Slide Image,WSI)是病理数字化工作流的起点。它并不是一张“超大照片”,而是由扫描仪在显微镜光路上逐行采集、再拼接压缩得到的多分辨率图像集合。理解它的成像原理、文件格式与金字塔结构,是后续分割、定量分析能否正确读取像素、坐标和物理尺度的前提。

一、WSI是怎么来的:从玻片到像素

数字切片扫描仪的核心是“光学显微 + 线阵/面阵相机 + 精密位移平台”。以常见的20×物镜扫描为例,其光学分辨率约为0.25 μm/pixel,即一个像素对应0.25微米组织。扫描时,平台按固定步进移动玻片,相机逐条带(strip)或逐视野(tile)曝光,系统记录每个视野的位置,再通过图像配准与拼接消除重叠区,形成一张覆盖整张玻片的“全分辨率”图像。由于单张全分辨率图可达数万×数万像素(例如40×下约100000×80000像素,未压缩约24 GB),必须做分块压缩与多级降采样,于是产生了金字塔层级。

这里有一个关键约定:WSI中的坐标通常以“层级0(level 0)”为基准,即最高分辨率层。其他层级都是level 0按2的幂次降采样得到。例如level 1是level 0的1/2,level 2是1/4,依此类推。OpenSlide等库读取时,返回的坐标默认是level 0坐标,这一点在后续做分割掩膜映射时必须牢记。

二、常见格式:SVS、NDPI、MRXS等

不同厂商的扫描仪输出不同容器格式,但底层大多遵循“金字塔 + 瓦片(tile)+ 元数据”的结构。常见格式对比如下:

格式典型厂商/来源容器基础特点
SVSAperio(Leica)TIFF兼容性好,OpenSlide支持成熟,常含JPEG压缩瓦片
NDPIHamamatsuTIFF含宏图与标签图,瓦片常为JPEG,元数据字段较丰富
MRXS3DHISTECH文件夹+索引由多个文件组成,需整体目录读取,不能只拷贝单文件
SCNLeicaTIFF部分版本需专用读取路径
DICOM WSI通用标准DICOM标准化程度高,适合归档与交换,读取需DICOM库

格式差异主要体现在:瓦片尺寸(如256×256或512×512)、压缩方式(JPEG/JPEG2000/WebP)、金字塔层数、是否内嵌缩略图(thumbnail)与标签图(label/macro)。实际项目中,若只处理SVS,可直接用OpenSlide;若涉及MRXS,必须保证整个文件夹完整。

三、金字塔层级与瓦片结构

金字塔的本质是“同一张切片的多级缓存”。以一张SVS为例,常见层级为:level 0(20×,0.25 μm/pixel)、level 1(10×,0.5 μm/pixel)、level 2(5×,1.0 μm/pixel)、level 3(2.5×,2.0 μm/pixel),以及一张缩略图。每一层内部再按瓦片切分,读取时按需加载,避免一次性载入整图。

下面这张SVG展示了WSI的金字塔层级、瓦片切分与元数据读取路径的关系,帮助建立“层级—坐标—物理尺度”的整体印象。

WSI金字塔层级与元数据读取关系 Level 0(最高分辨率) 20×,0.25 μm/pixel,约 100000×80000 px Level 1(1/2降采样) 10×,0.5 μm/pixel Level 2(1/4降采样) 5×,1.0 μm/pixel Level 3(1/8降采样) 2.5×,2.0 μm/pixel Thumbnail(缩略图) 瓦片切分(Tile) 每层按 256×256 或 512×512 切块 按需读取,避免整图载入内存 压缩方式:JPEG / JPEG2000 / WebP 瓦片坐标 = 层级坐标 / tile_size 元数据(Metadata) openslide.level_count:金字塔层数 openslide.level[0].downsample:降采样因子 openslide.mpp-x / mpp-y:物理像素尺寸 openslide.objective-power:物镜倍率 openslide.bounds-x/y:有效区域偏移 坐标约定(全文一致) • 所有读取坐标默认以 Level 0 为基准;若在 Level n 读取,需乘以该层 downsample 因子换算回 Level 0。 • 物理尺度换算:实际长度(μm) = 像素数 × mpp;分割掩膜映射回原图时,必须使用同一层级坐标。 • 示例:Level 2 下读取 (1000, 800),若 downsample=4,则 Level 0 坐标为 (4000, 3200)。

四、元数据读取与OpenSlide实战

OpenSlide是读取WSI最常用的开源库,支持SVS、NDPI、MRXS、SCN等格式。安装后,核心操作只有几步:打开文件、读取元数据、按层级读取区域、关闭文件。下面给出一个可直接运行的Python示例,用于读取一张SVS并输出关键元数据。

import openslide

slide = openslide.OpenSlide("sample.svs")

# 1. 金字塔层数
print("level_count:", slide.level_count)

# 2. 每层尺寸与降采样因子
for i in range(slide.level_count):
    dims = slide.level_dimensions[i]
    ds = slide.level_downsamples[i]
    print(f"level {i}: size={dims}, downsample={ds:.2f}")

# 3. 关键元数据
props = slide.properties
print("mpp-x:", props.get("openslide.mpp-x"))
print("mpp-y:", props.get("openslide.mpp-y"))
print("objective-power:", props.get("openslide.objective-power"))
print("vendor:", props.get("openslide.vendor"))

# 4. 读取Level 2的一块区域(坐标以Level 0为基准)
region = slide.read_region((4000, 3200), 2, (512, 512))
region = region.convert("RGB")
region.save("level2_region.png")

# 5. 读取缩略图
thumb = slide.get_thumbnail((1024, 1024))
thumb.save("thumbnail.png")

slide.close()

运行后,典型输出可能如下(示例数据,实际以文件为准):

level_count: 4
level 0: size=(100352, 79872), downsample=1.00
level 1: size=(50176, 39936), downsample=2.00
level 2: size=(25088, 19968), downsample=4.00
level 3: size=(6272, 4992), downsample=16.00
mpp-x: 0.25
mpp-y: 0.25
objective-power: 20
vendor: aperio

这里有两个容易踩坑的点。第一,read_region的坐标参数是Level 0坐标,而不是目标层级的坐标;第二个参数才是层级。第二,level_dimensions返回的是该层级的宽高,若直接用该层坐标去读,会得到错误区域。正确做法是:先确定目标层级,再把Level 0坐标传入,由OpenSlide内部完成换算。

实用检查清单:拿到一张WSI后,先确认四件事——① 格式是否被OpenSlide支持;② level_count与各层downsample是否合理;③ mpp-x/mpp-y是否存在(缺失时无法做物理尺度定量);④ 缩略图能否正常读出。这四项通过后,再进入分割与定量流程。

最后强调一个贯穿本课程的原则:WSI的所有分析都建立在“层级—坐标—物理尺度”三者一致的基础上。分割模型在Level 2上推理,掩膜要映射回Level 0;定量结果若以微米为单位,必须用mpp换算;可视化时若用缩略图,要按比例还原。把这一章的金字塔结构和元数据读取掌握牢固,后续的像素级分割与定量分析才不会在坐标和尺度上出错。