Source code for autods_pet.imaging.geometry

"""Geometry checks and spatial alignment for SimpleITK images."""

from __future__ import annotations

import SimpleITK as sitk


[docs] def check_same_geometry( a: sitk.Image, b: sitk.Image, tol: float = 1e-4, ) -> bool: """Check whether two images share the same spatial geometry. Compares size, spacing, origin, and direction cosine matrix. Parameters ---------- a : sitk.Image First image. b : sitk.Image Second image. tol : float Absolute tolerance for floating-point comparisons of spacing, origin, and direction elements. Returns ------- bool ``True`` if the images match in size, spacing, origin, and direction within *tol*. """ if a.GetSize() != b.GetSize(): return False if any(abs(a.GetSpacing()[i] - b.GetSpacing()[i]) > tol for i in range(3)): return False if any(abs(a.GetOrigin()[i] - b.GetOrigin()[i]) > tol for i in range(3)): return False if any(abs(a.GetDirection()[i] - b.GetDirection()[i]) > tol for i in range(9)): return False return True
[docs] def check_sub_geometry( a: sitk.Image, b: sitk.Image, tol: float = 1e-4, ) -> bool: """Check whether *a* lives on a compatible voxel grid as *b* (sub-volume). Returns ``True`` when spacing matches within *tol* and direction cosine axes are parallel (signs may differ - a Z-flip between LPS and RAS conventions is allowed). Size and origin may differ freely. This identifies the common DICOM SEG case where the mask only covers the slices that contain a contour (fewer slices, different origin) and may have been exported with a flipped axis convention. Parameters ---------- a : sitk.Image Candidate sub-volume (e.g. a partial-FOV mask). b : sitk.Image Reference volume (e.g. full PET or CT). tol : float Absolute tolerance for floating-point comparisons. Returns ------- bool ``True`` if spacing matches and direction axes are parallel. """ if any(abs(a.GetSpacing()[i] - b.GetSpacing()[i]) > tol for i in range(3)): return False # Compare direction cosines allowing sign flips (LPS ↔ RAS). if any( abs(abs(a.GetDirection()[i]) - abs(b.GetDirection()[i])) > tol for i in range(9) ): return False return True
[docs] def resample_to_reference( mask: sitk.Image, reference: sitk.Image, ) -> sitk.Image: """Resample *mask* onto the *reference* grid (zero-padded, nearest-neighbour). Used to embed a partial-FOV binary mask (e.g. a DICOM SEG covering only a few slices) into the full reference volume. Voxels outside the mask's original extent are set to 0. Parameters ---------- mask : sitk.Image Binary mask (sub-volume). reference : sitk.Image Full-size reference image whose grid defines the output. Returns ------- sitk.Image Resampled mask with the same size, origin, spacing, and direction as *reference*. """ return sitk.Resample( mask, reference, sitk.Transform(), # identity - same physical coordinate system sitk.sitkNearestNeighbor, 0, # default pixel value for out-of-bounds regions mask.GetPixelID(), )