Skip to content

Box

box #

Functions:

Name Description
calculate_image_boundaries

Calculate boundary RegionBox of a SimpleITK image.

BoundingBoxOutsideImageError #

BoundingBoxOutsideImageError(message: str)

Bases: Exception

Exception raised when the bounding box is outside the image.

Source code in src/imgtools/coretypes/box.py
def __init__(self, message: str) -> None:
    super().__init__(message)

BoxPadMethod #

Bases: str, enum.Enum

Enum for padding methods.

RegionBox dataclass #

RegionBox(
    min: imgtools.coretypes.spatial_types.Coordinate3D,
    max: imgtools.coretypes.spatial_types.Coordinate3D,
)

Represents a box in 3D space.

Attributes:

Name Type Description
min imgtools.coretypes.spatial_types.Coordinate3D

The minimum coordinate of the box.

max imgtools.coretypes.spatial_types.Coordinate3D

The maximum coordinate of the box.

size imgtools.coretypes.spatial_types.Size3D

The size of the box, calculated from the min and max coordinates.

Methods:

Name Description
check_out_of_bounds_coordinates

Adjust the coordinates to ensure that the max values are not greater than the image size.

copy

Create a copy of the RegionBox.

crop_image

Crop an image to the coordinates defined by the box.

crop_image_and_mask

Crop an image and mask to the coordinates defined by the box.

expand_to_cube

Convert the bounding box to a cube by making the size equal along all dimensions.

expand_to_min_size

Ensure that the bounding box has a minimum size along each dimension.

from_mask_bbox

Creates a RegionBox from the bounding box of a mask image.

from_mask_centroid

Creates a RegionBox from the centroid of a mask image.

from_tuple

Creates a RegionBox from a tuple of min and max coordinates.

pad

Expand the bounding box by a specified padding value.

check_out_of_bounds_coordinates #

check_out_of_bounds_coordinates(
    image: SimpleITK.Image,
) -> None

Adjust the coordinates to ensure that the max values are not greater than the image size.

Source code in src/imgtools/coretypes/box.py
def check_out_of_bounds_coordinates(self, image: sitk.Image) -> None:
    """Adjust the coordinates to ensure that the max values are not greater than the image size."""
    # if any of the max values are greater than the image size, set them to the image size,
    # and subtract the difference from the min values
    for idx, axis in enumerate(["x", "y", "z"]):
        max_value = getattr(self.max, axis)
        image_size = image.GetSize()[idx]
        if max_value > image_size:
            logger.debug(
                f"Adjusting box {axis} coordinates to be within the image size."
            )
            diff = max_value - image_size
            setattr(self.min, axis, getattr(self.min, axis) - diff)
            setattr(self.max, axis, image_size)

copy #

copy() -> imgtools.coretypes.box.RegionBox

Create a copy of the RegionBox.

Source code in src/imgtools/coretypes/box.py
def copy(self) -> RegionBox:
    """Create a copy of the RegionBox."""
    return RegionBox(self.min, self.max)

crop_image #

crop_image(image: SimpleITK.Image) -> SimpleITK.Image

Crop an image to the coordinates defined by the box.

Parameters:

Name Type Description Default
image #
SimpleITK.Image

The image to crop.

required

Returns:

Type Description
SimpleITK.Image

The cropped image.

Source code in src/imgtools/coretypes/box.py
def crop_image(self, image: sitk.Image) -> sitk.Image:
    """Crop an image to the coordinates defined by the box.

    Parameters
    ----------
    image : sitk.Image
        The image to crop.

    Returns
    -------
    sitk.Image
        The cropped image.
    """
    try:
        self.check_out_of_bounds_coordinates(image)
        cropped_image = sitk.RegionOfInterest(image, self.size, self.min)
    except Exception as e:
        msg = f"Error cropping image to the box: {e}"
        logger.exception(msg)
        raise e
    else:
        return cropped_image

crop_image_and_mask #

crop_image_and_mask(
    image: SimpleITK.Image, mask: SimpleITK.Image
) -> tuple[SimpleITK.Image, SimpleITK.Image]

Crop an image and mask to the coordinates defined by the box.

Parameters:

Name Type Description Default
image #
SimpleITK.Image

The image to crop.

required
mask #
SimpleITK.Image

The mask to crop.

required

Returns:

Type Description
tuple[SimpleITK.Image, SimpleITK.Image]

The cropped image and mask.

