Skip to content

Commit

Permalink
ENH: Smoke test for colormaps. Remove unused color transformations.
Browse files Browse the repository at this point in the history
  • Loading branch information
rkern committed Aug 4, 2014
1 parent e22f7a7 commit 4f7d3f4
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 221 deletions.
217 changes: 0 additions & 217 deletions chaco/color_spaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,55 +107,6 @@ def triwhite(x, y):
Z = (1-x-y)/y
return [X, Y, Z]


def adapt_whitepoint(src, dst):
""" Compute the adaptation matrix for converting XYZ tristimulus
values from a one standard illuminant to another using the Bradford
transform.
This implementation follows the presentation on
http://www.color.org/chadtag.html
The results are cached.
Parameters
----------
src : str
dst : str
The names of the standard illuminants to convert from and to,
respectively. Valid values are the keys of `whitepoints` like
'D65' or 'CIE A'.
Returns
-------
adapt : float array (3, 3)
Matrix-multiply this matrix against XYZ values with the `src`
whitepoint to get XYZ values with the `dst` whitepoint.
"""
# Check the cache first.
key = (src, dst)
if key not in adapt_whitepoint.cache:
bradford = np.array([[+0.8951, 0.2664, -0.1614],
[-0.7502, 1.7135, 0.0367],
[+0.0389, -0.0685, 1.0296]])

src_whitepoint = whitepoints[src][-1]
src_rgb = np.dot(bradford, src_whitepoint)
dst_whitepoint = whitepoints[dst][-1]
dst_rgb = np.dot(bradford, dst_whitepoint)

scale = dst_rgb / src_rgb
adapt_whitepoint.cache[key] = solve(
bradford,
scale[:, np.newaxis] * bradford,
)

adapt = adapt_whitepoint.cache[key]
return adapt

# Create the cache.
adapt_whitepoint.cache = {}

#### Data #####################################################################

# From the sRGB specification.
Expand All @@ -164,28 +115,6 @@ def adapt_whitepoint(src, dst):
[0.019334, 0.119193, 0.950227]])
rgb_from_xyz = inv(xyz_from_rgb)

# This transformation to LMS space keeps the peaks of colormatching curves
# normalized to 1.
lms_from_xyz = np.array([
[+0.2434974736455316, 0.8523911562030849, -0.0515994646411065],
[-0.3958579552426224, 1.1655483851630273, 0.0837969419671409],
[+0.0, 0.0, 0.6185822095756526],
])
xyz_from_lms = inv(lms_from_xyz)

# The transformation from XYZ to the ATD opponent colorspace. These are
# "official" values directly from Guth 1980.
atd_from_xyz = np.array([[+0., 0.9341, 0.],
[+0.7401, -0.6801, -0.1567],
[-0.0061, -0.0212, 0.0314]])
xyz_from_atd = inv(atd_from_xyz)

# Now we need to compute the intermediate transformations between LMS and ATD.
# We derive these directly from the other two instead of specifying potentially
# truncated values.
atd_from_lms = solve(lms_from_xyz.T, atd_from_xyz.T).T
lms_from_atd = solve(atd_from_xyz.T, lms_from_xyz.T).T


# XYZ white-point coordinates
# from http://en.wikipedia.org/wiki/Standard_illuminant
Expand Down Expand Up @@ -272,152 +201,6 @@ def finv(y):
return join_colors(xr*wp[0], yr*wp[1], zr*wp[2], axis)


def _uv(x, y, z):
""" The u, v formulae for CIE 1976 L*u*v* computations.
"""
denominator = (x + 15*y + 3*z)
zeros = (denominator == 0.0)
denominator = np.where(zeros, 1.0, denominator)
u_numerator = np.where(zeros, 4.0, 4 * x)
v_numerator = np.where(zeros, 9.0, 9 * y)

return u_numerator/denominator, v_numerator/denominator


