Source code for colour.blindness.machado2009

# -*- coding: utf-8 -*-
"""
Simulation of CVD - Machado, Oliveira and Fernandes (2009)
==========================================================

Defines *Machado et al. (2009)* objects for simulation of colour vision
deficiency:

-   :func:`colour.anomalous_trichromacy_cmfs_Machado2009`
-   :func:`colour.anomalous_trichromacy_matrix_Machado2009`
-   :func:`colour.cvd_matrix_Machado2009`

See Also
--------
`Machado et al. (2009) - CVD IPython Notebook
<http://nbviewer.ipython.org/github/colour-science/colour-ipython/\
blob/master/notebooks/cvd/Machado2009.ipynb>`_

References
----------
-   :cite:`Colblindorb` : Colblindor. (n.d.). Protanopia - Red-Green Color
    Blindness. Retrieved July 4, 2015, from http://www.color-blindness.com/\
protanopia-red-green-color-blindness/
-   :cite:`Colblindora` : Colblindor. (n.d.). Deuteranopia - Red-Green Color
    Blindness. Retrieved July 4, 2015, from http://www.color-blindness.com/\
deuteranopia-red-green-color-blindness/
-   :cite:`Colblindorc` : Colblindor. (n.d.). Tritanopia - Blue-Yellow Color
    Blindness. Retrieved July 4, 2015, from http://www.color-blindness.com/\
tritanopia-blue-yellow-color-blindness/
-   :cite:`Machado2009` : Machado, G. M., Oliveira, M. M., & Fernandes, L.
    (2009). A Physiologically-based Model for Simulation of Color Vision
    Deficiency. IEEE Transactions on Visualization and Computer Graphics,
    15(6), 1291-1298. doi:10.1109/TVCG.2009.113
"""

from __future__ import division, unicode_literals

import numpy as np

from colour.blindness import CVD_MATRICES_MACHADO2010
from colour.colorimetry import SpectralShape
from colour.utilities import (dot_matrix, dot_vector, tsplit, tstack,
                              usage_warning)

__author__ = 'Colour Developers'
__copyright__ = 'Copyright (C) 2013-2019 - Colour Developers'
__license__ = 'New BSD License - http://opensource.org/licenses/BSD-3-Clause'
__maintainer__ = 'Colour Developers'
__email__ = 'colour-science@googlegroups.com'
__status__ = 'Production'

__all__ = [
    'LMS_TO_WSYBRG_MATRIX', 'RGB_to_WSYBRG_matrix',
    'anomalous_trichromacy_cmfs_Machado2009',
    'anomalous_trichromacy_matrix_Machado2009', 'cvd_matrix_Machado2009'
]

LMS_TO_WSYBRG_MATRIX = np.array([
    [0.600, 0.400, 0.000],
    [0.240, 0.105, -0.700],
    [1.200, -1.600, 0.400],
])
"""
Ingling and Tsou (1977) matrix converting from cones responses to
opponent-colour space.

LMS_TO_WSYBRG_MATRIX : array_like, (3, 3)
"""


def RGB_to_WSYBRG_matrix(cmfs, primaries):
    """
    Computes the matrix transforming from *RGB* colourspace to opponent-colour
    space using *Machado et al. (2009)* method.

    Parameters
    ----------
    cmfs : LMS_ConeFundamentals
        *LMS* cone fundamentals colour matching functions.
    primaries : RGB_DisplayPrimaries
        *RGB* display primaries tri-spectral distributions.

    Returns
    -------
    ndarray
        Matrix transforming from *RGB* colourspace to opponent-colour space.

    Examples
    --------
    >>> from colour import DISPLAYS_RGB_PRIMARIES, LMS_CMFS
    >>> cmfs = LMS_CMFS['Stockman & Sharpe 2 Degree Cone Fundamentals']
    >>> d_LMS = np.array([15, 0, 0])
    >>> primaries = DISPLAYS_RGB_PRIMARIES['Apple Studio Display']
    >>> RGB_to_WSYBRG_matrix(  # doctest: +ELLIPSIS
    ...     cmfs, primaries)
    array([[  0.2126535...,   0.6704626...,   0.1168838...],
           [  4.7095295...,  12.4862869..., -16.1958165...],
           [-11.1518474...,  15.2534789...,  -3.1016315...]])
    """

    wavelengths = cmfs.wavelengths
    WSYBRG = dot_vector(LMS_TO_WSYBRG_MATRIX, cmfs.values)
    WS, YB, RG = tsplit(WSYBRG)

    extrapolator_args = {'method': 'Constant', 'left': 0, 'right': 0}
    primaries = primaries.copy().align(
        cmfs.shape, extrapolator_args=extrapolator_args)

    R, G, B = tsplit(primaries.values)

    WS_R = np.trapz(R * WS, wavelengths)
    WS_G = np.trapz(G * WS, wavelengths)
    WS_B = np.trapz(B * WS, wavelengths)

    YB_R = np.trapz(R * YB, wavelengths)
    YB_G = np.trapz(G * YB, wavelengths)
    YB_B = np.trapz(B * YB, wavelengths)

    RG_R = np.trapz(R * RG, wavelengths)
    RG_G = np.trapz(G * RG, wavelengths)
    RG_B = np.trapz(B * RG, wavelengths)

    M_G = np.array([
        [WS_R, WS_G, WS_B],
        [YB_R, YB_G, YB_B],
        [RG_R, RG_G, RG_B],
    ])

    PWS = 1 / (WS_R + WS_G + WS_B)
    PYB = 1 / (YB_R + YB_G + YB_B)
    PRG = 1 / (RG_R + RG_G + RG_B)

    M_G *= np.array([PWS, PYB, PRG])[:, np.newaxis]

    return M_G


[docs]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
[docs]def anomalous_trichromacy_matrix_Machado2009(cmfs, primaries, d_LMS): """ Computes *Machado et al. (2009)* *CVD* matrix for given *LMS* cone fundamentals colour matching functions and display primaries tri-spectral distributions with given :math:`\\Delta_{LMS}` shift amount in nanometers to simulate anomalous trichromacy. Parameters ---------- cmfs : LMS_ConeFundamentals *LMS* cone fundamentals colour matching functions. primaries : RGB_DisplayPrimaries *RGB* display primaries tri-spectral distributions. 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 ------- ndarray Anomalous trichromacy matrix. References ---------- :cite:`Colblindorb`, :cite:`Colblindora`, :cite:`Colblindorc`, :cite:`Machado2009` Examples -------- >>> from colour import DISPLAYS_RGB_PRIMARIES, LMS_CMFS >>> cmfs = LMS_CMFS['Stockman & Sharpe 2 Degree Cone Fundamentals'] >>> d_LMS = np.array([15, 0, 0]) >>> primaries = DISPLAYS_RGB_PRIMARIES['Apple Studio Display'] >>> anomalous_trichromacy_matrix_Machado2009(cmfs, primaries, d_LMS) ... # doctest: +ELLIPSIS array([[-0.2777465..., 2.6515008..., -1.3737543...], [ 0.2718936..., 0.2004786..., 0.5276276...], [ 0.0064404..., 0.2592157..., 0.7343437...]]) """ if cmfs.shape.interval != 1: cmfs = cmfs.copy().interpolate(SpectralShape(interval=1)) M_n = RGB_to_WSYBRG_matrix(cmfs, primaries) cmfs_a = anomalous_trichromacy_cmfs_Machado2009(cmfs, d_LMS) M_a = RGB_to_WSYBRG_matrix(cmfs_a, primaries) return dot_matrix(np.linalg.inv(M_n), M_a)
[docs]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))