diff --git a/masque/utils.py b/masque/utils.py deleted file mode 100644 index 5f7d987..0000000 --- a/masque/utils.py +++ /dev/null @@ -1,165 +0,0 @@ -""" -Various helper functions -""" -from typing import Any, Union, Tuple, Sequence, Dict, List -from abc import ABCMeta - -import numpy -from numpy.typing import NDArray, ArrayLike - - -# Type definitions -layer_t = Union[int, Tuple[int, int], str] -annotations_t = Dict[str, List[Union[int, float, str]]] - - -def is_scalar(var: Any) -> bool: - """ - Alias for 'not hasattr(var, "__len__")' - - Args: - var: Checks if `var` has a length. - """ - return not hasattr(var, "__len__") - - -def get_bit(bit_string: Any, bit_id: int) -> bool: - """ - Interprets bit number `bit_id` from the right (lsb) of `bit_string` as a boolean - - Args: - bit_string: Bit string to test - bit_id: Bit number, 0-indexed from the right (lsb) - - Returns: - Boolean value of the requested bit - """ - return bit_string & (1 << bit_id) != 0 - - -def set_bit(bit_string: Any, bit_id: int, value: bool) -> Any: - """ - Returns `bit_string`, with bit number `bit_id` set to boolean `value`. - - Args: - bit_string: Bit string to alter - bit_id: Bit number, 0-indexed from right (lsb) - value: Boolean value to set bit to - - Returns: - Altered `bit_string` - """ - mask = (1 << bit_id) - bit_string &= ~mask - if value: - bit_string |= mask - return bit_string - - -def rotation_matrix_2d(theta: float) -> NDArray[numpy.float64]: - """ - 2D rotation matrix for rotating counterclockwise around the origin. - - Args: - theta: Angle to rotate, in radians - - Returns: - rotation matrix - """ - return numpy.array([[numpy.cos(theta), -numpy.sin(theta)], - [numpy.sin(theta), +numpy.cos(theta)]]) - - -def normalize_mirror(mirrored: Sequence[bool]) -> Tuple[bool, float]: - """ - Converts 0-2 mirror operations `(mirror_across_x_axis, mirror_across_y_axis)` - into 0-1 mirror operations and a rotation - - Args: - mirrored: `(mirror_across_x_axis, mirror_across_y_axis)` - - Returns: - `mirror_across_x_axis` (bool) and - `angle_to_rotate` in radians - """ - - mirrored_x, mirrored_y = mirrored - mirror_x = (mirrored_x != mirrored_y) # XOR - angle = numpy.pi if mirrored_y else 0 - return mirror_x, angle - - -def remove_duplicate_vertices(vertices: ArrayLike, closed_path: bool = True) -> NDArray[numpy.float64]: - """ - Given a list of vertices, remove any consecutive duplicates. - - Args: - vertices: `[[x0, y0], [x1, y1], ...]` - closed_path: If True, `vertices` is interpreted as an implicity-closed path - (i.e. the last vertex will be removed if it is the same as the first) - - Returns: - `vertices` with no consecutive duplicates. - """ - vertices = numpy.array(vertices) - duplicates = (vertices == numpy.roll(vertices, 1, axis=0)).all(axis=1) - if not closed_path: - duplicates[0] = False - return vertices[~duplicates] - - -def remove_colinear_vertices(vertices: ArrayLike, closed_path: bool = True) -> NDArray[numpy.float64]: - """ - Given a list of vertices, remove any superflous vertices (i.e. - those which lie along the line formed by their neighbors) - - Args: - vertices: Nx2 ndarray of vertices - closed_path: If `True`, the vertices are assumed to represent an implicitly - closed path. If `False`, the path is assumed to be open. Default `True`. - - Returns: - `vertices` with colinear (superflous) vertices removed. - """ - vertices = remove_duplicate_vertices(vertices) - - # Check for dx0/dy0 == dx1/dy1 - - dv = numpy.roll(vertices, -1, axis=0) - vertices # [y1-y0, y2-y1, ...] - dxdy = dv * numpy.roll(dv, 1, axis=0)[:, ::-1] # [[dx0*(dy_-1), (dx_-1)*dy0], dx1*dy0, dy1*dx0]] - - dxdy_diff = numpy.abs(numpy.diff(dxdy, axis=1))[:, 0] - err_mult = 2 * numpy.abs(dxdy).sum(axis=1) + 1e-40 - - slopes_equal = (dxdy_diff / err_mult) < 1e-15 - if not closed_path: - slopes_equal[[0, -1]] = False - - return vertices[~slopes_equal] - - -class AutoSlots(ABCMeta): - """ - Metaclass for automatically generating __slots__ based on superclass type annotations. - - Superclasses must set `__slots__ = ()` to make this work properly. - - This is a workaround for the fact that non-empty `__slots__` can't be used - with multiple inheritance. Since we only use multiple inheritance with abstract - classes, they can have empty `__slots__` and their attribute type annotations - can be used to generate a full `__slots__` for the concrete class. - """ - def __new__(cls, name, bases, dctn): - parents = set() - for base in bases: - parents |= set(base.mro()) - - slots = tuple(dctn.get('__slots__', tuple())) - for parent in parents: - if not hasattr(parent, '__annotations__'): - continue - slots += tuple(getattr(parent, '__annotations__').keys()) - - dctn['__slots__'] = slots - return super().__new__(cls, name, bases, dctn) -