Source code in src/imgtools/coretypes/box.py
def crop_image_and_mask(
    self, image: sitk.Image, mask: sitk.Image
) -> tuple[sitk.Image, sitk.Image]:
    """Crop an image and mask to the coordinates defined by the box.

    Parameters
    ----------
    image : sitk.Image
        The image to crop.
    mask : sitk.Image
        The mask to crop.

    Returns
    -------
    tuple[sitk.Image, sitk.Image]
        The cropped image and mask.
    """
    cropped_image = self.crop_image(image)
    cropped_mask = self.crop_image(mask)

    return cropped_image, cropped_mask

expand_to_cube #

expand_to_cube(
    desired_size: int | None = None,
) -> imgtools.coretypes.box.RegionBox

Convert the bounding box to a cube by making the size equal along all dimensions.

This is done by finding which dimension is the largest, and then pad the other dimensions to make them equal to the desired size.

Parameters:

Name Type Description Default
desired_size #
int | None

The desired size of the cube. If None, the maximum dimension size is used

None

Returns:

Type Description
imgtools.coretypes.box.RegionBox

The bounding box converted to a cube.

Source code in src/imgtools/coretypes/box.py
def expand_to_cube(self, desired_size: int | None = None) -> RegionBox:
    """Convert the bounding box to a cube by making the size equal along all dimensions.

    This is done by finding which dimension is the largest,
    and then pad the other dimensions to make them equal to the desired size.

    Parameters
    ----------
    desired_size : int | None
        The desired size of the cube. If None, the maximum dimension size is used

    Returns
    -------
    RegionBox
        The bounding box converted to a cube.

    Raises
    ------
    ValueError
        If the desired size is smaller than the current maximum dimension size.
    """
    max_size = max(self.size)

    if not desired_size:
        return self.expand_to_min_size(max_size)

    if desired_size < max_size:
        msg = (
            f"Desired size {desired_size} is smaller than"
            f" the current maximum dimension size {max_size}."
        )
        raise ValueError(msg)

    return self.expand_to_min_size(desired_size)

expand_to_min_size #

expand_to_min_size(
    size: int = 5,
) -> imgtools.coretypes.box.RegionBox

Ensure that the bounding box has a minimum size along each dimension.

Parameters:

Name Type Description Default
size #
int

The minimum size of the bounding box along each dimension.

5

Returns:

Type Description
imgtools.coretypes.box.RegionBox

The bounding box with a minimum size along each dimension.

Notes

Validation is done to ensure that any min coordinates that are negative are set to 0, and the difference is added to the maximum coordinates.

If an extra dimension is not an integer (e.g. 1.5), the bounding box is shifted 1 voxel towards the minimum in that dimension.

Source code in src/imgtools/coretypes/box.py
def expand_to_min_size(self, size: int = 5) -> RegionBox:
    """Ensure that the bounding box has a minimum size along each dimension.

    Parameters
    ----------
    size : int
        The minimum size of the bounding box along each dimension.

    Returns
    -------
    RegionBox
        The bounding box with a minimum size along each dimension.

    Notes
    -----
    Validation is done to ensure that any min coordinates that are negative are set to 0,
    and the difference is added to the maximum coordinates.

    If an extra dimension is not an integer (e.g. 1.5), the bounding box is shifted 1 voxel towards the minimum in that dimension.
    """
    # Calculate extra dimensions to add to the existing coordinates
    extra_x = max(0, size - self.size.width) / 2
    extra_y = max(0, size - self.size.height) / 2
    extra_z = max(0, size - self.size.depth) / 2

    # Round extra dimension values UP to the nearest integer before adding to existing minimum coordinates
    min_coord = self.min - tuple(
        [math.ceil(extra) for extra in (extra_x, extra_y, extra_z)]
    )

    # Round extra dimensions DOWN to the nearest integer before adding to existing maximum coordinates
    max_coord = self.max + tuple(
        [math.floor(extra) for extra in (extra_x, extra_y, extra_z)]
    )

    # Adjust negative coordinates to ensure that the min values are not negative
    self._adjust_negative_coordinates(min_coord, max_coord)

    return RegionBox(min=min_coord, max=max_coord)

from_mask_bbox classmethod #

from_mask_bbox(
    mask: SimpleITK.Image, label: int = 1
) -> imgtools.coretypes.box.RegionBox

Creates a RegionBox from the bounding box of a mask image.

Parameters:

Name Type Description Default
mask #
SimpleITK.Image

The input mask image.

required
label #
int
1

Returns:

Type Description
imgtools.coretypes.box.RegionBox

The bounding box coordinates as a RegionBox object.

