Beispiel #1
0
    def __getattr__(self, attribute: str) -> Any:
        """
        Return given attribute value while handling deprecation.

        Parameters
        ----------
        attribute
            Attribute name.

        Returns
        -------
        :class:`object`
            Attribute value.

        Raises
        ------
        AttributeError
            If the attribute is not defined.
        """

        change = self._changes.get(attribute)

        if change is not None:
            if not isinstance(change, ObjectRemoved):

                usage_warning(str(change))

                return (getattr(self._module, attribute) if isinstance(
                    change, ObjectFutureRemove) else get_attribute(change[1]))
            else:
                raise AttributeError(str(change))

        return getattr(self._module, attribute)
Beispiel #2
0
    def __getattr__(self, attribute):
        """
        Returns given attribute value while handling deprecation.

        Parameters
        ----------
        attribute : unicode
            Attribute name.

        Returns
        -------
        object
            Attribute value.

        Raises
        ------
        AttributeError
            If the attribute is not defined.
        """

        change = self._changes.get(attribute)
        if change is not None:
            if not isinstance(change, Removed):
                usage_warning(str(change))
                return get_attribute(change[1])
            else:
                raise AttributeError(str(change))

        return getattr(self._module, attribute)
Beispiel #3
0
def handle_arguments_deprecation(changes, **kwargs):
    """
    Handles arguments deprecation according to desired API changes mapping.

    Parameters
    ----------
    changes : dict
        Dictionary of desired API changes.

    Other Parameters
    ----------------
    \\**kwargs : dict, optional
        Keywords arguments to handle.

    Returns
    -------
    dict
        Handled keywords arguments.

    Examples
    --------
    >>> changes = {
    ...     'ArgumentRenamed': [[
    ...         'argument_1_name',
    ...         'argument_1_new_name',
    ...     ]],
    ...     'ArgumentFutureRename': [[
    ...         'argument_2_name',
    ...         'argument_2_new_name',
    ...     ]],
    ...     'ArgumentRemoved': ['argument_3_name'],
    ...     'ArgumentFutureRemove': ['argument_4_name'],
    ... }
    >>> handle_arguments_deprecation(changes, argument_1_name=True,
    ...                             argument_2_name=True, argument_4_name=True)
    ... # doctest: +SKIP
    {'argument_4_name': True, 'argument_1_new_name': True, \
'argument_2_new_name': True}
    """

    changes = build_API_changes(changes)

    for kwarg in kwargs.copy():
        change = changes.get(kwarg)

        if change is None:
            continue

        if not isinstance(change, ArgumentRemoved):

            usage_warning(str(change))

            if isinstance(change, ArgumentFutureRemove):
                continue
            else:
                kwargs[change[1]] = kwargs.pop(kwarg)
        else:
            raise ValueError(str(change))

    return kwargs
Beispiel #4
0
    def __getattr__(self, attribute):
        """
        Returns given attribute value while handling deprecation.

        Parameters
        ----------
        attribute : unicode
            Attribute name.

        Returns
        -------
        object
            Attribute value.

        Raises
        ------
        AttributeError
            If the attribute is not defined.
        """

        change = self._changes.get(attribute)
        if change is not None:
            if not isinstance(change, Removed):
                usage_warning(str(change))
                return get_attribute(change[1])
            else:
                raise AttributeError(str(change))

        return getattr(self._module, attribute)
Beispiel #5
0
def CCT_to_xy_Kang2002(CCT):
    """
    Returns the *CIE xy* chromaticity coordinates from given correlated colour
    temperature :math:`T_{cp}` using *Kang et al. (2002)* method.

    Parameters
    ----------
    CCT : numeric or array_like
        Correlated colour temperature :math:`T_{cp}`.

    Returns
    -------
    ndarray
        *CIE xy* chromaticity coordinates.

    Raises
    ------
    ValueError
        If the correlated colour temperature is not in appropriate domain.

    References
    ----------
    :cite:`Kang2002a`

    Examples
    --------
    >>> CCT_to_xy_Kang2002(6504.38938305)  # doctest: +ELLIPSIS
    array([ 0.313426 ...,  0.3235959...])
    """

    CCT = as_float_array(CCT)

    if np.any(CCT[np.asarray(np.logical_or(CCT < 1667, CCT > 25000))]):
        usage_warning(('Correlated colour temperature must be in domain '
                       '[1667, 25000], unpredictable results may occur!'))

    CCT_3 = CCT**3
    CCT_2 = CCT**2

    x = np.where(
        CCT <= 4000,
        -0.2661239 * 10**9 / CCT_3 - 0.2343589 * 10**6 / CCT_2 +
        0.8776956 * 10**3 / CCT + 0.179910,
        -3.0258469 * 10**9 / CCT_3 + 2.1070379 * 10**6 / CCT_2 +
        0.2226347 * 10**3 / CCT + 0.24039,
    )

    x_3 = x**3
    x_2 = x**2

    cnd_l = [CCT <= 2222, np.logical_and(CCT > 2222, CCT <= 4000), CCT > 4000]
    i = -1.1063814 * x_3 - 1.34811020 * x_2 + 2.18555832 * x - 0.20219683
    j = -0.9549476 * x_3 - 1.37418593 * x_2 + 2.09137015 * x - 0.16748867
    k = 3.0817580 * x_3 - 5.8733867 * x_2 + 3.75112997 * x - 0.37001483
    y = np.select(cnd_l, [i, j, k])

    xy = tstack([x, y])

    return xy
Beispiel #6
0
    def interpolator_args(self):
        # Docstrings are omitted for documentation purposes.
        usage_warning(
            str(
                ObjectRenamed('Signal.interpolator_args',
                              'Signal.interpolator_kwargs')))

        return self.interpolator_kwargs
Beispiel #7
0
    def extrapolator_args(self, value):
        # Docstrings are omitted for documentation purposes.
        usage_warning(
            str(
                ObjectRenamed('Signal.extrapolator_args',
                              'Signal.extrapolator_kwargs')))

        self.extrapolator_kwargs = value
Beispiel #8
0
def cctf_decoding(value, function='sRGB', **kwargs):
    """
    Decodes non-linear :math:`R'G'B'` values to linear :math:`RGB` values using
    given decoding colour component transfer function (Decoding CCTF).

    Parameters
    ----------
    value : numeric or array_like
        Non-linear :math:`R'G'B'` values.
    function : unicode, optional
        {:attr:`colour.CCTF_DECODINGS`},
        Computation function.

    Other Parameters
    ----------------
    \\**kwargs : dict, optional
        Keywords arguments for the relevant decoding CCTF of the
        :attr:`colour.CCTF_DECODINGS` attribute collection.

    Warnings
    --------
    For *ITU-R BT.2100*, only the electro-optical transfer functions
    (EOTFs / EOCFs) are exposed by this definition, please refer to the
    :func:`colour.oetf_inverse` definition for the inverse opto-electronic
    transfer functions (OETF / OECF).

    Returns
    -------
    numeric or ndarray
        Linear :math:`RGB` values.

    Examples
    --------
    >>> cctf_decoding(0.391006842619746, function='PLog', log_reference=400)
    ... # doctest: +ELLIPSIS
    0.1...
    >>> cctf_decoding(0.182011532850008, function='ST 2084', L_p=1000)
    ... # doctest: +ELLIPSIS
    0.1...
    >>> cctf_decoding(  # doctest: +ELLIPSIS
    ...     0.461356129500442, function='ITU-R BT.1886')
    0.1...
    """

    if 'itu-r bt.2100' in function.lower():
        usage_warning(
            'With the "ITU-R BT.2100" method, only the electro-optical '
            'transfer functions (EOTFs / EOCFs) are exposed by this '
            'definition, please refer to the "colour.oetf_inverse" definition '
            'for the inverse opto-electronic transfer functions (OETF / OECF).'
        )

    function = CCTF_DECODINGS[function]

    return function(value, **filter_kwargs(function, **kwargs))
Beispiel #9
0
def CCT_to_xy_Kang2002(CCT):
    """
    Returns the *CIE XYZ* tristimulus values *xy* chromaticity coordinates from
    given correlated colour temperature :math:`T_{cp}` using
    *Kang et al. (2002)* method.

    Parameters
    ----------
    CCT : numeric or array_like
        Correlated colour temperature :math:`T_{cp}`.

    Returns
    -------
    ndarray
        *xy* chromaticity coordinates.

    Raises
    ------
    ValueError
        If the correlated colour temperature is not in appropriate domain.

    References
    ----------
    :cite:`Kang2002a`

    Examples
    --------
    >>> CCT_to_xy_Kang2002(6504.38938305)  # doctest: +ELLIPSIS
    array([ 0.313426 ...,  0.3235959...])
    """

    CCT = as_float_array(CCT)

    if np.any(CCT[np.asarray(np.logical_or(CCT < 1667, CCT > 25000))]):
        usage_warning(('Correlated colour temperature must be in domain '
                       '[1667, 25000], unpredictable results may occur!'))

    x = np.where(
        CCT <= 4000,
        -0.2661239 * 10 ** 9 / CCT ** 3 - 0.2343589 * 10 ** 6 / CCT ** 2 +
        0.8776956 * 10 ** 3 / CCT + 0.179910,
        -3.0258469 * 10 ** 9 / CCT ** 3 + 2.1070379 * 10 ** 6 / CCT ** 2 +
        0.2226347 * 10 ** 3 / CCT + 0.24039,
    )

    cnd_l = [CCT <= 2222, np.logical_and(CCT > 2222, CCT <= 4000), CCT > 4000]
    i = -1.1063814 * x ** 3 - 1.34811020 * x ** 2 + 2.18555832 * x - 0.20219683
    j = -0.9549476 * x ** 3 - 1.37418593 * x ** 2 + 2.09137015 * x - 0.16748867
    k = 3.0817580 * x ** 3 - 5.8733867 * x ** 2 + 3.75112997 * x - 0.37001483
    y = np.select(cnd_l, [i, j, k])

    xy = tstack([x, y])

    return xy
Beispiel #10
0
def encoding_cctf(value, function='sRGB', **kwargs):
    """
    Encodes linear :math:`RGB` values to non linear :math:`R'G'B'` values using
    given encoding colour component transfer function (Encoding CCTF).

    Parameters
    ----------
    value : numeric or array_like
        Linear :math:`RGB` values.
    function : unicode, optional
        {:attr:`colour.ENCODING_CCTFS`},
        Computation function.

    Other Parameters
    ----------------
    \\**kwargs : dict, optional
        Keywords arguments for the relevant encoding CCTF of the
        :attr:`colour.ENCODING_CCTFS` attribute collection.

    Warning
    -------
    For *ITU-R BT.2100*, only the reverse electro-optical transfer functions
    (EOTFs / EOCFs) are exposed by this definition, please refer to the
    :func:`colour.oetf` definition for the opto-electronic transfer functions
    (OETF / OECF).

    Returns
    -------
    numeric or ndarray
        Non linear :math:`R'G'B'` values.

    Examples
    --------
    >>> encoding_cctf(0.18, function='PLog', log_reference=400)
    ... # doctest: +ELLIPSIS
    0.3910068...
    >>> encoding_cctf(0.18, function='ST 2084', L_p=1000)
    ... # doctest: +ELLIPSIS
    0.1820115...
    >>> encoding_cctf(  # doctest: +ELLIPSIS
    ...     0.11699185725296059, function='ITU-R BT.1886')
    0.4090077...
    """

    if 'itu-r bt.2100' in function.lower():
        usage_warning(
            'For "ITU-R BT.2100", only the reverse electro-optical transfer '
            'functions (EOTFs / EOCFs) are exposed by this definition, please '
            'refer to the "colour.oetf" definition for the opto-electronic '
            'transfer functions (OETF / OECF).')

    function = ENCODING_CCTFS[function]

    return function(value, **filter_kwargs(function, **kwargs))
Beispiel #11
0
def decoding_cctf(value, function='Cineon', **kwargs):
    """
    Decodes non-linear :math:`R'G'B'` values to linear :math:`RGB` values using
    given decoding colour component transfer function (Decoding CCTF).

    Parameters
    ----------
    value : numeric or array_like
        Non-linear :math:`R'G'B'` values.
    function : unicode, optional
        {:attr:`colour.DECODING_CCTFS`},
        Computation function.

    Other Parameters
    ----------------
    \\**kwargs : dict, optional
        Keywords arguments for the relevant decoding CCTF of the
        :attr:`colour.DECODING_CCTFS` attribute collection.

    Warning
    -------
    For *ITU-R BT.2100*, only the electro-optical transfer functions
    (EOTFs / EOCFs) are exposed by this definition, please refer to the
    :func:`colour.oetf_reverse` definition for the reverse opto-electronic
    transfer functions (OETF / OECF).

    Returns
    -------
    numeric or ndarray
        Linear :math:`RGB` values.

    Examples
    --------
    >>> decoding_cctf(0.391006842619746, function='PLog', log_reference=400)
    ... # doctest: +ELLIPSIS
    0.1...
    >>> decoding_cctf(0.182011532850008, function='ST 2084', L_p=1000)
    ... # doctest: +ELLIPSIS
    0.1...
    >>> decoding_cctf(  # doctest: +ELLIPSIS
    ...     0.461356129500442, function='ITU-R BT.1886')
    0.1...
    """

    if 'itu-r bt.2100' in function.lower():
        usage_warning(
            'For "ITU-R BT.2100", only the electro-optical transfer functions '
            '(EOTFs / EOCFs) are exposed by this definition, please refer to '
            'the "colour.oetf_reverse" definition for the reverse '
            'opto-electronic transfer functions (OETF / OECF).')

    function = DECODING_CCTFS[function]

    return function(value, **filter_kwargs(function, **kwargs))
