Skip to content

Commit

Permalink
Merge pull request #124 from colour-science/feature/chromatic_adaptation
Browse files Browse the repository at this point in the history
PR: Implement "colour.chromatic_adaptation" convenient definition that transforms "CIE XYZ" tristimulus values from one viewing condition to another.
  • Loading branch information
KelSolaar committed Sep 9, 2014
2 parents bf58f87 + dd79cac commit 5d72b41
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 58 deletions.
4 changes: 2 additions & 2 deletions colour/adaptation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from __future__ import absolute_import

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

__all__ = ['CHROMATIC_ADAPTATION_METHODS']
__all__ += ['chromatic_adaptation_matrix']
__all__ += ['chromatic_adaptation_matrix', 'chromatic_adaptation']
105 changes: 80 additions & 25 deletions colour/adaptation/cat.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
===============================
Defines various chromatic adaptation transforms (CAT) and objects to
calculate the chromatic adaptation matrix between two given *CIE XYZ*
colourspace matrices:
calculate the chromatic adaptation matrix from test viewing conditions
*CIE XYZ_w* colourspace matrix to reference viewing conditions *CIE XYZ_wr*
colourspace matrix.
- :attr:`XYZ_SCALING_CAT`: *XYZ Scaling* CAT [1]_
- :attr:`VON_KRIES_CAT`: *Johannes Von Kries* CAT [1]_
Expand All @@ -19,6 +20,8 @@
- :attr:`CAT02_CAT`: *CAT02* CAT [3]_
- :attr:`BS_CAT`: *S. Bianco* and R. Schettini* CAT [4]_
- :attr:`BS_PC_CAT`: *S. Bianco* and R. Schettini PC* CAT [4]_
- :func:`chromatic_adaptation_matrix`
- :func:`chromatic_adaptation`
See Also
--------
Expand Down Expand Up @@ -67,7 +70,8 @@
'BS_CAT',
'BS_PC_CAT',
'CHROMATIC_ADAPTATION_METHODS',
'chromatic_adaptation_matrix']
'chromatic_adaptation_matrix',
'chromatic_adaptation']

XYZ_SCALING_CAT = np.array(np.identity(3)).reshape((3, 3))
"""
Expand Down Expand Up @@ -129,7 +133,7 @@
CMCCAT2000_CAT = np.array(
[[0.7982, 0.3389, -0.1371],
[-0.5918, 1.5512, 0.0406],
[0.0008, 0.239, 0.9753]])
[0.0008, 0.0239, 0.9753]])
"""
*CMCCAT2000* chromatic adaptation transform. [5]_
Expand Down Expand Up @@ -190,17 +194,18 @@
"""


def chromatic_adaptation_matrix(XYZ1, XYZ2, method='CAT02'):
def chromatic_adaptation_matrix(XYZ_w, XYZ_wr, method='CAT02'):
"""
Returns the *chromatic adaptation* matrix from given source and target
*CIE XYZ* colourspace *array_like* variables.
Returns the *chromatic adaptation* matrix from test viewing conditions
*CIE XYZ* colourspace matrix to reference viewing conditions *CIE XYZ_wr*
colourspace matrix.
Parameters
----------
XYZ1 : array_like, (3,)
*CIE XYZ* source *array_like* variable.
XYZ2 : array_like, (3,)
*CIE XYZ* target *array_like* variable.
XYZ_w : array_like, (3,)
Source viewing condition *CIE XYZ* colourspace matrix.
XYZ_wr : array_like, (3,)
Target viewing condition *CIE XYZ* colourspace matrix.
method : unicode, optional
('XYZ Scaling', 'Von Kries', 'Bradford', 'Sharp', 'Fairchild,
'CMCCAT97', 'CMCCAT2000', 'CAT02', 'Bianco', 'Bianco PC'),
Expand All @@ -218,24 +223,26 @@ def chromatic_adaptation_matrix(XYZ1, XYZ2, method='CAT02'):
References
----------
.. [4] http://brucelindbloom.com/Eqn_ChromAdapt.html
(Last accessed 24 February 2014)
.. [6] **Mark D. Fairchild**, *Color Appearance Models, 3nd Edition*,
The Wiley-IS&T Series in Imaging Science and Technology,
published June 2013, ASIN: B00DAYO8E2,
Locations 4193-4252.
Examples
--------
>>> XYZ1 = np.array([1.09923822, 1.000, 0.35445412])
>>> XYZ2 = np.array([0.96907232, 1.000, 1.121792157])
>>> chromatic_adaptation_matrix(XYZ1, XYZ2) # doctest: +ELLIPSIS
>>> XYZ_w = np.array([1.09923822, 1.000, 0.35445412])
>>> XYZ_wr = np.array([0.96907232, 1.000, 1.121792157])
>>> chromatic_adaptation_matrix(XYZ_w, XYZ_wr) # doctest: +ELLIPSIS
array([[ 0.8714561..., -0.1320467..., 0.4039483...],
[-0.0963880..., 1.0490978..., 0.160403... ],
[ 0.0080207..., 0.0282636..., 3.0602319...]])
Using *Bradford* method:
>>> XYZ1 = np.array([1.09923822, 1.000, 0.35445412])
>>> XYZ2 = np.array([0.96907232, 1.000, 1.121792157])
>>> XYZ_w = np.array([1.09923822, 1.000, 0.35445412])
>>> XYZ_wr = np.array([0.96907232, 1.000, 1.121792157])
>>> method = 'Bradford'
>>> chromatic_adaptation_matrix(XYZ1, XYZ2, method) # doctest: +ELLIPSIS
>>> chromatic_adaptation_matrix(XYZ_w, XYZ_wr, method) # noqa # doctest: +ELLIPSIS
array([[ 0.8518131..., -0.1134786..., 0.4124804...],
[-0.1277659..., 1.0928930..., 0.1341559...],
[ 0.0845323..., -0.1434969..., 3.3075309...]])
Expand All @@ -249,19 +256,67 @@ def chromatic_adaptation_matrix(XYZ1, XYZ2, method='CAT02'):
'methods: "{1}".'.format(method,
CHROMATIC_ADAPTATION_METHODS.keys()))

