pydcm.transforms — authoritative references & precision¶
There is no single gold-standard transforms library in Python. Medical-image ML preprocessing is fragmented, and two numeric families genuinely disagree:
- scipy.ndimage family (B-spline resampling, filtering, connected components) — the de-facto numeric reference; nnU-Net and skimage build on it.
- torch family (
grid_sample) — a separate, incompatible interpolation convention; MONAISpacing/Resizeuse it.
"Correct" for a preprocessing op therefore means "matches the implementation the target framework actually used to train the model" — it is defined per-framework, not by math. pydicom is not in this picture: it does DICOM-defined pixel transforms only (rescale / modality LUT / VOI LUT), never spatial/ML preprocessing. Validate against the op's own upstream below — never against another reproduction (custom_ops, pydcm) — those share lineage and would validate circularly.
To avoid mixing families, import a preset (from pydcm.transforms import nnunet as T
or monai as T); it binds the divergent ops and shares the rest.
Per-op reference¶
| op | authoritative reference | precision vs reference |
|---|---|---|
resample_cubic |
scipy.ndimage / skimage.resize(order=3) |
bit-exact |
resample_separate_z |
scipy.ndimage (per-slice cubic + nearest-z, nnU-Net) | bit-exact |
resample_nearest |
scipy.ndimage order=0 |
bit-exact |
resample_grid_sample |
torch.nn.functional.grid_sample (align_corners=False) |
≤1 fp32 ULP¹ |
resample_to_spacing, resize |
MONAI Spacing/Resize (half-pixel) |
≤1 fp32 ULP¹ |
normalize_ct |
nnU-Net CTNormalization (clip + fixed mean/std) | bit-exact |
normalize_zscore |
MONAI NormalizeIntensity (self-computed stats) |
bit-exact² |
scale_intensity_range |
MONAI ScaleIntensityRange |
tight tolerance |
scale_intensity_range_percentiles |
np.percentile (linear) + MONAI |
tight tolerance |
adjust_contrast |
MONAI AdjustContrast |
~1e-5 (powf) |
crop, pad, crop_foreground, flip |
numpy / MONAI definitions (exact index) | exact |
center_crop, spatial_pad, divisible_pad |
MONAI CenterSpatialCrop/SpatialPad/DivisiblePad |
exact |
rotate90 |
np.rot90 (+ world-coord preservation) |
exact |
gaussian_smooth |
MONAI GaussianSmooth (reflect, separable) |
tight tolerance |
argmax |
np.argmax over channels |
exact |
connected_components |
scipy.ndimage label |
exact |
keep_largest_connected_component, fill_holes, remove_small_objects, as_discrete |
MONAI post-transforms | exact |
sliding_window_positions |
MONAI dense_patch_slices / _get_scan_interval |
exact |
gaussian_importance_map(convention='nnunet') |
nnU-Net V2 get_gaussian (scipy gaussian_filter) |
bit-exact |
gaussian_importance_map(convention='monai') |
MONAI compute_importance_map (sampled exp) |
≤1 fp32 ULP¹ |
¹ Not bit-exact: the reference's last-bit rounding is not reproducible from any closed
form (torch's affine_grid SIMD linspace; expf). Numerically identical otherwise.
² np.std ddof=0 (population), matching MONAI's default.
Presets — which op a preset binds¶
| preset | resample |
gaussian_importance_map / sliding_window_inference |
|---|---|---|
transforms.nnunet |
scipy cubic spline (labels → nearest) | convention='nnunet' |
transforms.monai |
torch grid_sample (labels → nearest) |
convention='monai' |
Every other op is identical in both presets and in the flat module. For nnU-Net's
anisotropic thick-slice path, call resample_separate_z explicitly.