"""Gradio app. Main UI definition and layout.""" from __future__ import annotations import html import io import logging from typing import Any import gradio as gr from pipeline.pipeline import run_diagnosis from storage.cache import get_cache from storage.database import init_db, list_recent, record_diagnosis from ui.components import ( HEADER_HTML, THEME_CSS, defect_pills_html, diagnosis_html, render_history, stats_html, ) from ui.theme import build_theme logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) DEFAULT_FILM_TYPES = [ "Kodak Portra 400 (35mm)", "Kodak Tri-X 400 (35mm)", "Kodak Ektar 100 (35mm)", "Ilford HP5 Plus (35mm)", "Ilford Delta 100 (35mm)", "Ilford FP4 Plus (120)", "CineStill 800T (35mm)", "Fujifilm Pro 400H (35mm)", "Fomapan 400 (35mm)", "Other / Unknown", ] STORAGE_OPTIONS = [ "fridge, sealed", "freezer, sealed", "room temp, sealed", "room temp, loose", "shoe box, attic", "shoe box, basement", "unknown", ] RESOLUTION_OPTIONS = [2000, 3000, 4000, 5000, 6000, 8000] def _image_to_bytes(pil_image: Any) -> bytes: buf = io.BytesIO() pil_image.save(buf, format="PNG") return buf.getvalue() def run_pipeline( image: Any, film_type: str, film_age_years: int, storage: str, scan_dpi: int, progress: gr.Progress = gr.Progress(), ) -> tuple[str, str, str, str]: """Gradio handler for the diagnose button.""" if image is None: empty = '
No image provided.
' return empty, empty, empty, render_history(list_recent(limit=10)) try: progress(0.0, "Hashing image for cache lookup...") cache = get_cache() image_bytes = _image_to_bytes(image) cached = cache.get(image_bytes) if cached is not None: logger.info("Returning cached diagnosis") result = cached else: progress(0.1, "Stage 1/2: running vision defect extraction...") result = run_diagnosis( image=image, film_type=film_type or "Unknown 35mm", film_age_years=int(film_age_years or 0), storage=storage or "unknown", scan_resolution_dpi=int(scan_dpi or 4000), ) progress(0.85, "Stage 2/2: persisting diagnosis...") try: record_diagnosis(result) except Exception as exc: # pragma: no cover logger.warning("Failed to record diagnosis: %s", exc) cache.put(image_bytes, result) progress(1.0, "Done.") counts = result.get("defects", {}).get("label_counts", {}) or {} stats = stats_html(result) pills = defect_pills_html(counts) diag = diagnosis_html(result.get("diagnosis", {}).get("diagnosis_text", "")) history = render_history(list_recent(limit=10)) return stats, pills, diag, history except Exception as exc: # pragma: no cover logger.exception("Pipeline failed") err = ( '"
f"{html.escape(str(exc))}Awaiting scan.
' ) with gr.Group(elem_classes="halide-card"): gr.Markdown('Awaiting scan.
' ) with gr.Group(elem_classes="halide-card"): gr.Markdown('Awaiting scan.
' ) with gr.Column(scale=1): with gr.Group(elem_classes="halide-card"): gr.Markdown('