Source code for

# This source code is part of the Gecos package and is distributed
# under the 3-Clause BSD License. Please see 'LICENSE.rst' for further
# information.

__author__ = "Patrick Kunzmann"
__all__ = ["ColorSpace"]

from os.path import join, dirname, realpath
import itertools
import numpy as np
from .colors import lab_to_rgb

SPACE_FILE_NAME = join(dirname(realpath(__file__)), "space.npy")

[docs]class ColorSpace(): """ Create a color space, that spans the complete *RGB* gamut of the *Lab* color space. The *Lab* components are discretized into integer values. A color space describes the boundaries of the color scheme to be generated. It uses the *Lab* color space. In addition to the inherent limit to colors, that can also be displayed in *RGB* space, further parts of the space can be removed by calling :func:`remove()`. Parameters ---------- file_name : str, optional The path of a custom file to load the precalculated *RGB* convertible space from. Attributes ---------- shape : tuple of int The shape of the space, i.e. the amount of *Lab* values in each dimension. lab : ndarray, shape=(100, 256, 256, 3), dtype=int The complete discretized *Lab* color space in the *ab* range of ``-128`` to ``127``. space : shape=(100, 256, 256), dtype=bool The allowed part of the `lab` attribute, i.e. the part that is convertible into *RGB* and was not manually removed. """ def __init__(self, file_name=None): if file_name is None: file_name = SPACE_FILE_NAME l = np.arange(100) a = b = np.arange(-128,128) self._lab = np.zeros((100,256,256,3), dtype=int) self._lab[:,:,:,0] = l[:, np.newaxis, np.newaxis] self._lab[:,:,:,1] = a[np.newaxis, :, np.newaxis] self._lab[:,:,:,2] = b[np.newaxis, np.newaxis, :] with open(file_name, "rb") as file: self._space = np.load(file)
[docs] def remove(self, mask): """ Remove a portion of the color space. Parameters ---------- space : ndarray, shape=(100, 256, 256), dtype=bool The space is removed where this mask is true. Examples -------- Remove space below a defined lightness: >>> L_MIN = 50 >>> space = ColorSpace() >>> lab = space.lab >>> l = lab[..., 0] >>> space.remove(l < L_MIN) """ self._space &= ~mask
[docs] def get_rgb_space(self): """ Convert the *Lab* colors of the space in *RGB* colors. Returns ------- rgb : ndarray, shape=(100,256,256,3), dtype=float The *RGB* colors. Colors that cannot be displayed in *RGB* or were manually removed from the space are ``NaN``. """ rgb = lab_to_rgb(self._lab) rgb[~self._space] = np.nan return rgb
@property def space(self): return self._space.copy() @property def shape(self): return (100, 256, 256) @property def lab(self): return self._lab.copy() @staticmethod def _generate(file_name=None): """ Precalculate which part of the *Lab* space can be displayed in *RGB* and save the result as boolean mask into a *NumPy* ``.npy`` file. """ lab = np.zeros((100, 256, 256, 3), dtype=int) lab[:,:,:,0] = np.arange(100 )[:, np.newaxis, np.newaxis] lab[:,:,:,1] = np.arange(-128, 128)[np.newaxis, :, np.newaxis] lab[:,:,:,2] = np.arange(-128, 128)[np.newaxis, np.newaxis, :] rgb = lab_to_rgb(lab) space = np.ones((100, 256, 256), dtype=bool) space[np.isnan(rgb).any(axis=-1)] = False if file_name is None: file_name = SPACE_FILE_NAME with open(file_name, "wb") as file:, space)