Beispiel #12
0
def encoding_cctf(value, function='sRGB', **kwargs):
    """
    Encodes linear :math:`RGB` values to non linear :math:`R'G'B'` values using
    given encoding colour component transfer function (Encoding CCTF).

    Parameters
    ----------
    value : numeric or array_like
        Linear :math:`RGB` values.
    function : unicode, optional
        {:attr:`colour.ENCODING_CCTFS`},
        Computation function.

    Other Parameters
    ----------------
    \\**kwargs : dict, optional
        Keywords arguments for the relevant encoding CCTF of the
        :attr:`colour.ENCODING_CCTFS` attribute collection.

    Warning
    -------
    For *ITU-R BT.2100*, only the reverse electro-optical transfer functions
    (EOTFs / EOCFs) are exposed by this definition, please refer to the
    :func:`colour.oetf` definition for the opto-electronic transfer functions
    (OETF / OECF).

    Returns
    -------
    numeric or ndarray
        Non linear :math:`R'G'B'` values.

    Examples
    --------
    >>> encoding_cctf(0.18, function='PLog', log_reference=400)
    ... # doctest: +ELLIPSIS
    0.3910068...
    >>> encoding_cctf(0.18, function='ST 2084', L_p=1000)
    ... # doctest: +ELLIPSIS
    0.1820115...
    >>> encoding_cctf(  # doctest: +ELLIPSIS
    ...     0.11699185725296059, function='ITU-R BT.1886')
    0.4090077...
    """

    if 'itu-r bt.2100' in function.lower():
        usage_warning(
            'For "ITU-R BT.2100", only the reverse electro-optical transfer '
            'functions (EOTFs / EOCFs) are exposed by this definition, please '
            'refer to the "colour.oetf" definition for the opto-electronic '
            'transfer functions (OETF / OECF).')

    function = ENCODING_CCTFS[function]

    return function(value, **filter_kwargs(function, **kwargs))
Beispiel #13
0
def RGB_to_HEX(RGB):
    """
    Converts from *RGB* colourspace to hexadecimal representation.

    Parameters
    ----------
    RGB : array_like
        *RGB* colourspace array.

    Returns
    -------
    unicode
        Hexadecimal representation.

    Notes
    -----

    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``RGB``    | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    Examples
    --------
    >>> RGB = np.array([0.66666667, 0.86666667, 1.00000000])
    >>> # Doctests skip for Python 2.x compatibility.
    >>> RGB_to_HEX(RGB)  # doctest: +SKIP
    '#aaddff'
    """

    RGB = to_domain_1(RGB)

    if np.any(RGB < 0):
        usage_warning(
            '"RGB" array contains negative values, those will be clipped, '
            'unpredictable results may occur!')

        RGB = np.clip(RGB, 0, np.inf)

    if np.any(RGB > 1):
        usage_warning(
            '"RGB" array contains values over 1 and will be normalised, '
            'unpredictable results may occur!')

        RGB = eotf_inverse_sRGB(normalise_maximum(eotf_sRGB(RGB)))

    to_HEX = np.vectorize('{0:02x}'.format)

    HEX = to_HEX((RGB * 255).astype(np.uint8)).astype(object)
    HEX = np.asarray('#') + HEX[..., 0] + HEX[..., 1] + HEX[..., 2]

    return HEX
Beispiel #14
0
def CCT_to_xy_CIE_D(CCT):
    """
    Returns the *CIE xy* chromaticity coordinates of a
    *CIE Illuminant D Series* from its correlated colour temperature
    :math:`T_{cp}`.

    Parameters
    ----------
    CCT : numeric or array_like
        Correlated colour temperature :math:`T_{cp}`.

    Returns
    -------
    ndarray
        *CIE xy* chromaticity coordinates.

    Raises
    ------
    ValueError
        If the correlated colour temperature is not in appropriate domain.

    References
    ----------
    :cite:`Wyszecki2000z`

    Examples
    --------
    >>> CCT_to_xy_CIE_D(6504.38938305)  # doctest: +ELLIPSIS
    array([ 0.3127077...,  0.3291128...])
    """

    CCT = as_float_array(CCT)

    if np.any(CCT[np.asarray(np.logical_or(CCT < 4000, CCT > 25000))]):
        usage_warning(('Correlated colour temperature must be in domain '
                       '[4000, 25000], unpredictable results may occur!'))

    CCT_3 = CCT**3
    CCT_2 = CCT**2

    x = np.where(
        CCT <= 7000,
        -4.607 * 10**9 / CCT_3 + 2.9678 * 10**6 / CCT_2 +
        0.09911 * 10**3 / CCT + 0.244063,
        -2.0064 * 10**9 / CCT_3 + 1.9018 * 10**6 / CCT_2 +
        0.24748 * 10**3 / CCT + 0.23704,
    )

    y = daylight_locus_function(x)

    xy = tstack([x, y])

    return xy
Beispiel #15
0
def lightness_Wyszecki1963(Y: FloatingOrArrayLike) -> FloatingOrNDArray:
    """
    Return the *Lightness* :math:`W` of given *luminance* :math:`Y` using
    *Wyszecki (1963)* method.


    Parameters
    ----------
    Y
        *Luminance* :math:`Y`.

    Returns
    -------
    :class:`numpy.floating` or :class:`numpy.ndarray`
        *Lightness* :math:`W`.

    Notes
    -----
    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``Y``      | [0, 100]              | [0, 1]        |
    +------------+-----------------------+---------------+

    +------------+-----------------------+---------------+
    | **Range**  | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``W``      | [0, 100]              | [0, 1]        |
    +------------+-----------------------+---------------+

    References
    ----------
    :cite:`Wyszecki1963b`

    Examples
    --------
    >>> lightness_Wyszecki1963(12.19722535)  # doctest: +ELLIPSIS
    40.5475745...
    """

    Y = to_domain_100(Y)

    if np.any(Y < 1) or np.any(Y > 98):
        usage_warning(
            '"W*" Lightness computation is only applicable for '
            '1% < "Y" < 98%, unpredictable results may occur!'
        )

    W = 25 * spow(Y, 1 / 3) - 17

    return as_float(from_range_100(W))
Beispiel #16
0
def RGB_to_HEX(RGB: ArrayLike) -> StrOrNDArray:
    """
    Convert from *RGB* colourspace to hexadecimal representation.

    Parameters
    ----------
    RGB
        *RGB* colourspace array.

    Returns
    -------
    :class:`str` or :class:`numpy.array`
        Hexadecimal representation.

    Notes
    -----
    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``RGB``    | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    Examples
    --------
    >>> RGB = np.array([0.66666667, 0.86666667, 1.00000000])
    >>> RGB_to_HEX(RGB)
    '#aaddff'
    """

    RGB = to_domain_1(RGB)

    if np.any(RGB < 0):
        usage_warning(
            '"RGB" array contains negative values, those will be clipped, '
            "unpredictable results may occur!")

        RGB = as_float_array(np.clip(RGB, 0, np.inf))

    if np.any(RGB > 1):
        usage_warning(
            '"RGB" array contains values over 1 and will be normalised, '
            "unpredictable results may occur!")

        RGB = eotf_inverse_sRGB(normalise_maximum(eotf_sRGB(RGB)))

    to_HEX = np.vectorize("{:02x}".format)

    HEX = to_HEX(as_int_array(RGB * 255, dtype=np.uint8)).astype(object)
    HEX = np.asarray("#") + HEX[..., 0] + HEX[..., 1] + HEX[..., 2]

    return HEX
Beispiel #17
0
def lightness_Wyszecki1963(Y):
    """
    Returns the *Lightness* :math:`W` of given *luminance* :math:`Y` using
    *Wyszecki (1963)* method.


    Parameters
    ----------
    Y : numeric or array_like
        *luminance* :math:`Y`.

    Returns
    -------
    numeric or array_like
        *Lightness* :math:`W`.

    Notes
    -----

    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``Y``      | [0, 100]              | [0, 1]        |
    +------------+-----------------------+---------------+

    +------------+-----------------------+---------------+
    | **Range**  | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``W``      | [0, 100]              | [0, 1]        |
    +------------+-----------------------+---------------+

    References
    ----------
    :cite:`Wyszecki1963b`

    Examples
    --------
    >>> lightness_Wyszecki1963(12.19722535)  # doctest: +ELLIPSIS
    40.5475745...
    """

    Y = to_domain_100(Y)

    if np.any(Y < 1) or np.any(Y > 98):
        usage_warning('"W*" Lightness computation is only applicable for '
                      '1% < "Y" < 98%, unpredictable results may occur!')

    W = 25 * spow(Y, 1 / 3) - 17

    return from_range_100(W)
Beispiel #18
0
def CCT_to_xy_CIE_D(CCT):
    """
    Converts from the correlated colour temperature :math:`T_{cp}` of a
    *CIE Illuminant D Series* to the chromaticity of that
    *CIE Illuminant D Series* illuminant.

    Parameters
    ----------
    CCT : numeric or array_like
        Correlated colour temperature :math:`T_{cp}`.

    Returns
    -------
    ndarray
        *xy* chromaticity coordinates.

    Raises
    ------
    ValueError
        If the correlated colour temperature is not in appropriate domain.

    References
    ----------
    :cite:`Wyszecki2000z`

    Examples
    --------
    >>> CCT_to_xy_CIE_D(6504.38938305)  # doctest: +ELLIPSIS
    array([ 0.3127077...,  0.3291128...])
    """

    CCT = as_float_array(CCT)

    if np.any(CCT[np.asarray(np.logical_or(CCT < 4000, CCT > 25000))]):
        usage_warning(('Correlated colour temperature must be in domain '
                       '[4000, 25000], unpredictable results may occur!'))

    x = np.where(
        CCT <= 7000,
        -4.607 * 10 ** 9 / CCT ** 3 + 2.9678 * 10 ** 6 / CCT ** 2 +
        0.09911 * 10 ** 3 / CCT + 0.244063,
        -2.0064 * 10 ** 9 / CCT ** 3 + 1.9018 * 10 ** 6 / CCT ** 2 +
        0.24748 * 10 ** 3 / CCT + 0.23704,
    )

    y = daylight_locus_function(x)

    xy = tstack([x, y])

    return xy
Beispiel #19
0
def write_LUT_IridasCube(LUT, path, decimals=7):
    """
    Writes given *LUT* to given  *Iridas* *.cube* *LUT* file.

    Parameters
    ----------
    LUT : LUT3x1D or LUT3d or LUTSequence
        :class:`LUT3x1D`, :class:`LUT3D` or :class:`LUTSequence` class instance
        to write at given path.
    path : unicode
        *LUT* path.
    decimals : int, optional
        Formatting decimals.

    Returns
    -------
    bool
        Definition success.

    Warning
    -------
    -   If a :class:`LUTSequence` class instance is passed as ``LUT``, the
        first *LUT* in the *LUT* sequence will be used.

    References
    ----------
    :cite:`AdobeSystems2013b`

    Examples
    --------
    Writing a 3x1D *Iridas* *.cube* *LUT*:

    >>> from colour.algebra import spow
    >>> domain = np.array([[-0.1, -0.2, -0.4], [1.5, 3.0, 6.0]])
    >>> LUT = LUT3x1D(
    ...     spow(LUT3x1D.linear_table(16, domain), 1 / 2.2),
    ...     'My LUT',
    ...     domain,
    ...     comments=['A first comment.', 'A second comment.'])
    >>> write_LUT_IridasCube(LUT, 'My_LUT.cube')  # doctest: +SKIP

    Writing a 3D *Iridas* *.cube* *LUT*:

    >>> domain = np.array([[-0.1, -0.2, -0.4], [1.5, 3.0, 6.0]])
    >>> LUT = LUT3D(
    ...     spow(LUT3D.linear_table(16, domain), 1 / 2.2),
    ...     'My LUT',
    ...     np.array([[-0.1, -0.2, -0.4], [1.5, 3.0, 6.0]]),
    ...     comments=['A first comment.', 'A second comment.'])
    >>> write_LUT_IridasCube(LUT, 'My_LUT.cube')  # doctest: +SKIP
    """

    if isinstance(LUT, LUTSequence):
        LUT = LUT[0]
        usage_warning('"LUT" is a "LUTSequence" instance was passed, '
                      'using first sequence "LUT":\n'
                      '{0}'.format(LUT))

    assert not LUT.is_domain_explicit(), '"LUT" domain must be implicit!'

    if isinstance(LUT, LUT1D):
        LUT = LUT.as_LUT(LUT3x1D)

    assert (isinstance(LUT, LUT3x1D) or
            isinstance(LUT, LUT3D)), '"LUT" must be a 1D, 3x1D or 3D "LUT"!'

    is_3x1D = isinstance(LUT, LUT3x1D)

    size = LUT.size
    if is_3x1D:
        assert 2 <= size <= 65536, '"LUT" size must be in domain [2, 65536]!'
    else:
        assert 2 <= size <= 256, '"LUT" size must be in domain [2, 256]!'

    def _format_array(array):
        """
        Formats given array as an *Iridas* *.cube* data row.
        """

        return '{1:0.{0}f} {2:0.{0}f} {3:0.{0}f}'.format(decimals, *array)

    with open(path, 'w') as cube_file:
        cube_file.write('TITLE "{0}"\n'.format(LUT.name))

        if LUT.comments:
            for comment in LUT.comments:
                cube_file.write('# {0}\n'.format(comment))

        cube_file.write('{0} {1}\n'.format(
            'LUT_1D_SIZE' if is_3x1D else 'LUT_3D_SIZE', LUT.table.shape[0]))

        default_domain = np.array([[0, 0, 0], [1, 1, 1]])
        if not np.array_equal(LUT.domain, default_domain):
            cube_file.write('DOMAIN_MIN {0}\n'.format(
                _format_array(LUT.domain[0])))
            cube_file.write('DOMAIN_MAX {0}\n'.format(
                _format_array(LUT.domain[1])))

        if not is_3x1D:
            table = LUT.table.reshape([-1, 3], order='F')
        else:
            table = LUT.table

        for row in table:
            cube_file.write('{0}\n'.format(_format_array(row)))

    return True