def xyz2luv(xyz, axis=-1, wp=whitepoints['D65'][-1]):
""" Convert XYZ tristimulus values to CIE L*u*v*.
Parameters
----------
xyz : float array
XYZ values.
axis : int, optional
The axis of the XYZ values.
wp : list of 3 floats, optional
The XYZ tristimulus values of the whitepoint.
Returns
-------
luv : float array
The L*u*v* colors.
"""
x, y, z, axis = separate_colors(xyz, axis)
xn, yn, zn = x/wp[0], y/wp[1], z/wp[2]
Ls = 116.0 * np.power(yn, 1./3) - 16.0
small_mask = (y <= 0.008856*wp[1])
Ls[small_mask] = 903.0 * y[small_mask] / wp[1]
unp, vnp = _uv(*wp)
up, vp = _uv(x, y, z)
us = 13 * Ls * (up - unp)
vs = 13 * Ls * (vp - vnp)

return join_colors(Ls, us, vs, axis)


def luv2xyz(luv, axis=-1, wp=whitepoints['D65'][-1]):
""" Convert CIE L*u*v* colors to XYZ tristimulus values.
Parameters
----------
luv : float array
L*u*v* values.
axis : int, optional
The axis of the XYZ values.
wp : list of 3 floats, optional
The XYZ tristimulus values of the whitepoint.
Returns
-------
xyz : float array
The XYZ colors.
"""
Ls, us, vs, axis = separate_colors(luv, axis)
unp, vnp = _uv(*wp)
small_mask = (Ls <= 903.3 * 0.008856)
y = wp[1] * ((Ls + 16.0) / 116.0) ** 3
y[small_mask] = Ls[small_mask] * wp[1] / 903.0
# Where L==0, X=Y=Z=0. Avoid the nans and infs in the meantime.
black = (Ls == 0)
Ls[black] = 1.0

up = us / (13*Ls) + unp
vp = vs / (13*Ls) + vnp
x = 9.0 * y * up / (4.0 * vp)
z = -x / 3.0 - 5.0 * y + 3.0 * y/vp

x[black] = 0.0
y[black] = 0.0
z[black] = 0.0

return join_colors(x, y, z, axis)


def xyz2hcl(xyz, axis=-1, wp=whitepoints['D65'][-1], luvlab='luv'):
""" Convert XYZ tristimulus values to HCL.
HCL (Hue - Chroma - Luminance) is a cylindrical-coordinate form of
Luv or Lab.
Parameters
----------
xyz : float array
XYZ values.
axis : int, optional
The axis of the XYZ values.
wp : list of 3 floats, optional
The XYZ tristimulus values of the whitepoint.
luvlab : 'luv' or 'lab', optional
Whether to use the L*u*v* or L*a*b*.
Returns
-------
hcl : float array
The HCL colors.
"""
if luvlab == 'luv':
xyz2lxx = xyz2luv
else:
xyz2lxx = xyz2lab

Ls, x1, x2, axis = separate_colors(xyz2lxx(xyz, axis=axis, wp=wp), axis)
Cs = np.hypot(x1, x2)
Hs = (180. / np.pi) * np.arctan2(x2, x1)

return join_colors(Hs, Cs, Ls, axis)


def hcl2xyz(hcl, axis=-1, wp=whitepoints['D65'][-1], luvlab='luv'):
""" Convert HCL values to XYZ tristimulus values.
Parameters
----------
hcl : float array
Te HCL colors.
axis : int, optional
The axis of the XYZ values.
wp : list of 3 floats, optional
The XYZ tristimulus values of the whitepoint.
luvlab : 'luv' or 'lab', optional
Whether to use the L*u*v* or L*a*b*.
Returns
-------
xyz : float array
XYZ values.
"""
if luvlab == 'luv':
lxx2xyz = luv2xyz
else:
lxx2xyz = lab2xyz

Hs, Cs, Ls, axis = separate_colors(hcl, axis)
theta = Hs * (np.pi / 180)
x1 = Cs * np.cos(theta)
x2 = Cs * np.sin(theta)

