colorization / generate_colorblind_plates.py
stvident's picture
Upload folder using huggingface_hub
96a945a verified
"""
Generate Ishihara-style pseudoisochromatic plates using PIL text for the digit mask.
Guaranteed correct digit shapes.
"""
import numpy as np
from PIL import Image, ImageDraw, ImageFont
import random, math
def hex2rgb(h):
h = h.lstrip('#')
return tuple(int(h[i:i+2], 16) for i in (0, 2, 4))
def jitter_color(rgb, amount=20):
return tuple(max(0, min(255, c + random.randint(-amount, amount))) for c in rgb)
def make_text_mask(number_str, size, font_size=None):
"""Render number_str as a clean white-on-black mask using PIL default font."""
if font_size is None:
font_size = int(size * 0.38) # big β€” ~212px tall for 560
mask = Image.new("L", (size, size), 0)
draw = ImageDraw.Draw(mask)
# Try to get a large font; fall back to default if not available
try:
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", font_size)
except Exception:
try:
font = ImageFont.truetype("/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf", font_size)
except Exception:
font = ImageFont.load_default()
# Get bounding box and centre text
bbox = draw.textbbox((0, 0), number_str, font=font)
tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1]
x = (size - tw) // 2 - bbox[0]
y = (size - th) // 2 - bbox[1]
draw.text((x, y), number_str, fill=255, font=font)
return np.array(mask) > 128
def generate_plate(
size=560,
number_str="74",
fg_colors=None,
bg_colors=None,
out_path="plate.png",
font_size=None,
):
rng = random.Random(42)
mask = make_text_mask(number_str, size, font_size=font_size)
img = Image.new("RGB", (size, size), (215, 215, 215))
draw = ImageDraw.Draw(img)
cx, cy, radius = size // 2, size // 2, size // 2 - 6
draw.ellipse([cx-radius, cy-radius, cx+radius, cy+radius], fill=(210, 210, 210))
step = 9
dot_r_min, dot_r_max = 3, 7
for gx in range(dot_r_max, size - dot_r_max, step):
for gy in range(dot_r_max, size - dot_r_max, step):
px = gx + rng.randint(-3, 3)
py = gy + rng.randint(-3, 3)
if math.hypot(px - cx, py - cy) > radius - dot_r_max:
continue
r = rng.randint(dot_r_min, dot_r_max)
py_c = max(0, min(size-1, py))
px_c = max(0, min(size-1, px))
is_fg = bool(mask[py_c, px_c])
pool = fg_colors if is_fg else bg_colors
color = hex2rgb(rng.choice(pool))
color = jitter_color(color, 18)
draw.ellipse([px-r, py-r, px+r, py+r], fill=color)
draw.ellipse([cx-radius, cy-radius, cx+radius, cy+radius],
outline=(150,150,150), width=3)
img.save(out_path)
fg = int(mask.sum())
print(f" {out_path} number={number_str!r} fg_pixels={fg}")
OUT = "/home/ai24mtech02001/.openclaw/workspace/colorization-webapp/tutorial/colorblind"
print("Generating plates...")
# 1. DEMO β€” "12" vivid red vs blue, visible to everyone
generate_plate(
number_str="12",
fg_colors=["#C0392B","#E74C3C","#A93226","#B03A2E","#D44333"],
bg_colors=["#2471A3","#1A5276","#2E86C1","#1F618D","#5499C2","#2980B9"],
out_path=f"{OUT}/plate_demo.png",
)
# 2. RED-GREEN β€” "74"
# Normal: green pops on orange. Protan/Deutan: both look similar brownish.
generate_plate(
number_str="74",
fg_colors=["#4A7C00","#5B8C00","#6B9E0E","#527A00","#3D6B00","#618500"],
bg_colors=["#C47A1E","#D4892E","#BF6D10","#CC8022","#D99030",
"#B86008","#E09840","#A85500","#CB7818"],
out_path=f"{OUT}/plate_redgreen.png",
)
# 3. BLUE-YELLOW β€” "6"
# Normal: yellow pops on blue. Tritanope: similar.
generate_plate(
number_str="6",
fg_colors=["#E8C200","#F0CA00","#D4B000","#DCBA00","#F5D000","#EBC400"],
bg_colors=["#2C3E7A","#1C2D68","#3B4F90","#253580","#162870","#324488","#2A3C82"],
out_path=f"{OUT}/plate_blueyellow.png",
)
print("Done.")