Skip to content

Commit

Permalink
Implement "CIE 1994" chromatic adaptation model.
Browse files Browse the repository at this point in the history
References #120.
  • Loading branch information
KelSolaar committed Sep 12, 2014
1 parent 7e1be23 commit 4ec13c1
Show file tree
Hide file tree
Showing 4 changed files with 329 additions and 166 deletions.
4 changes: 2 additions & 2 deletions colour/adaptation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

from .cat import CHROMATIC_ADAPTATION_METHODS
from .cat import chromatic_adaptation_matrix, chromatic_adaptation
from .cie1994 import cie1994
from .cie1994 import chromatic_adaptation_cie1994

__all__ = ['CHROMATIC_ADAPTATION_METHODS']
__all__ += ['chromatic_adaptation_matrix', 'chromatic_adaptation']
__all__ += ['cie1994']
__all__ += ['chromatic_adaptation_cie1994']
268 changes: 236 additions & 32 deletions colour/adaptation/cie1994.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
Defines *CIE 1994* chromatic adaptation model objects:
- :func:`chromatic_adaptation_cie1994`
See Also
--------
Expand All @@ -15,26 +16,41 @@
References
----------
.. [1] `CIE 109-1994 A Method of Predicting Corresponding Colours under
Different Chromatic and Illuminance Adaptations
<http://div1.cie.co.at/?i_ca_id=551&pubid=34>`_,
ISBN-13: 978-3-900734-51-0
"""

from __future__ import division, unicode_literals

import math
import numpy as np

from colour.adaptation.cat import VON_KRIES_CAT
from colour.utilities import warning

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

__all__ = []

CIE1994_XYZ_TO_RGB_MATRIX = np.array(
[[0.40024, 0.70760, -0.08081],
[-0.22630, 1.16532, 0.04570],
[0.00000, 0.00000, 0.91822]])
__all__ = ['CIE1994_XYZ_TO_RGB_MATRIX',
'CIE1994_RGB_TO_XYZ_MATRIX',
'chromatic_adaptation_cie1994',
'XYZ_to_RGB_cie1994',
'RGB_to_XYZ_cie1994',
'intermediate_values',
'effective_adapting_responses',
'beta_1',
'beta_2',
'exponential_factors',
'K_coefficient',
'corresponding_colour']

CIE1994_XYZ_TO_RGB_MATRIX = VON_KRIES_CAT
"""
*CIE 1994* colour appearance model *CIE XYZ* colourspace to cone
responses matrix.
Expand All @@ -43,15 +59,77 @@
"""

CIE1994_RGB_TO_XYZ_MATRIX = np.linalg.inv(CIE1994_XYZ_TO_RGB_MATRIX)
"""
*CIE 1994* colour appearance model cone responses to *CIE XYZ* colourspace
matrix.
CIE1994_RGB_TO_XYZ_MATRIX : array_like, (3, 3)
"""


def chromatic_adaptation_cie1994(XYZ_1,
xy_o1,
xy_o2,
Y_o,
E_o1,
E_o2,
n=1):
"""
Adapts given *CIE XYZ_1* colourspace stimulus from test viewing conditions
to reference viewing conditions using *CIE 1994* chromatic adaptation
model.
Parameters
----------
XYZ : array_like, (3,)
*CIE XYZ_1* colourspace matrix of test sample / stimulus in domain
[0, 100].
xy_o1 : array_like, (2,)
Chromaticity coordinates :math:`x_{o1}` and :math:`y_{o1}` of test
illuminant and background.
xy_o2 : array_like, (2,)
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 in
domain [18, 100].
E_o1 : numeric
Test illuminance :math:`E_{o1}` in lux.
E_o2 : numeric
Reference illuminance :math:`E_{o2}` in lux.
n : numeric, optional
Noise component in fundamental primary system.
Returns
-------
ndarray, (3,)
Adapted *CIE XYZ_2* colourspace test stimulus.
Warning
-------
The input domain of that definition is non standard!
Notes
-----
- Input *CIE XYZ_1* colourspace matrix is in domain [0, 100].
- Output *CIE XYZ_2* colourspace matrix is in domain [0, 100].
Examples
--------
>>> XYZ_1 = np.array([28.0, 21.26, 5.27])
>>> xy_o1 = (0.4476, 0.4074)
>>> xy_o2 = (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) # noqa # doctest: +ELLIPSIS
array([ 24.0337952..., 21.1562121..., 17.6430119...])
"""

if not 18 < Y_o < 100:
warning(('"Y_o" luminance factor must be in [18, 100] domain, '
'unpredictable results may occur!'))

def cie1994(XYZ_1,
xy_o1,
xy_o2,
E_o1,
E_o2,
Y_o, # should be between 18 and 100---> issue warnings
n=1):
XYZ_1 = np.ravel(XYZ_1)

RGB_1 = XYZ_to_RGB_cie1994(XYZ_1)
Expand All @@ -65,7 +143,7 @@ def cie1994(XYZ_1,
bRGB_o1 = exponential_factors(RGB_o1)
bRGB_o2 = exponential_factors(RGB_o2)

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

RGB_2 = corresponding_colour(
RGB_1, Y_o, xez_1, xez_2, bRGB_o1, bRGB_o2, K, n)
Expand All @@ -90,15 +168,35 @@ def XYZ_to_RGB_cie1994(XYZ):
Examples
--------
>>> XYZ = np.array([19.01, 20, 21.78])
>>> XYZ = np.array([28.0, 21.26, 5.27])
>>> XYZ_to_RGB_cie1994(XYZ) # doctest: +ELLIPSIS
array([ 20.000520..., 19.999783..., 19.998831...])
array([ 25.8244273..., 18.6791422..., 4.8390194...])
"""