return lxx2xyz(join_colors(Ls, x1, x2, axis), axis=axis, wp=wp)


# RGB values that will be displayed on a screen are always nonlinear
# R'G'B' values. To get the XYZ value of the color that will be
# displayed you need a calibrated monitor with a profile.
Expand Down
17 changes: 13 additions & 4 deletions chaco/default_colormaps.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#------------------------------------------------------------------------------
# Copyright (c) 2005, Enthought, Inc.
# Copyright (c) 2005-2014, Enthought, Inc.
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
Expand Down Expand Up @@ -56,7 +56,10 @@ def cmap(range, **traits):

# Look a little like the wrapped function.
cmap.__name__ = 'reversed_' + func.__name__
cmap.__doc__ = 'Reversed: ' + func.__doc__
if func.__doc__ is not None:
cmap.__doc__ = 'Reversed: ' + func.__doc__
else:
cmap.__doc__ = 'Reversed: ' + func.__name__
return cmap

def center(func, center=0.0):
Expand Down Expand Up @@ -90,7 +93,10 @@ def cmap(range, **traits):

# Look a little like the wrapped function.
cmap.__name__ = 'centered_' + func.__name__
cmap.__doc__ = 'Centered: ' + func.__doc__
if func.__doc__ is not None:
cmap.__doc__ = 'Centered: ' + func.__doc__
else:
cmap.__doc__ = 'Centered: ' + func.__name__
return cmap

def fix(func, range):
Expand Down Expand Up @@ -125,7 +131,10 @@ def cmap(dummy_range, **traits):

# Look a little like the wrapped function.
cmap.__name__ = 'fixed_' + func.__name__
cmap.__doc__ = 'Fixed: ' + func.__doc__
if func.__doc__ is not None:
cmap.__doc__ = 'Fixed: ' + func.__doc__
else:
cmap.__doc__ = 'Fixed: ' + func.__name__
return cmap


Expand Down
49 changes: 49 additions & 0 deletions chaco/tests/default_colormaps_test_case.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#------------------------------------------------------------------------------
# Copyright (c) 2014, Enthought, Inc.
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only
# under the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
# Thanks for using Enthought open source!
#
#------------------------------------------------------------------------------

import unittest

import numpy as np
from numpy.testing import assert_array_equal

from chaco.api import DataRange1D
from .. import default_colormaps


class DefaultColormapsTestCase(unittest.TestCase):

def test_default_colormaps_smoke(self):
# Runs some data through each of the default colormaps and do basic
# sanity checks.
x = np.linspace(-1.5, 2.0, 8)
datarange = DataRange1D(low_setting=-1.0, high_setting=1.5)
for cmap_func in default_colormaps.color_map_functions:
cmapper = cmap_func(datarange)
rgba = cmapper.map_screen(x)
self.assertEqual(rgba.shape, (8, 4))
self.assertTrue(np.isfinite(rgba).all())
self.assertTrue((rgba >= 0.0).all())
self.assertTrue((rgba <= 1.0).all())
# No transparency for any of the defaults.
assert_array_equal(rgba[:, -1], np.ones(8))
assert_array_equal(rgba[0], rgba[1])
assert_array_equal(rgba[-2], rgba[-1])
r_cmapper = default_colormaps.reverse(cmap_func)(datarange)
r_rgba = r_cmapper.map_screen(x)
assert_array_equal(r_rgba, rgba[::-1])
c_cmapper = default_colormaps.center(cmap_func)(datarange)
self.assertEqual(c_cmapper.range.low, -1.5)
self.assertEqual(c_cmapper.range.high, 1.5)
f_cmapper = default_colormaps.fix(cmap_func,
(0.0, 1.0))(datarange)
self.assertEqual(f_cmapper.range.low, 0.0)
self.assertEqual(f_cmapper.range.high, 1.0)

0 comments on commit 4f7d3f4

Please sign in to comment.