XYZ1, XYZ2 = np.ravel(XYZ1), np.ravel(XYZ2)
XYZ_w, XYZ_wr = np.ravel(XYZ_w), np.ravel(XYZ_wr)

if (XYZ1 == XYZ2).all():
if (XYZ_w == XYZ_wr).all():
# Skip the chromatic adaptation computation if the two input matrices
# are the same, no adaptation is needed.
return np.identity(3)

rgb_source = np.ravel(np.dot(method_matrix, XYZ1))
rgb_target = np.ravel(np.dot(method_matrix, XYZ2))
crd = np.diagflat(np.array(
rgb_source = np.ravel(np.dot(method_matrix, XYZ_w))
rgb_target = np.ravel(np.dot(method_matrix, XYZ_wr))
D = np.diagflat(np.array(
[[rgb_target[0] / rgb_source[0],
rgb_target[1] / rgb_source[1],
rgb_target[2] / rgb_source[2]]])).reshape((3, 3))
cat = np.dot(np.dot(np.linalg.inv(method_matrix), crd), method_matrix)
cat = np.dot(np.dot(np.linalg.inv(method_matrix), D), method_matrix)

return cat


def chromatic_adaptation(XYZ, XYZ_w, XYZ_wr, method='CAT02'):
"""
Adapts given *CIE XYZ* colourspace stimulus from test viewing conditions
*CIE XYZ_w* colourspace matrix to reference viewing conditions *CIE XYZ_wr*
colourspace matrix. [6]_
Parameters
----------
XYZ : array_like, (3,)
*CIE XYZ* colourspace stimulus to adapt.
XYZ_w : array_like, (3,)
Source viewing condition *CIE XYZ* colourspace whitepoint matrix.
XYZ_wr : array_like, (3,)
Target viewing condition *CIE XYZ* colourspace whitepoint matrix.
method : unicode, optional
('XYZ Scaling', 'Von Kries', 'Bradford', 'Sharp', 'Fairchild,
'CMCCAT97', 'CMCCAT2000', 'CAT02', 'Bianco', 'Bianco PC'),
Chromatic adaptation method.
Returns
-------
ndarray, (3,)
*CIE XYZ_c* colourspace matrix of the stimulus corresponding colour.
Examples
--------
>>> XYZ = np.array([0.92193107, 1, 1.03744246])
>>> XYZ_w = np.array([1.09923822, 1.000, 0.35445412])
>>> XYZ_wr = np.array([0.96907232, 1.000, 1.121792157])
>>> chromatic_adaptation(XYZ, XYZ_w, XYZ_wr) # doctest: +ELLIPSIS
array([ 1.0904489..., 1.1266438..., 3.2104727...])
Using *Bradford* method:
>>> XYZ = np.array([0.92193107, 1, 1.03744246])
>>> XYZ_w = np.array([1.09923822, 1.000, 0.35445412])
>>> XYZ_wr = np.array([0.96907232, 1.000, 1.121792157])
>>> method = 'Bradford'
>>> chromatic_adaptation(XYZ, XYZ_w, XYZ_wr, method) # noqa # doctest: +ELLIPSIS
array([ 1.0997590..., 1.1142807..., 3.3658090...])
"""

cat = chromatic_adaptation_matrix(XYZ_w, XYZ_wr, method)
XYZ_a = np.dot(cat, XYZ)

return XYZ_a
126 changes: 95 additions & 31 deletions colour/adaptation/tests/tests_cat.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@

from __future__ import division, unicode_literals

import numpy
import numpy as np
import sys

if sys.version_info[:2] <= (2, 6):
import unittest2 as unittest
else:
import unittest

from colour.adaptation import chromatic_adaptation_matrix
from colour.adaptation import chromatic_adaptation_matrix, chromatic_adaptation

__author__ = 'Colour Developers'
__copyright__ = 'Copyright (C) 2013 - 2014 - Colour Developers'
Expand All @@ -24,7 +24,8 @@
__email__ = 'colour-science@googlegroups.com'
__status__ = 'Production'

__all__ = ['TestChromaticAdaptationMatrix']
__all__ = ['TestChromaticAdaptationMatrix',
'TestChromaticAdaptation']


class TestChromaticAdaptationMatrix(unittest.TestCase):
Expand All @@ -39,66 +40,129 @@ def test_chromatic_adaptation_matrix(self):
definition.
"""

numpy.testing.assert_almost_equal(
np.testing.assert_almost_equal(
chromatic_adaptation_matrix(
numpy.array([1.09923822, 1.000, 0.35445412]),
numpy.array([0.96907232, 1.000, 1.121792157])),
numpy.array(
np.array([1.09923822, 1.000, 0.35445412]),
np.array([0.96907232, 1.000, 1.121792157])),
np.array(
[0.87145615, -0.13204674, 0.40394832,
-0.09638805, 1.04909781, 0.1604033,
0.0080207, 0.02826367, 3.06023194]).reshape((3, 3)),
decimal=7)

numpy.testing.assert_almost_equal(
np.testing.assert_almost_equal(
chromatic_adaptation_matrix(
numpy.array([1.92001986, 1, -0.1241347]),
numpy.array([1.0131677, 1.000, 2.11217686])),
numpy.array(
np.array([1.92001986, 1, -0.1241347]),
np.array([1.0131677, 1.000, 2.11217686])),
np.array(
[0.91344833, -1.20588903, -3.74768526,
-0.81680514, 2.3858187, -1.46988227,
-0.05367575, -0.31122239, -20.35255049]).reshape((3, 3)),
decimal=7)

numpy.testing.assert_almost_equal(
np.testing.assert_almost_equal(
chromatic_adaptation_matrix(
numpy.array([1.92001986, 1, -0.1241347]),
numpy.array([1.0131677, 1.000, 2.11217686])),
numpy.linalg.inv(chromatic_adaptation_matrix(
numpy.array([1.0131677, 1.000, 2.11217686]),
numpy.array([1.92001986, 1., -0.1241347]))))
np.array([1.92001986, 1, -0.1241347]),
np.array([1.0131677, 1.000, 2.11217686])),
np.linalg.inv(chromatic_adaptation_matrix(
np.array([1.0131677, 1.000, 2.11217686]),
np.array([1.92001986, 1., -0.1241347]))))

numpy.testing.assert_almost_equal(
np.testing.assert_almost_equal(
chromatic_adaptation_matrix(
numpy.array([1.09850, 1.00000, 0.35585]),
numpy.array([0.99072, 1.00000, 0.85223]),
np.array([1.09850, 1.00000, 0.35585]),
np.array([0.99072, 1.00000, 0.85223]),
method='XYZ Scaling'),
numpy.array([0.90188439, 0., 0.,
0., 1., 0.,
0., 0., 2.39491359]).reshape((3, 3)),
np.array([0.90188439, 0., 0.,
0., 1., 0.,
0., 0., 2.39491359]).reshape((3, 3)),
decimal=7)

numpy.testing.assert_almost_equal(
np.testing.assert_almost_equal(
chromatic_adaptation_matrix(
numpy.array([1.09850, 1.00000, 0.35585]),
numpy.array([0.99072, 1.00000, 0.85223]),
np.array([1.09850, 1.00000, 0.35585]),
np.array([0.99072, 1.00000, 0.85223]),
method='Bradford'),
numpy.array(
np.array(
[0.89051629, -0.08291357, 0.26809449,
-0.09715236, 1.07542618, 0.08794629,
0.05389701, -0.09085576, 2.48385527]).reshape((3, 3)),
decimal=7)

numpy.testing.assert_almost_equal(
np.testing.assert_almost_equal(
chromatic_adaptation_matrix(
numpy.array([1.09850, 1.00000, 0.35585]),
numpy.array([0.99072, 1.00000, 0.85223]),
np.array([1.09850, 1.00000, 0.35585]),
np.array([0.99072, 1.00000, 0.85223]),
method='Von Kries'),
numpy.array(
np.array(
[0.9574884, -0.16436134, 0.29023559,
-0.01805393, 1.01853791, 0.00363729,
0., 0., 2.39491359]).reshape((3, 3)),
decimal=7)


class TestChromaticAdaptation(unittest.TestCase):
"""
Defines :func:`colour.adaptation.cat.chromatic_adaptation` definition unit
tests methods.
"""

def test_chromatic_adaptation(self):
"""
Tests :func:`colour.adaptation.cat.chromatic_adaptation` definition.
"""

np.testing.assert_almost_equal(
chromatic_adaptation(
np.array([0.96907232, 1, 1.12179215]),
np.array([1.09923822, 1.000, 0.35445412]),
np.array([0.96907232, 1.000, 1.121792157])),
np.array([1.16560336, 1.13562999, 3.4689805]),
decimal=7)

np.testing.assert_almost_equal(
chromatic_adaptation(
np.array([1.92001986, 1, -0.1241347]),
np.array([1.09923822, 1.000, 0.35445412]),
np.array([1.0131677, 1.000, 2.11217686])),
np.array([1.22145866, 0.72788376, -0.61217097]),
decimal=7)

np.testing.assert_almost_equal(
chromatic_adaptation(
np.array([1.0131677, 1, 2.11217686]),
np.array([1.92001986, 1, -0.1241347]),
np.array([1.0131677, 1.000, 2.11217686])),
np.array([-8.19618675, -1.54639319, -43.35379111]),
decimal=7)

np.testing.assert_almost_equal(
chromatic_adaptation(
np.array([0.96907232, 1, 1.12179215]),
np.array([1.09850, 1.00000, 0.35585]),
np.array([0.99072, 1.00000, 0.85223]),
method='XYZ Scaling'),
np.array([0.8739912, 1., 2.68659526]),
decimal=7)

np.testing.assert_almost_equal(
chromatic_adaptation(
np.array([0.96907232, 1, 1.12179215]),
np.array([1.09850, 1.00000, 0.35585]),
np.array([0.99072, 1.00000, 0.85223]),
method='Bradford'),
np.array([1.08080741, 1.07993598, 2.74774368]),
decimal=7)

np.testing.assert_almost_equal(
chromatic_adaptation(
np.array([0.96907232, 1, 1.12179215]),
np.array([1.09850, 1.00000, 0.35585]),
np.array([0.99072, 1.00000, 0.85223]),
method='Von Kries'),
np.array([1.08909817, 1.00512263, 2.68659526]),
decimal=7)


if __name__ == '__main__':
unittest.main()

0 comments on commit 5d72b41

Please sign in to comment.