Skip to content

Commit 596e83d

Browse files
committed
Add colour vision deficiency simulation filters.
This is based on matplotlib#3279 in essence, but rewritten to use colorspacious and modernized somewhat.
1 parent b932575 commit 596e83d

File tree

2 files changed

+76
-5
lines changed

2 files changed

+76
-5
lines changed

lib/matplotlib/artist.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -924,13 +924,20 @@ def set_agg_filter(self, filter_func):
924924
925925
Parameters
926926
----------
927-
filter_func : callable
928-
A filter function, which takes a (m, n, 3) float array and a dpi
929-
value, and returns a (m, n, 3) array.
927+
filter_func : callable or str or None
928+
A filter function, or the name of a builtin one.
930929
931-
.. ACCEPTS: a filter function, which takes a (m, n, 3) float array
932-
and a dpi value, and returns a (m, n, 3) array
930+
If passed a callable, then it should have the signature:
931+
932+
def filter_func(np.ndarray[(M, N, 3), float]) -> \
933+
np.ndarray[(M, N, 3), float]
934+
935+
If passed a string, it should be one of the names accepted by
936+
`matplotlib.colors.get_color_filter`.
933937
"""
938+
if isinstance(filter_func, str):
939+
from . import colors
940+
filter_func = colors.get_color_filter(filter_func)
934941
self._agg_filter = filter_func
935942
self.stale = True
936943

lib/matplotlib/colors.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2394,3 +2394,67 @@ def from_levels_and_colors(levels, colors, extend='neither'):
23942394

23952395
norm = BoundaryNorm(levels, ncolors=n_data_colors)
23962396
return cmap, norm
2397+
2398+
2399+
def get_color_filter(name):
2400+
"""
2401+
Given a color filter name, create a color filter function.
2402+
2403+
Parameters
2404+
----------
2405+
name : str
2406+
The color filter name, one of the following:
2407+
2408+
- ``'greyscale'``: Convert the input to luminosity.
2409+
- ``'deuteranopia'``: Simulate the most common form of red-green
2410+
colorblindness.
2411+
- ``'protanopia'``: Simulate a rarer form of red-green colorblindness.
2412+
- ``'tritanopia'``: Simulate the rare form of blue-yellow
2413+
colorblindness.
2414+
2415+
Color conversions use `colorspacious`_.
2416+
2417+
Returns
2418+
-------
2419+
callable
2420+
A color filter function that has the form:
2421+
2422+
def filter(input: np.ndarray[M, N, D])-> np.ndarray[M, N, D]
2423+
2424+
where (M, N) are the image dimentions, and D is the color depth (3 for
2425+
RGB, 4 for RGBA). Alpha is passed through unchanged and otherwise
2426+
ignored.
2427+
"""
2428+
from colorspacious import cspace_converter
2429+
2430+
filter = _api.check_getitem({
2431+
'greyscale': 'greyscale',
2432+
'deuteranopia': 'deuteranomaly',
2433+
'protanopia': 'protanomaly',
2434+
'tritanopia': 'tritanomaly',
2435+
}, name=name)
2436+
2437+
if filter == 'greyscale':
2438+
rgb_to_jch = cspace_converter('sRGB1', 'JCh')
2439+
jch_to_rgb = cspace_converter('JCh', 'sRGB1')
2440+
2441+
def filter(im):
2442+
greyscale_JCh = rgb_to_jch(im)
2443+
greyscale_JCh[..., 1] = 0
2444+
im = jch_to_rgb(greyscale_JCh)
2445+
return im
2446+
else:
2447+
cvd_space = {'name': 'sRGB1+CVD', 'cvd_type': filter,
2448+
'severity': 100}
2449+
filter = cspace_converter(cvd_space, "sRGB1")
2450+
2451+
def filter_func(im, dpi):
2452+
alpha = None
2453+
if im.shape[-1] == 4:
2454+
im, alpha = im[..., :3], im[..., 3]
2455+
im = filter(im)
2456+
if alpha is not None:
2457+
im = np.dstack((im, alpha))
2458+
return np.clip(im, 0, 1), 0, 0
2459+
2460+
return filter_func

0 commit comments

Comments
 (0)