Beispiel #20
0
def XYZ_to_Hunt(XYZ,
                XYZ_w,
                XYZ_b,
                L_A,
                surround=HUNT_VIEWING_CONDITIONS['Normal Scenes'],
                L_AS=None,
                CCT_w=None,
                XYZ_p=None,
                p=None,
                S=None,
                S_w=None,
                helson_judd_effect=False,
                discount_illuminant=True):
    """
    Computes the *Hunt* colour appearance model correlates.

    Parameters
    ----------
    XYZ : array_like
        *CIE XYZ* tristimulus values of test sample / stimulus.
    XYZ_w : array_like
        *CIE XYZ* tristimulus values of reference white.
    XYZ_b : array_like
        *CIE XYZ* tristimulus values of background.
    L_A : numeric or array_like
        Adapting field *luminance* :math:`L_A` in :math:`cd/m^2`.
    surround : Hunt_InductionFactors, optional
         Surround viewing conditions induction factors.
    L_AS : numeric or array_like, optional
        Scotopic luminance :math:`L_{AS}` of the illuminant, approximated if
        not specified.
    CCT_w : numeric or array_like, optional
        Correlated color temperature :math:`T_{cp}`: of the illuminant, needed
        to approximate :math:`L_{AS}`.
    XYZ_p : array_like, optional
        *CIE XYZ* tristimulus values of proximal field, assumed to be equal to
        background if not specified.
    p : numeric or array_like, optional
        Simultaneous contrast / assimilation factor :math:`p` with value
        normalised to domain [-1, 0] when simultaneous contrast occurs and
        normalised to domain [0, 1] when assimilation occurs.
    S : numeric or array_like, optional
        Scotopic response :math:`S` to the stimulus, approximated using
        tristimulus values :math:`Y` of the stimulus if not specified.
    S_w : numeric or array_like, optional
        Scotopic response :math:`S_w` for the reference white, approximated
        using the tristimulus values :math:`Y_w` of the reference white if not
        specified.
    helson_judd_effect : bool, optional
        Truth value indicating whether the *Helson-Judd* effect should be
        accounted for.
    discount_illuminant : bool, optional
       Truth value indicating if the illuminant should be discounted.

    Returns
    -------
    Hunt_Specification
        *Hunt* colour appearance model specification.

    Raises
    ------
    ValueError
        If an illegal arguments combination is specified.

    Notes
    -----

    +--------------------------+-----------------------+---------------+
    | **Domain**               | **Scale - Reference** | **Scale - 1** |
    +==========================+=======================+===============+
    | ``XYZ``                  | [0, 100]              | [0, 1]        |
    +--------------------------+-----------------------+---------------+
    | ``XYZ_w``                | [0, 100]              | [0, 1]        |
    +--------------------------+-----------------------+---------------+
    | ``XYZ_b``                | [0, 100]              | [0, 1]        |
    +--------------------------+-----------------------+---------------+
    | ``XYZ_p``                | [0, 100]              | [0, 1]        |
    +--------------------------+-----------------------+---------------+

    +--------------------------+-----------------------+---------------+
    | **Range**                | **Scale - Reference** | **Scale - 1** |
    +==========================+=======================+===============+
    | ``Hunt_Specification.h`` | [0, 360]              | [0, 1]        |
    +--------------------------+-----------------------+---------------+

    References
    ----------
    :cite:`Fairchild2013u`, :cite:`Hunt2004b`

    Examples
    --------
    >>> XYZ = np.array([19.01, 20.00, 21.78])
    >>> XYZ_w = np.array([95.05, 100.00, 108.88])
    >>> XYZ_b = np.array([95.05, 100.00, 108.88])
    >>> L_A = 318.31
    >>> surround = HUNT_VIEWING_CONDITIONS['Normal Scenes']
    >>> CCT_w = 6504.0
    >>> XYZ_to_Hunt(XYZ, XYZ_w, XYZ_b, L_A, surround, CCT_w=CCT_w)
    ... # doctest: +ELLIPSIS
    Hunt_Specification(J=30.0462678..., C=0.1210508..., h=269.2737594..., \
s=0.0199093..., Q=22.2097654..., M=0.1238964..., H=None, HC=None)
    """
    XYZ = to_domain_100(XYZ)
    XYZ_w = to_domain_100(XYZ_w)
    XYZ_b = to_domain_100(XYZ_b)
    _X, Y, _Z = tsplit(XYZ)
    _X_w, Y_w, _Z_w = tsplit(XYZ_w)
    X_b, Y_b, _Z_b = tsplit(XYZ_b)

    # Arguments handling.
    if XYZ_p is not None:
        X_p, Y_p, Z_p = tsplit(to_domain_100(XYZ_p))
    else:
        X_p = X_b
        Y_p = Y_b
        Z_p = Y_b
        usage_warning('Unspecified proximal field "XYZ_p" argument, using '
                      'background "XYZ_b" as approximation!')

    if surround.N_cb is None:
        N_cb = 0.725 * spow(Y_w / Y_b, 0.2)
        usage_warning('Unspecified "N_cb" argument, using approximation: '
                      '"{0}"'.format(N_cb))
    if surround.N_bb is None:
        N_bb = 0.725 * spow(Y_w / Y_b, 0.2)
        usage_warning('Unspecified "N_bb" argument, using approximation: '
                      '"{0}"'.format(N_bb))

    if L_AS is None and CCT_w is None:
        raise ValueError('Either the scotopic luminance "L_AS" of the '
                         'illuminant or its correlated colour temperature '
                         '"CCT_w" must be specified!')
    if L_AS is None:
        L_AS = illuminant_scotopic_luminance(L_A, CCT_w)
        usage_warning(
            'Unspecified "L_AS" argument, using approximation from "CCT": '
            '"{0}"'.format(L_AS))

    if (S is None and S_w is not None) or (S is not None and S_w is None):
        raise ValueError('Either both stimulus scotopic response "S" and '
                         'reference white scotopic response "S_w" arguments '
                         'need to be specified or none of them!')
    elif S is None and S_w is None:
        S = Y
        S_w = Y_w
        usage_warning(
            'Unspecified stimulus scotopic response "S" and reference '
            'white scotopic response "S_w" arguments, using '
            'approximation: "{0}", "{1}"'.format(S, S_w))

    if p is None:
        usage_warning(
            'Unspecified simultaneous contrast / assimilation "p" '
            'argument, model will not account for simultaneous chromatic '
            'contrast!')

    XYZ_p = tstack([X_p, Y_p, Z_p])

    # Computing luminance level adaptation factor :math:`F_L`.
    F_L = luminance_level_adaptation_factor(L_A)

    # Computing test sample chromatic adaptation.
    rgb_a = chromatic_adaptation(XYZ, XYZ_w, XYZ_b, L_A, F_L, XYZ_p, p,
                                 helson_judd_effect, discount_illuminant)

    # Computing reference white chromatic adaptation.
    rgb_aw = chromatic_adaptation(XYZ_w, XYZ_w, XYZ_b, L_A, F_L, XYZ_p, p,
                                  helson_judd_effect, discount_illuminant)

    # Computing opponent colour dimensions.
    # Computing achromatic post adaptation signals.
    A_a = achromatic_post_adaptation_signal(rgb_a)
    A_aw = achromatic_post_adaptation_signal(rgb_aw)

    # Computing colour difference signals.
    C = colour_difference_signals(rgb_a)
    C_w = colour_difference_signals(rgb_aw)

    # -------------------------------------------------------------------------
    # Computing the *hue* angle :math:`h_s`.
    # -------------------------------------------------------------------------
    h = hue_angle(C)
    # hue_w = hue_angle(C_w)
    # TODO: Implement hue quadrature & composition computation.

    # -------------------------------------------------------------------------
    # Computing the correlate of *saturation* :math:`s`.
    # -------------------------------------------------------------------------
    # Computing eccentricity factors.
    e_s = eccentricity_factor(h)

    # Computing low luminance tritanopia factor :math:`F_t`.
    F_t = low_luminance_tritanopia_factor(L_A)

    M_yb = yellowness_blueness_response(C, e_s, surround.N_c, N_cb, F_t)
    M_rg = redness_greenness_response(C, e_s, surround.N_c, N_cb)
    M_yb_w = yellowness_blueness_response(C_w, e_s, surround.N_c, N_cb, F_t)
    M_rg_w = redness_greenness_response(C_w, e_s, surround.N_c, N_cb)

    # Computing overall chromatic response.
    M = overall_chromatic_response(M_yb, M_rg)
    M_w = overall_chromatic_response(M_yb_w, M_rg_w)

    s = saturation_correlate(M, rgb_a)

    # -------------------------------------------------------------------------
    # Computing the correlate of *brightness* :math:`Q`.
    # -------------------------------------------------------------------------
    # Computing achromatic signal :math:`A`.
    A = achromatic_signal(L_AS, S, S_w, N_bb, A_a)
    A_w = achromatic_signal(L_AS, S_w, S_w, N_bb, A_aw)

    Q = brightness_correlate(A, A_w, M, surround.N_b)
    brightness_w = brightness_correlate(A_w, A_w, M_w, surround.N_b)
    # TODO: Implement whiteness-blackness :math:`Q_{wb}` computation.

    # -------------------------------------------------------------------------
    # Computing the correlate of *Lightness* :math:`J`.
    # -------------------------------------------------------------------------
    J = lightness_correlate(Y_b, Y_w, Q, brightness_w)

    # -------------------------------------------------------------------------
    # Computing the correlate of *chroma* :math:`C_{94}`.
    # -------------------------------------------------------------------------
    C_94 = chroma_correlate(s, Y_b, Y_w, Q, brightness_w)

    # -------------------------------------------------------------------------
    # Computing the correlate of *colourfulness* :math:`M_{94}`.
    # -------------------------------------------------------------------------
    M_94 = colourfulness_correlate(F_L, C_94)

    return Hunt_Specification(J, C_94, from_range_degrees(h), s, Q, M_94, None,
                              None)
Beispiel #21
0
def detect_colour_checkers_segmentation(
    image: ArrayLike,
    samples: Integer = 16,
    additional_data: Boolean = False,
    **kwargs: Any,
) -> Union[Tuple[ColourCheckerSwatchesData, ...], Tuple[NDArray, ...]]:
    """
    Detect the colour checkers swatches in given image using segmentation.

    Parameters
    ----------
    image : array_like
        Image to detect the colour checkers swatches in.
    samples : int
        Samples count to use to compute the swatches colours. The effective
        samples count is :math:`samples^2`.
    additional_data : bool, optional
        Whether to output additional data.

    Other Parameters
    ----------------
    aspect_ratio
        Colour checker aspect ratio, e.g. 1.5.
    aspect_ratio_minimum
        Minimum colour checker aspect ratio for detection: projective geometry
        might reduce the colour checker aspect ratio.
    aspect_ratio_maximum
        Maximum colour checker aspect ratio for detection: projective geometry
        might increase the colour checker aspect ratio.
    swatches
        Colour checker swatches total count.
    swatches_horizontal
        Colour checker swatches horizontal columns count.
    swatches_vertical
        Colour checker swatches vertical row count.
    swatches_count_minimum
        Minimum swatches count to be considered for the detection.
    swatches_count_maximum
        Maximum swatches count to be considered for the detection.
    swatches_chromatic_slice
        A `slice` instance defining chromatic swatches used to detect if the
        colour checker is upside down.
    swatches_achromatic_slice
        A `slice` instance defining achromatic swatches used to detect if the
        colour checker is upside down.
    swatch_minimum_area_factor
        Swatch minimum area factor :math:`f` with the minimum area :math:`m_a`
        expressed as follows: :math:`m_a = image_w * image_h / s_c / f` where
        :math:`image_w`, :math:`image_h` and :math:`s_c` are respectively the
        image width, height and the swatches count.
    swatch_contour_scale
        As the image is filtered, the swatches area will tend to shrink, the
        generated contours can thus be scaled.
    cluster_contour_scale
        As the swatches are clustered, it might be necessary to adjust the
        cluster scale so that the masks are centred better on the swatches.
    working_width
        Size the input image is resized to for detection.
    fast_non_local_means_denoising_kwargs
        Keyword arguments for :func:`cv2.fastNlMeansDenoising` definition.
    adaptive_threshold_kwargs
        Keyword arguments for :func:`cv2.adaptiveThreshold` definition.
    interpolation_method
        Interpolation method used when resizing the images, `cv2.INTER_CUBIC`
        and `cv2.INTER_LINEAR` methods are recommended.

    Returns
    -------
    :class`tuple`
        Tuple of :class:`ColourCheckerSwatchesData` class instances or
        colour checkers swatches.

    Examples
    --------
    >>> import os
    >>> from colour import read_image
    >>> from colour_checker_detection import TESTS_RESOURCES_DIRECTORY
    >>> path = os.path.join(TESTS_RESOURCES_DIRECTORY,
    ...                     'colour_checker_detection', 'detection',
    ...                     'IMG_1967.png')
    >>> image = read_image(path)
    >>> detect_colour_checkers_segmentation(image)  # doctest: +SKIP
    (array([[ 0.361626... ,  0.2241066...,  0.1187837...],
           [ 0.6280594...,  0.3950883...,  0.2434766...],
           [ 0.3326232...,  0.3156182...,  0.2891038...],
           [ 0.3048414...,  0.2738973...,  0.1069985...],
           [ 0.4174869...,  0.3199669...,  0.3081552...],
           [ 0.347873 ...,  0.4413193...,  0.2931614...],
           [ 0.6816301...,  0.3539050...,  0.0753397...],
           [ 0.2731050...,  0.2528467...,  0.3312920...],
           [ 0.6192335...,  0.2703833...,  0.1866387...],
           [ 0.3068567...,  0.1803366...,  0.1919807...],
           [ 0.4866354...,  0.4594004...,  0.0374186...],
           [ 0.6518523...,  0.4010608...,  0.0171886...],
           [ 0.1941571...,  0.1855801...,  0.2750632...],
           [ 0.2799946...,  0.3854609...,  0.1241038...],
           [ 0.5537481...,  0.2139004...,  0.1267332...],
           [ 0.7208045...,  0.5152904...,  0.0061946...],
           [ 0.5778360...,  0.2578533...,  0.2687992...],
           [ 0.1809450...,  0.3174742...,  0.2959902...],
           [ 0.7427522...,  0.6107554...,  0.4398439...],
           [ 0.6296108...,  0.5177606...,  0.3728032...],
           [ 0.5139589...,  0.4216307...,  0.2992694...],
           [ 0.3704401...,  0.3033927...,  0.2093089...],
           [ 0.2641854...,  0.2154007...,  0.1441267...],
           [ 0.1650098...,  0.1345239...,  0.0817437...]], dtype=float32),)
    """

    image = as_float_array(image, FLOAT_DTYPE_DEFAULT)[..., :3]

    settings = Structure(**SETTINGS_SEGMENTATION_COLORCHECKER_CLASSIC)
    settings.update(**kwargs)

    image = adjust_image(image, settings.working_width,
                         settings.interpolation_method)

    swatches_h, swatches_v = (
        settings.swatches_horizontal,
        settings.swatches_vertical,
    )

    colour_checkers_colours = []
    colour_checkers_data = []
    for colour_checker in extract_colour_checkers_segmentation(
            image, **settings):
        width, height = colour_checker.shape[1], colour_checker.shape[0]
        masks = swatch_masks(width, height, swatches_h, swatches_v, samples)

        swatch_colours = []
        for mask in masks:
            swatch_colours.append(
                np.mean(
                    colour_checker[mask[0]:mask[1], mask[2]:mask[3], ...],
                    axis=(0, 1),
                ))

        # The colour checker might be flipped: The mean standard deviation
        # of some expected normalised chromatic and achromatic neutral
        # swatches is computed. If the chromatic mean is lesser than the
        # achromatic mean, it means that the colour checker is flipped.
        std_means = []
        for slice_ in [
                settings.swatches_chromatic_slice,
                settings.swatches_achromatic_slice,
        ]:
            swatch_std_mean = as_float_array(swatch_colours[slice_])
            swatch_std_mean /= swatch_std_mean[..., 1][..., np.newaxis]
            std_means.append(np.mean(np.std(swatch_std_mean, 0)))
        if std_means[0] < std_means[1]:
            usage_warning("Colour checker was seemingly flipped,"
                          " reversing the samples!")
            swatch_colours = swatch_colours[::-1]

        colour_checkers_colours.append(np.asarray(swatch_colours))
        colour_checkers_data.append((colour_checker, masks))

    if additional_data:
        return tuple(
            ColourCheckerSwatchesData(tuple(colour_checkers_colours[i]), *
                                      colour_checkers_data[i]) for i,
            colour_checker_colours in enumerate(colour_checkers_colours))
    else:
        return tuple(colour_checkers_colours)