return CIE1994_XYZ_TO_RGB_MATRIX.dot(XYZ)


def RGB_to_XYZ_cie1994(RGB):
"""
Converts from cone responses to *CIE XYZ* colourspace.
Parameters
----------
RGB : array_like, (3,)
Cone responses.
Returns
-------
ndarray, (3,)
*CIE XYZ* colourspace matrix.
Examples
--------
>>> RGB = np.array([25.8244273, 18.6791422, 4.8390194])
>>> RGB_to_XYZ_cie1994(RGB) # doctest: +ELLIPSIS
array([ 28. , 21.26, 5.27])
"""

return CIE1994_RGB_TO_XYZ_MATRIX.dot(RGB)


Expand All @@ -108,8 +206,8 @@ def intermediate_values(xy_o):
Parameters
----------
xy_o : array_like
Chromaticity coordinates *xy* of whitepoint.
xy_o : array_like, (2,)
Chromaticity coordinates :math:`x_o` and :math:`y_o` of whitepoint.
Returns
-------
Expand All @@ -118,9 +216,11 @@ def intermediate_values(xy_o):
Examples
--------
>>> xy_o = (0.4476, 0.4074)
>>> intermediate_values(xy_o) # doctest: +ELLIPSIS
array([ 1.1185719..., 0.9329553..., 0.3268087...])
"""

# Illuminant chromaticity coordinates.
x_o, y_o = xy_o

# Computing :math:`\xi`, :math:`\eta`, :math:`\zeta` values.
Expand All @@ -133,6 +233,31 @@ def intermediate_values(xy_o):

def effective_adapting_responses(Y_o, E_o, xez):
"""
Derives the effective adapting responses in the fundamental primary system
of the test or reference field.
Parameters
----------
Y_o : numeric
Luminance factor :math:`Y_o` of achromatic background as percentage in
domain [18, 100].
E_o : numeric
Test or reference illuminance :math:`E_{o}` in lux.
xez: ndarray, (3,)
Intermediate values :math:`\\xi`, :math:`\eta`, :math:`\zeta`.
Returns
-------
ndarray, (3,)
Effective adapting responses.
Examples
--------
>>> Y_o = 20
>>> E_o = 1000
>>> xez = np.array([1.11857195, 0.9329553, 0.32680879])
>>> effective_adapting_responses(Y_o, E_o, xez) # doctest: +ELLIPSIS
array([ 71.2105020..., 59.3937790..., 20.8052937...])
"""

RGB_o = ((Y_o * E_o) / (100 * math.pi)) * xez
Expand Down Expand Up @@ -220,7 +345,48 @@ def exponential_factors(RGB_o):
return np.array([bR_o, bG_o, bB_o])


def coefficient_K(Y_o, xez_1, xez_2, bRGB_o1, bRGB_o2, n):
def K_coefficient(Y_o, xez_1, xez_2, bRGB_o1, bRGB_o2, n=1):
"""
Computes the coefficient :math:`K` for correcting the difference between
the test and references illuminances.
Parameters
----------
Y_o : numeric
Luminance factor :math:`Y_o` of achromatic background as percentage in
domain [18, 100].
xez_1: ndarray, (3,)
Intermediate values :math:`\\xi_1`, :math:`\eta_1`, :math:`\zeta_1` for
the test illuminant and background.
xez_2: ndarray, (3,)
Intermediate values :math:`\\xi_2`, :math:`\eta_2`, :math:`\zeta_2` for
the reference illuminant and background.
bRGB_o1: ndarray, (3,)
Chromatic adaptation exponential factors :math:`\\beta_1(R_{o1})`,
`math:`\\beta_1(G_{o1})` and :math:`\\beta_2(B_{o1})` of test sample.
bRGB_o2: ndarray, (3,)
Chromatic adaptation exponential factors :math:`\\beta_1(R_{o2})`,
`math:`\\beta_1(G_{o2})` and :math:`\\beta_2(B_{o2})` of reference
sample.
n : numeric, optional
Noise component in fundamental primary system.
Returns
-------
numeric
Coefficient :math:`K`.
Examples
--------
>>> Y_o = 20
>>> xez_1 = np.array([1.11857195, 0.9329553, 0.32680879])
>>> xez_2 = np.array([1.00000372, 1.00000176, 0.99999461])
>>> bRGB_o1 = np.array([3.74852518, 3.63920879, 2.78924811])
>>> bRGB_o2 = np.array([3.68102374, 3.68102256, 3.56557351])
>>> K_coefficient(Y_o, xez_1, xez_2, bRGB_o1, bRGB_o2)
1.0
"""

xi_1, eta_1, zeta_1 = xez_1
xi_2, eta_2, zeta_2 = xez_2

Expand All @@ -235,7 +401,55 @@ def coefficient_K(Y_o, xez_1, xez_2, bRGB_o1, bRGB_o2, n):
return K


def corresponding_colour(RGB_1, Y_o, xez_1, xez_2, bRGB_o1, bRGB_o2, K, n):
def corresponding_colour(RGB_1, Y_o, xez_1, xez_2, bRGB_o1, bRGB_o2, K, n=1):
"""
Computes the corresponding colour cone responses of given test sample cone
responses :math:`RGB_1`.
Parameters
----------
RGB_1: ndarray, (3,)
Test sample cone responses :math:`RGB_1`.
Y_o : numeric
Luminance factor :math:`Y_o` of achromatic background as percentage in
domain [18, 100].
xez_1: ndarray, (3,)
Intermediate values :math:`\\xi_1`, :math:`\eta_1`, :math:`\zeta_1` for
the test illuminant and background.
xez_2: ndarray, (3,)
Intermediate values :math:`\\xi_2`, :math:`\eta_2`, :math:`\zeta_2` for
the reference illuminant and background.
bRGB_o1: ndarray, (3,)
Chromatic adaptation exponential factors :math:`\\beta_1(R_{o1})`,
`math:`\\beta_1(G_{o1})` and :math:`\\beta_2(B_{o1})` of test sample.
bRGB_o2: ndarray, (3,)
Chromatic adaptation exponential factors :math:`\\beta_1(R_{o2})`,
`math:`\\beta_1(G_{o2})` and :math:`\\beta_2(B_{o2})` of reference
sample.
K : numeric
Coefficient :math:`K`.
n : numeric, optional
Noise component in fundamental primary system.
Returns
-------
ndarray, (3,)
Corresponding colour cone responses of given test sample cone
responses.
Examples
--------
>>> RGB_1 = np.array([25.8244273, 18.6791422, 4.8390194])
>>> Y_o = 20
>>> xez_1 = np.array([1.11857195, 0.9329553, 0.32680879])
>>> xez_2 = np.array([1.00000372, 1.00000176, 0.99999461])
>>> bRGB_o1 = np.array([3.74852518, 3.63920879, 2.78924811])
>>> bRGB_o2 = np.array([3.68102374, 3.68102256, 3.56557351])
>>> K = 1.0
>>> corresponding_colour(RGB_1, Y_o, xez_1, xez_2, bRGB_o1, bRGB_o2, K) # noqa # doctest: +ELLIPSIS
array([ 23.1636901..., 20.0211948..., 16.2001664...])
"""

R_1, G_1, B_1 = RGB_1
xi_1, eta_1, zeta_1 = xez_1
xi_2, eta_2, zeta_2 = xez_2
Expand All @@ -251,14 +465,4 @@ def corresponding_colour(RGB_1, Y_o, xez_1, xez_2, bRGB_o1, bRGB_o2, K, n):
G_2 = RGBc(eta_1, eta_2, bG_o1, bG_o2, G_1)
B_2 = RGBc(zeta_1, zeta_2, bB_o1, bB_o2, B_1)

return np.array([R_2, G_2, B_2])


print(cie1994(XYZ_1=np.array([28.0, 21.26, 5.27]),
xy_o1=(0.4476, 0.4074),
xy_o2=(0.3127, 0.3290),
E_o1=1000,
E_o2=1000,
Y_o=20))

# [ 24.03379521 21.15621214 17.64301199]
return np.array([R_2, G_2, B_2])
Loading

0 comments on commit 4ec13c1

Please sign in to comment.