Pictologics 详细使用指南
版本 :v0.4.1
许可 :Apache-2.0 | PyPI :pictologics
主页 :https://martonkolossvary.github.io/pictologics/
1. 概述
Pictologics 是一个 IBSI 合规(Image Biomarker Standardisation Initiative,图像生物标志物标准化倡议)的影像组学特征提取 Python 库。与 pyradiomics 相比,它采用声明式流水线(Pipeline)架构,用代码定义预处理步骤链,特征命名遵循 IBSI 4 字符代码标准。
与 pyradiomics 的核心差异
| 维度 | pyradiomics | pictologics |
|---|---|---|
| 配置方式 | YAML 参数文件 | 代码内 add_config() 声明式步骤 |
| 特征命名 | 自有命名(如 original_firstorder_Mean) |
IBSI 标准(如 mean_intensity_Q4LE) |
| 掩膜值语义 | correctMask 自动修正 |
显式 binarize_mask 步骤 + keep_largest_component |
| 内部并行 | 无 | Numba JIT 自动加速底层计算 |
| 跨配置去重 | 无 | 内置特征去重引擎 |
| 结果格式化 | 手动 pd.DataFrame |
内置 format_results() + save_results() |
| 过滤器 | 无 | 内置 Gabor / LoG / Laws / Riesz / Wavelet 等 |
| 加载格式 | SimpleITK 全支持 | DICOM 文件夹 / NIfTI / MHA / DICOM-SEG |
2. 安装
bash
pip install pictologics
依赖清单(自动安装)
| 包名 | 版本要求 | 用途 |
|---|---|---|
numpy |
≥ 2.0 | 数组运算 |
numba |
≥ 0.62, < 0.63 | JIT 编译加速 |
pandas |
≥ 2.0 | DataFrame 结果 |
pydicom |
≥ 2.2 | DICOM 读取 |
highdicom |
≥ 0.22 | DICOM-SEG 支持 |
nibabel |
≥ 3.2 | NIfTI 读取 |
SimpleITK |
--- | 图像插值/重采样 |
PyWavelets |
≥ 1.4 | 小波滤波 |
matplotlib |
≥ 3.10 | 可视化 |
scipy |
≥ 1.16 | 科学计算 |
tqdm |
≥ 4.66 | 进度条 |
Pillow |
≥ 11.1 | 图像 I/O |
pyyaml |
≥ 6.0 | YAML 模板解析 |
pymcubes |
≥ 0.1.6 | Marching Cubes(形态学特征) |
pyjpegls |
≥ 1.0 | JPEG-LS 解码(DICOM 压缩) |
Numba JIT 预热
import pictologics 时会自动执行 Numba JIT 预热(约 30 秒),对 200+ 个核函数进行编译。可通过环境变量跳过:
python
import os
os.environ["PICTOLOGICS_DISABLE_WARMUP"] = "1"
import pictologics
⚠️ 注意:跳过预热后,首次调用
pipeline.run()时 Numba 仍会延迟编译,首对数据耗时稍长。
3. 核心概念
3.1 Image Dataclass
python
from pictologics import Image
@dataclass
class Image:
array: np.ndarray # 3D (X, Y, Z) 浮点数组
spacing: tuple[float, float, float] # 体素间距 (mm)
origin: tuple[float, float, float] # 左上角原点 (mm)
direction: np.ndarray | None # 3×3 方向余弦矩阵
modality: str = "Unknown" # 模态标签
source_mask: np.ndarray | None # 有效体素掩膜(布尔)
⚠️ 关键 :
array使用 (X, Y, Z) 轴顺序(即numpy的 column-major),与 NIfTI 的(i, j, k)索引一致。
3.2 RadiomicsPipeline --- 核心流水线
python
from pictologics import RadiomicsPipeline
pipeline = RadiomicsPipeline(
deduplicate=True, # 启用跨配置特征去重(默认)
load_standard=True, # 加载标准模板配置(默认)
)
核心方法:
| 方法 | 说明 |
|---|---|
add_config(name, steps, source_mode) |
注册一个处理配置 |
run(image, mask, config_names) |
对一对 image/mask 执行指定配置 |
clear_log() |
清空处理日志 |
get_all_standard_config_names() |
列出所有标准配置名 |
3.3 SourceMode --- 源图像有效性模式
python
from pictologics import SourceMode
| 模式 | 枚举值 | 说明 |
|---|---|---|
FULL_IMAGE |
"full_image" |
所有体素含真实数据(DICOM 完整 FOV) |
ROI_ONLY |
"roi_only" |
仅 ROI 内体素有效,外部为哨兵值(如 -2048) |
AUTO |
"auto" |
自动检测哨兵值,若检测到则按 ROI_ONLY 处理 |
4. 数据加载
4.1 load_image() --- 通用图像加载
python
from pictologics.loader import load_image
Image = load_image(
path: str, # 文件夹路径(DICOM系列)或文件路径(.nii/.nii.gz/.mha/.dcm)
dataset_index: int = 0, # DICOM 数据集索引(多序列时选择第几个)
recursive: bool = False, # DICOM 文件夹中递归搜索最佳序列
reference_image: Optional[Image] = None, # 参考图像(用于重定位到同一空间)
transpose_axes: tuple | None = None, # 轴转置
fill_value: float = 0.0, # 重定位填充值
apply_rescale: bool = True, # DICOM RescaleSlope/Intercept
subvoxel_tolerance: float = 0.5, # 亚体素偏移容差
subvoxel_warning_threshold: float = 0.01,
min_overlap_fraction: float = 0.5, # 最小重叠比例
)
支持的输入格式:
| 输入 | 识别方式 | 说明 |
|---|---|---|
| DICOM 文件夹 | path.is_dir() → _load_dicom_series() |
自动扫描 .dcm 文件 |
.nii / .nii.gz |
扩展名匹配 →_load_nifti() |
NIfTI-1 格式 |
.mha / .mhd |
扩展名匹配 →_load_meta_image() |
MetaImage 格式 |
.dcm |
扩展名匹配 →_load_dicom_file() |
单文件 DICOM |
| DICOM-SEG | 内容检测 →load_seg() |
DICOM 分割对象 |
4.2 load_seg() --- DICOM-SEG 加载
python
from pictologics import load_seg
# 从 DICOM-SEG 文件加载分割掩膜
mask = load_seg(
"path/to/seg.dcm",
reference_image=image_obj, # 可选:参考图像
combine_segments: bool = True, # 合并多个 segment 为单一掩膜
)
4.3 create_full_mask() --- 全图掩膜
当没有提供掩膜时,生成覆盖整个图像的全 1 掩膜:
python
from pictologics import create_full_mask
full_mask = create_full_mask(image_obj)
# 等同于:所有体素都视为 ROI
4.4 load_and_merge_images() --- 多文件合并
加载多个掩膜文件并合并为单一体积:
python
from pictologics import load_and_merge_images
merged = load_and_merge_images(
image_paths=["mask1.nii.gz", "mask2.nii.gz"],
conflict_resolution="max", # "max" / "sum" / "first"
binarize=True, # 二值化输出
)
5. Pipeline 配置
5.1 add_config() --- 注册处理配置
python
pipeline.add_config(
name="my_config", # 唯一配置名
steps=[...], # 步骤列表(见下)
source_mode="full_image", # 源图像有效性模式
sentinel_value=None, # 显式哨兵值(可选)
)
5.2 步骤详解
5.2.1 resample --- 重采样
python
{
"step": "resample",
"params": {
"new_spacing": (1.0, 1.0, 1.0), # 目标体素间距 (mm)
"interpolation": "linear", # 图像插值:nearest / linear / bspline
"mask_interpolation": "nearest", # 掩膜插值:nearest(推荐)
"mask_threshold": 0.5, # 掩膜二值化阈值
"round_intensities": False, # 重采样后是否四舍五入
},
}
5.2.2 resegment --- 强度重分割
根据强度范围重新定义 ROI(排除范围外的体素):
python
{
"step": "resegment",
"params": {
"range_min": -1000, # 强度下限(如 CT 肺窗)
"range_max": 200, # 强度上限
},
}
5.2.3 filter_outliers --- 异常值过滤
python
{
"step": "filter_outliers",
"params": {
"sigma": 3.0, # 标准差倍数(默认 3)
},
}
5.2.4 round_intensities --- 强度取整
python
{"step": "round_intensities", "params": {}}
将浮点强度值四舍五入为整数,适用于某些离散化方法的要求。
5.2.5 keep_largest_component --- 保留最大连通域
python
{
"step": "keep_largest_component",
"params": {
"apply_to": "both", # "morph" / "intensity" / "both"
},
}
等同于 pyradiomics 的
correctMask: true。
5.2.6 binarize_mask --- 掩膜二值化
python
{
"step": "binarize_mask",
"params": {
"threshold": 0.5, # 阈值(≥ threshold → 1)
"apply_to": "both", # "morph" / "intensity" / "both"
# 或使用 mask_values:
# "mask_values": 1, # 仅保留值为 1 的体素
# "mask_values": [1, 2, 3], # 保留多个值
# "mask_values": (10, 100), # 保留范围内的值
},
}
⚠️ 重要 :pictologics 的 ROI 掩膜使用 值 == 1 语义。如果掩膜中有多值标签,必须先用
binarize_mask转换。
5.2.7 discretise --- 强度离散化
python
# 方法 1:固定 bin 数(FBN)
{
"step": "discretise",
"params": {
"method": "FBN",
"n_bins": 32,
},
}
# 方法 2:固定 bin 宽度(FBS)
{
"step": "discretise",
"params": {
"method": "FBS",
"bin_width": 25.0, # HU 宽度(CT 推荐 25)
},
}
⚠️ 纹理特征(glcm/glrlm/glszm/gldzm/ngtdm/ngldm)和直方图/IVH 特征依赖离散化步骤。
5.2.8 filter --- 图像滤波
python
# Gabor 滤波
{
"step": "filter",
"params": {
"type": "gabor",
"sigma": 2.0,
"theta": 0.0, # 方向(弧度)
"lambda": 5.0, # 波长
"gamma": 0.5, # 椭圆度
},
}
# Laplacian of Gaussian (LoG)
{
"step": "filter",
"params": {
"type": "laplacian_of_gaussian",
"sigma": 2.0,
},
}
# 小波变换
{
"step": "filter",
"params": {
"type": "wavelet",
"wavelet": "coif1", # 小波基
"level": 1, # 分解级数
},
}
支持的所有滤波器类型:
| 类型 | 参数 |
|---|---|
mean |
kernel_size |
laplacian_of_gaussian |
sigma |
gabor |
sigma, theta, lambda, gamma |
laws |
kernel_type (L5/E5/S5/W5/R5) |
riesz_transform |
order |
riesz_log |
sigma, order |
riesz_simoncelli |
order |
simoncelli_wavelet |
scale, order |
wavelet |
wavelet |
5.2.9 extract_features --- 特征提取
python
{
"step": "extract_features",
"params": {
"families": [
"intensity", # 一阶统计量
"morphology", # 形态学特征
"texture", # 全部纹理矩阵(glcm/glrlm/glszm/gldzm/ngtdm/ngldm)
"histogram", # 强度直方图
"ivh", # 强度-体积直方图
],
# 也可单独指定纹理子类:
# "families": ["intensity", "morphology", "glcm", "glrlm"],
},
}
6. 特征族与 IBSI 命名
6.1 特征族映射
| family 名称 | IBSI 分类 | 特征数 | 需要离散化 |
|---|---|---|---|
intensity |
一阶统计量 + 空间强度 + 局部强度 | ~18 + 8 + 2 | ❌ 否 |
morphology |
形态学(体积/表面积/球形度等) | ~29 | ❌ 否 |
texture |
全部纹理矩阵(同下 6 类之和) | ~104 | ✅ 是 |
glcm |
灰度共生矩阵 | ~23 | ✅ 是 |
glrlm |
灰度游程长度矩阵 | ~16 | ✅ 是 |
glszm |
灰度区域大小矩阵 | ~16 | ✅ 是 |
gldzm |
灰度距离区域矩阵 | ~16 | ✅ 是 |
ngtdm |
邻域灰度差异矩阵 | ~5 | ✅ 是 |
ngldm |
邻域灰度依赖矩阵 | ~17 | ✅ 是 |
histogram |
强度直方图 | ~23 | ✅ 是 |
ivh |
强度-体积直方图 | ~9 | ✅ 是 |
| 合计 | --- | ~170 | --- |
6.2 IBSI 4 字符代码
每个特征名末尾附有 IBSI 标准代码,如:
standard__mean_intensity_Q4LE
standard__intensity_variance_ECT3
standard__intensity_skewness_KE2A
standard__morphology_volume_approx_YRKZ
standard__glcm_joint_maximum_84IQ
代码格式:
config_name__feature_name_IBSI_CODE
6.3 内置标准模板
python
pipeline = RadiomicsPipeline(load_standard=True)
# 6 个预定义配置
print(pipeline.get_all_standard_config_names())
# ['standard_fbn_8', 'standard_fbn_16', 'standard_fbn_32', 'standard_fbn_64',
# 'standard_fbs_16', 'standard_fbs_25']
| 模板名 | 重采样 | 离散化方法 | 参数 | 特征族 |
|---|---|---|---|---|
standard_fbn_8 |
无 | FBN | 8 bins | 全部 |
standard_fbn_16 |
无 | FBN | 16 bins | 全部 |
standard_fbn_32 |
无 | FBN | 32 bins | 全部 |
standard_fbn_64 |
无 | FBN | 64 bins | 全部 |
standard_fbs_16 |
无 | FBS | bin_width=16 | 全部 |
standard_fbs_25 |
无 | FBS | bin_width=25 | 全部 |
⚠️ 标准模板不含重采样 步骤------需自行添加
resample步骤。
7. 运行与结果
7.1 pipeline.run() --- 执行提取
python
results = pipeline.run(
image="path/to/dicom_folder", # str 或 Image 对象
mask="path/to/roi.nii.gz", # str 或 Image 对象,None = 全图
subject_id=None, # 可选受试者 ID
config_names=None, # 运行哪些配置,None = 全部
mask_subvoxel_tolerance=0.5,
mask_subvoxel_warning_threshold=0.01,
mask_min_overlap_fraction=0.5,
)
# 返回值:dict[str, pd.Series]
# {'my_config': pd.Series(feature_name → value), ...}
7.2 format_results() --- 格式化结果
python
from pictologics import format_results
# Wide 格式(每 config 的特征展平为一列)
feat = format_results(
results,
fmt="wide", # "wide" 或 "long"
meta={"subject": "001"}, # 前置元数据列
output_type="dict", # "dict" / "pandas" / "json"
config_col="config", # long 格式中 config 标签列名
)
# Wide 输出示例:
# {
# "subject": "001",
# "my_config__mean_intensity_Q4LE": -817.4,
# "my_config__intensity_variance_ECT3": 7186.8,
# ...
# }
7.3 save_results() --- 保存结果
python
from pictologics import save_results
save_results(
results,
path="features.csv", # .csv / .xlsx / .json
fmt="wide",
meta={"subject_id": "001"},
config_col="config",
)
7.4 结果完整性保证
pictologics 保证每个配置返回完整的特征集:
| 场景 | 行为 |
|---|---|
| 提取成功 | 所有特征值有效 |
| 个别特征计算失败 | 该特征为 NaN,其余保留 |
ROI 为空(EmptyROIMaskError) |
全部特征为 NaN |
| 运行时异常 | 全部特征为 NaN |
8. 特征去重
8.1 工作原理
当多个配置共享相同的预处理步骤时,pictologics 自动识别并仅计算一次,后续配置直接从缓存读取。
python
pipeline = RadiomicsPipeline(deduplicate=True) # 默认启用
pipeline.add_config("config_A", steps=[...])
pipeline.add_config("config_B", steps=[...]) # 与 A 共享部分步骤?
results = pipeline.run(image=img, mask=mask)
# 查看去重统计
print(pipeline.deduplication_stats)
# {'reused_families': 3, 'computed_families': 7, 'cache_hit_rate': 0.3}
8.2 去重规则版本
python
from pictologics import DeduplicationRules, get_default_rules
# 使用特定版本规则
pipeline = RadiomicsPipeline(
deduplication_rules="1.0", # 版本字符串
)
# 自定义规则
rules = get_default_rules()
pipeline.deduplication_rules = rules
9. 常见问题与解决方案
9.1 ⚠️ DICOM LPS ↔ NIfTI RAS 坐标系不匹配
症状:
ValueError: Origin mismatch between mask (164.8, 180.0, 4.75)
and image (-164.8, -180.0, -300.25)
ValueError: Direction mismatch between mask and image.
原因:DICOM 使用 LPS 坐标系(Left-Posterior-Superior),NIfTI 使用 RAS(Right-Anterior-Superior)。origin 符号相反,direction 可能为单位矩阵与负单位矩阵的差异。
解决方案:强制掩膜的 origin/spacing/direction 与图像对齐:
python
from pictologics import Image as PlgImage
from pictologics.loader import load_image
img = load_image("dicom_folder/")
mask = load_image("roi.nii.gz")
# 强制掩膜空间元数据对齐到图像
mask_fixed = PlgImage(
array=mask.array,
spacing=img.spacing,
origin=img.origin,
direction=img.direction,
modality=mask.modality,
)
results = pipeline.run(image=img, mask=mask_fixed, ...)
原理:掩膜二值数组本身定义在图像体素空间内,origin/spacing/direction 差异仅是坐标系转换的产物,对齐是安全的。
9.2 ROI 掩膜值必须为 1
pictologics 使用 mask_values=1 语义。如果掩膜值不是 1,需用 binarize_mask 步骤转换:
python
{"step": "binarize_mask", "params": {"threshold": 0.5}}
# 或
{"step": "binarize_mask", "params": {"mask_values": [2, 3, 4]}}
9.3 空 ROI 处理
当预处理后 ROI 为空(0 个体素),pipeline 返回 NaN 特征列,不抛出异常。
9.4 Numba 警告
RuntimeWarning: invalid value encountered in sqrt
原因 :某些 ROI 强度范围为负值,导致 RMS、energy 等指标在中间计算中出现负数开方。pictologics 自动将此类特征设为 NaN,不影响其余特征。
9.5 并行处理注意事项
RadiomicsPipeline对象不可跨进程共享 (内部有状态_configs,_log,_dedup*)- 每个 worker 进程需独立创建 pipeline 实例
- 设置
PICTOLOGICS_DISABLE_WARMUP=1可跳过每进程的 JIT 预热
10. 并行处理模板
python
import os
from concurrent.futures import ProcessPoolExecutor
os.environ.setdefault("PICTOLOGICS_DISABLE_WARMUP", "1")
def process_pair(args):
from pictologics import RadiomicsPipeline, Image as PlgImage
from pictologics.loader import load_image
from pictologics.results import format_results
img_folder, roi_path, config_steps = args
img = load_image(img_folder)
mask = load_image(roi_path)
mask = PlgImage(
array=mask.array,
spacing=img.spacing,
origin=img.origin,
direction=img.direction,
modality=mask.modality,
)
pipeline = RadiomicsPipeline(deduplicate=False, load_standard=False)
pipeline.add_config("cfg", steps=config_steps, source_mode="full_image")
results = pipeline.run(image=img, mask=mask, config_names=["cfg"])
return format_results(results, fmt="wide", meta={
"image_path": img_folder,
"roi_path": roi_path,
}, output_type="dict")
# 使用
with ProcessPoolExecutor(max_workers=8) as pool:
futures = [pool.submit(process_pair, task) for task in tasks]
11. API 快速参考
11.1 导入路径
python
# 顶层公共 API
from pictologics import (
RadiomicsPipeline,
SourceMode,
Image,
load_image,
load_seg,
create_full_mask,
load_and_merge_images,
format_results,
save_results,
warmup_jit,
)
# 特征去重
from pictologics import (
ConfigurationAnalyzer,
DeduplicationPlan,
DeduplicationRules,
get_default_rules,
)
# loader 模块
from pictologics.loader import load_image, _validate_geometry
from pictologics.loaders import load_seg
11.2 Image Dataclass 字段
| 字段 | 类型 | 说明 |
|---|---|---|
array |
np.ndarray[float] |
3D (X, Y, Z) 体素数组 |
spacing |
tuple[float, float, float] |
体素间距 mm |
origin |
tuple[float, float, float] |
体素原点 mm |
direction |
`np.ndarray[3,3] | None` |
modality |
str |
模态标签 |
source_mask |
`np.ndarray[bool] | None` |
11.3 RadiomicsPipeline 方法签名
python
class RadiomicsPipeline:
def __init__(self,
deduplicate: bool = True,
deduplication_rules: str | DeduplicationRules | None = None,
load_standard: bool = True,
) -> None: ...
def add_config(self,
name: str,
steps: list[dict],
source_mode: str = "full_image",
sentinel_value: float | None = None,
) -> RadiomicsPipeline: ...
def run(self,
image: str | Image,
mask: str | Image | None = None,
subject_id: str | None = None,
config_names: list[str] | None = None,
mask_subvoxel_tolerance: float = 0.5,
mask_subvoxel_warning_threshold: float = 0.01,
mask_min_overlap_fraction: float = 0.5,
) -> dict[str, pd.Series]: ...
def clear_log(self) -> None: ...
def get_all_standard_config_names(self) -> list[str]: ...
# Properties
deduplication_enabled: bool # get/set
deduplication_rules: DeduplicationRules # get/set
last_deduplication_plan: DeduplicationPlan | None # get
deduplication_stats: dict # get
12. 完整示例
python
#!/usr/bin/env python
"""端到端 pictologics 特征提取示例"""
import os
os.environ["PICTOLOGICS_DISABLE_WARMUP"] = "1"
from pictologics import (
RadiomicsPipeline,
Image as PlgImage,
format_results,
save_results,
)
from pictologics.loader import load_image
# ── 1. 定义处理步骤 ──────────────────────────────────
STEPS = [
# 1mm 各向同性重采样
{
"step": "resample",
"params": {
"new_spacing": (1, 1, 1),
"interpolation": "linear",
"mask_interpolation": "nearest",
"mask_threshold": 0.5,
},
},
# 保留最大连通域
{"step": "keep_largest_component", "params": {"apply_to": "both"}},
# FBS 离散化(CT 推荐 bin_width=25)
{"step": "discretise", "params": {"method": "FBS", "bin_width": 25}},
# 提取全部特征
{
"step": "extract_features",
"params": {
"families": ["intensity", "morphology", "texture", "histogram", "ivh"],
},
},
]
# ── 2. 加载数据(修复 LPS ↔ RAS 坐标系差异)──────────
IMG_FOLDER = r"E:\data\patient001\dicom"
ROI_FILE = r"E:\data\patient001\mask.nii.gz"
img = load_image(IMG_FOLDER)
mask_raw = load_image(ROI_FILE)
# 强制掩膜元数据与图像对齐
mask = PlgImage(
array=mask_raw.array,
spacing=img.spacing,
origin=img.origin,
direction=img.direction,
modality=mask_raw.modality,
)
# ── 3. 创建流水线并运行 ──────────────────────────────
pipeline = RadiomicsPipeline(deduplicate=False, load_standard=False)
pipeline.add_config("my_config", steps=STEPS, source_mode="full_image")
results = pipeline.run(image=img, mask=mask, config_names=["my_config"])
# ── 4. 格式化与保存 ──────────────────────────────────
# Wide 格式(一行一患者)
feat_dict = format_results(
results,
fmt="wide",
meta={"patient": "001", "image_path": IMG_FOLDER},
output_type="dict",
)
# 或直接用 save_results 写入文件
save_results(
results,
path="features.csv",
fmt="wide",
meta={"patient": "001"},
)
运行结果示例
| patient | image_path | my_config__mean_intensity_Q4LE | my_config__intensity_variance_ECT3 | ... |
|---|---|---|---|---|
| 001 | E:\data... | -817.41 | 7186.85 | ... |
附录:预处理步骤速查
| 步骤名 | 必/可选 | 参数 | 说明 |
|---|---|---|---|
resample |
可选 | new_spacing, interpolation, mask_interpolation, mask_threshold |
重采样到各向同性 |
resegment |
可选 | range_min, range_max |
按强度范围重定义 ROI |
filter_outliers |
可选 | sigma |
去除强度异常值 |
round_intensities |
可选 | 无 | CT 整数化 |
keep_largest_component |
推荐 | apply_to |
仅保留最大连通域 |
binarize_mask |
按需 | threshold 或 mask_values |
掩膜二值化 |
discretise |
纹理必需 | method, n_bins 或 bin_width |
强度离散化 |
filter |
可选 | type + 滤波器参数 |
图像滤波 |
extract_features |
必需 | families |
提取指定特征族 |