使用 Matplotlib 模拟色觉缺陷

Beginner

This tutorial is from open-source community. Access the source code

简介

Matplotlib 是 Python 中一个流行的数据可视化库。它有各种内置功能,包括模拟色觉缺陷的能力。本实验将指导你完成使用 Matplotlib 模拟色觉缺陷的步骤。

虚拟机使用提示

虚拟机启动完成后,点击左上角切换到“笔记本”标签页,以访问 Jupyter Notebook 进行练习。

有时,你可能需要等待几秒钟让 Jupyter Notebook 完成加载。由于 Jupyter Notebook 的限制,操作验证无法自动化。

如果你在学习过程中遇到问题,随时向 Labby 提问。课程结束后提供反馈,我们会及时为你解决问题。

导入必要的库和模块

首先,我们需要导入必要的库和模块,包括 Matplotlib、NumPy 和 colorspacious。我们还设置了想要模拟的颜色滤镜选项。

import functools
from pathlib import Path

import colorspacious

import numpy as np

_BUTTON_NAME = "Filter"
_BUTTON_HELP = "Simulate color vision deficiencies"
_MENU_ENTRIES = {
    "None": None,
    "Greyscale": "greyscale",
    "Deuteranopia": "deuteranomaly",
    "Protanopia": "protanomaly",
    "Tritanopia": "tritanomaly",
}

定义颜色滤镜函数

接下来,我们定义一个函数,该函数根据颜色滤镜名称创建一个颜色滤镜函数。此函数使用 colorspacious 模块根据颜色滤镜名称将输入图像转换为不同的颜色空间。

def _get_color_filter(name):
    """
    给定一个颜色滤镜名称,创建一个颜色滤镜函数。

    参数
    ----------
    name : str
        颜色滤镜名称,以下之一:

        - ``"none"``:...
        - ``"greyscale"``: 将输入转换为亮度。
        - ``"deuteranopia"``: 模拟最常见的红绿色盲形式。
        - ``"protanopia"``: 模拟一种较罕见的红绿色盲形式。
        - ``"tritanopia"``: 模拟罕见的蓝黄色盲形式。

        颜色转换使用 `colorspacious`_。

    返回
    -------
    callable
        一个颜色滤镜函数,其形式为:

        def filter(input: np.ndarray[M, N, D])-> np.ndarray[M, N, D]

        其中 (M, N) 是图像尺寸,D 是颜色深度(RGB 为 3,RGBA 为 4)。透明度通道直接传递,其他情况下忽略。
    """
    if name not in _MENU_ENTRIES:
        raise ValueError(f"不支持的滤镜名称:{name!r}")
    name = _MENU_ENTRIES[name]

    if name is None:
        return None

    elif name == "greyscale":
        rgb_to_jch = colorspacious.cspace_converter("sRGB1", "JCh")
        jch_to_rgb = colorspacious.cspace_converter("JCh", "sRGB1")

        def convert(im):
            greyscale_JCh = rgb_to_jch(im)
            greyscale_JCh[..., 1] = 0
            im = jch_to_rgb(greyscale_JCh)
            return im

    else:
        cvd_space = {"name": "sRGB1+CVD", "cvd_type": name, "severity": 100}
        convert = colorspacious.cspace_converter(cvd_space, "sRGB1")

    def filter_func(im, dpi):
        alpha = None
        if im.shape[-1] == 4:
            im, alpha = im[..., :3], im[..., 3]
        im = convert(im)
        if alpha is not None:
            im = np.dstack((im, alpha))
        return np.clip(im, 0, 1), 0, 0

    return filter_func

设置菜单项

我们定义一个函数,该函数根据所选的颜色滤镜名称设置菜单项。此函数会根据所选内容更新颜色滤镜函数。

def _set_menu_entry(tb, name):
    tb.canvas.figure.set_agg_filter(_get_color_filter(name))
    tb.canvas.draw_idle()

设置工具栏

接下来,我们定义一个函数,该函数根据所使用的后端类型来设置工具栏。此函数创建一个按钮,允许用户选择要模拟的颜色滤镜类型。

def setup(figure):
    tb = figure.canvas.toolbar
    if tb is None:
        return
    for cls in type(tb).__mro__:
        pkg = cls.__module__.split(".")[0]
        if pkg!= "matplotlib":
            break
    if pkg == "gi":
        _setup_gtk(tb)
    elif pkg in ("PyQt5", "PySide2", "PyQt6", "PySide6"):
        _setup_qt(tb)
    elif pkg == "tkinter":
        _setup_tk(tb)
    elif pkg == "wx":
        _setup_wx(tb)
    else:
        raise NotImplementedError("The current backend is not supported")

创建示例图像

我们创建示例图像以演示颜色滤镜功能。我们导入一张格蕾丝·霍珀(Grace Hopper)的示例图像,并使用 Matplotlib 进行绘制。我们还创建了一个正弦波的图表。

if __name__ == '__main__':
    import matplotlib.pyplot as plt

    from matplotlib import cbook

    plt.rcParams['figure.hooks'].append('mplcvd:setup')

    fig, axd = plt.subplot_mosaic(
        [
            ['viridis', 'turbo'],
            ['photo', 'lines']
        ]
    )

    delta = 0.025
    x = y = np.arange(-3.0, 3.0, delta)
    X, Y = np.meshgrid(x, y)
    Z1 = np.exp(-X**2 - Y**2)
    Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2)
    Z = (Z1 - Z2) * 2

    imv = axd['viridis'].imshow(
        Z, interpolation='bilinear',
        origin='lower', extent=[-3, 3, -3, 3],
        vmax=abs(Z).max(), vmin=-abs(Z).max()
    )
    fig.colorbar(imv)
    imt = axd['turbo'].imshow(
        Z, interpolation='bilinear', cmap='turbo',
        origin='lower', extent=[-3, 3, -3, 3],
        vmax=abs(Z).max(), vmin=-abs(Z).max()
    )
    fig.colorbar(imt)

    ## 一张示例图像
    with cbook.get_sample_data('grace_hopper.jpg') as image_file:
        photo = plt.imread(image_file)
    axd['photo'].imshow(photo)

    th = np.linspace(0, 2*np.pi, 1024)
    for j in [1, 2, 4, 6]:
        axd['lines'].plot(th, np.sin(th * j), label=f'$\\omega={j}$')
    axd['lines'].legend(ncols=2, loc='upper right')
    plt.show()

总结

在本实验中,我们学习了如何使用 Matplotlib 模拟色觉缺陷。我们使用 colorspacious 模块根据所选的颜色滤镜名称将输入图像转换为不同的颜色空间。我们还创建了示例图像来演示颜色滤镜功能。