Beispiel #22
0
warning('This is a fifth warning and it has been filtered!')

filter_warnings(False, python_warnings=False)

warning('This is a sixth warning and it has not been filtered!')

filter_warnings(False, python_warnings=False)

filter_warnings(colour_warnings=False, colour_runtime_warnings=True)

runtime_warning('This is a first runtime warning and it has been filtered!')

filter_warnings(colour_warnings=False, colour_usage_warnings=True)

usage_warning('This is a first usage warning and it has been filtered!')

print('\n')

message_box('Overall "Colour" Examples')

message_box('N-Dimensional Arrays Support')

XYZ = np.array([0.20654008, 0.12197225, 0.05136952])
illuminant = np.array([0.31270, 0.32900])
message_box('Using 1d "array_like" parameter:\n' '\n{0}'.format(XYZ))
print(colour.XYZ_to_Lab(XYZ, illuminant=illuminant))

print('\n')

XYZ = np.tile(XYZ, (6, 1))
Beispiel #23
0
def colour_fidelity_index_CIE2017(
    sd_test: SpectralDistribution,
    additional_data: Boolean = False
) -> Union[Floating, ColourRendering_Specification_CIE2017]:
    """
    Return the *CIE 2017 Colour Fidelity Index* (CFI) :math:`R_f` of given
    spectral distribution.

    Parameters
    ----------
    sd_test
        Test spectral distribution.
    additional_data
        Whether to output additional data.

    Returns
    -------
    :class:`numpy.floating` or \
:class:`colour.quality.ColourRendering_Specification_CIE2017`
        *CIE 2017 Colour Fidelity Index* (CFI) :math:`R_f`.

    References
    ----------
    :cite:`CIETC1-902017`

    Examples
    --------
    >>> from colour.colorimetry import SDS_ILLUMINANTS
    >>> sd = SDS_ILLUMINANTS['FL2']
    >>> colour_fidelity_index_CIE2017(sd)  # doctest: +ELLIPSIS
    70.1208254...
    """

    if sd_test.shape.start > 380 or sd_test.shape.end < 780:
        usage_warning("Test spectral distribution shape does not span the"
                      "recommended 380-780nm range, missing values will be"
                      "filled with zeros!")

        # NOTE: "CIE 2017 Colour Fidelity Index" standard recommends filling
        # missing values with zeros.
        sd_test = cast(SpectralDistribution, sd_test.copy())
        sd_test.extrapolator = Extrapolator
        sd_test.extrapolator_kwargs = {
            "method": "constant",
            "left": 0,
            "right": 0,
        }

    if sd_test.shape.interval > 5:
        raise ValueError("Test spectral distribution interval is greater than"
                         "5nm which is the maximum recommended value "
                         'for computing the "CIE 2017 Colour Fidelity Index"!')

    shape = SpectralShape(
        SPECTRAL_SHAPE_CIE2017.start,
        SPECTRAL_SHAPE_CIE2017.end,
        sd_test.shape.interval,
    )

    CCT, D_uv = tsplit(CCT_reference_illuminant(sd_test))
    sd_reference = sd_reference_illuminant(CCT, shape)

    # NOTE: All computations except CCT calculation use the
    # "CIE 1964 10 Degree Standard Observer".
    # pylint: disable=E1102
    cmfs_10 = reshape_msds(MSDS_CMFS["CIE 1964 10 Degree Standard Observer"],
                           shape)

    # pylint: disable=E1102
    sds_tcs = reshape_msds(load_TCS_CIE2017(shape), shape)

    test_tcs_colorimetry_data = tcs_colorimetry_data(sd_test, sds_tcs, cmfs_10)
    reference_tcs_colorimetry_data = tcs_colorimetry_data(
        sd_reference, sds_tcs, cmfs_10)

    delta_E_s = np.empty(len(sds_tcs.labels))
    for i, _delta_E in enumerate(delta_E_s):
        delta_E_s[i] = euclidean_distance(
            test_tcs_colorimetry_data[i].Jpapbp,
            reference_tcs_colorimetry_data[i].Jpapbp,
        )

    R_s = as_float_array(delta_E_to_R_f(delta_E_s))
    R_f = as_float_scalar(delta_E_to_R_f(np.average(delta_E_s)))

    if additional_data:
        return ColourRendering_Specification_CIE2017(
            sd_test.name,
            sd_reference,
            R_f,
            R_s,
            CCT,
            D_uv,
            (test_tcs_colorimetry_data, reference_tcs_colorimetry_data),
            delta_E_s,
        )
    else:
        return R_f
Beispiel #24
0
def write_image(image,
                path,
                bit_depth='float32',
                method='OpenImageIO',
                **kwargs):
    """
    Writes given image at given path using given method.

    Parameters
    ----------
    image : array_like
        Image data.
    path : unicode
        Image path.
    bit_depth : unicode, optional
        **{'float32', 'uint8', 'uint16', 'float16'}**,
        Bit depth to write the image at, for the *Imageio* method, the image
        data is converted with :func:`colour.io.convert_bit_depth` definition
        prior to writing the image.
    method : unicode, optional
        **{'OpenImageIO', 'Imageio'}**,
        Write method, i.e. the image library used for writing images.

    Other Parameters
    ----------------
    attributes : array_like, optional
        {:func:`colour.io.write_image_OpenImageIO`},
        An array of :class:`colour.io.ImageAttribute_Specification` class
        instances used to set attributes of the image.

    Returns
    -------
    bool
        Definition success.

    Notes
    -----
    -   If the given method is *OpenImageIO* but the library is not available
        writing will be performed by *Imageio*.
    -   If the given method is *Imageio*, ``kwargs`` is passed directly to the
        wrapped definition.

    Examples
    --------
    Basic image writing:

    >>> import os
    >>> import colour
    >>> path = os.path.join(colour.__path__[0], 'io', 'tests', 'resources',
    ...                     'CMS_Test_Pattern.exr')
    >>> image = read_image(path)  # doctest: +SKIP
    >>> path = os.path.join(colour.__path__[0], 'io', 'tests', 'resources',
    ...                     'CMSTestPattern.tif')
    >>> write_image(image, path)  # doctest: +SKIP
    True

    Advanced image writing while setting attributes using *OpenImageIO*:

    >>> compression = ImageAttribute_Specification('Compression', 'none')
    >>> write_image(image, path, bit_depth='uint8', attributes=[compression])
    ... # doctest: +SKIP
    True
    """

    if method.lower() == 'openimageio':  # pragma: no cover
        if not is_openimageio_installed():
            usage_warning(
                '"OpenImageIO" related API features are not available, '
                'switching to "Imageio"!')
            method = 'Imageio'

    function = WRITE_IMAGE_METHODS[method]

    if method.lower() == 'openimageio':  # pragma: no cover
        kwargs = filter_kwargs(function, **kwargs)

    return function(image, path, bit_depth, **kwargs)
Beispiel #25
0
def colour_fidelity_index_CIE2017(sd_test, additional_data=False):
    """
    Returns the *CIE 2017 Colour Fidelity Index* (CFI) :math:`R_f` of given
    spectral distribution.

    Parameters
    ----------
    sd_test : SpectralDistribution
        Test spectral distribution.
    additional_data : bool, optional
        Whether to output additional data.

    Returns
    -------
    numeric or ColourRendering_Specification_CIE2017
        *CIE 2017 Colour Fidelity Index* (CFI) :math:`R_f`.

    References
    ----------
    :cite:`CIETC1-902017`

    Examples
    --------
    >>> from colour.colorimetry import SDS_ILLUMINANTS
    >>> sd = SDS_ILLUMINANTS['FL2']
    >>> colour_fidelity_index_CIE2017(sd)  # doctest: +ELLIPSIS
    70.1208254...
    """

    if sd_test.shape.start > 380 or sd_test.shape.end < 780:
        usage_warning('Test spectral distribution shape does not span the'
                      'recommended 380-780nm range, missing values will be'
                      'filled with zeros!')

        # NOTE: "CIE 2017 Colour Fidelity Index" standard recommends filling
        # missing values with zeros.
        sd_test = sd_test.copy()
        sd_test.extrapolator = Extrapolator
        sd_test.extrapolator_kwargs = {
            'method': 'constant',
            'left': 0,
            'right': 0
        }

    if sd_test.shape.interval > 5:
        raise ValueError('Test spectral distribution interval is greater than'
                         '5nm which is the maximum recommended value '
                         'for computing the "CIE 2017 Colour Fidelity Index"!')

    shape = SpectralShape(SPECTRAL_SHAPE_CIE2017.start,
                          SPECTRAL_SHAPE_CIE2017.end, sd_test.shape.interval)

    CCT, D_uv = CCT_reference_illuminant(sd_test)
    sd_reference = sd_reference_illuminant(CCT, shape)

    # NOTE: All computations except CCT calculation use the
    # "CIE 1964 10 Degree Standard Observer".
    cmfs_10 = MSDS_CMFS['CIE 1964 10 Degree Standard Observer'].copy().align(
        shape)

    sds_tcs = load_TCS_CIE2017(shape).align(shape)

    test_tcs_colorimetry_data = tcs_colorimetry_data(sd_test, sds_tcs, cmfs_10)
    reference_tcs_colorimetry_data = tcs_colorimetry_data(
        sd_reference, sds_tcs, cmfs_10)

    delta_E_s = np.empty(len(sds_tcs.labels))
    for i, _delta_E in enumerate(delta_E_s):
        delta_E_s[i] = euclidean_distance(
            test_tcs_colorimetry_data[i].Jpapbp,
            reference_tcs_colorimetry_data[i].Jpapbp)

    R_s = delta_E_to_R_f(delta_E_s)
    R_f = delta_E_to_R_f(np.average(delta_E_s))

    if additional_data:
        return ColourRendering_Specification_CIE2017(
            sd_test.name, sd_reference, R_f, R_s, CCT, D_uv,
            (test_tcs_colorimetry_data, reference_tcs_colorimetry_data),
            delta_E_s)
    else:
        return R_f
Beispiel #26
0
def ootf_reverse_BT2100_HLG(F_D, L_B=0, L_W=1000, gamma=None):
    """
    Defines *Recommendation ITU-R BT.2100* *Reference HLG* reverse opto-optical
    transfer function (OOTF / OOCF).

    Parameters
    ----------
    F_D : numeric or array_like
        :math:`F_D` is the luminance of a displayed linear component
        :math:`{R_D, G_D, or B_D}`, in :math:`cd/m^2`.
    L_B : numeric, optional
        :math:`L_B` is the display luminance for black in :math:`cd/m^2`.
    L_W : numeric, optional
        :math:`L_W` is nominal peak luminance of the display in :math:`cd/m^2`
        for achromatic pixels.
    gamma : numeric, optional
        System gamma value, 1.2 at the nominal display peak luminance of
        :math:`1000 cd/m^2`.

    Returns
    -------
    numeric or ndarray
        :math:`E` is the signal for each colour component
        :math:`{R_S, G_S, B_S}` proportional to scene linear light and scaled
        by camera exposure.

    Notes
    -----

    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``F_D``    | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    +------------+-----------------------+---------------+
    | **Range**  | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``E``      | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    References
    ----------
    :cite:`Borer2017a`, :cite:`InternationalTelecommunicationUnion2016a`

    Examples
    --------
    >>> ootf_reverse_BT2100_HLG(63.095734448019336)  # doctest: +ELLIPSIS
    0.1000000...
    """

    F_D = np.atleast_1d(to_domain_1(F_D))

    if F_D.shape[-1] != 3:
        usage_warning(
            '"Recommendation ITU-R BT.2100" "Reference HLG OOTF" uses '
            'RGB Luminance in computations and expects a vector input, thus '
            'the given input array will be stacked to compose a vector for '
            'internal computations but a single component will be output.')
        R_D = G_D = B_D = F_D
    else:
        R_D, G_D, B_D = tsplit(F_D)

    Y_D = np.sum(BT2100_HLG_WEIGHTS * tstack([R_D, G_D, B_D]), axis=-1)

    alpha = L_W - L_B
    beta = L_B

    if gamma is None:
        gamma = gamma_function_BT2100_HLG(L_W)

    R_S = np.where(
        Y_D == beta,
        0.0,
        (np.abs((Y_D - beta) / alpha)**((1 - gamma) / gamma)) * (R_D - beta) /
        alpha,
    )
    G_S = np.where(
        Y_D == beta,
        0.0,
        (np.abs((Y_D - beta) / alpha)**((1 - gamma) / gamma)) * (G_D - beta) /
        alpha,
    )
    B_S = np.where(
        Y_D == beta,
        0.0,
        (np.abs((Y_D - beta) / alpha)**((1 - gamma) / gamma)) * (B_D - beta) /
        alpha,
    )

    if F_D.shape[-1] != 3:
        return as_float(from_range_1(R_S))
    else:
        RGB_S = tstack([R_S, G_S, B_S])

        return from_range_1(RGB_S)
