""" Adapted from "The Color Blind Simulation function" by Matthew Wickline and the Human - Computer Interaction Resource Network (http://hcirn.com/), 2000 - 2001. """ import numpy as np rBlind = { "protan": {"cpu": 0.735, "cpv": 0.265, "am": 1.273463, "ayi": -0.073894}, "deutan": {"cpu": 1.14, "cpv": -0.14, "am": 0.968437, "ayi": 0.003331}, "tritan": {"cpu": 0.171, "cpv": -0.003, "am": 0.062921, "ayi": 0.292119}, } def rgb2xyz(rgb): r = rgb[0] g = rgb[1] b = rgb[2] x = 0.430574 * r + 0.341550 * g + 0.178325 * b y = 0.222015 * r + 0.706655 * g + 0.071330 * b z = 0.020183 * r + 0.129553 * g + 0.939180 * b return x, y, z def xyz2rgb(xyz): x = xyz[0] y = xyz[1] z = xyz[2] r = 3.063218 * x - 1.393325 * y - 0.475802 * z g = -0.969243 * x + 1.875966 * y + 0.041555 * z b = 0.067871 * x - 0.228834 * y + 1.069251 * z return r, g, b def anomylize(a, b): v = 1.75 d = v * 1 + 1 return ( (v * b[0] + a[0] * 1) / d, (v * b[1] + a[1] * 1) / d, (v * b[2] + a[2] * 1) / d, ) def monochrome(rgb): z = rgb[0] * 0.299 + rgb[1] * 0.587 + rgb[2] * 0.114 return z, z, z def blindMK(rgb, t): gamma = 2.2 wx = 0.312713 wy = 0.329016 wz = 0.358271 r = rgb[0] g = rgb[1] b = rgb[2] c_rgb = (r**gamma, g**gamma, b**gamma) c_xyz = rgb2xyz(c_rgb) sum_xyz = sum(c_xyz) c_u = 0 c_v = 0 if sum_xyz != 0: c_u = c_xyz[0] / sum_xyz c_v = c_xyz[1] / sum_xyz nx = wx * c_xyz[1] / wy nz = wz * c_xyz[1] / wy d_y = 0 if c_u < rBlind[t]["cpu"]: clm = (rBlind[t]["cpv"] - c_v) / (rBlind[t]["cpu"] - c_u) else: clm = (c_v - rBlind[t]["cpv"]) / (c_u - rBlind[t]["cpu"]) clyi = c_v - c_u * clm d_u = (rBlind[t]["ayi"] - clyi) / (clm - rBlind[t]["am"]) d_v = (clm * d_u) + clyi s_x = d_u * c_xyz[1] / d_v s_y = c_xyz[1] s_z = (1 - (d_u + d_v)) * c_xyz[1] / d_v s_rgb = xyz2rgb((s_x, s_y, s_z)) d_x = nx - s_x d_z = nz - s_z d_rgb = xyz2rgb((d_x, d_y, d_z)) if d_rgb[0]: const = 0 if s_rgb[0] < 0 else 1 adjr = (const - s_rgb[0]) / d_rgb[0] else: adjr = 0 if d_rgb[1]: const = 0 if s_rgb[1] < 0 else 1 adjg = (const - s_rgb[1]) / d_rgb[1] else: adjg = 0 if d_rgb[2]: const = 0 if s_rgb[2] < 0 else 1 adjb = (const - s_rgb[2]) / d_rgb[2] else: adjb = 0 adjust = max( [ 0 if adjr > 1 or adjr < 0 else adjr, 0 if adjg > 1 or adjg < 0 else adjg, 0 if adjb > 1 or adjb < 0 else adjb, ] ) s_r = s_rgb[0] + (adjust * d_rgb[0]) s_g = s_rgb[1] + (adjust * d_rgb[1]) s_b = s_rgb[2] + (adjust * d_rgb[2]) def z(v): if v <= 0: const = 0.0 elif v >= 1: const = 1.0 else: const = v ** (1 / gamma) return const return z(s_r), z(s_g), z(s_b) fBlind = { "Normal": lambda v: v, "Protanopia": lambda v: blindMK(v, "protan"), "Protanomaly": lambda v: anomylize(v, blindMK(v, "protan")), "Deuteranopia": lambda v: blindMK(v, "deutan"), "Deuteranomaly": lambda v: anomylize(v, blindMK(v, "deutan")), "Tritanopia": lambda v: blindMK(v, "tritan"), "Tritanomaly": lambda v: anomylize(v, blindMK(v, "tritan")), "Achromatopsia": lambda v: monochrome(v), "Achromatomaly": lambda v: anomylize(v, monochrome(v)), } # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! def simulate_image(img_path, colorblind_type): """ :param img_path: :param colorblind_type: Type of colourblindness to simulate, can be: * 'Normal': Normal vision * 'Protanopia': Red-green colorblindness (1% males) * 'Protanomaly': Red-green colorblindness (1% males, 0.01% females) * 'Deuteranopia': Red-green colorblindness (1% males) * 'Deuteranomaly': Red-green colorblindness (most common type: 6% males, 0.4% females) * 'Tritanopia': Blue-yellow colourblindness (<1% males and females) * 'Tritanomaly' Blue-yellow colourblindness (0.01% males and females) * 'Achromatopsia': Total colourblindness * 'Achromatomaly': Total colourblindness :return: """ import matplotlib.image as mpimg import matplotlib.pyplot as plt filter_function = fBlind[colorblind_type] img = mpimg.imread(img_path) n_rows = img.shape[0] n_columns = img.shape[1] filtered_img = np.zeros((n_rows, n_columns, 3)) for r in range(n_rows): for c in range(n_columns): filtered_img[r, c] = filter_function(img[r, c, 0:3]) fig, axes = plt.subplots(1, 2, figsize=(12, 6)) axes[0].imshow(img) axes[1].imshow(filtered_img) axes[0].axis("off") axes[1].axis("off") axes[0].set_title("Normal Vision") axes[1].set_title("With " + colorblind_type) plt.show() def colorblind_filter(color, colorblind_type="Deuteranomaly"): """ Transforms an (r,g,b) colour into a simulation of how a person with colourblindnes would see that colour. :param color: rgb colour tuple to convert :param colorblind_type: Type of colourblindness to simulate, can be: * 'Normal': Normal vision * 'Protanopia': Red-green colorblindness (1% males) * 'Protanomaly': Red-green colorblindness (1% males, 0.01% females) * 'Deuteranopia': Red-green colorblindness (1% males) * 'Deuteranomaly': Red-green colorblindness (most common type: 6% males, 0.4% females) * 'Tritanopia': Blue-yellow colourblindness (<1% males and females) * 'Tritanomaly' Blue-yellow colourblindness (0.01% males and females) * 'Achromatopsia': Total colourblindness * 'Achromatomaly': Total colourblindness :return: """ filter_function = fBlind[colorblind_type] return filter_function(color) def simulate_colors(colors, colorblind_type="Deuteranomaly", one_row=None, show=True): """ Simulate the appearance of colors with and without colourblindness. :param colors: A list of (r,g,b) colour tuples, with r, g andb floats between 0 and 1. :param colorblind_type: Type of colourblindness to simulate, can be: * 'Normal': Normal vision * 'Protanopia': Red-green colorblindness (1% males) * 'Protanomaly': Red-green colorblindness (1% males, 0.01% females) * 'Deuteranopia': Red-green colorblindness (1% males) * 'Deuteranomaly': Red-green colorblindness (most common type: 6% males, 0.4% females) * 'Tritanopia': Blue-yellow colourblindness (<1% males and females) * 'Tritanomaly' Blue-yellow colourblindness (0.01% males and females) * 'Achromatopsia': Total colourblindness * 'Achromatomaly': Total colourblindness :param one_row: If True display colours on one row, if False as a grid. If one_row=None a grid is used when there are more than 8 colours. :param show: if True, calls ``plt.show()``. :return: """ import matplotlib.pyplot as plt from distinctipy import distinctipy filtered_colors = [colorblind_filter(color, colorblind_type) for color in colors] fig, axes = plt.subplots(1, 2, figsize=(8, 4)) distinctipy.color_swatch( colors, ax=axes[0], one_row=one_row, title="Viewed with Normal Sight" ) distinctipy.color_swatch( filtered_colors, ax=axes[1], one_row=one_row, title="Viewed with " + colorblind_type + " Colour Blindness", ) if show: plt.show() def simulate_clusters( dataset="s2", colorblind_type="Deuteranomaly", colorblind_distinct=False, show=True, ): """ Simulates the appearance of an example clustering dataset with and without colourblindness. :param dataset: The dataset to display, the options are: * s1, s2, s3, s4: 15 clusters with increasing overlaps from s1 to s4 * a1: 20 clusters * a2: 35 clusters * a3: 50 clusters * b1: 100 clusters :param colorblind_type: Type of colourblindness to simulate, can be: * 'Normal': Normal vision * 'Protanopia': Red-green colorblindness (1% males) * 'Protanomaly': Red-green colorblindness (1% males, 0.01% females) * 'Deuteranopia': Red-green colorblindness (1% males) * 'Deuteranomaly': Red-green colorblindness (most common type: 6% males, 0.4% females) * 'Tritanopia': Blue-yellow colourblindness (<1% males and females) * 'Tritanomaly' Blue-yellow colourblindness (0.01% males and females) * 'Achromatopsia': Total colourblindness * 'Achromatomaly': Total colourblindness :param colorblind_distinct: If True generate colours to be as distinct as possible for colorblind_type. Else generate colours that are as distinct as possible for normal vision. :param show: if True, calls ``plt.show()``. :return: """ import matplotlib.pyplot as plt import pandas as pd from distinctipy import distinctipy if dataset not in ("s1", "s2", "s3", "s4", "a1", "a2", "a3", "b1"): raise ValueError("dataset must be s1, s2, s3, s4, a1, a2, a3 or b1") URL = ( "https://raw.githubusercontent.com/alan-turing-institute/distinctipy/" "main/distinctipy/datasets/" ) df = pd.read_csv(URL + dataset + ".csv") if colorblind_distinct: orig_colors = distinctipy.get_colors( df["cluster"].nunique(), colorblind_type=colorblind_type ) else: orig_colors = distinctipy.get_colors(df["cluster"].nunique()) orig_cmap = distinctipy.get_colormap(orig_colors) filtered_colors = [ colorblind_filter(color, colorblind_type) for color in orig_colors ] filtered_cmap = distinctipy.get_colormap(filtered_colors) fig, axes = plt.subplots(1, 2, figsize=(10, 5)) fig.tight_layout(rect=[0, 0.03, 1, 0.95]) fig.suptitle(str(df["cluster"].nunique()) + " clusters", fontsize=20) axes[0].scatter(df["x"], df["y"], c=df["cluster"], cmap=orig_cmap, s=6) axes[0].get_xaxis().set_visible(False) axes[0].get_yaxis().set_visible(False) axes[0].set_title("With Normal Vision") axes[1].scatter(df["x"], df["y"], c=df["cluster"], cmap=filtered_cmap, s=6) axes[1].get_xaxis().set_visible(False) axes[1].get_yaxis().set_visible(False) axes[1].set_title("With " + colorblind_type + " Colourblindness") if show: plt.show() def _main(): from distinctipy import distinctipy colors = distinctipy.get_colors(36) simulate_colors(colors, "Deuteranomaly") if __name__ == "__main__": _main()