import numpy as np import SimpleITK as sitk from config.constant import LABEL_MAP def resample_img(sitk_image, out_spacing=[0.5, 0.5, 0.5], is_label=False, interpolator=None, cval=None): """重採樣到 out_spacing,完整保留物理範圍(邊界 margin 不裁剪): - CT(is_label=False):线性插值。本流程是上採樣(~0.75-1mm -> 0.5mm), 线性即無漣波的理想重建;B-spline 會在身體 margin 等強邊界產生 非物理的 undershoot/overshoot(量測:~0.5% 體素低於 air floor -1024 HU,最低 -1274,沿體輪廓形成黑暈)。 - 預設填充值 = 影像最小值(air),而非 GetPixelIDValue() (對 int16 CT 回傳佔位值 2 ≈ 軟組織,會把 margin 填成軟組織)。 - 輸出尺寸用 ceil 覆蓋原物理尺寸(round 會讓遠端端點被裁 ≤0.25mm)。 label(is_label=True):最近邻、填充 0。 interpolator:明確指定插值器(None = 依 is_label 取 Linear / NearestNeighbor; 例如 SMD 等 piecewise-linear 場用 sitk.sitkBSpline,三阶 B 样条对分段 线性场为精确重建、無漣波)。 cval:明確指定填充值(None = 上述預設)。 """ original_spacing = np.array(sitk_image.GetSpacing(), dtype=float) original_size = np.array(sitk_image.GetSize()) out_spacing = np.array(out_spacing, dtype=float) physical = original_size * original_spacing out_size = [max(1, int(np.ceil(physical[i] / out_spacing[i] - 1e-6))) for i in range(3)] resample = sitk.ResampleImageFilter() resample.SetOutputSpacing(out_spacing.tolist()) resample.SetSize(out_size) resample.SetOutputDirection(sitk_image.GetDirection()) resample.SetOutputOrigin(sitk_image.GetOrigin()) resample.SetTransform(sitk.Transform()) if is_label: resample.SetInterpolator(interpolator or sitk.sitkNearestNeighbor) resample.SetDefaultPixelValue(cval if cval is not None else 0) elif interpolator is not None: resample.SetInterpolator(interpolator) # SMD 等 signed 場的填充:預設 0 = 表面層值(比影像 min/max 安全, # 不會製造假的零穿越環);可用品值可用 cval 覆蓋 resample.SetDefaultPixelValue(float(cval) if cval is not None else 0.0) else: resample.SetInterpolator(sitk.sitkLinear) # air 值 = 影像最小 HU(statistics 濾波器 streaming 計算,不載入整張 array) stats = sitk.StatisticsImageFilter() stats.Execute(sitk_image) resample.SetDefaultPixelValue(float(stats.GetMinimum())) return resample.Execute(sitk_image)