Beispiel #27
0
def write_LUT_SonySPI3D(LUT, path, decimals=7):
    """
    Writes given *LUT* to given *Sony* *.spi3d* *LUT* file.

    Parameters
    ----------
    LUT : LUT3D
        :class:`LUT3D` or :class:`LUTSequence` class instance to write at given
        path.
    path : unicode
        *LUT* path.
    decimals : int, optional
        Formatting decimals.

    Returns
    -------
    bool
        Definition success.

    Warning
    -------
    -   If a :class:`LUTSequence` class instance is passed as ``LUT``, the
        first *LUT* in the *LUT* sequence will be used.

    Examples
    --------
    Writing a 3D *Sony* *.spi3d* *LUT*:

    >>> LUT = LUT3D(
    ...     LUT3D.linear_table(16) ** (1 / 2.2),
    ...     'My LUT',
    ...     np.array([[0, 0, 0], [1, 1, 1]]),
    ...     comments=['A first comment.', 'A second comment.'])
    >>> write_LUT_SonySPI3D(LUT, 'My_LUT.cube')  # doctest: +SKIP
    """

    if isinstance(LUT, LUTSequence):
        LUT = LUT[0]
        usage_warning('"LUT" is a "LUTSequence" instance was passed, '
                      'using first sequence "LUT":\n'
                      '{0}'.format(LUT))

    assert not LUT.is_domain_explicit(), '"LUT" domain must be implicit!'

    assert isinstance(LUT, LUT3D), '"LUT" must be either a 3D "LUT"!'

    assert np.array_equal(LUT.domain, np.array([
        [0, 0, 0],
        [1, 1, 1],
    ])), '"LUT" domain must be [[0, 0, 0], [1, 1, 1]]!'

    def _format_array(array):
        """
        Formats given array as a *Sony* *.spi3d* data row.
        """

        return '{1:d} {2:d} {3:d} {4:0.{0}f} {5:0.{0}f} {6:0.{0}f}'.format(
            decimals, *array)

    with open(path, 'w') as spi3d_file:
        spi3d_file.write('SPILUT 1.0\n')

        spi3d_file.write('3 3\n')

        spi3d_file.write('{0} {0} {0}\n'.format(LUT.size))

        indexes = DEFAULT_INT_DTYPE(
            LUT.linear_table(LUT.size) * (LUT.size - 1)).reshape([-1, 3])
        table = LUT.table.reshape([-1, 3])

        for i, row in enumerate(indexes):
            spi3d_file.write('{0}\n'.format(
                _format_array(list(row) + list(table[i]))))

        if LUT.comments:
            for comment in LUT.comments:
                spi3d_file.write('# {0}\n'.format(comment))

    return True
Beispiel #28
0
def convert(a, source, target, **kwargs):
    """
    Converts given object :math:`a` from source colour representation to target
    colour representation using the automatic colour conversion graph.

    The conversion is performed by finding the shortest path in a
    `NetworkX <https://networkx.github.io/>`_ :class:`DiGraph` class instance.

    The conversion path adopts the **'1'** domain-range scale and the object
    :math:`a` is expected to be *soft* normalised accordingly. For example,
    *CIE XYZ* tristimulus values arguments for use with the *CAM16* colour
    appearance model should be in domain `[0, 1]` instead of the domain
    `[0, 100]` used with the **'Reference'** domain-range scale. The arguments
    are typically converted as follows:

    -   *Scalars* in domain-range `[0, 10]`, e.g *Munsell Value* are
        scaled by *10*.
    -   *Percentages* in domain-range `[0, 100]` are scaled by *100*.
    -   *Degrees* in domain-range `[0, 360]` are scaled by *360*.
    -   *Integers* in domain-range `[0, 2**n -1]` where `n` is the bit
        depth are scaled by *2**n -1*.

    See the `Domain-Range Scales <../basics.html#domain-range-scales>`_ page
    for more information.

    Parameters
    ----------
    a : array_like or numeric or SpectralDistribution
        Object :math:`a` to convert. If :math:`a` represents a reflectance,
        transmittance or absorptance value, the expectation is that it is
        viewed under *CIE Standard Illuminant D Series* *D65*. The illuminant
        can be changed on a per definition basis along the conversion path.
    source : unicode
        Source colour representation, i.e. the source node in the automatic
        colour conversion graph.
    target : unicode
        Target colour representation, i.e. the target node in the automatic
        colour conversion graph.

    Other Parameters
    ----------------
    \\**kwargs : dict, optional
        {'\\*'},
        Please refer to the documentation of the supported conversion
        definitions.

        Arguments for the conversion definitions are passed as keyword
        arguments whose names is those of the conversion definitions and values
        set as dictionaries. For example, in the conversion from spectral
        distribution to *sRGB* colourspace, passing arguments to the
        :func:`colour.sd_to_XYZ` definition is done as follows::

            convert(sd, 'Spectral Distribution', 'sRGB', sd_to_XYZ={\
'illuminant': ILLUMINANTS_SDS['FL2']})

        It is also possible to pass keyword arguments directly to the various
        conversion definitions irrespective of their name. This is
        ``dangerous`` and could cause unexpected behaviour because of
        unavoidable discrepancies with the underlying
        :func:`colour.utilities.filter_kwargs` definition between Python 2.7
        and 3.x. Using this direct keyword arguments passing mechanism might
        also ends up passing incompatible arguments to a given conversion
        definition. Consider the following conversion::

             convert(sd, 'Spectral Distribution', 'sRGB', 'illuminant': \
ILLUMINANTS_SDS['FL2'])

        Because both the :func:`colour.sd_to_XYZ` and
        :func:`colour.XYZ_to_sRGB` definitions have an *illuminant* argument,
        `ILLUMINANTS_SDS['FL2']` will be passed to both of them and will raise
        an exception in the :func:`colour.XYZ_to_sRGB` definition. This will
        be addressed in the future by either catching the exception and trying
        a new time without the keyword argument or more elegantly via type
        checking.

        With that in mind, this mechanism offers some good benefits: For
        example, it allows defining a conversion from *CIE XYZ* colourspace to
        *n* different colour models while passing an illuminant argument but
        without having to explicitly define all the explicit conversion
        definition arguments::

            a = np.array([0.20654008, 0.12197225, 0.05136952])
            illuminant = ILLUMINANTS['CIE 1931 2 Degree Standard Observer']\
['D65']
            for model in ('CIE xyY', 'CIE Lab'):
                convert(a, 'CIE XYZ', model, illuminant=illuminant)

        Instead of::

            for model in ('CIE xyY', 'CIE Lab'):
                convert(a, 'CIE XYZ', model, XYZ_to_xyY={'illuminant': \
illuminant}, XYZ_to_Lab={'illuminant': illuminant})

        Mixing both approaches is possible for the brevity benefits. It is made
        possible because the keyword arguments directly passed are filtered
        first and then the resulting dict is updated with the explicit
        conversion definition arguments::

            illuminant = ILLUMINANTS['CIE 1931 2 Degree Standard Observer']\
['D65']
             convert(sd, 'Spectral Distribution', 'sRGB', 'illuminant': \
ILLUMINANTS_SDS['FL2'], XYZ_to_sRGB={'illuminant': illuminant})

        For inspection purposes, verbose is enabled by passing arguments to the
        :func:`colour.describe_conversion_path` definition via the ``verbose``
        keyword argument as follows::

            convert(sd, 'Spectral Distribution', 'sRGB', \
verbose={'mode': 'Long'})

    Returns
    -------
    ndarray or numeric or SpectralDistribution
        Converted object :math:`a`.

    Warnings
    --------
    The domain-range scale is **'1'** and cannot be changed.

    Notes
    -----
    -   Various defaults have been systematically adopted compared to the
        low-level *Colour* API:

        -   The default illuminant for the computation is
            *CIE Standard Illuminant D Series* *D65*. It can be changed on a
            per definition basis along the conversion path.
        -   The default *RGB* colourspace is the *sRGB* colourspace. It can
            also be changed on a per definition basis along the conversion
            path.
        -   Most of the colour appearance models have defaults set according to
            *IEC 61966-2-1:1999* viewing conditions, i.e. *sRGB* 64 Lux ambiant
            illumination, 80 :math:`cd/m^2`, adapting field luminance about
            20% of a white object in the scene.

    -   The **RGB** colour representation is assumed to be linear and
        representing *scene-referred* imagery. To convert such *RGB* values to
        *output-referred* (*display-referred*) imagery, i.e. encode the *RGB*
        values using an encoding colour component transfer function
        (Encoding CCTF) / opto-electronic transfer function (OETF / OECF), the
        **Output-Referred RGB** representation must be used.

    Examples
    --------
    >>> from colour import COLOURCHECKERS_SDS
    >>> sd = COLOURCHECKERS_SDS['ColorChecker N Ohta']['dark skin']
    >>> convert(sd, 'Spectral Distribution', 'sRGB',
    ...     verbose={'mode': 'Short', 'width': 75})
    ... # doctest: +ELLIPSIS
    ===========================================================================
    *                                                                         *
    *   [ Conversion Path ]                                                   *
    *                                                                         *
    *   "sd_to_XYZ" --> "XYZ_to_sRGB"                                         *
    *                                                                         *
    ===========================================================================
    array([ 0.4567579...,  0.3098698...,  0.2486192...])
    >>> illuminant = ILLUMINANTS_SDS['FL2']
    >>> convert(sd, 'Spectral Distribution', 'sRGB',
    ...     sd_to_XYZ={'illuminant': illuminant})
    ... # doctest: +ELLIPSIS
    array([ 0.4792457...,  0.3167696...,  0.1736272...])
    >>> a = np.array([0.45675795, 0.30986982, 0.24861924])
    >>> convert(a, 'Output-Referred RGB', 'CAM16UCS')
    ... # doctest: +ELLIPSIS
    array([ 0.3999481...,  0.0920655...,  0.0812752...])
    >>> a = np.array([0.39994811, 0.09206558, 0.08127526])
    >>> convert(a, 'CAM16UCS', 'sRGB', verbose={'mode': 'Short', 'width': 75})
    ... # doctest: +ELLIPSIS
    ===========================================================================
    *                                                                         *
    *   [ Conversion Path ]                                                   *
    *                                                                         *
    *   "UCS_Luo2006_to_JMh_CIECAM02" --> "JMh_CAM16_to_CAM16" -->            *
    *   "CAM16_to_XYZ" --> "XYZ_to_sRGB"                                      *
    *                                                                         *
    ===========================================================================
    array([ 0.4567576...,  0.3098826...,  0.2486222...])
    """

    # TODO: Remove the following warning whenever the automatic colour
    # conversion graph implementation is considered stable.
    usage_warning(
        'The "Automatic Colour Conversion Graph" is a beta feature, be '
        'mindful of this when using it. Please report any unexpected '
        'behaviour and do not hesitate to ask any questions should they arise.'
        '\nThis warning can be disabled with the '
        '"colour.utilities.suppress_warnings" context manager as follows:\n'
        'with colour.utilities.suppress_warnings(colour_usage_warnings=True): '
        '\n    convert(*args, **kwargs)')

    source, target = source.lower(), target.lower()

    conversion_path = _conversion_path(source, target)

    verbose_kwargs = copy(kwargs)
    for conversion_function in conversion_path:
        conversion_function_name = _lower_order_function(
            conversion_function).__name__

        # Filtering compatible keyword arguments passed directly and
        # irrespective of any conversion function name.
        filtered_kwargs = filter_kwargs(conversion_function, **kwargs)

        # Filtering keyword arguments passed as dictionary with the
        # conversion function name.
        filtered_kwargs.update(kwargs.get(conversion_function_name, {}))

        a = conversion_function(a, **filtered_kwargs)

        if conversion_function_name in verbose_kwargs:
            verbose_kwargs[conversion_function_name]['return'] = a
        else:
            verbose_kwargs[conversion_function_name] = {'return': a}

    if 'verbose' in verbose_kwargs:
        verbose_kwargs.update(verbose_kwargs.pop('verbose'))
        describe_conversion_path(source, target, **verbose_kwargs)

    return a
