Spaces:
Running
Running
| import gradio as gr | |
| import numpy as np | |
| from PIL import Image | |
| theme_id = "HokageM/EVA_01" | |
| theme = gr.themes.ThemeClass.from_hub(theme_id) | |
| HEADER = """ | |
| This is a color vision correction tool. | |
| The `Deuteranomaly` and `Protanomaly` sliders control the strength of the correction. In this context, correction means that the image colors are adjusted to make color differences more distinguishable for people with color vision deficiencies. | |
| """ | |
| DESCRIPTION_FORMULA = r""" | |
| I have a mix of deuteranomaly and protanomaly, which motivated me to create this tool. With the correction enabled, colorblind tests become much more solvable for me. | |
| The correction formula comes from this [paper](https://arxiv.org/abs/1711.10662) by Jinmi Lee: | |
| $$ | |
| \begin{bmatrix} | |
| R' \\ | |
| G' \\ | |
| B' | |
| \end{bmatrix} | |
| = | |
| \begin{bmatrix} | |
| 1 - \frac{\alpha_d}{2} & \frac{\alpha_d}{2} & 0 \\ | |
| \frac{\alpha_p}{2} & 1 - \frac{\alpha_p}{2} & 0 \\ | |
| \frac{\alpha_p}{4} & \frac{\alpha_d}{4} & 1 - \frac{\alpha_p + \alpha_d}{4} | |
| \end{bmatrix} | |
| \begin{bmatrix} | |
| R \\ | |
| G \\ | |
| B | |
| \end{bmatrix} | |
| $$ | |
| You can set $\alpha_d$ by adjusting the `Deuteranomaly` slider and $\alpha_p$ by adjusting the `Protanomaly` slider. | |
| """ | |
| def correction_for_colorblindness(image_array: np.array, degree_protanomaly: float, degree_deuteranomaly: float): | |
| """Apply a simple protanomaly/deuteranomaly correction matrix. | |
| The input image is converted to float during calculation to avoid uint8 | |
| overflow/underflow. The result is clipped back into the valid RGB range. | |
| """ | |
| if degree_protanomaly < 0 or degree_deuteranomaly < 0: | |
| raise ValueError("Colorblindness correction degrees must be >= 0.") | |
| r = image_array[..., 0].astype(float) | |
| g = image_array[..., 1].astype(float) | |
| b = image_array[..., 2].astype(float) | |
| corrected = image_array.astype(float).copy() | |
| corrected[..., 0] = (1 - degree_deuteranomaly / 2) * r + ( | |
| degree_deuteranomaly / 2 | |
| ) * g | |
| corrected[..., 1] = (degree_protanomaly / 2) * r + (1 - degree_protanomaly / 2) * g | |
| corrected[..., 2] = ( | |
| (degree_protanomaly / 4) * r | |
| + (degree_deuteranomaly / 4) * g | |
| + (1 - (degree_deuteranomaly + degree_protanomaly) / 4) * b | |
| ) | |
| return np.clip(corrected, 0, 255).astype(np.uint8) | |
| def load_demo(): | |
| return Image.open("./color_blind_test.jpg") | |
| def unload(): | |
| return None, None | |
| with gr.Blocks() as demo: | |
| gr.Markdown("# Byakugan Visualizer") | |
| gr.Markdown(HEADER) | |
| with gr.Row(): | |
| with gr.Column(): | |
| deuter_degree = gr.Slider(step=0.5, minimum=0, maximum=5, label=r"Deuteranomaly") | |
| prot_degree = gr.Slider(step=0.5, minimum=0, maximum=5, label="Protanomaly") | |
| correction_btn = gr.Button("Create Correction", variant="primary") | |
| gr.Markdown(DESCRIPTION_FORMULA, latex_delimiters=[{"left": "$$", "right": "$$", "display": True}, | |
| {"left": "$", "right": "$", "display": False}]) | |
| with gr.Row(): | |
| img = gr.Image() | |
| processed_img = gr.Image() | |
| with gr.Row(): | |
| load_demo_btn = gr.Button("Load Colorblind Test", variant="primary") | |
| unload_btn = gr.Button("Unload Images", variant="secondary") | |
| deuter_degree.change( | |
| fn=correction_for_colorblindness, | |
| inputs=[img, deuter_degree, prot_degree], | |
| outputs=[processed_img], | |
| ) | |
| prot_degree.change( | |
| fn=correction_for_colorblindness, | |
| inputs=[img, deuter_degree, prot_degree], | |
| outputs=[processed_img], | |
| ) | |
| correction_btn.click( | |
| fn=correction_for_colorblindness, | |
| inputs=[img, deuter_degree, prot_degree], | |
| outputs=[processed_img], | |
| ) | |
| load_demo_btn.click( | |
| fn=load_demo, | |
| outputs=[img], | |
| ) | |
| unload_btn.click( | |
| fn=unload, | |
| outputs=[img, processed_img], | |
| ) | |
| demo.launch(theme=theme) | |