Source code for pycolormap_2d.colormap_2d

"""
pycolormap_2d.colormap_2d.py
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This module contains the ColorMap2D base class and its instantiable children.
"""

import abc
from typing import Tuple, TypeVar, Generic, Any
from importlib.resources import files

import numpy as np
from numpy.typing import NDArray

Number = TypeVar("Number", int, float)


[docs] class BaseColorMap2D(Generic[Number], metaclass=abc.ABCMeta): """Abstract class providing the basic functionality of the 2D color map. :param colormap_npy_loc: The location of the numpy file that contains the color map data. :type colormap_npy_loc: str :param range_x: The range of input x-values. Used to adapt the color map to un-normalized input data. :type range_x: Tuple[float, float] :param range_y: The range of input y-values. Used to adapt the color map to un-normalized input data. :type range_y: Tuple[float, float] """ _cmap_data: NDArray[np.uint8] _cmap_width: int _cmap_height: int range_x: Tuple[float, float] range_y: Tuple[float, float] def __init__(self, colormap_npy_loc: str, range_x: Tuple[Number, Number], range_y: Tuple[Number, Number]) -> None: self._type_check_range_args(range_x, "range_x") self._type_check_range_args(range_y, "range_y") self.range_x = range_x self.range_y = range_y # Load color map data from resource file. ref = files("pycolormap_2d") / colormap_npy_loc with ref.open("rb") as f: self._cmap_data = np.load(f) self._cmap_width = self._cmap_data.shape[0] self._cmap_height = self._cmap_data.shape[1] def __repr__(self) -> str: class_name = type(self).__name__ return f'{class_name}' def __call__(self, x: Number, y: Number) -> NDArray[np.uint8]: return self.sample(x, y) @staticmethod def _type_check_range_args(arg_value: Any, arg_name: str) -> None: if type(arg_value) is not tuple: raise ValueError( f"Argument '{arg_name}' expected to be of type Tuple[Number, " f"Number]. Instead was {type(arg_name).__name__}.") if len(arg_value) != 2: raise ValueError( f"Argument '{arg_name}' is expected to have two elements. " f"Instead had length {len(arg_value)}.") if type(arg_value[0]) not in [int, float] or type(arg_value[1]) \ not in [int, float]: raise ValueError( f"Argument '{arg_name}' expected to be of type Tuple[Number, " f"Number]. Instead was Tuple[{type(arg_value[0]).__name__}, " f"{type(arg_value[1]).__name__}].") @staticmethod def _clamp(v: Number, interval: Tuple[Number, Number]) -> Number: return min(interval[1], max(interval[0], v)) @staticmethod def _linearly_scale_value(value: float, from_range: Tuple[float, float], to_range: Tuple[float, float]) -> float: return (value - from_range[0]) * (to_range[1] - to_range[0]) / ( from_range[1] - from_range[0]) + to_range[0] def get_cmap_data(self) -> NDArray[np.uint8]: return self._cmap_data.copy() def _scale_x(self, x: float) -> float: return self._linearly_scale_value(x, self.range_x, (0.0, float(self._cmap_width - 1))) def _scale_y(self, y: float) -> float: return self._linearly_scale_value(y, self.range_y, (0.0, float(self._cmap_height - 1))) def _sample(self, x: Number, y: Number) -> NDArray[np.uint8]: image_x = int(self._clamp(round(self._scale_x(x)), (0, self._cmap_width - 1))) image_y = int(self._clamp(round(self._scale_y(y)), (0, self._cmap_height - 1))) return self._cmap_data[image_x, image_y, :]
[docs] @abc.abstractmethod def sample(self, x: Number, y: Number) -> NDArray[np.uint8]: """Get the color value at position (x, y). :param x: The x-coordinate (in the x_range given in the constructor or [0, 1] otherwise). :type x: int or float :param y: The y-coordinate (in the y_range given in the constructor or [0, 1] otherwise). :type y: int or float :rtype: NDArray[np.uint8] with shape (3,) """ pass
[docs] class ColorMap2DBremm(BaseColorMap2D): """ColorMap2D using the Bremm color map. :param range_x: The range of input x-values. Can be used to adapt the color map to un-normalized input data. :type range_x: Tuple[float, float] :param range_y: The range of input y-values. Can be used to adapt the color map to un-normalized input data. :type range_y: Tuple[float, float] """ def __init__(self, range_x: Tuple[float, float] = (0.0, 1.0), range_y: Tuple[float, float] = (0.0, 1.0)) -> None: super().__init__("data/bremm.npy", range_x, range_y)
[docs] def sample(self, x: Number, y: Number) -> NDArray[np.uint8]: return super()._sample(x, y)
[docs] class ColorMap2DCubeDiagonal(BaseColorMap2D): """ColorMap2D using the CubeDiagonal color map. :param range_x: The range of input x-values. Can be used to adapt the color map to un-normalized input data. :type range_x: Tuple[float, float] :param range_y: The range of input y-values. Can be used to adapt the color map to un-normalized input data. :type range_y: Tuple[float, float] """ def __init__(self, range_x: Tuple[float, float] = (0.0, 1.0), range_y: Tuple[float, float] = (0.0, 1.0)) -> None: super().__init__("data/cubediagonal.npy", range_x, range_y)
[docs] def sample(self, x: Number, y: Number) -> NDArray[np.uint8]: return super()._sample(x, y)
[docs] class ColorMap2DSchumann(BaseColorMap2D): """ColorMap2D using the Schumann color map. :param range_x: The range of input x-values. Can be used to adapt the color map to un-normalized input data. :type range_x: Tuple[float, float] :param range_y: The range of input y-values. Can be used to adapt the color map to un-normalized input data. :type range_y: Tuple[float, float] """ def __init__(self, range_x: Tuple[float, float] = (0.0, 1.0), range_y: Tuple[float, float] = (0.0, 1.0)) -> None: super().__init__("data/schumann.npy", range_x, range_y)
[docs] def sample(self, x: Number, y: Number) -> NDArray[np.uint8]: return super()._sample(x, y)
[docs] class ColorMap2DSteiger(BaseColorMap2D): """ColorMap2D using the Steiger color map. :param range_x: The range of input x-values. Can be used to adapt the color map to un-normalized input data. :type range_x: Tuple[float, float] :param range_y: The range of input y-values. Can be used to adapt the color map to un-normalized input data. :type range_y: Tuple[float, float] """ def __init__(self, range_x: Tuple[float, float] = (0.0, 1.0), range_y: Tuple[float, float] = (0.0, 1.0)) -> None: super().__init__("data/steiger.npy", range_x, range_y)
[docs] def sample(self, x: Number, y: Number) -> NDArray[np.uint8]: return super()._sample(x, y)
[docs] class ColorMap2DTeuling2(BaseColorMap2D): """ColorMap2D using the Teuling2 color map. :param range_x: The range of input x-values. Can be used to adapt the color map to un-normalized input data. :type range_x: Tuple[float, float] :param range_y: The range of input y-values. Can be used to adapt the color map to un-normalized input data. :type range_y: Tuple[float, float] """ def __init__(self, range_x: Tuple[float, float] = (0.0, 1.0), range_y: Tuple[float, float] = (0.0, 1.0)) -> None: super().__init__("data/teulingfig2.npy", range_x, range_y)
[docs] def sample(self, x: Number, y: Number) -> NDArray[np.uint8]: return super()._sample(x, y)
[docs] class ColorMap2DZiegler(BaseColorMap2D): """ColorMap2D using the Ziegler color map. :param range_x: The range of input x-values. Can be used to adapt the color map to un-normalized input data. :type range_x: Tuple[float, float] :param range_y: The range of input y-values. Can be used to adapt the color map to un-normalized input data. :type range_y: Tuple[float, float] """ def __init__(self, range_x: Tuple[float, float] = (0.0, 1.0), range_y: Tuple[float, float] = (0.0, 1.0)) -> None: super().__init__("data/ziegler.npy", range_x, range_y)
[docs] def sample(self, x: Number, y: Number) -> NDArray[np.uint8]: return super()._sample(x, y)