Beispiel #29
0
def write_LUT_IridasCube(LUT, path, decimals=7):
    """
    Writes given *LUT* to given  *Iridas* *.cube* *LUT* file.

    Parameters
    ----------
    LUT : LUT3x1D or LUT3d or LUTSequence
        :class:`LUT3x1D`, :class:`LUT3D` or :class:`LUTSequence` class instance
        to write at given path.
    path : unicode
        *LUT* path.
    decimals : int, optional
        Formatting decimals.

    Returns
    -------
    bool
        Definition success.

    Warning
    -------
    -   If a :class:`LUTSequence` class instance is passed as ``LUT``, the
        first *LUT* in the *LUT* sequence will be used.

    References
    ----------
    :cite:`AdobeSystems2013b`

    Examples
    --------
    Writing a 3x1D *Iridas* *.cube* *LUT*:

    >>> from colour.algebra import spow
    >>> domain = np.array([[-0.1, -0.2, -0.4], [1.5, 3.0, 6.0]])
    >>> LUT = LUT3x1D(
    ...     spow(LUT3x1D.linear_table(16, domain), 1 / 2.2),
    ...     'My LUT',
    ...     domain,
    ...     comments=['A first comment.', 'A second comment.'])
    >>> write_LUT_IridasCube(LUT, 'My_LUT.cube')  # doctest: +SKIP

    Writing a 3D *Iridas* *.cube* *LUT*:

    >>> domain = np.array([[-0.1, -0.2, -0.4], [1.5, 3.0, 6.0]])
    >>> LUT = LUT3D(
    ...     spow(LUT3D.linear_table(16, domain), 1 / 2.2),
    ...     'My LUT',
    ...     np.array([[-0.1, -0.2, -0.4], [1.5, 3.0, 6.0]]),
    ...     comments=['A first comment.', 'A second comment.'])
    >>> write_LUT_IridasCube(LUT, 'My_LUT.cube')  # doctest: +SKIP
    """

    if isinstance(LUT, LUTSequence):
        LUT = LUT[0]
        usage_warning('"LUT" is a "LUTSequence" instance was passed, '
                      'using first sequence "LUT":\n'
                      '{0}'.format(LUT))

    assert not LUT.is_domain_explicit(), '"LUT" domain must be implicit!'

    if isinstance(LUT, LUT1D):
        LUT = LUT.as_LUT(LUT3x1D)

    assert (isinstance(LUT, LUT3x1D)
            or isinstance(LUT, LUT3D)), '"LUT" must be a 1D, 3x1D or 3D "LUT"!'

    is_3x1D = isinstance(LUT, LUT3x1D)

    size = LUT.size
    if is_3x1D:
        assert 2 <= size <= 65536, '"LUT" size must be in domain [2, 65536]!'
    else:
        assert 2 <= size <= 256, '"LUT" size must be in domain [2, 256]!'

    def _format_array(array):
        """
        Formats given array as an *Iridas* *.cube* data row.
        """

        return '{1:0.{0}f} {2:0.{0}f} {3:0.{0}f}'.format(decimals, *array)

    with open(path, 'w') as cube_file:
        cube_file.write('TITLE "{0}"\n'.format(LUT.name))

        if LUT.comments:
            for comment in LUT.comments:
                cube_file.write('# {0}\n'.format(comment))

        cube_file.write('{0} {1}\n'.format(
            'LUT_1D_SIZE' if is_3x1D else 'LUT_3D_SIZE', LUT.table.shape[0]))

        default_domain = np.array([[0, 0, 0], [1, 1, 1]])
        if not np.array_equal(LUT.domain, default_domain):
            cube_file.write('DOMAIN_MIN {0}\n'.format(
                _format_array(LUT.domain[0])))
            cube_file.write('DOMAIN_MAX {0}\n'.format(
                _format_array(LUT.domain[1])))

        if not is_3x1D:
            table = LUT.table.reshape([-1, 3], order='F')
        else:
            table = LUT.table

        for row in table:
            cube_file.write('{0}\n'.format(_format_array(row)))

    return True
Beispiel #30
0
def anomalous_trichromacy_cmfs_Machado2009(cmfs, d_LMS):
    """
    Shifts given *LMS* cone fundamentals colour matching functions with given
    :math:`\\Delta_{LMS}` shift amount in nanometers to simulate anomalous
    trichromacy using *Machado et al. (2009)* method.

    Parameters
    ----------
    cmfs : LMS_ConeFundamentals
        *LMS* cone fundamentals colour matching functions.
    d_LMS : array_like
        :math:`\\Delta_{LMS}` shift amount in nanometers.

    Notes
    -----
    -   Input *LMS* cone fundamentals colour matching functions interval is
        expected to be 1 nanometer, incompatible input will be interpolated
        at 1 nanometer interval.
    -   Input :math:`\\Delta_{LMS}` shift amount is in domain [0, 20].

    Returns
    -------
    LMS_ConeFundamentals
        Anomalous trichromacy *LMS* cone fundamentals colour matching
        functions.

    Warning
    -------
    *Machado et al. (2009)* simulation of tritanomaly is based on the shift
    paradigm as an approximation to the actual phenomenon and restrain the
    model from trying to model tritanopia.
    The pre-generated matrices are using a shift value in domain [5, 59]
    contrary to the domain [0, 20] used for protanomaly and deuteranomaly
    simulation.

    References
    ----------
    :cite:`Colblindorb`, :cite:`Colblindora`, :cite:`Colblindorc`,
    :cite:`Machado2009`

    Examples
    --------
    >>> from colour import LMS_CMFS
    >>> cmfs = LMS_CMFS['Stockman & Sharpe 2 Degree Cone Fundamentals']
    >>> cmfs[450]
    array([ 0.0498639,  0.0870524,  0.955393 ])
    >>> anomalous_trichromacy_cmfs_Machado2009(cmfs, np.array([15, 0, 0]))[450]
    ... # doctest: +ELLIPSIS
    array([ 0.0891288...,  0.0870524 ,  0.955393  ])
    """

    cmfs = cmfs.copy()
    if cmfs.shape.interval != 1:
        cmfs.interpolate(SpectralShape(interval=1))

    cmfs.extrapolator_args = {'method': 'Constant', 'left': 0, 'right': 0}

    L, M, _S = tsplit(cmfs.values)
    d_L, d_M, d_S = tsplit(d_LMS)

    if d_S != 0:
        usage_warning(
            '"Machado et al. (2009)" simulation of tritanomaly is based on '
            'the shift paradigm as an approximation to the actual phenomenon '
            'and restrain the model from trying to model tritanopia.\n'
            'The pre-generated matrices are using a shift value in domain '
            '[5, 59] contrary to the domain [0, 20] used for protanomaly and '
            'deuteranomaly simulation.')

    area_L = np.trapz(L, cmfs.wavelengths)
    area_M = np.trapz(M, cmfs.wavelengths)

    def alpha(x):
        """
        Computes :math:`alpha` factor.
        """

        return (20 - x) / 20

    # Corrected equations as per:
    # http://www.inf.ufrgs.br/~oliveira/pubs_files/
    # CVD_Simulation/CVD_Simulation.html#Errata
    L_a = alpha(d_L) * L + 0.96 * area_L / area_M * (1 - alpha(d_L)) * M
    M_a = alpha(d_M) * M + 1 / 0.96 * area_M / area_L * (1 - alpha(d_M)) * L
    S_a = cmfs[cmfs.wavelengths - d_S][:, 2]

    LMS_a = tstack([L_a, M_a, S_a])
    cmfs[cmfs.wavelengths] = LMS_a

    severity = '{0}, {1}, {2}'.format(d_L, d_M, d_S)
    template = '{0} - Anomalous Trichromacy ({1})'
    cmfs.name = template.format(cmfs.name, severity)
    cmfs.strict_name = template.format(cmfs.strict_name, severity)

    return cmfs
Beispiel #31
0
def write_LUT_SonySPI1D(LUT, path, decimals=7):
    """
    Writes given *LUT* to given *Sony* *.spi1d* *LUT* file.

    Parameters
    ----------
    LUT : LUT1D or LUT2d
        :class:`LUT1D`, :class:`LUT2D` or :class:`LUTSequence` class instance
        to write at given path.
    path : unicode
        *LUT* path.
    decimals : int, optional
        Formatting decimals.

    Returns
    -------
    bool
        Definition success.

    Warning
    -------
    -   If a :class:`LUTSequence` class instance is passed as ``LUT``, the
        first *LUT* in the *LUT* sequence will be used.

    Examples
    --------
    Writing a 1D *Sony* *.spi1d* *LUT*:

    >>> from colour.algebra import spow
    >>> domain = np.array([-0.1, 1.5])
    >>> LUT = LUT1D(
    ...     spow(LUT1D.linear_table(16), 1 / 2.2),
    ...     'My LUT',
    ...     domain,
    ...     comments=['A first comment.', 'A second comment.'])
    >>> write_LUT_SonySPI1D(LUT, 'My_LUT.cube')  # doctest: +SKIP

    Writing a 2D *Sony* *.spi1d* *LUT*:

    >>> domain = np.array([[-0.1, -0.1, -0.1], [1.5, 1.5, 1.5]])
    >>> LUT = LUT2D(
    ...     spow(LUT2D.linear_table(16), 1 / 2.2),
    ...     'My LUT',
    ...     domain,
    ...     comments=['A first comment.', 'A second comment.'])
    >>> write_LUT_SonySPI1D(LUT, 'My_LUT.cube')  # doctest: +SKIP
    """

    if isinstance(LUT, LUTSequence):
        LUT = LUT[0]
        usage_warning('"LUT" is a "LUTSequence" instance was passed, '
                      'using first sequence "LUT":\n'
                      '{0}'.format(LUT))

    assert not LUT.is_domain_explicit(), '"LUT" domain must be implicit!'

    assert (isinstance(LUT, LUT1D) or isinstance(
        LUT, LUT2D)), '"LUT" must be either a 1D or 2D "LUT"!'

    is_1D = isinstance(LUT, LUT1D)

    if is_1D:
        domain = LUT.domain
    else:
        domain = np.unique(LUT.domain)

        assert len(domain) == 2, 'Non-uniform "LUT" domain is unsupported!'

    def _format_array(array):
        """
        Formats given array as a *Sony* *.spi1d* data row.
        """

        return ' {1:0.{0}f} {2:0.{0}f} {3:0.{0}f}'.format(decimals, *array)

    with open(path, 'w') as spi1d_file:
        spi1d_file.write('Version 1\n')

        spi1d_file.write('From {1:0.{0}f} {2:0.{0}f}\n'.format(
            decimals, *domain))

        spi1d_file.write('Length {0}\n'.format(
            LUT.table.size if is_1D else LUT.table.shape[0]))

        spi1d_file.write('Components {0}\n'.format(1 if is_1D else 3))

        spi1d_file.write('{\n')
        for row in LUT.table:
            if is_1D:
                spi1d_file.write(' {1:0.{0}f}\n'.format(decimals, row))
            else:
                spi1d_file.write('{0}\n'.format(_format_array(row)))
        spi1d_file.write('}\n')

        if LUT.comments:
            for comment in LUT.comments:
                spi1d_file.write('# {0}\n'.format(comment))

    return True
Beispiel #32
0
def msds_cmfs_anomalous_trichromacy_Machado2009(
        cmfs: LMS_ConeFundamentals, d_LMS: ArrayLike) -> LMS_ConeFundamentals:
    """
    Shift given *LMS* cone fundamentals colour matching functions with given
    :math:`\\Delta_{LMS}` shift amount in nanometers to simulate anomalous
    trichromacy using *Machado et al. (2009)* method.

    Parameters
    ----------
    cmfs
        *LMS* cone fundamentals colour matching functions.
    d_LMS
        :math:`\\Delta_{LMS}` shift amount in nanometers.

    Notes
    -----
    -   Input *LMS* cone fundamentals colour matching functions interval is
        expected to be 1 nanometer, incompatible input will be interpolated
        at 1 nanometer interval.
    -   Input :math:`\\Delta_{LMS}` shift amount is in domain [0, 20].

    Returns
    -------
    :class:`colour.LMS_ConeFundamentals`
        Anomalous trichromacy *LMS* cone fundamentals colour matching
        functions.

    Warnings
    --------
    *Machado et al. (2009)* simulation of tritanomaly is based on the shift
    paradigm as an approximation to the actual phenomenon and restrain the
    model from trying to model tritanopia.
    The pre-generated matrices are using a shift value in domain [5, 59]
    contrary to the domain [0, 20] used for protanomaly and deuteranomaly
    simulation.

    References
    ----------
    :cite:`Colblindorb`, :cite:`Colblindora`, :cite:`Colblindorc`,
    :cite:`Machado2009`

    Examples
    --------
    >>> from colour.colorimetry import MSDS_CMFS_LMS
    >>> cmfs = MSDS_CMFS_LMS['Stockman & Sharpe 2 Degree Cone Fundamentals']
    >>> cmfs[450]
    array([ 0.0498639,  0.0870524,  0.955393 ])
    >>> msds_cmfs_anomalous_trichromacy_Machado2009(
    ...     cmfs, np.array([15, 0, 0]))[450]  # doctest: +ELLIPSIS
    array([ 0.0891288...,  0.0870524 ,  0.955393  ])
    """

    cmfs = cast(LMS_ConeFundamentals, cmfs.copy())
    if cmfs.shape.interval != 1:
        cmfs.interpolate(SpectralShape(cmfs.shape.start, cmfs.shape.end, 1))

    cmfs.extrapolator_kwargs = {"method": "Constant", "left": 0, "right": 0}

    L, M, _S = tsplit(cmfs.values)
    d_L, d_M, d_S = tsplit(d_LMS)

    if d_S != 0:
        usage_warning(
            '"Machado et al. (2009)" simulation of tritanomaly is based on '
            "the shift paradigm as an approximation to the actual phenomenon "
            "and restrain the model from trying to model tritanopia.\n"
            "The pre-generated matrices are using a shift value in domain "
            "[5, 59] contrary to the domain [0, 20] used for protanomaly and "
            "deuteranomaly simulation.")

    area_L = np.trapz(L, cmfs.wavelengths)
    area_M = np.trapz(M, cmfs.wavelengths)

    def alpha(x: NDArray) -> NDArray:
        """Compute :math:`alpha` factor."""

        return (20 - x) / 20

    # Corrected equations as per:
    # http://www.inf.ufrgs.br/~oliveira/pubs_files/
    # CVD_Simulation/CVD_Simulation.html#Errata
    L_a = alpha(d_L) * L + 0.96 * area_L / area_M * (1 - alpha(d_L)) * M
    M_a = alpha(d_M) * M + 1 / 0.96 * area_M / area_L * (1 - alpha(d_M)) * L
    S_a = as_float_array(cmfs[cmfs.wavelengths - d_S])[:, 2]

    LMS_a = tstack([L_a, M_a, S_a])
    cmfs[cmfs.wavelengths] = LMS_a

    severity = f"{d_L}, {d_M}, {d_S}"
    template = "{0} - Anomalous Trichromacy ({1})"
    cmfs.name = template.format(cmfs.name, severity)
    cmfs.strict_name = template.format(cmfs.strict_name, severity)

    return cmfs
