| # Architecture β Color-UX-Access |
|
|
| > How the system works: screenshot β CVD simulation β VLM β WCAG report. |
| > For deployment specifics, see `docs/DEPLOYMENT.md`. For test patterns, see `docs/TESTING.md`. |
|
|
| --- |
|
|
| ## Overview |
|
|
| **Purpose:** Simulate how any webpage screenshot looks through the eyes of a colorblind user, then use a 32B VLM to audit it as an accessibility expert would β reporting findings against WCAG 2.1 standards. |
|
|
| **Serves:** A person with CVD (8% of men, 0.5% of women) who encounters sites daily using color alone to convey meaning β error states, required fields, status indicators. |
|
|
| **Philosophy:** The app is a proxy for the user's own eyes. It captures the visual experience the way a colorblind user would encounter it β not how the DOM is structured. |
|
|
| --- |
|
|
| ## System Architecture |
|
|
| ``` |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| β USER FLOW β |
| β Upload PNG βββΊ CVD Gallery (10 types) βββΊ WCAG Report β |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| |
| Screenshot (file upload) |
| β |
| βΌ |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| β Stage 1: CVD Simulation (CPU β instant, no GPU needed) β |
| β 10 variants via deficiency_config (8 DaltonLens + 2 grayscale) β |
| β β Gallery: original + protanopia, deuteranopia, tritanopia, β |
| β protanomaly, deuteranomaly, tritanomaly, β |
| β severe_protanopia, severe_deuteranopia, β |
| β achromatopsia, achromatomaly β |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| β |
| βΌ |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| β Stage 2: VLM Inference (GPU β ~90s first call, <5s cached) β |
| β β |
| β Local dev: analyze_with_vlm() β HF Router API β |
| β β CohereLabs/aya-vision-32b β |
| β β |
| β HF Space: @spaces.GPU(duration=120) wraps entire UI β |
| β same HF Router API β same model β |
| β β |
| β Modal: upload_screenshot() β vlm_inference_fn(A10G) β |
| β same HF Router API β same model β |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| β |
| βΌ |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| β Stage 3: WCAG Report (format_wcag_report()) β |
| β JSON β Markdown with severity icons, WCAG links, remediation β |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| ``` |
|
|
| --- |
|
|
| ## Entrypoint Decision |
|
|
| | Context | File to run | Screenshot source | |
| |---------|-------------|------------------| |
| | Local development | `python app/app.py` | Playwright (auto-capture URL) | |
| | HF Space | `python app_space.py` | User uploads PNG | |
| | Modal | `modal_app.py` | User uploads PNG | |
| | CLI VLM test | `python -m vlm.vlm_inference screenshot.png` | Pre-captured PNG | |
|
|
| --- |
|
|
| ## Package Layout |
|
|
| ### `color_ux_access/` β Core (no Gradio dependency) |
|
|
| ``` |
| color_ux_access/ |
| βββ __init__.py from .cvd_sim import simulate_cvd, CVD_VARIANTS |
| βββ cvd_sim.py DaltonLens wrapper, 10 types + grayscale |
| β Public API: simulate_cvd(image, cvd_type) -> PIL Image |
| βββ capture.py Playwright page β full-page screenshot |
| Public API: take_screenshot(page, url, timeout=60000) -> PIL Image |
| ``` |
|
|
| ### `vlm/` β Vision Language Model inference |
|
|
| ``` |
| vlm/ |
| βββ vlm_inference.py HF Router β aya-vision-32b (CLI tool + helper fns) |
| β API: load_model_api(), analyze_image_api() |
| βββ vlm_inference_llama.py llama.cpp GGUF β local vision model (offline) |
| β API: load_llama_model(), analyze_image_with_llama() |
| βββ accessibility_report.py WCAG report generation + markdown formatting |
| API: AccessibilityReport.generate_report(), |
| AccessibilityReport.format_report_as_markdown() |
| ``` |
|
|
| ### `app/` β Local dev Gradio app |
|
|
| ``` |
| app/ |
| βββ app.py gr.Blocks β URL input β Playwright screenshot β CVD β VLM |
| βββ custom_theme.py ColorUXAccessTheme (accessible blue/gray, Inter font) |
| ``` |
|
|
| ### VLM backends (`MODELS` dict in `app.py`) |
|
|
| | Key | Model | Params | Prize | |
| |-----|-------|--------|-------| |
| | `aya-vision-32b` | CohereLabs/aya-vision-32b | 32B | Default, Cohere | |
| | `minicpm-v-4.6` | openbmb/mini-cpm-v-4_6 | ~4B | OpenBMB ($5K swap) | |
| | `nemotron-15b` | nvidia/Nemotron-4-15B-base | ~15B | NVIDIA (unconfirmed) | |
| | `qwen2-vl-7b-gguf` | Qwen2-VL-7B-Instruct GGUF | ~7B | llama.cpp offline | |
| |
| --- |
| |
| ## VLM Prompt System |
| |
| The VLM receives a structured prompt instructing it to return WCAG-specific JSON: |
| |
| ``` |
| You are an accessibility expert specializing in colorblind user experience. |
| Analyze screenshots for WCAG 2.1 compliance issues. |
| For each finding, cite the specific success criterion (1.1.1, 1.4.1, 1.4.3, or 1.4.11). |
| Output a JSON object with this structure: |
| { |
| "findings": [ |
| { |
| "type": "Low Contrast | Color Only Information | Missing Text Alternative | Insufficient Non-Text Contrast", |
| "wcag_criterion": "1.4.1 | 1.4.3 | 1.1.1 | 1.4.11", |
| "description": "...", |
| "severity": "critical | serious | moderate", |
| "location": "Top-left, center, etc." |
| } |
| ], |
| "summary": "Overall assessment", |
| "passes": true/false |
| } |
| ``` |
| |
| Expected response fields: `type`, `wcag_criterion`, `description`, `severity`, `location`, `summary`, `passes`. |
|
|
| --- |
|
|
| ## CVD Simulation Internals |
|
|
| ```python |
| # color_ux_access/cvd_sim.py |
| from daltonlens import simulate |
| import numpy as np |
| from PIL import Image |
| |
| # Singleton simulator (lazy init) |
| _simulator = simulate.Simulator_Machado2009() |
| |
| _DEFFICIENCY_MAP = { |
| 'deuteranopia': simulate.Deficiency.DEUTAN, |
| 'protanopia': simulate.Deficiency.PROTAN, |
| 'tritanopia': simulate.Deficiency.TRITAN, |
| } |
| |
| _SEVERITY_MAP = { |
| 'deuteranopia': 1.0, # dichromacy β full deficiency |
| 'deuteranomaly': 0.6, # anomalous trichromacy β partial |
| } |
| |
| def simulate_cvd(image, cvd_type='deuteranopia') -> Image.Image: |
| if cvd_type == 'achromatopsia': |
| return _grayscale(image) |
| deficiency = _DEFFICIENCY_MAP[cvd_type] |
| severity = _SEVERITY_MAP[cvd_type] |
| im_np = np.array(image, dtype=np.uint8) |
| result_np = _simulator.simulate_cvd(im_np, deficiency, severity) |
| return Image.fromarray(result_np) |
| ``` |
|
|
| Achromatopsia is grayscale via Rec.709 luma coefficients, not a DaltonLens simulator. |
|
|
| --- |
|
|
| ## Gradio 6 Compatibility |
|
|
| In Gradio 6, `theme` and `css` are `launch()` parameters, NOT `Blocks()` constructor parameters. |
|
|
| ```python |
| # β
Correct (Gradio 6) |
| with gr.Blocks(title='Color-UX-Access') as demo: |
| ... |
| demo.launch( |
| theme=gr.themes.Base(primary_hue='blue', secondary_hue='gray'), |
| css=":root { --color-primary: #1E88E5; }", |
| ) |
| |
| # β Wrong (Gradio 5 pattern β will break in Gradio 6) |
| with gr.Blocks(title='Color-UX-Access', theme=custom_theme, css="...") as demo: |
| ... |
| ``` |
|
|
| Test: `pytest tests/test_app_space.py::test_blocks_theme_not_in_constructor -v` |
|
|
| --- |
|
|
| ## WCAG Standards Referenced |
|
|
| | Criterion | Name | What it covers | |
| |-----------|------|---------------| |
| | 1.4.1 | Use of Color | Color cannot be the only means of conveying information | |
| | 1.4.3 | Contrast (Minimum) | 4.5:1 normal text, 3:1 large text | |
| | 1.4.6 | Contrast (Enhanced) | 7:1 normal text, 4.5:1 large text | |
| | 1.4.11 | Non-text Contrast | 3:1 for UI components and graphical objects | |
|
|
| --- |
|
|
| *See also: `docs/DEPLOYMENT.md` for HF Space and Modal specifics, |
| `docs/EVALUATION.md` for sponsor prize matrix and model-swap details.* |