Source code for radioviz.geometry.mask

#  Copyright 2025–2026 European Union
#  Author: Bulgheroni Antonio (antonio.bulgheroni@ec.europa.eu)
#  SPDX-License-Identifier: EUPL-1.2

"""
Module for creating masks from matplotlib selectors.

This module provides functionality to generate 2D masks starting from a geometry description.

.. seealso::
    :mod:`radioviz.tools.region_tool`
"""

from __future__ import annotations

from typing import Any

import numpy as np
from numpy.typing import NDArray

from radioviz.models.roi_model import ROIType


[docs] def axes_pixel_grid(shape: tuple[int, int]) -> tuple[NDArray[Any], NDArray[Any]]: """ Generate X, Y arrays of pixel centers in data coordinates. Creates coordinate grids for pixel centers based on the given image shape. These coordinates are useful for generating masks that correspond to specific regions of interest. :param shape: The shape of the image as (height, width) :type shape: tuple[int, int] :return: Tuple of X and Y coordinate arrays :rtype: tuple[numpy.ndarray, numpy.ndarray] """ h, w = shape xs = np.arange(w) ys = np.arange(h) X, Y = np.meshgrid(xs, ys) return X, Y
[docs] def rectangle_mask(geometry: dict[str, Any], image_shape: tuple[int, int]) -> NDArray[np.bool_]: """ Create a boolean mask for a rectangular selection. Generates a boolean mask representing the area defined by a rotated rectangle. :param geometry: Dictionary containing rectangle extents and rotation angle :type geometry: dict[str, Any] :param image_shape: The shape of the image as (height, width) :type image_shape: tuple[int, int] :return: Boolean mask where True represents pixels within the rectangle :rtype: numpy.ndarray """ ny, nx = image_shape y, x = np.ogrid[:ny, :nx] xmin, xmax, ymin, ymax = geometry['extents'] # Center and half-sizes xc = (xmin + xmax) / 2 yc = (ymin + ymax) / 2 a = (xmax - xmin) / 2 b = (ymax - ymin) / 2 # Rotation angle (degrees → radians) theta = np.deg2rad(geometry['angle']) cos_t = np.cos(theta) sin_t = np.sin(theta) # Shift to center dx = x - xc dy = y - yc # Rotate coordinates x_rot = dx * cos_t + dy * sin_t y_rot = -dx * sin_t + dy * cos_t # Axis-aligned rectangle test in rotated frame mask: NDArray[np.bool_] = (np.abs(x_rot) <= a) & (np.abs(y_rot) <= b) return mask
[docs] def ellipse_mask(geometry: dict[str, Any], image_shape: tuple[int, int]) -> NDArray[np.bool_]: """ Create a boolean mask for an elliptical selection. Generates a boolean mask representing the area defined by a rotated ellipse. :param geometry: Dictionary containing ellipse extents and rotation angle :type geometry: dict[str, Any] :param image_shape: The shape of the image as (height, width) :type image_shape: tuple[int, int] :return: Boolean mask where True represents pixels within the ellipse :rtype: numpy.ndarray """ ny, nx = image_shape y, x = np.ogrid[:ny, :nx] xmin, xmax, ymin, ymax = geometry['extents'] # Center and semi-axes xc = (xmin + xmax) / 2 yc = (ymin + ymax) / 2 a = (xmax - xmin) / 2 b = (ymax - ymin) / 2 # Rotation theta = np.deg2rad(geometry['angle']) cos_t = np.cos(theta) sin_t = np.sin(theta) # Shifted coordinates dx = x - xc dy = y - yc # Rotate coordinates x_rot = dx * cos_t + dy * sin_t y_rot = -dx * sin_t + dy * cos_t # Ellipse equation mask: NDArray[np.bool_] = (x_rot / a) ** 2 + (y_rot / b) ** 2 <= 1 return mask
[docs] def polygon_mask(geometry: dict[str, Any], image_shape: tuple[int, int]) -> NDArray[np.bool_]: """ Create a boolean mask for a polygonal selection. Generates a boolean mask representing the area defined by a polygon. :param geometry: Dictionary containing polygon vertices :type geometry: dict[str, Any] :param image_shape: The shape of the image as (height, width) :type image_shape: tuple[int, int] :return: Boolean mask where True represents pixels within the polygon :rtype: numpy.ndarray """ from matplotlib.path import Path verts = geometry['verts'] path = Path(verts) X, Y = axes_pixel_grid(image_shape) points = np.column_stack((X.ravel(), Y.ravel())) mask = np.asarray(path.contains_points(points), dtype=bool).reshape(image_shape) return mask
[docs] def geometry_to_mask(region_type: ROIType, geometry: dict[str, Any], image_shape: tuple[int, int]) -> NDArray[np.bool_]: """ Convert geometry to a boolean mask based on region type. Maps a region type to its corresponding mask generation function and returns the mask for the given geometry. :param region_type: The type of region (e.g., RECTANGLE, ELLIPSE) :type region_type: ROIType :param geometry: Dictionary containing geometry data :type geometry: dict[str, Any] :param image_shape: The shape of the image as (height, width) :type image_shape: tuple[int, int] :return: Boolean mask for the specified region type :rtype: numpy.ndarray """ return { ROIType.RECTANGLE: rectangle_mask, ROIType.ELLIPSE: ellipse_mask, ROIType.CIRCLE: ellipse_mask, ROIType.POLYGON: polygon_mask, }[region_type](geometry, image_shape)