Beispiel #33
0
def chromatic_adaptation_CIE1994(XYZ_1, xy_o1, xy_o2, Y_o, E_o1, E_o2, n=1):
    """
    Adapts given stimulus *CIE XYZ_1* tristimulus values from test viewing
    conditions to reference viewing conditions using *CIE 1994* chromatic
    adaptation model.

    Parameters
    ----------
    XYZ_1 : array_like
        *CIE XYZ* tristimulus values of test sample / stimulus.
    xy_o1 : array_like
        Chromaticity coordinates :math:`x_{o1}` and :math:`y_{o1}` of test
        illuminant and background.
    xy_o2 : array_like
        Chromaticity coordinates :math:`x_{o2}` and :math:`y_{o2}` of reference
        illuminant and background.
    Y_o : numeric
        Luminance factor :math:`Y_o` of achromatic background as percentage
        normalised to domain [18, 100] in **'Reference'** domain-range scale.
    E_o1 : numeric
        Test illuminance :math:`E_{o1}` in :math:`cd/m^2`.
    E_o2 : numeric
        Reference illuminance :math:`E_{o2}` in :math:`cd/m^2`.
    n : numeric, optional
        Noise component in fundamental primary system.

    Returns
    -------
    ndarray
        Adapted *CIE XYZ_2* tristimulus values of test stimulus.

    Notes
    -----

    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``XYZ_1``  | [0, 100]              | [0, 1]        |
    +------------+-----------------------+---------------+
    | ``Y_o``    | [0, 100]              | [0, 1]        |
    +------------+-----------------------+---------------+

    +------------+-----------------------+---------------+
    | **Range**  | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``XYZ_2``  | [0, 100]              | [0, 1]        |
    +------------+-----------------------+---------------+

    References
    ----------
    :cite:`CIETC1-321994b`

    Examples
    --------
    >>> XYZ_1 = np.array([28.00, 21.26, 5.27])
    >>> xy_o1 = np.array([0.4476, 0.4074])
    >>> xy_o2 = np.array([0.3127, 0.3290])
    >>> Y_o = 20
    >>> E_o1 = 1000
    >>> E_o2 = 1000
    >>> chromatic_adaptation_CIE1994(XYZ_1, xy_o1, xy_o2, Y_o, E_o1, E_o2)
    ... # doctest: +ELLIPSIS
    array([ 24.0337952...,  21.1562121...,  17.6430119...])
    """

    XYZ_1 = to_domain_100(XYZ_1)
    Y_o = to_domain_100(Y_o)
    E_o1 = as_float_array(E_o1)
    E_o2 = as_float_array(E_o2)

    if np.any(Y_o < 18) or np.any(Y_o > 100):
        usage_warning(('"Y_o" luminance factor must be in [18, 100] domain, '
                       'unpredictable results may occur!'))

    RGB_1 = XYZ_to_RGB_CIE1994(XYZ_1)

    xez_1 = intermediate_values(xy_o1)
    xez_2 = intermediate_values(xy_o2)

    RGB_o1 = effective_adapting_responses(xez_1, Y_o, E_o1)
    RGB_o2 = effective_adapting_responses(xez_2, Y_o, E_o2)

    bRGB_o1 = exponential_factors(RGB_o1)
    bRGB_o2 = exponential_factors(RGB_o2)

    K = K_coefficient(xez_1, xez_2, bRGB_o1, bRGB_o2, Y_o, n)

    RGB_2 = corresponding_colour(RGB_1, xez_1, xez_2, bRGB_o1, bRGB_o2, Y_o, K,
                                 n)
    XYZ_2 = RGB_to_XYZ_CIE1994(RGB_2)

    return from_range_100(XYZ_2)
Beispiel #34
0
def write_LUT_SonySPI3D(LUT, path, decimals=7):
    """
    Writes given *LUT* to given *Sony* *.spi3d* *LUT* file.

    Parameters
    ----------
    LUT : LUT3D
        :class:`LUT3D` or :class:`LUTSequence` class instance to write at given
        path.
    path : unicode
        *LUT* path.
    decimals : int, optional
        Formatting decimals.

    Returns
    -------
    bool
        Definition success.

    Warning
    -------
    -   If a :class:`LUTSequence` class instance is passed as ``LUT``, the
        first *LUT* in the *LUT* sequence will be used.

    Examples
    --------
    Writing a 3D *Sony* *.spi3d* *LUT*:

    >>> LUT = LUT3D(
    ...     LUT3D.linear_table(16) ** (1 / 2.2),
    ...     'My LUT',
    ...     np.array([[0, 0, 0], [1, 1, 1]]),
    ...     comments=['A first comment.', 'A second comment.'])
    >>> write_LUT_SonySPI3D(LUT, 'My_LUT.cube')  # doctest: +SKIP
    """

    if isinstance(LUT, LUTSequence):
        LUT = LUT[0]
        usage_warning('"LUT" is a "LUTSequence" instance was passed, '
                      'using first sequence "LUT":\n'
                      '{0}'.format(LUT))

    assert not LUT.is_domain_explicit(), '"LUT" domain must be implicit!'

    assert isinstance(LUT, LUT3D), '"LUT" must be either a 3D "LUT"!'

    assert np.array_equal(LUT.domain, np.array([
        [0, 0, 0],
        [1, 1, 1],
    ])), '"LUT" domain must be [[0, 0, 0], [1, 1, 1]]!'

    def _format_array(array):
        """
        Formats given array as a *Sony* *.spi3d* data row.
        """

        return '{1:d} {2:d} {3:d} {4:0.{0}f} {5:0.{0}f} {6:0.{0}f}'.format(
            decimals, *array)

    with open(path, 'w') as spi3d_file:
        spi3d_file.write('SPILUT 1.0\n')

        spi3d_file.write('3 3\n')

        spi3d_file.write('{0} {0} {0}\n'.format(LUT.size))

        indexes = DEFAULT_INT_DTYPE(
            LUT.linear_table(LUT.size) * (LUT.size - 1)).reshape([-1, 3])
        table = LUT.table.reshape([-1, 3])

        for i, row in enumerate(indexes):
            spi3d_file.write('{0}\n'.format(
                _format_array(list(row) + list(table[i]))))

        if LUT.comments:
            for comment in LUT.comments:
                spi3d_file.write('# {0}\n'.format(comment))

    return True
Beispiel #35
0
def chromatic_adaptation_CIE1994(XYZ_1, xy_o1, xy_o2, Y_o, E_o1, E_o2, n=1):
    """
    Adapts given stimulus *CIE XYZ_1* tristimulus values from test viewing
    conditions to reference viewing conditions using *CIE 1994* chromatic
    adaptation model.

    Parameters
    ----------
    XYZ_1 : array_like
        *CIE XYZ* tristimulus values of test sample / stimulus.
    xy_o1 : array_like
        Chromaticity coordinates :math:`x_{o1}` and :math:`y_{o1}` of test
        illuminant and background.
    xy_o2 : array_like
        Chromaticity coordinates :math:`x_{o2}` and :math:`y_{o2}` of reference
        illuminant and background.
    Y_o : numeric
        Luminance factor :math:`Y_o` of achromatic background as percentage
        normalised to domain [18, 100] in **'Reference'** domain-range scale.
    E_o1 : numeric
        Test illuminance :math:`E_{o1}` in :math:`cd/m^2`.
    E_o2 : numeric
        Reference illuminance :math:`E_{o2}` in :math:`cd/m^2`.
    n : numeric, optional
        Noise component in fundamental primary system.

    Returns
    -------
    ndarray
        Adapted *CIE XYZ_2* tristimulus values of test stimulus.

    Notes
    -----

    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``XYZ_1``  | [0, 100]              | [0, 1]        |
    +------------+-----------------------+---------------+
    | ``Y_o``    | [0, 100]              | [0, 1]        |
    +------------+-----------------------+---------------+

    +------------+-----------------------+---------------+
    | **Range**  | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``XYZ_2``  | [0, 100]              | [0, 1]        |
    +------------+-----------------------+---------------+

    References
    ----------
    :cite:`CIETC1-321994b`

    Examples
    --------
    >>> XYZ_1 = np.array([28.00, 21.26, 5.27])
    >>> xy_o1 = np.array([0.4476, 0.4074])
    >>> xy_o2 = np.array([0.3127, 0.3290])
    >>> Y_o = 20
    >>> E_o1 = 1000
    >>> E_o2 = 1000
    >>> chromatic_adaptation_CIE1994(XYZ_1, xy_o1, xy_o2, Y_o, E_o1, E_o2)
    ... # doctest: +ELLIPSIS
    array([ 24.0337952...,  21.1562121...,  17.6430119...])
    """

    XYZ_1 = to_domain_100(XYZ_1)
    Y_o = to_domain_100(Y_o)
    E_o1 = as_float_array(E_o1)
    E_o2 = as_float_array(E_o2)

    if np.any(Y_o < 18) or np.any(Y_o > 100):
        usage_warning(('"Y_o" luminance factor must be in [18, 100] domain, '
                       'unpredictable results may occur!'))

    RGB_1 = XYZ_to_RGB_CIE1994(XYZ_1)

    xez_1 = intermediate_values(xy_o1)
    xez_2 = intermediate_values(xy_o2)

    RGB_o1 = effective_adapting_responses(xez_1, Y_o, E_o1)
    RGB_o2 = effective_adapting_responses(xez_2, Y_o, E_o2)

    bRGB_o1 = exponential_factors(RGB_o1)
    bRGB_o2 = exponential_factors(RGB_o2)

    K = K_coefficient(xez_1, xez_2, bRGB_o1, bRGB_o2, Y_o, n)

    RGB_2 = corresponding_colour(RGB_1, xez_1, xez_2, bRGB_o1, bRGB_o2, Y_o, K,
                                 n)
    XYZ_2 = RGB_to_XYZ_CIE1994(RGB_2)

    return from_range_100(XYZ_2)
Beispiel #36
0
def ootf_reverse_BT2100_HLG(F_D, L_B=0, L_W=1000, gamma=None):
    """
    Defines *Recommendation ITU-R BT.2100* *Reference HLG* reverse opto-optical
    transfer function (OOTF / OOCF).

    Parameters
    ----------
    F_D : numeric or array_like
        :math:`F_D` is the luminance of a displayed linear component
        :math:`{R_D, G_D, or B_D}`, in :math:`cd/m^2`.
    L_B : numeric, optional
        :math:`L_B` is the display luminance for black in :math:`cd/m^2`.
    L_W : numeric, optional
        :math:`L_W` is nominal peak luminance of the display in :math:`cd/m^2`
        for achromatic pixels.
    gamma : numeric, optional
        System gamma value, 1.2 at the nominal display peak luminance of
        :math:`1000 cd/m^2`.

    Returns
    -------
    numeric or ndarray
        :math:`E` is the signal for each colour component
        :math:`{R_S, G_S, B_S}` proportional to scene linear light and scaled
        by camera exposure.

    Notes
    -----

    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``F_D``    | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    +------------+-----------------------+---------------+
    | **Range**  | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``E``      | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    References
    ----------
    :cite:`Borer2017a`, :cite:`InternationalTelecommunicationUnion2016a`

    Examples
    --------
    >>> ootf_reverse_BT2100_HLG(63.095734448019336)  # doctest: +ELLIPSIS
    0.1000000...
    """

    F_D = np.atleast_1d(to_domain_1(F_D))

    if F_D.shape[-1] != 3:
        usage_warning(
            '"Recommendation ITU-R BT.2100" "Reference HLG OOTF" uses '
            'RGB Luminance in computations and expects a vector input, thus '
            'the given input array will be stacked to compose a vector for '
            'internal computations but a single component will be output.')
        R_D = G_D = B_D = F_D
    else:
        R_D, G_D, B_D = tsplit(F_D)

    Y_D = np.sum(BT2100_HLG_WEIGHTS * tstack([R_D, G_D, B_D]), axis=-1)

    alpha = L_W - L_B
    beta = L_B

    if gamma is None:
        gamma = gamma_function_BT2100_HLG(L_W)

    R_S = np.where(
        Y_D == beta,
        0.0,
        (np.abs((Y_D - beta) / alpha) **
         ((1 - gamma) / gamma)) * (R_D - beta) / alpha,
    )
    G_S = np.where(
        Y_D == beta,
        0.0,
        (np.abs((Y_D - beta) / alpha) **
         ((1 - gamma) / gamma)) * (G_D - beta) / alpha,
    )
    B_S = np.where(
        Y_D == beta,
        0.0,
        (np.abs((Y_D - beta) / alpha) **
         ((1 - gamma) / gamma)) * (B_D - beta) / alpha,
    )

    if F_D.shape[-1] != 3:
        return as_float(from_range_1(R_S))
    else:
        RGB_S = tstack([R_S, G_S, B_S])

        return from_range_1(RGB_S)
Beispiel #37
0
def CCT_to_xy_Hernandez1999(CCT, optimisation_kwargs=None, **kwargs):
    """
    Returns the *CIE xy* chromaticity coordinates from given correlated colour
    temperature :math:`T_{cp}` using *Hernandez-Andres et al. (1999)* method.

    Parameters
    ----------
    CCT : numeric or array_like
        Correlated colour temperature :math:`T_{cp}`.
    optimisation_kwargs : dict_like, optional
        Parameters for :func:`scipy.optimize.minimize` definition.

    Other Parameters
    ----------------
    \\**kwargs : dict, optional
        Keywords arguments for deprecation management.

    Returns
    -------
    ndarray
        *CIE xy* chromaticity coordinates.

    Warnings
    --------
    *Hernandez-Andres et al. (1999)* method for computing *CIE xy* chromaticity
    coordinates from given correlated colour temperature is not a bijective
    function and might produce unexpected results. It is given for consistency
    with other correlated colour temperature computation methods but should be
    avoided for practical applications. The current implementation relies on
    optimization using :func:`scipy.optimize.minimize` definition and thus has
    reduced precision and poor performance.

    References
    ----------
    :cite:`Hernandez-Andres1999a`

    Examples
    --------
    >>> CCT_to_xy_Hernandez1999(6500.7420431786531)  # doctest: +ELLIPSIS
    array([ 0.3127...,  0.329...])
    """

    optimisation_kwargs = handle_arguments_deprecation(
        {
            'ArgumentRenamed':
            [['optimisation_parameters', 'optimisation_kwargs']],
        }, **kwargs).get('optimisation_kwargs', optimisation_kwargs)

    usage_warning('"Hernandez-Andres et al. (1999)" method for computing '
                  '"CIE xy" chromaticity coordinates from given correlated '
                  'colour temperature is not a bijective function and and'
                  'might produce unexpected results. It is given for '
                  'consistency with other correlated colour temperature '
                  'computation methods but should be avoided for practical '
                  'applications.')

    CCT = as_float_array(CCT)
    shape = list(CCT.shape)
    CCT = np.atleast_1d(CCT.reshape([-1, 1]))

    def objective_function(xy, CCT):
        """
        Objective function.
        """

        objective = np.linalg.norm(xy_to_CCT_Hernandez1999(xy) - CCT)

        return objective

    optimisation_settings = {
        'method': 'Nelder-Mead',
        'options': {
            'fatol': 1e-10,
        },
    }
    if optimisation_kwargs is not None:
        optimisation_settings.update(optimisation_kwargs)

    CCT = as_float_array([
        minimize(
            objective_function,
            x0=CCS_ILLUMINANTS['CIE 1931 2 Degree Standard Observer']['D65'],
            args=(CCT_i, ),
            **optimisation_settings).x for CCT_i in CCT
    ])

    return as_numeric(CCT.reshape(shape + [2]))
