From 5e59017b3f3398ac482f859629d2ede5f791e68c Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Fri, 18 Jul 2014 14:01:18 -0400 Subject: [PATCH] Add greyscale and color-blindness filters. --- examples/pylab_examples/color_blindness.py | 44 ++++++ lib/matplotlib/artist.py | 3 + lib/matplotlib/colors.py | 162 +++++++++++++++++++++ 3 files changed, 209 insertions(+) create mode 100755 examples/pylab_examples/color_blindness.py diff --git a/examples/pylab_examples/color_blindness.py b/examples/pylab_examples/color_blindness.py new file mode 100755 index 000000000000..78fad53d27c4 --- /dev/null +++ b/examples/pylab_examples/color_blindness.py @@ -0,0 +1,44 @@ +import numpy as np +import matplotlib.cm as cm +import matplotlib.mlab as mlab +import matplotlib.pyplot as plt + +delta = 0.025 +x = y = np.arange(-3.0, 3.0, delta) +X, Y = np.meshgrid(x, y) +Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0) +Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1) +Z = Z2-Z1 # difference of Gaussians + +def plot_data(ax): + im = ax.imshow(Z, interpolation='bilinear', cmap='jet', + origin='lower', extent=[-3,3,-3,3], + vmax=abs(Z).max(), vmin=-abs(Z).max()) + return im + +# Display the same plot 4 different ways: +# - no filter +# - luminosity filter +# - Deuteranope color blindness filter +# - Tritanope color blindness filter + +ax = plt.subplot(2, 2, 1) +plot_data(ax) +ax.set_title("Original") + +ax = plt.subplot(2, 2, 2) +im = plot_data(ax) +im.set_agg_filter('luminosity') +ax.set_title("Luminosity") + +ax = plt.subplot(2, 2, 3) +im = plot_data(ax) +im.set_agg_filter("deuteranope") +ax.set_title("Deuteranope") + +ax = plt.subplot(2, 2, 4) +im = plot_data(ax) +im.set_agg_filter("tritanope") +ax.set_title("Tritanope") + +plt.show() diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index d1ef2e59750e..dfe868efff9a 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -696,6 +696,9 @@ def set_agg_filter(self, filter_func): set agg_filter fuction. """ + if isinstance(filter_func, six.string_types): + from . import colors + filter_func = colors.get_color_filter(filter_func) self._agg_filter = filter_func def draw(self, renderer, *args, **kwargs): diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index dbc96197c134..794a09b6367c 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1630,3 +1630,165 @@ def from_levels_and_colors(levels, colors, extend='neither'): norm = BoundaryNorm(levels, ncolors=n_data_colors) return cmap, norm + + +def luminosity_filter(im): + """ + Convert a RGB or RGBA image to a greyscale image based on each + pixel's luminosity. + + Parameters + ---------- + im : Nx3 or Nx4 array of floating-point pixels + The input image, in RGB or RGBA. + + Returns + ------- + im : Nx1 array of float-point greyscale pixels + The output image, converted to luminosity + """ + im = np.asarray(im) + im = im[..., 0:3] + return np.average(im, -1, weights=[.21, .72, 0.07]) + + +def average_filter(im): + """ + Convert a RGB or RGBA image to a greyscale image by average the + red, green and blue values for each pixel. + + Parameters + ---------- + im : Nx3 or Nx4 array of floating-point pixels + The input image, in RGB or RGBA. + + Returns + ------- + im : Nx1 array of float-point greyscale pixels + The output image + """ + im = np.asarray(im) + im = im[..., 0:3] + return np.average(im, -1) + + +_rgb_to_lms_matrix = np.array([ + [17.8824, 43.5161, 4.11935], + [3.45565, 27.1554, 3.86714], + [0.0299566, 0.184309, 1.46709]]).T + + +_lms_to_rgb_matrix = np.linalg.inv(_rgb_to_lms_matrix) + + +_deuteranope_matrix = np.array([ + [1, 0, 0], + [0.494207, 0, 1.24827], + [0, 0, 1]]).T + + +_deuteranope_chain = _rgb_to_lms_matrix.dot( + _deuteranope_matrix).dot( + _lms_to_rgb_matrix) + + +def deuteranope_filter(im): + """ + Convert a RGB or RGBA image to one that simulates Deuteranope + color blindness. + + Parameters + ---------- + im : Nx3 or Nx4 array of floating-point pixels + The input image, in RGB or RGBA. + + Returns + ------- + im : Nx1 array of float-point greyscale pixels + The output image + """ + im = im[..., 0:3] + im = np.dot(im, _deuteranope_chain) + return im + + +_protanope_matrix = np.array([ + [0, 2.02344, -2.52581], + [0, 1, 0], + [0, 0, 1]]).T + + +_protanope_chain = _rgb_to_lms_matrix.dot( + _protanope_matrix).dot( + _lms_to_rgb_matrix) + + +def protanope_filter(im): + """ + Convert a RGB or RGBA image to one that simulates Protanope + color blindness. + + Parameters + ---------- + im : Nx3 or Nx4 array of floating-point pixels + The input image, in RGB or RGBA. + + Returns + ------- + im : Nx1 array of float-point greyscale pixels + The output image + """ + im = im[..., 0:3] + im = np.dot(im, _protanope_chain) + return im + + +_tritanope_matrix = np.array([ + [1, 0, 0], + [0, 1, 0], + [-0.395913, 0.801109, 0]]).T + + +_tritanope_chain = _rgb_to_lms_matrix.dot( + _tritanope_matrix).dot( + _lms_to_rgb_matrix) + + +def tritanope_filter(im): + """ + Convert a RGB or RGBA image to one that simulates Tritanope + color blindness. + + Parameters + ---------- + im : Nx3 or Nx4 array of floating-point pixels + The input image, in RGB or RGBA. + + Returns + ------- + im : Nx1 array of float-point greyscale pixels + The output image + """ + im = im[..., 0:3] + im = np.dot(im, _tritanope_chain) + return im + + +def get_color_filter(name): + """ + Given a color filter name, returns a color filter function. + """ + mapping = { + 'luminosity': luminosity_filter, + 'average': average_filter, + 'deuteranope': deuteranope_filter, + 'protanope': protanope_filter, + 'tritanope': tritanope_filter + } + + filter = mapping[name.lower()] + + def wrap_filter(im, dpi): + return filter(im), 0, 0 + + return wrap_filter