Source code for darsia.utils.box

from __future__ import annotations

from typing import Optional, Union
from warnings import warn

import numpy as np

import darsia


[docs] def bounding_box( voxels: darsia.VoxelArray, padding: int = 0, max_size: Optional[Union[list, tuple]] = None, ) -> tuple[slice, ...]: """ Determine bounding box for a set of given coordinates. Args: voxels (VoxelArray): voxel array of size N x dim, using matrix indexing in 2d. padding (int): padding to create a slightly larger bounding box. Might be of interest if the area that is prescribed in coords cover slightly less than strictly needed. Default is 0. max_size (list or tuple, optional): max size of bounding box in each dimension. Returns: tuple of slices: slices with ranges from min to max value per dimension. """ bounding_box: tuple[slice, ...] = () for dim in range(voxels.shape[1]): # Padding is added to the bounding box while making sure that it never extends out # of the scope of the 0 and max_size (where max size should be externally provided) # and should maximally be the maximal size of the image in each dimension. min_value = max(np.min(voxels[:, dim]) - padding, 0) max_value = ( min(np.max(voxels[:, dim]) + padding, max_size[dim]) if max_size is not None else np.max(voxels[:, dim]) + padding ) bounding_box = *bounding_box, slice(min_value, max_value) return bounding_box
[docs] def bounding_box_inverse(bounding_box: tuple) -> darsia.VoxelArray: """ Returns an array that would produce the same bounding box from the bounding_box() function above. Args: tuple of slices: slices with ranges from min to max value per dimension. Returns: voxels (VoxelArray): voxel array of size N x dim, using matrix indexing in 2d. """ voxels = darsia.VoxelArray( [ [bounding_box[0].start, bounding_box[1].start], [bounding_box[0].stop, bounding_box[1].start], [bounding_box[0].stop, bounding_box[1].stop], [bounding_box[0].start, bounding_box[1].stop], ] ) return voxels
[docs] def perimeter(box: Union[tuple, np.ndarray]) -> Union[int, float]: """ Returns the perimeter of a box. Accepts both tuples of slices as well as arrays of coordinates as input. Args: box (tuple of slices or np.ndarray): definition of a box, either as tuple of slices, or coordinates (can also use metric units) Returns: float or int (depending on input): perimeter """ # Convert to array box = box if isinstance(box, np.ndarray) else bounding_box_inverse(box) # Extract min and max for each dimension min_x = np.min(box[:, 0]) max_x = np.max(box[:, 0]) min_y = np.min(box[:, 1]) max_y = np.max(box[:, 1]) # Determine perimeter perimeter = 2 * (max_x - min_x) + 2 * (max_y - min_y) return perimeter
[docs] def random_patches( mask: np.ndarray, width: int, num_patches: int ) -> Optional[list[tuple[slice]]]: """Utility to extract random patches from a mask. Args: mask (np.ndarray): binary mask to extract patches from. width (int): width of the patches. num_patches (int): number of patches to extract. Returns: list of tuples: patches as slices. None: if the mask is too small to extract patches. NOTE: The function utilizes randomness. For reproducibility, the seed is fixed. """ # Fix seed for reproducibility np.random.seed(42) # Determine indices of mask larger_mask = np.zeros((mask.shape[0] + width, mask.shape[1] + width), dtype=bool) larger_mask[: mask.shape[0], : mask.shape[1]] = mask # Determine indices of mask indices = np.nonzero(mask) moved_indices = tuple([indices[i] + width for i in range(len(indices))]) test_moved_indices = larger_mask[moved_indices] # Check if the mask is too small to extract patches if not np.any(test_moved_indices): warn("The mask is too small to extract patches.") return None # Continue with feasible indices restricted_indices = tuple( [indices[i][test_moved_indices] for i in range(len(indices))] ) # Randomly select patches num_eligible_points = len(restricted_indices[0]) random_ids = np.unique( (np.random.rand(num_patches) * num_eligible_points).astype(int) ) patch_indices = np.transpose( tuple([restricted_indices[i][random_ids] for i in range(len(indices))]) ) # Create patches patches = [ ( slice(patch[0], patch[0] + width, None), slice(patch[1], patch[1] + width, None), ) for patch in patch_indices ] return patches