Beispiel #38
0
def read_image(path, bit_depth='float32', method='OpenImageIO', **kwargs):
    """
    Reads the image at given path using given method.

    Parameters
    ----------
    path : unicode
        Image path.
    bit_depth : unicode, optional
        **{'float32', 'uint8', 'uint16', 'float16'}**,
        Returned image bit depth, for the *Imageio* method, the image data is
        converted with :func:`colour.io.convert_bit_depth` definition after
        reading the image, for the *OpenImageIO* method, the bit depth
        conversion behaviour is driven directly by the library, this definition
        only converts to the relevant data type after reading.
    method : unicode, optional
        **{'OpenImageIO', 'Imageio'}**,
        Read method, i.e. the image library used for reading images.

    Other Parameters
    ----------------
    attributes : bool, optional
        {:func:`colour.io.read_image_OpenImageIO`},
        Whether to return the image attributes.

    Returns
    -------
    ndarray
        Image as a ndarray.

    Notes
    -----
    -   If the given method is *OpenImageIO* but the library is not available
        writing will be performed by *Imageio*.
    -   If the given method is *Imageio*, ``kwargs`` is passed directly to the
        wrapped definition.
    -   For convenience, single channel images are squeezed to 2d arrays.

    Examples
    --------
    >>> import os
    >>> import colour
    >>> path = os.path.join(colour.__path__[0], 'io', 'tests', 'resources',
    ...                     'CMS_Test_Pattern.exr')
    >>> image = read_image(path)
    >>> image.shape  # doctest: +SKIP
    (1267, 1274, 3)
    >>> image.dtype
    dtype('float32')
    """

    if method.lower() == 'openimageio':  # pragma: no cover
        if not is_openimageio_installed():
            usage_warning(
                '"OpenImageIO" related API features are not available, '
                'switching to "Imageio"!')
            method = 'Imageio'

    function = READ_IMAGE_METHODS[method]

    if method.lower() == 'openimageio':  # pragma: no cover
        kwargs = filter_kwargs(function, **kwargs)

    return function(path, bit_depth, **kwargs)
Beispiel #39
0
def cvd_matrix_Machado2009(deficiency, severity):
    """
    Computes *Machado et al. (2009)* *CVD* matrix for given deficiency and
    severity using the pre-computed matrices dataset.

    Parameters
    ----------
    deficiency : unicode
        {'Protanomaly', 'Deuteranomaly', 'Tritanomaly'}
        Colour blindness / vision deficiency types :
        - *Protanomaly* : defective long-wavelength cones (L-cones). The
        complete absence of L-cones is known as *Protanopia* or
        *red-dichromacy*.
        - *Deuteranomaly* : defective medium-wavelength cones (M-cones) with
        peak of sensitivity moved towards the red sensitive cones. The complete
        absence of M-cones is known as *Deuteranopia*.
        - *Tritanomaly* : defective short-wavelength cones (S-cones), an
        alleviated form of blue-yellow color blindness. The complete absence of
        S-cones is known as *Tritanopia*.
    severity : numeric
        Severity of the colour vision deficiency in domain [0, 1].

    Returns
    -------
    ndarray
        *CVD* matrix.

    References
    ----------
    :cite:`Colblindorb`, :cite:`Colblindora`, :cite:`Colblindorc`,
    :cite:`Machado2009`

    Examples
    --------
    >>> cvd_matrix_Machado2009('Protanomaly', 0.15)  # doctest: +ELLIPSIS
    array([[ 0.7869875...,  0.2694875..., -0.0564735...],
           [ 0.0431695...,  0.933774 ...,  0.023058 ...],
           [-0.004238 ..., -0.0024515...,  1.0066895...]])
    """

    if deficiency.lower() == 'tritanomaly':
        usage_warning(
            '"Machado et al. (2009)" simulation of tritanomaly is based on '
            'the shift paradigm as an approximation to the actual phenomenon '
            'and restrain the model from trying to model tritanopia.\n'
            'The pre-generated matrices are using a shift value in domain '
            '[5, 59] contrary to the domain [0, 20] used for protanomaly and '
            'deuteranomaly simulation.')

    matrices = CVD_MATRICES_MACHADO2010[deficiency]
    samples = np.array(sorted(matrices.keys()))
    index = min(np.searchsorted(samples, severity), len(samples) - 1)

    a = samples[index]
    b = samples[min(index + 1, len(samples) - 1)]

    m1, m2 = matrices[a], matrices[b]

    if a == b:
        # 1.0 severity CVD matrix, returning directly.
        return m1
    else:
        return m1 + (severity - a) * ((m2 - m1) / (b - a))
Beispiel #40
0
def anomalous_trichromacy_cmfs_Machado2009(cmfs, d_LMS):
    """
    Shifts given *LMS* cone fundamentals colour matching functions with given
    :math:`\\Delta_{LMS}` shift amount in nanometers to simulate anomalous
    trichromacy using *Machado et al. (2009)* method.

    Parameters
    ----------
    cmfs : LMS_ConeFundamentals
        *LMS* cone fundamentals colour matching functions.
    d_LMS : array_like
        :math:`\\Delta_{LMS}` shift amount in nanometers.

    Notes
    -----
    -   Input *LMS* cone fundamentals colour matching functions interval is
        expected to be 1 nanometer, incompatible input will be interpolated
        at 1 nanometer interval.
    -   Input :math:`\\Delta_{LMS}` shift amount is in domain [0, 20].

    Returns
    -------
    LMS_ConeFundamentals
        Anomalous trichromacy *LMS* cone fundamentals colour matching
        functions.

    Warning
    -------
    *Machado et al. (2009)* simulation of tritanomaly is based on the shift
    paradigm as an approximation to the actual phenomenon and restrain the
    model from trying to model tritanopia.
    The pre-generated matrices are using a shift value in domain [5, 59]
    contrary to the domain [0, 20] used for protanomaly and deuteranomaly
    simulation.

    References
    ----------
    :cite:`Colblindorb`, :cite:`Colblindora`, :cite:`Colblindorc`,
    :cite:`Machado2009`

    Examples
    --------
    >>> from colour import LMS_CMFS
    >>> cmfs = LMS_CMFS['Stockman & Sharpe 2 Degree Cone Fundamentals']
    >>> cmfs[450]
    array([ 0.0498639,  0.0870524,  0.955393 ])
    >>> anomalous_trichromacy_cmfs_Machado2009(cmfs, np.array([15, 0, 0]))[450]
    ... # doctest: +ELLIPSIS
    array([ 0.0891288...,  0.0870524 ,  0.955393  ])
    """

    cmfs = cmfs.copy()
    if cmfs.shape.interval != 1:
        cmfs.interpolate(SpectralShape(interval=1))

    cmfs.extrapolator_args = {'method': 'Constant', 'left': 0, 'right': 0}

    L, M, _S = tsplit(cmfs.values)
    d_L, d_M, d_S = tsplit(d_LMS)

    if d_S != 0:
        usage_warning(
            '"Machado et al. (2009)" simulation of tritanomaly is based on '
            'the shift paradigm as an approximation to the actual phenomenon '
            'and restrain the model from trying to model tritanopia.\n'
            'The pre-generated matrices are using a shift value in domain '
            '[5, 59] contrary to the domain [0, 20] used for protanomaly and '
            'deuteranomaly simulation.')

    area_L = np.trapz(L, cmfs.wavelengths)
    area_M = np.trapz(M, cmfs.wavelengths)

    def alpha(x):
        """
        Computes :math:`alpha` factor.
        """

        return (20 - x) / 20

    # Corrected equations as per:
    # http://www.inf.ufrgs.br/~oliveira/pubs_files/
    # CVD_Simulation/CVD_Simulation.html#Errata
    L_a = alpha(d_L) * L + 0.96 * area_L / area_M * (1 - alpha(d_L)) * M
    M_a = alpha(d_M) * M + 1 / 0.96 * area_M / area_L * (1 - alpha(d_M)) * L
    S_a = cmfs[cmfs.wavelengths - d_S][:, 2]

    LMS_a = tstack([L_a, M_a, S_a])
    cmfs[cmfs.wavelengths] = LMS_a

    severity = '{0}, {1}, {2}'.format(d_L, d_M, d_S)
    template = '{0} - Anomalous Trichromacy ({1})'
    cmfs.name = template.format(cmfs.name, severity)
    cmfs.strict_name = template.format(cmfs.strict_name, severity)

    return cmfs
Beispiel #41
0
def CCT_to_xy_McCamy1992(
        CCT: FloatingOrArrayLike,
        optimisation_kwargs: Optional[Dict] = None) -> NDArray:
    """
    Return the *CIE xy* chromaticity coordinates from given correlated colour
    temperature :math:`T_{cp}` using *McCamy (1992)* method.

    Parameters
    ----------
    CCT
        Correlated colour temperature :math:`T_{cp}`.
    optimisation_kwargs
        Parameters for :func:`scipy.optimize.minimize` definition.

    Returns
    -------
    :class:`numpy.ndarray`
        *CIE xy* chromaticity coordinates.

    Warnings
    --------
    *McCamy (1992)* method for computing *CIE xy* chromaticity coordinates
    from given correlated colour temperature is not a bijective function and
    might produce unexpected results. It is given for consistency with other
    correlated colour temperature computation methods but should be avoided
    for practical applications. The current implementation relies on
    optimization using :func:`scipy.optimize.minimize` definition and thus has
    reduced precision and poor performance.

    References
    ----------
    :cite:`Wikipedia2001`

    Examples
    --------
    >>> CCT_to_xy_McCamy1992(6505.0805913074782)  # doctest: +ELLIPSIS
    array([ 0.3127...,  0.329...])
    """

    usage_warning('"McCamy (1992)" method for computing "CIE xy" '
                  "chromaticity coordinates from given correlated colour "
                  "temperature is not a bijective function and might produce "
                  "unexpected results. It is given for consistency with other "
                  "correlated colour temperature computation methods but "
                  "should be avoided for practical applications.")

    CCT = as_float_array(CCT)
    shape = list(CCT.shape)
    CCT = np.atleast_1d(CCT.reshape([-1, 1]))

    def objective_function(xy: ArrayLike,
                           CCT: FloatingOrArrayLike) -> FloatingOrNDArray:
        """Objective function."""

        objective = np.linalg.norm(
            xy_to_CCT_McCamy1992(xy) - as_float_array(CCT))

        return as_float(objective)

    optimisation_settings = {
        "method": "Nelder-Mead",
        "options": {
            "fatol": 1e-10,
        },
    }
    if optimisation_kwargs is not None:
        optimisation_settings.update(optimisation_kwargs)

    xy = as_float_array([
        minimize(
            objective_function,
            x0=CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"]["D65"],
            args=(CCT_i, ),
            **optimisation_settings,
        ).x for CCT_i in as_float_array(CCT)
    ])

    return np.reshape(xy, (shape + [2]))
Beispiel #42
0
def cvd_matrix_Machado2009(deficiency, severity):
    """
    Computes *Machado et al. (2009)* *CVD* matrix for given deficiency and
    severity using the pre-computed matrices dataset.

    Parameters
    ----------
    deficiency : unicode
        {'Protanomaly', 'Deuteranomaly', 'Tritanomaly'}
        Colour blindness / vision deficiency types :
        - *Protanomaly* : defective long-wavelength cones (L-cones). The
        complete absence of L-cones is known as *Protanopia* or
        *red-dichromacy*.
        - *Deuteranomaly* : defective medium-wavelength cones (M-cones) with
        peak of sensitivity moved towards the red sensitive cones. The complete
        absence of M-cones is known as *Deuteranopia*.
        - *Tritanomaly* : defective short-wavelength cones (S-cones), an
        alleviated form of blue-yellow color blindness. The complete absence of
        S-cones is known as *Tritanopia*.
    severity : numeric
        Severity of the colour vision deficiency in domain [0, 1].

    Returns
    -------
    ndarray
        *CVD* matrix.

    References
    ----------
    :cite:`Colblindorb`, :cite:`Colblindora`, :cite:`Colblindorc`,
    :cite:`Machado2009`

    Examples
    --------
    >>> cvd_matrix_Machado2009('Protanomaly', 0.15)  # doctest: +ELLIPSIS
    array([[ 0.7869875...,  0.2694875..., -0.0564735...],
           [ 0.0431695...,  0.933774 ...,  0.023058 ...],
           [-0.004238 ..., -0.0024515...,  1.0066895...]])
    """

    if deficiency.lower() == 'tritanomaly':
        usage_warning(
            '"Machado et al. (2009)" simulation of tritanomaly is based on '
            'the shift paradigm as an approximation to the actual phenomenon '
            'and restrain the model from trying to model tritanopia.\n'
            'The pre-generated matrices are using a shift value in domain '
            '[5, 59] contrary to the domain [0, 20] used for protanomaly and '
            'deuteranomaly simulation.')

    matrices = CVD_MATRICES_MACHADO2010[deficiency]
    samples = np.array(sorted(matrices.keys()))
    index = min(np.searchsorted(samples, severity), len(samples) - 1)

    a = samples[index]
    b = samples[min(index + 1, len(samples) - 1)]

    m1, m2 = matrices[a], matrices[b]

    if a == b:
        # 1.0 severity CVD matrix, returning directly.
        return m1
    else:
        return m1 + (severity - a) * ((m2 - m1) / (b - a))