Matplotlib による色覚異常のシミュレーション

Beginner

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

はじめに

Matplotlib は、Python における人気のあるデータ可視化ライブラリです。色覚欠損をシミュレートする機能を含む、さまざまな組み込み機能があります。この実験では、Matplotlib を使用して色覚欠損をシミュレートする手順を案内します。

VM のヒント

VM の起動が完了したら、左上隅をクリックしてノートブックタブに切り替え、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"Unsupported filter name: {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)

    ## A sample image
    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 モジュールを使用しました。また、色フィルタ関数を示すためにサンプル画像を作成しました。