Generate per-landmark Gaussian heatmaps for 2D or 3D coordinates. Notes: - Coordinates are interpreted in voxel units and expected in (Y, X) for 2D or (Z, Y, X) for 3D. - Target spatial_shape is (Y, X) for 2D and (Z, Y, X) for 3D. - Output layout uses channel-first
| 751 | |
| 752 | |
| 753 | class GenerateHeatmap(Transform): |
| 754 | """ |
| 755 | Generate per-landmark Gaussian heatmaps for 2D or 3D coordinates. |
| 756 | |
| 757 | Notes: |
| 758 | - Coordinates are interpreted in voxel units and expected in (Y, X) for 2D or (Z, Y, X) for 3D. |
| 759 | - Target spatial_shape is (Y, X) for 2D and (Z, Y, X) for 3D. |
| 760 | - Output layout uses channel-first convention with one channel per landmark. |
| 761 | - Input points shape: (N, D) where N is number of landmarks, D is spatial dimensions (2 or 3). |
| 762 | - Output heatmap shape: (N, Y, X) for 2D or (N, Z, Y, X) for 3D. |
| 763 | - Each channel index corresponds to one landmark. |
| 764 | |
| 765 | Args: |
| 766 | sigma: gaussian standard deviation. A single value is broadcast across all spatial dimensions. |
| 767 | spatial_shape: optional fallback spatial shape. If ``None`` it must be provided when calling the transform. |
| 768 | truncated: extent, in multiples of ``sigma``, used to crop the gaussian support window. |
| 769 | normalize: normalize every heatmap channel to ``[0, 1]`` when ``True``. |
| 770 | dtype: target dtype for the generated heatmaps (accepts numpy or torch dtypes). |
| 771 | |
| 772 | Raises: |
| 773 | ValueError: when ``sigma`` is non-positive or ``spatial_shape`` cannot be resolved. |
| 774 | |
| 775 | """ |
| 776 | |
| 777 | backend = [TransformBackends.NUMPY, TransformBackends.TORCH] |
| 778 | |
| 779 | def __init__( |
| 780 | self, |
| 781 | sigma: Sequence[float] | float = 5.0, |
| 782 | spatial_shape: Sequence[int] | None = None, |
| 783 | truncated: float = 4.0, |
| 784 | normalize: bool = True, |
| 785 | dtype: np.dtype | torch.dtype | type = np.float32, |
| 786 | ) -> None: |
| 787 | if isinstance(sigma, Sequence) and not isinstance(sigma, (str, bytes)): |
| 788 | if any(s <= 0 for s in sigma): |
| 789 | raise ValueError("Argument `sigma` values must be positive.") |
| 790 | self._sigma = tuple(float(s) for s in sigma) |
| 791 | else: |
| 792 | if float(sigma) <= 0: |
| 793 | raise ValueError("Argument `sigma` must be positive.") |
| 794 | self._sigma = (float(sigma),) |
| 795 | if truncated <= 0: |
| 796 | raise ValueError("Argument `truncated` must be positive.") |
| 797 | self.truncated = float(truncated) |
| 798 | self.normalize = normalize |
| 799 | self.torch_dtype = get_equivalent_dtype(dtype, torch.Tensor) |
| 800 | self.numpy_dtype = get_equivalent_dtype(dtype, np.ndarray) |
| 801 | # Validate that dtype is floating-point for meaningful Gaussian values |
| 802 | if not self.torch_dtype.is_floating_point: |
| 803 | raise ValueError(f"Argument `dtype` must be a floating-point type, got {self.torch_dtype}") |
| 804 | self.spatial_shape = None if spatial_shape is None else tuple(int(s) for s in spatial_shape) |
| 805 | |
| 806 | def __call__(self, points: NdarrayOrTensor, spatial_shape: Sequence[int] | None = None) -> NdarrayOrTensor: |
| 807 | """ |
| 808 | Args: |
| 809 | points: landmark coordinates as ndarray/Tensor with shape (N, D), |
| 810 | ordered as (Y, X) for 2D or (Z, Y, X) for 3D, where N is the number |
no outgoing calls
searching dependent graphs…