Source code in src/imgtools/coretypes/box.py
@classmethod
def from_mask_bbox(cls, mask: sitk.Image, label: int = 1) -> RegionBox:
    """Creates a RegionBox from the bounding box of a mask image.

    Parameters
    ----------
    mask : sitk.Image
        The input mask image.
    label : int, optional

    Returns
    -------
    RegionBox
        The bounding box coordinates as a RegionBox object.
    """

    mask_uint = sitk.Cast(mask, sitk.sitkUInt8)
    stats = sitk.LabelShapeStatisticsImageFilter()
    stats.Execute(mask_uint)
    xstart, ystart, zstart, xsize, ysize, zsize = stats.GetBoundingBox(
        label
    )

    return RegionBox(
        Coordinate3D(xstart, ystart, zstart),
        Coordinate3D(xstart + xsize, ystart + ysize, zstart + zsize),
    )

from_mask_centroid classmethod #

from_mask_centroid(
    mask: SimpleITK.Image,
    label: int = 1,
    desired_size: int | None = None,
) -> imgtools.coretypes.box.RegionBox

Creates a RegionBox from the centroid of a mask image.

Parameters:

Name Type Description Default
mask #
SimpleITK.Image

The input mask image.

required
label #
int

label in the mask image to calculate the centroid.

1
desired_size #
int | None

The desired size of the box. If None, the minimum size default from expand_to_min_size is used.

None

Returns:

Type Description
imgtools.coretypes.box.RegionBox

The bounding box coordinates as a RegionBox object.

Source code in src/imgtools/coretypes/box.py
@classmethod
def from_mask_centroid(
    cls, mask: sitk.Image, label: int = 1, desired_size: int | None = None
) -> RegionBox:
    """Creates a RegionBox from the centroid of a mask image.

    Parameters
    ----------
    mask : sitk.Image
        The input mask image.
    label : int, optional
        label in the mask image to calculate the centroid.
    desired_size : int | None, optional
        The desired size of the box. If None, the minimum size default from `expand_to_min_size` is used.

    Returns
    -------
    RegionBox
        The bounding box coordinates as a RegionBox object.
    """
    mask_uint = sitk.Cast(mask, sitk.sitkUInt8)
    stats = sitk.LabelShapeStatisticsImageFilter()
    stats.Execute(mask_uint)

    centroid = stats.GetCentroid(label)
    centroid_idx = mask.TransformPhysicalPointToIndex(centroid)

    return RegionBox(
        Coordinate3D(*centroid_idx), Coordinate3D(*centroid_idx)
    ).expand_to_cube(desired_size)

from_tuple classmethod #

from_tuple(
    coordmin: tuple[int, int, int],
    coordmax: tuple[int, int, int],
) -> imgtools.coretypes.box.RegionBox

Creates a RegionBox from a tuple of min and max coordinates.

Source code in src/imgtools/coretypes/box.py
@classmethod
def from_tuple(
    cls, coordmin: tuple[int, int, int], coordmax: tuple[int, int, int]
) -> RegionBox:
    """Creates a RegionBox from a tuple of min and max coordinates."""
    return cls(Coordinate3D(*coordmin), Coordinate3D(*coordmax))

pad #

pad(
    padding: int,
    method: imgtools.coretypes.box.BoxPadMethod = imgtools.coretypes.box.BoxPadMethod.SYMMETRIC,
) -> imgtools.coretypes.box.RegionBox

Expand the bounding box by a specified padding value.

Can be applied symmetrically on both sides or only at the end of the box. If the padded result has negative coordinates, they region is adjusted by shifting the min coordinates to 0 and adding the difference to the max coordinates.

Parameters:

Name Type Description Default
padding #
int

The padding value to expand the bounding box.

required
method #
imgtools.coretypes.box.BoxPadMethod

The padding method to use. Default is BoxPadMethod.SYMMETRIC. Options are: - BoxPadMethod.SYMMETRIC: Pad symmetrically on both sides. - BoxPadMethod.END: Pad only at the end of the box (furthest from the origin).

imgtools.coretypes.box.BoxPadMethod.SYMMETRIC

Returns:

Type Description
imgtools.coretypes.box.RegionBox

The expanded bounding box.

Examples:

