Simulación de deficiencias en la visión del color con Matplotlib

Beginner

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

Introducción

Matplotlib es una popular biblioteca de visualización de datos en Python. Tiene varias características integradas, incluyendo la capacidad de simular deficiencias en la visión del color. Esta práctica te guiará a través de los pasos para utilizar Matplotlib para simular deficiencias en la visión del color.

Consejos sobre la VM

Una vez finalizada la inicialización de la VM, haz clic en la esquina superior izquierda para cambiar a la pestaña Cuaderno y acceder a Jupyter Notebook para practicar.

A veces, es posible que tengas que esperar unos segundos a que Jupyter Notebook termine de cargarse. La validación de las operaciones no se puede automatizar debido a las limitaciones de Jupyter Notebook.

Si tienes problemas durante el aprendizaje, no dudes en preguntar a Labby. Proporciona retroalimentación después de la sesión y resolveremos rápidamente el problema para ti.

Importar las bibliotecas y módulos necesarios

En primer lugar, necesitamos importar las bibliotecas y módulos necesarios, incluyendo Matplotlib, NumPy y colorspacious. También establecemos las opciones de filtro de color que queremos simular.

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",
}

Definir la función de filtro de color

A continuación, definimos una función que crea una función de filtro de color basada en el nombre del filtro de color. Esta función utiliza el módulo colorspacious para convertir la imagen de entrada a un espacio de color diferente según el nombre del filtro de color.

def _get_color_filter(name):
    """
    Dado el nombre de un filtro de color, crea una función de filtro de color.

    Parámetros
    ----------
    name : str
        El nombre del filtro de color, uno de los siguientes:

        - ``"none"``:...
        - ``"greyscale"``: Convertir la entrada a luminosidad.
        - ``"deuteranopia"``: Simular la forma más común de daltonismo de rojo-verde.
        - ``"protanopia"``: Simular una forma menos común de daltonismo de rojo-verde.
        - ``"tritanopia"``: Simular la forma rara de daltonismo azul-amarillo.

        Las conversiones de color utilizan `colorspacious`_.

    Devuelve
    -------
    callable
        Una función de filtro de color que tiene la forma:

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

        donde (M, N) son las dimensiones de la imagen y D es la profundidad de color (3 para RGB, 4 para RGBA). El canal alfa se pasa sin cambios y en caso contrario se ignora.
    """
    if name not in _MENU_ENTRIES:
        raise ValueError(f"Nombre de filtro no admitido: {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

Establecer la entrada del menú

Definimos una función que establece la entrada del menú basada en el nombre del filtro de color seleccionado. Esta función actualiza la función de filtro de color según la selección.

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

Configurar la barra de herramientas

A continuación, definimos una función que configura la barra de herramientas según el tipo de backend utilizado. Esta función crea un botón que permite al usuario seleccionar el tipo de filtro de color que se desea simular.

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")

Crear imágenes de muestra

Creamos imágenes de muestra para demostrar la función de filtro de color. Importamos una imagen de muestra de Grace Hopper y la representamos utilizando Matplotlib. También creamos una gráfica de ondas sinusoidales.

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()

Resumen

En este laboratorio, aprendimos cómo simular deficiencias en la visión del color utilizando Matplotlib. Utilizamos el módulo colorspacious para convertir la imagen de entrada a un espacio de color diferente según el nombre del filtro de color seleccionado. También creamos imágenes de muestra para demostrar la función de filtro de color.