Source code for ultrasound_metrics.metrics.utils

"""
Utility functions for ultrasound metrics calculations.

Helper functions for dimension handling, indexing, and interpolation used by various metrics.
"""

from typing import Optional, Union

import numpy as np
from numpy.typing import NDArray


[docs] def set_default_measurement_dims(img_shape: tuple[int, ...]) -> NDArray[np.integer]: """ Set default measurement dimensions based on image shape. Parameters ---------- img_shape Shape of the input image. Returns ------- ndarray Array of valid measurement dimensions. """ default_bf_image_space_dims = [1, 2, 3] non_singleton_dims_of_img = np.where(np.array(img_shape) > 1)[0] dims_to_measure = [d for d in default_bf_image_space_dims if d in non_singleton_dims_of_img] if len(dims_to_measure) == 0: raise ValueError( "No valid dimensions to measure. Assumed space dimensions are [1,2,3], but these are all singleton." ) return np.array(dims_to_measure)
[docs] def get_subscript_idx_of_target_max_within_roi( img: NDArray[np.floating], roi_indices: NDArray[np.integer] ) -> tuple[int, ...]: """ Get subscript index of maximum value within ROI. Parameters ---------- img Input image array. roi_indices Indices defining the region of interest. Returns ------- tuple Subscript index of the maximum value within the ROI. """ roi_idxs = [tuple(idx) for idx in roi_indices] voxel_values = np.array([img[idx] for idx in roi_idxs]) voxel_max_idx = np.argmax(voxel_values) idx_of_max_in_roi_indices = roi_idxs[voxel_max_idx] return idx_of_max_in_roi_indices
[docs] def reduce_image_to_only_measurement_dimensions( img: NDArray[np.floating], measurement_dimensions: NDArray[np.integer], img_max_idx: tuple[int, ...] ) -> NDArray[np.floating]: """ Reduce image to only specified measurement dimensions. Parameters ---------- img Input image array. measurement_dimensions Dimensions to keep in the output. img_max_idx Index of maximum value in the image. Returns ------- ndarray Image reduced to measurement dimensions only. """ idxs_to_keep_of_each_dimension: list[Union[int, slice]] = list(img_max_idx) for dimension_to_keep in measurement_dimensions: idxs_to_keep_of_each_dimension[dimension_to_keep] = slice(None) img_with_measurement_dimensions_only = img[tuple(idxs_to_keep_of_each_dimension)] return img_with_measurement_dimensions_only
[docs] def reduce_max_idx_to_only_measurement_dimensions( img_max_idx: NDArray[np.integer], dims_to_measure: NDArray[np.integer] ) -> tuple[int, ...]: """ Reduce maximum index to only measurement dimensions. Parameters ---------- img_max_idx Index of maximum value in the image. dims_to_measure Dimensions to keep. Returns ------- tuple Maximum index reduced to measurement dimensions only. """ indices = np.array(img_max_idx)[dims_to_measure] # Convert each element to an integer before creating the tuple return tuple(int(i) for i in indices)
[docs] def find_max_idx_within_expected_target_radius( img: NDArray[np.floating], img_max_idx: tuple[int, ...], radius: int ) -> tuple[int, ...]: """ Find maximum index within expected target radius. Parameters ---------- img Input image array. img_max_idx Initial maximum index. radius Search radius around the initial maximum. Returns ------- tuple Index of maximum value within the specified radius. """ img_subscript_indices = np.indices(img.shape) mask_center = np.array(img_max_idx) for _ in range(0, img.ndim): mask_center = mask_center[..., None] # convert shape for subtraction with matrix img_mask = np.sqrt(np.sum((img_subscript_indices - mask_center) ** 2, axis=0)) <= radius masked_img = img * img_mask max_idx = np.unravel_index(np.argmax(masked_img), img.shape) return tuple(int(i) for i in max_idx)
[docs] def reduce_image_to_1D_at_point( img: NDArray[np.floating], point: tuple[int, ...], keep_dim: int ) -> NDArray[np.floating]: """ Reduce image to 1D at specified point. Parameters ---------- img Input image array. point Point coordinates to extract. keep_dim Dimension to keep (others will be sliced at the point). Returns ------- ndarray 1D array extracted from the image at the specified point. """ n_dim = img.ndim img_slice: list[Union[int, slice]] = [slice(None)] * n_dim for dim in range(0, n_dim): if dim != keep_dim: img_slice[dim] = point[dim] return img[tuple(img_slice)]
[docs] def calculate_linear_interpolation(a: float, b: float, a_idx: int, b_idx: int, interp_val: float) -> float: """ Calculate linear interpolation between two points. Parameters ---------- a Value at first point. b Value at second point. a_idx Index of first point. b_idx Index of second point. interp_val Value to interpolate to. Returns ------- float Interpolated index value. """ return a_idx + (b - interp_val) * (b_idx - a_idx) / (b - a)
[docs] def reorder_measured_dims_into_original_shape( sll_of_measured_dims: NDArray[np.floating], n_dim_of_input_image: int, measurement_dimensions: NDArray[np.integer] ) -> list[Optional[float]]: """ Reorder measured dimensions into original image shape. Parameters ---------- sll_of_measured_dims Measured values for each dimension. n_dim_of_input_image Number of dimensions in the original image. measurement_dimensions Dimensions that were measured. Returns ------- list List of measured values reordered to match original image dimensions. """ sll_of_input_img_shape: list[Optional[float]] = [None] * n_dim_of_input_image for index, dim in enumerate(measurement_dimensions): val = sll_of_measured_dims[index] sll_of_input_img_shape[dim] = float(val) if val is not None else None return sll_of_input_img_shape