>>> box = RegionBox(
...     Coordinate3D(5, 5, 5),
...     Coordinate3D(10, 10, 10),
... )
>>> box.pad(5)
RegionBox(
... min=Coordinate3D(x=0, y=0, z=0),
... max=Coordinate3D(x=15, y=15, z=15)
... size=(15, 15, 15)
)
>>> box.pad(5, method=BoxPadMethod.END)
RegionBox(
... min=Coordinate3D(x=5, y=5, z=5),
... max=Coordinate3D(x=15, y=15, z=15)
... size=(10, 10, 10)
)
Source code in src/imgtools/coretypes/box.py
def pad(
    self, padding: int, method: BoxPadMethod = BoxPadMethod.SYMMETRIC
) -> RegionBox:
    """Expand the bounding box by a specified padding value.

    Can be applied symmetrically on both sides or only at the end of the box.
    If the padded result has negative coordinates, they region is adjusted by
    shifting the min coordinates to 0 and adding the difference to the max coordinates.

    Parameters
    ----------
    padding : int
        The padding value to expand the bounding box.
    method : BoxPadMethod, optional
        The padding method to use. Default is BoxPadMethod.SYMMETRIC.
        Options are:
        - BoxPadMethod.SYMMETRIC: Pad symmetrically on both sides.
        - BoxPadMethod.END: Pad only at the end of the box (furthest from the origin).

    Returns
    -------
    RegionBox
        The expanded bounding box.

    Examples
    --------
    >>> box = RegionBox(
    ...     Coordinate3D(5, 5, 5),
    ...     Coordinate3D(10, 10, 10),
    ... )
    >>> box.pad(5)
    RegionBox(
    ... min=Coordinate3D(x=0, y=0, z=0),
    ... max=Coordinate3D(x=15, y=15, z=15)
    ... size=(15, 15, 15)
    )
    >>> box.pad(5, method=BoxPadMethod.END)
    RegionBox(
    ... min=Coordinate3D(x=5, y=5, z=5),
    ... max=Coordinate3D(x=15, y=15, z=15)
    ... size=(10, 10, 10)
    )
    """
    if padding == 0:
        return self

    match method:
        case BoxPadMethod.SYMMETRIC:
            padded_min = self.min - padding
            padded_max = self.max + padding
        case BoxPadMethod.END:
            padded_min = self.min
            padded_max = self.max + padding
        case _:
            errmsg = f"Invalid padding method: {method}"
            errmsg += f" Options are: {BoxPadMethod.__members__.keys()}"
            raise ValueError(errmsg)

    self._adjust_negative_coordinates(padded_min, padded_max)

    return RegionBox(min=padded_min, max=padded_max)

calculate_image_boundaries #

calculate_image_boundaries(
    image: SimpleITK.Image,
    use_world_coordinates: bool = False,
) -> imgtools.coretypes.box.RegionBox

Calculate boundary RegionBox of a SimpleITK image.

Calculates the origin and size of the image in either index or world coordinates.

Parameters:

Name Type Description Default

image #

SimpleITK.Image

The input SimpleITK image.

required

use_world_coordinates #

bool

If True, the origin and size are calculated in world coordinates. Meant to be used internally and for debugging purposes. Use with caution as it may not work as the resulting RegionBox may not work as expected.

False

Returns:

Type Description
imgtools.coretypes.box.RegionBox

Examples:

>>> calculate_image_boundaries(image)
RegionBox(
    min=Coordinate3D(x=0, y=0, z=0),
    max=Coordinate3D(x=512, y=512, z=135)
    size=Size3D(w=512, h=512, d=135)
)
Source code in src/imgtools/coretypes/box.py
def calculate_image_boundaries(
    image: sitk.Image, use_world_coordinates: bool = False
) -> RegionBox:
    """Calculate boundary RegionBox of a SimpleITK image.

    Calculates the origin and size of the image in either index or world coordinates.

    Parameters
    ----------
    image: sitk.Image
        The input SimpleITK image.
    use_world_coordinates: bool, optional
        If True, the origin and size are calculated in world coordinates.
        Meant to be used internally and for debugging purposes.
        Use with caution as it may not work as the resulting RegionBox
        may not work as expected.

    Returns
    -------
    RegionBox

    Examples
    --------
    >>> calculate_image_boundaries(image)
    RegionBox(
        min=Coordinate3D(x=0, y=0, z=0),
        max=Coordinate3D(x=512, y=512, z=135)
        size=Size3D(w=512, h=512, d=135)
    )
    """

    if use_world_coordinates:
        min_coord = Coordinate3D(*image.GetOrigin())
        size = Size3D(*image.GetSize())

    else:
        min_coord = Coordinate3D(
            *image.TransformPhysicalPointToIndex(image.GetOrigin())
        )
        size = Size3D(*image.GetSize())

    return RegionBox(min_coord, min_coord + size)