跳至正文
仇天赐.研究与工程笔记
搜索标题、摘要和标签 Esc 关闭
返回技术档案

CT 强度预处理:先确认单位,再谈归一化

梳理 CT 数据从存储值到模型输入的转换,避免默认 HU、重复缩放、逐图拉伸和空间信息丢失。

回顾整理

本文归入 2025.01 主题档案,整理于 2026.11.05。月份用于组织阅读路径,不代表具体实验或项目的完成日期。

模型看到的数字究竟是什么

这篇回顾讨论 CT 数据进入视觉模型之前的处理契约,归档月份仅用于整理主题。预处理最危险的问题往往不是公式复杂,而是数字看起来正常,含义却已经改变。

同样是一个浮点数组,它可能保存探测器读数、负对数投影、重建衰减量、经标定的 CT 值,也可能只是显示截图。它们对应不同的物理意义。看到扩展名或文件夹名里有「CT」,不足以直接套用固定 HU 窗口。

把存储转换与显示转换分开

DICOM CT 模块定义了存储值与输出单位之间的线性关系,并对输出为 HU 的条件作出规定;多能量或派生图像需要额外核对类型与单位。DICOM CT Image Module

output_value = rescale_slope * stored_value + rescale_intercept

应确认读取工具是否已经应用转换,再决定是否执行公式。重复乘斜率、加截距会悄悄改变整个分布。增强多帧对象还需要检查每帧的变换信息,不能假定所有帧都共享某个顶层字段。

显示窗决定如何把数值映射到屏幕亮度,不等同于恢复物理量。若输入只有导出的显示图,应保留这个限制,不能假装通过反归一化还原完整 CT 数值。

让归一化成为明确契约

一种可审查的策略是:在确认单位后固定裁剪范围,再线性映射到模型输入区间。范围可以来自任务先验,也可以由训练集统计得到;不能看完测试集后再调整。

import numpy as np

def fixed_scale(image, low, high):
    if not np.isfinite([low, high]).all() or high <= low:
        raise ValueError("invalid intensity bounds")
    x = np.asarray(image, dtype=np.float32)
    if not np.isfinite(x).all():
        raise ValueError("inspect invalid pixels before scaling")
    x = np.clip(x, low, high)
    return (x - low) / (high - low)

函数故意不内置 HU 范围,也不自动填补缺失值。填补策略需要结合背景、坏点和任务含义决定。输出配置应保存单位、上下界、转换顺序以及统计所用数据版本。

填充值还应与真实背景区分。为了补齐尺寸而加入的常数,可能在计算均值、分位数和前景范围时占据很大权重。应保留有效区域掩膜,在约定区域内计算统计,并明确填充值进入模型时使用什么数值。

每次转换可同时记录上下界的截断比例。如果某批输入突然出现大量截断,应先核查单位与转换是否一致,再判断是否真的存在分布变化。均值相近不代表整个输入分布相同。

逐图拉伸可能抹去有用差异

逐图最小最大归一化能让每张图填满显示范围,却可能把不同样本的绝对强度差异消掉。一个孤立异常值也可能支配整个比例尺。逐体标准化则要说明统计范围:全体素、前景区域,还是某个稳定参考区域。

这不意味着所有任务都必须保留绝对强度,而是要明确模型应获得什么信息。可在验证集比较固定范围、训练集统计和逐体统计,并同时观察任务结果与异常样本。nnU-Net 强调预处理、训练和推理选择需要共同考虑;这里借鉴的是配置应成体系的思路。nnU-Net,2018

强度正确,还要守住空间关系

预处理记录不能只有均值和标准差。轴顺序、方向、体素间距、裁剪范围以及回填位置,会直接影响标签对齐与结果解释。图像和离散标签可以使用不同插值方法,但必须应用同一空间变换。

常见失败包括:训练使用已缩放数据,推理却重复缩放;重采样后仍沿用旧间距计算边界误差;为去背景而裁掉目标边缘。建议固定几份人工核对样本,保存变换前后的同一解剖位置与数值分位数。发现异常先追转换链,再改网络。

参考

笔记至此,探索继续。交流与纠错