关于 pathml
pathml 是面向计算病理学的 Python 工具包,用于处理全视野病理图像并构建机器学习流程。它将切片读取、切块、预处理和特征提取纳入统一的 SlideData 与 Pipeline 抽象。
主要功能
- 01读取多种全视野图像格式
- 02切片切块与组织检测
- 03染色归一化与核分割
- 04支持多重免疫荧光图像处理
- 05提取图像块特征并导出数据
适用场景
- 01构建病理图像分类训练集
- 02处理 H&E 切片并统一染色
- 03分割病理图像中的细胞核
- 04批量处理多个全视野切片
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ jaechang-hits/ sciagent-skills --skill pathml安装后在 Claude Code 中输入/pathml确认已加载。
详细介绍
pathml
概述
PathML 是一个 Python 工具包,专为全视野图像(WSI)的计算病理工作流设计。它提供从原始切片文件(SVS、NDPI、MRXS、TIFF)到图块提取、预处理(染色归一化、细胞核分割、组织检测)、特征提取和机器学习的统一流程。PathML 集成了常用的 Python 机器学习和图像处理库,并通过 SlideData 和 Pipeline 抽象简化 WSI 处理的复杂性。
适用场景
- 处理全视野 H&E 图像:将大型 WSI 切分为图块,并对来自不同扫描仪或批次的切片进行染色差异归一化。
- 病理切片上的细胞核分割:使用内置分割流程,在 H&E 或 DAPI 染色的 WSI 中检测并分割细胞核。
- 从 WSI 构建机器学习训练数据集:提取带有关联标签的图块,用于训练组织分类器、肿瘤检测器或生存预测模型。
- 多重免疫荧光(mIF)图像分析:处理多通道免疫荧光切片,并进行针对各通道的预处理和特征提取。
- 跨队列染色归一化:应用 Macenko 或 Vahadane 染色归一化,协调来自多个机构的 H&E 切片。
- 为下游机器学习提取特征:从图块中提取手工设计或深度学习特征,用于患者级预测任务。
- 对于标准 2D 显微镜图像(非 WSI),可直接使用
scikit-image或cellpose,无需承担 PathML 的额外开销。
前置条件
- Python 软件包:
pathml、torch、torchvision、numpy、scikit-image、openslide-python - 系统:OpenSlide C 库(读取 WSI 所必需)
- 数据要求:SVS、NDPI、MRXS 或 TIFF 格式的 WSI 文件;建议使用 GPU 进行分割
- 环境:Python 3.8+;用于深度学习预处理的 CUDA 兼容 GPU
# Install system dependency first
conda install -c conda-forge openslide
# Install PathML
pip install pathml
# For GPU support
pip install torch torchvision --extra-index-url https://download.pytorch.org/whl/cu118
快速入门
from pathml.core import SlideData
from pathml.preprocessing import Pipeline
from pathml.preprocessing.transforms import BoxBlur, TissueDetectionHE
# Load → build pipeline → tile → preprocess
slide = SlideData("tumor.svs", name="demo")
pipeline = Pipeline([BoxBlur(kernel_size=3), TissueDetectionHE(mask_name="tissue")])
slide.run(pipeline, tile_size=256, tile_stride=256)
# Inspect tiles
from pathml.core import Tile
tiles = [t for t in slide.tiles if t.masks["tissue"].any()]
print(f"Tissue tiles: {len(tiles)} of {len(slide.tiles)}")
工作流
步骤 1:加载全视野图像
from pathml.core import SlideData
# Load an H&E whole-slide image
slide = SlideData("path/to/slide.svs", name="tumor_slide_001")
print(f"Slide name: {slide.name}")
print(f"Slide shape: {slide.slide.shape}")
print(f"Slide properties: {slide.slide.properties}")
步骤 2:定义预处理流程
from pathml.preprocessing import Pipeline
from pathml.preprocessing.transforms import (
BoxBlur,
TissueDetectionHE,
HEStainNormalization,
)
# Build a preprocessing pipeline for H&E slides
pipeline = Pipeline([
BoxBlur(kernel_size=5), # smooth image
TissueDetectionHE(mask_name="tissue"), # detect tissue regions
HEStainNormalization(target="normalize"), # normalize H&E staining
])
print(f"Pipeline steps: {len(pipeline.transforms)}")
步骤 3:创建 TileDataset
from pathml.core import TileDataset
# Tile the slide into 256x256 patches at 20x magnification
slide.generate_tiles(
shape=(256, 256),
stride=(256, 256),
pad=False,
level=0, # pyramid level 0 = highest resolution
coords_format="fractional",
)
print(f"Total tiles generated: {len(slide.tiles)}")
步骤 4:运行预处理流程
# Apply preprocessing pipeline to all tiles
slide.run(pipeline, distributed=False, tile_pad=False)
print("Pipeline complete — tiles preprocessed")
# Inspect a single tile
tile = slide.tiles[0]
print(f"Tile shape: {tile.image.shape}") # (256, 256, 3)
print(f"Tile masks: {list(tile.masks.keys())}")
步骤 5:细胞核分割
from pathml.preprocessing.transforms import NuclearSegmentation
# Run Hematoxylin-channel nuclear segmentation
seg_pipeline = Pipeline([
TissueDetectionHE(mask_name="tissue"),
NuclearSegmentation(mask_name="nuclei"),
])
slide.run(seg_pipeline, distributed=False)
# Count nuclei per tile
for tile in list(slide.tiles)[:5]:
n_nuclei = tile.masks["nuclei"].max()
print(f"Tile {tile.coords}: {n_nuclei} nuclei detected")
步骤 6:特征提取
import numpy as np
from pathml.core import SlideDataset
features = []
for tile in slide.tiles:
if "tissue" in tile.masks and tile.masks["tissue"].any():
img = tile.image
feat = {
"mean_r": img[:, :, 0].mean(),
"mean_g": img[:, :, 1].mean(),
"mean_b": img[:, :, 2].mean(),
"std_r": img[:, :, 0].std(),
"n_nuclei": int(tile.masks["nuclei"].max()) if "nuclei" in tile.masks else 0,
"tile_x": tile.coords[0],
"tile_y": tile.coords[1],
}
features.append(feat)
import pandas as pd
df = pd.DataFrame(features)
df.to_csv("slide_features.csv", index=False)
print(f"Extracted features from {len(df)} tissue tiles -> slide_features.csv")
步骤 7:保存并导出处理后的切片
import h5py
# Save slide data (tiles + masks) to HDF5
slide.write("processed_slide.h5")
print("Slide saved to processed_slide.h5")
# Reload for downstream use
from pathml.core import SlideData
slide_loaded = SlideData.read("processed_slide.h5")
print(f"Reloaded: {len(slide_loaded.tiles)} tiles")
关键参数
| 参数 | 默认值 | 范围 / 选项 | 作用 |
|---|---|---|---|
shape | (256, 256) | (64,64) – (1024,1024) | 图块的像素尺寸 |
stride | 等于 shape | 任意 ≤ shape 的元组 | 图块中心之间的步长;stride < shape 时图块会重叠 |
level | 0 | 0 – 最大金字塔层级 | 金字塔分辨率层级(0 = 全分辨率) |
kernel_size | 5 | 奇数 3–21 | BoxBlur 中的平滑核尺寸 |
mask_name | 必需 | 任意字符串 | 存储在 tile.masks 中的输出掩码名称 |
distributed | False | True、False | 为大型切片启用 Dask 分布式处理 |
pad | False | True、False | 将边缘图块填充至完整的 shape 尺寸 |
常用方案
方案:仅保留组织图块
适用场景:排除背景图块,以减少后续步骤中的内存和计算开销。
# Filter tiles to only tissue regions after running tissue detection pipeline
tissue_tiles = [t for t in slide.tiles if "tissue" in t.masks and t.masks["tissue"].mean() > 0.5]
print(f"Tissue tiles: {len(tissue_tiles)} / {len(slide.tiles)} total")
方案:将图块导出为 PNG 文件
适用场景:创建带标签的图块数据集,用于在 PyTorch 中训练自定义分类器。
from PIL import Image
import numpy as np
from pathlib import Path
output_dir = Path("tiles_png")
output_dir.mkdir(exist_ok=True)
for i, tile in enumerate(slide.tiles):
if "tissue" in tile.masks and tile.masks["tissue"].mean() > 0.5:
img = Image.fromarray(tile.image.astype(np.uint8))
img.save(output_dir / f"tile_{i:05d}_x{tile.coords[0]}_y{tile.coords[1]}.png")
print(f"Saved {i+1} tiles to {output_dir}/")
方案:批量处理多个切片
适用场景:对某个目录中的 WSI 文件运行相同的预处理流程。
from pathlib import Path
from pathml.core import SlideData
from pathml.preprocessing import Pipeline
from pathml.preprocessing.transforms import TissueDetectionHE, HEStainNormalization
pipeline = Pipeline([
TissueDetectionHE(mask_name="tissue"),
HEStainNormalization(target="normalize"),
])
wsi_dir = Path("slides/")
for wsi_path in sorted(wsi_dir.glob("*.svs")):
slide = SlideData(str(wsi_path), name=wsi_path.stem)
slide.generate_tiles(shape=(256, 256), stride=(256, 256), level=0)
slide.run(pipeline, distributed=False)
slide.write(f"processed/{wsi_path.stem}.h5")
print(f"Processed {wsi_path.name}: {len(slide.tiles)} tiles")
预期输出
slide.tiles— 可迭代对象,包含Tile对象;每个对象都有.image(numpy 数组)和.masks(numpy 数组字典)slide_features.csv— 按图块整理的表格特征(颜色统计、细胞核计数、坐标)processed_slide.h5— HDF5 文件,包含图块、掩码和供下游使用的元数据- PNG 图块文件(可选)— 可直接加载为 PyTorch
ImageFolder数据集
故障排查
| 问题 | 原因 | 解决方案 |
|---|---|---|
openslide.lowlevel.OpenSlideUnsupportedFormatError | 未安装 OpenSlide C 库,或不支持 WSI 格式 | conda install -c conda-forge openslide;检查格式兼容性 |
分割过程中出现 CUDA out of memory | 图块尺寸对 GPU 来说过大 | 将图块 shape 缩小至 (128, 128),或在 CPU 上使用 distributed=False 运行 |
generate_tiles 后 slide.tiles 为空 | 层级索引超出范围,或所有图块都被筛除 | 使用 level=0;通过 slide.slide.level_count 检查切片金字塔 |
| 染色标准化生成黑色图块 | 源切片对比度过低,或组织检测失败 | 在标准化前应用 TissueDetectionHE;检查组织掩码的覆盖范围 |
在 tile.masks 中出现 KeyError: 'nuclei' | 尚未运行分割流程 | 访问掩码前,先使用 slide.run() 运行 NuclearSegmentation 流程 |
| 图块生成速度很慢 | 大型 SVS 使用了高分辨率的 0 级 | 使用较低的金字塔层级(level=1 或 level=2)加快原型验证 |
AttributeError: SlideData has no attribute 'write' | PathML 版本较旧 | 使用 pip install --upgrade pathml 获取 HDF5 保存/加载支持 |
参考资料
- PathML 文档 — 官方文档和教程
- PathML GitHub (Dana-Farber/PathML) — 源代码和示例
- Rosenthal et al. (2022), Cell Systems — PathML 论文 — 原始论文
- OpenSlide 文档 — PathML 底层使用的 WSI 读取库
评论
支持 Markdown