project-halide / ui /components.py
Lonelyguyse1's picture
Deploy Project Halide Gradio Space
908aa37 verified
Raw
History Blame Contribute Delete
20.2 kB
"""UI components. Defect list rendering and shared visual helpers."""
from __future__ import annotations
import base64
import functools
import html
import json
import time
from pathlib import Path
import re
from typing import Iterable
from data.schemas import LABEL_DISPLAY_NAMES
from data.preprocessing import image_to_data_uri
@functools.lru_cache(maxsize=1)
def _logo_html() -> str:
path = Path(__file__).resolve().parents[1] / "assets" / "logo.jpg"
if not path.exists():
return '<span class="halide-brand-mark halide-brand-mark-text">H</span>'
encoded = base64.b64encode(path.read_bytes()).decode("ascii")
return (
'<img class="halide-brand-mark" '
f'src="data:image/jpeg;base64,{encoded}" alt="" />'
)
COMPARE_DEFAULT_SPLIT = 50
HEADER_HTML = f"""
<div id="halide-header">
<div class="halide-brand-lockup">
{_logo_html()}
<div>
<span class="halide-kicker">Project Halide</span>
<h1>Analog Film Diagnostic Workbench</h1>
</div>
</div>
<div class="halide-model-strip">
<a href="https://huggingface.co/Lonelyguyse1" target="_blank" rel="noreferrer">Lonelyguyse1</a>
<a href="https://huggingface.co/Lonelyguyse1/halide-vision" target="_blank" rel="noreferrer">halide-vision</a>
<span>MiniCPM-V 4.6</span>
<span>Nemotron Mini 4B</span>
</div>
</div>
"""
EMPTY_STATE = '<p class="halide-muted">Awaiting scan.</p>'
REPORT_EMPTY_STATE = (
'<div class="halide-empty-card">'
'<span>Report</span>'
'<strong>No scan analyzed yet</strong>'
'<p>Results, evidence counts, and physical fixes will appear here after a GPU run.</p>'
"</div>"
)
LIGHTTABLE_EMPTY_STATE = (
'<div class="halide-empty-lighttable">'
'<div class="halide-empty-frame-grid">'
'<div><span>Original</span></div>'
'<div><span>Validated overlay</span></div>'
"</div>"
'<div class="halide-empty-center">'
'<span>Ready</span>'
'<strong>No scan loaded</strong>'
"</div>"
"</div>"
)
REPORT_SECTIONS = {
"root cause": "Root cause",
"evidence": "Evidence",
"physical fixes": "Physical fixes",
"confidence": "Confidence",
"next inspection": "Next inspection",
}
def defect_pills_html(label_counts: dict[str, int]) -> str:
"""Render defect counts as colored pills."""
if not label_counts:
return '<p class="halide-muted">No validated defects detected.</p>'
pills: list[str] = []
for label, count in sorted(label_counts.items(), key=lambda kv: -kv[1]):
display = LABEL_DISPLAY_NAMES.get(label, label.replace("_", " ").title())
pills.append(
f'<span class="halide-defect-pill {html.escape(label)}">'
f"{html.escape(display)} <strong>{int(count)}</strong></span>"
)
return '<div class="halide-pill-row">' + "".join(pills) + "</div>"
def compact_label_counts(label_counts: dict[str, int]) -> str:
if not label_counts:
return "none"
parts = []
for label, count in sorted(label_counts.items(), key=lambda kv: (-kv[1], kv[0])):
display = LABEL_DISPLAY_NAMES.get(label, label.replace("_", " ").title())
parts.append(f"{display}: {int(count)}")
return ", ".join(parts)
def defect_table_rows(result: dict | None) -> list[list[str]]:
"""Return rows for the evidence dataframe."""
if not result:
return []
defects = (result.get("defects", {}) or {}).get("defects", []) or []
if not defects:
return [["", "No validated defects", "", ""]]
rows: list[list[str]] = []
for index, defect in enumerate(defects, start=1):
label = str(defect.get("label", ""))
display = LABEL_DISPLAY_NAMES.get(label, label.replace("_", " ").title())
bbox = defect.get("bbox", []) or []
if len(bbox) == 4:
box_text = ", ".join(f"{float(v):.3f}" for v in bbox)
else:
box_text = "invalid"
confidence = defect.get("confidence")
confidence_text = (
"not emitted" if confidence is None else f"{float(confidence):.2f}"
)
rows.append([str(index), display, confidence_text, box_text])
return rows
def stats_html(result: dict) -> str:
"""Render a stats card with defect counts and timing."""
defects = result.get("defects", {}) or {}
diagnosis = result.get("diagnosis", {}) or {}
total = result.get("total_seconds", 0.0) or 0.0
vision_s = defects.get("inference_seconds", 0.0) or 0.0
reasoning_s = diagnosis.get("reasoning_seconds", 0.0) or 0.0
rows: list[str] = []
rows.append(_stat_row("Total defects", str(defects.get("defect_count", 0))))
rows.append(_stat_row("Dropped (invalid)", str(defects.get("dropped_count", 0))))
rows.append(_stat_row("Duplicates removed", str(defects.get("duplicate_count", 0))))
rows.append(_stat_row("Edge artifacts removed", str(defects.get("edge_artifact_count", 0))))
rows.append(_stat_row("CV assist boxes", str(defects.get("classical_assist_count", 0))))
rows.append(_stat_row("Resized for model", "yes" if defects.get("resized_for_model") else "no"))
rows.append(_stat_row("Vision inference", f"{vision_s:.2f}s"))
rows.append(_stat_row("Reasoning", f"{reasoning_s:.2f}s"))
rows.append(_stat_row("Total", f"{total:.2f}s"))
rows.append(_stat_row("Vision model", _truncate(defects.get("model_path", ""), 46)))
rows.append(_stat_row("Reasoning model", _truncate(diagnosis.get("model_path", ""), 46)))
return (
'<div class="halide-panel-title">Run telemetry</div>'
f'<div class="halide-stats">{"".join(rows)}</div>'
)
def _stat_row(label: str, value: str) -> str:
return (
'<div class="halide-stat">'
f'<span class="halide-stat-label">{html.escape(label)}</span>'
f'<span>{html.escape(value)}</span>'
"</div>"
)
def _truncate(s: str, n: int) -> str:
if len(s) <= n:
return s
return "..." + s[-(n - 3):]
def diagnosis_html(text: str) -> str:
"""Render the Nemotron Markdown report into structured HTML."""
return render_markdown_report(text or "(no diagnosis produced)")
def review_frame_html(original, annotated) -> str:
"""Render reliable full-size image links independent of Gradio fullscreen."""
original_uri = image_to_data_uri(original, max_side=1800, quality=92)
overlay_uri = image_to_data_uri(annotated, max_side=1800, quality=92)
return (
'<div class="halide-review-actions">'
'<a href="'
+ original_uri
+ '" target="_blank" rel="noreferrer">Open original</a>'
'<a href="'
+ overlay_uri
+ '" target="_blank" rel="noreferrer">Open overlay</a>'
"</div>"
)
def comparison_viewer_html(original, annotated) -> str:
"""Render an aligned before/after viewer using the same image canvas."""
original_uri = image_to_data_uri(original, max_side=1800, quality=92)
overlay_uri = image_to_data_uri(annotated, max_side=1800, quality=92)
return (
'<div class="halide-compare-viewer" '
f'style="--halide-split: {COMPARE_DEFAULT_SPLIT}%;">'
'<div class="halide-compare-stage">'
f'<img class="halide-compare-base" src="{original_uri}" alt="" />'
'<div class="halide-compare-overlay">'
f'<img src="{overlay_uri}" alt="" />'
"</div>"
'<div class="halide-compare-divider"></div>'
'<span class="halide-compare-label original">Original</span>'
'<span class="halide-compare-label overlay">Validated overlay</span>'
"</div>"
'<input class="halide-compare-range" type="range" min="0" max="100" '
f'value="{COMPARE_DEFAULT_SPLIT}" '
'aria-label="Compare original and validated overlay" '
'oninput="const viewer=this.closest('
"'.halide-compare-viewer'); "
"if (viewer) { viewer.style.setProperty('--halide-split', this.value + '%'); }"
'" />'
"</div>"
)
def render_markdown_report(text: str) -> str:
"""Render the constrained diagnosis Markdown used by Nemotron.
This intentionally supports only the report shapes we request from the
model: section headings, paragraphs, bullets, and numbered fixes.
"""
sections: list[dict[str, list[str] | str]] = []
current: dict[str, list[str] | str] = {
"title": "Report",
"lines": [],
}
for raw_line in text.replace("\r\n", "\n").split("\n"):
line = raw_line.strip()
if line.startswith("## "):
if current["lines"]:
sections.append(current)
title_key = line[3:].strip().lower()
current = {
"title": REPORT_SECTIONS.get(title_key, line[3:].strip() or "Report"),
"lines": [],
}
continue
current["lines"].append(line)
if current["lines"] or not sections:
sections.append(current)
rendered = []
for section in sections:
title = html.escape(str(section["title"]))
body = _render_report_lines(section["lines"]) # type: ignore[arg-type]
rendered.append(
'<section class="halide-report-section">'
f'<div class="halide-report-heading">{title}</div>'
f'<div class="halide-report-body">{body}</div>'
"</section>"
)
return '<div class="halide-report">' + "".join(rendered) + "</div>"
def _render_report_lines(lines: list[str]) -> str:
blocks: list[str] = []
paragraph: list[str] = []
bullet_items: list[str] = []
ordered_items: list[str] = []
def flush_paragraph() -> None:
if paragraph:
blocks.append(
"<p>"
+ " ".join(_render_inline(part) for part in paragraph if part)
+ "</p>"
)
paragraph.clear()
def flush_bullets() -> None:
if bullet_items:
items = "".join(f"<li>{item}</li>" for item in bullet_items)
blocks.append(f"<ul>{items}</ul>")
bullet_items.clear()
def flush_ordered() -> None:
if ordered_items:
items = "".join(f"<li>{item}</li>" for item in ordered_items)
blocks.append(f"<ol>{items}</ol>")
ordered_items.clear()
for line in lines:
if not line:
flush_paragraph()
flush_bullets()
flush_ordered()
continue
if line.startswith("### "):
flush_paragraph()
flush_bullets()
flush_ordered()
blocks.append(
f'<h4 class="halide-report-subheading">{_render_inline(line[4:].strip())}</h4>'
)
continue
numbered = re.match(r"^\d+\.\s+(.*)$", line)
if line.startswith("- "):
flush_paragraph()
flush_ordered()
bullet_items.append(_render_inline(line[2:].strip()))
elif numbered:
flush_paragraph()
flush_bullets()
ordered_items.append(_render_inline(numbered.group(1).strip()))
else:
flush_bullets()
flush_ordered()
paragraph.append(line)
flush_paragraph()
flush_bullets()
flush_ordered()
return "".join(blocks) or '<p class="halide-muted">No report text.</p>'
def _render_inline(text: str) -> str:
escaped = html.escape(text)
escaped = re.sub(r"`([^`]+)`", r"<code>\1</code>", escaped)
escaped = re.sub(r"\*\*([^*]+)\*\*", r"<strong>\1</strong>", escaped)
escaped = re.sub(r"__([^_]+)__", r"<strong>\1</strong>", escaped)
escaped = re.sub(r"(?<!\*)\*([^*]+)\*(?!\*)", r"<em>\1</em>", escaped)
escaped = re.sub(r"(?<!_)_([^_]+)_(?!_)", r"<em>\1</em>", escaped)
return escaped
def metadata_html(result: dict) -> str:
"""Render a compact metadata strip for the current run."""
meta = result.get("film_metadata", {}) or {}
confidence = str(meta.get("metadata_confidence", "low") or "low")
rows = [
_stat_row("Film stock", str(meta.get("film_type", "Unknown"))),
_stat_row("Age", f"{meta.get('film_age_years', 0)} years"),
_stat_row("Storage", str(meta.get("storage", "unknown"))),
_stat_row("Scan DPI", str(meta.get("scan_resolution_dpi", "unknown"))),
_stat_row("Metadata confidence", confidence.title()),
]
return (
'<div class="halide-panel-title">Film dossier</div>'
f'<div class="halide-stats compact">{"".join(rows)}</div>'
)
def confidence_notice_html(result: dict) -> str:
"""Render a small evidence-quality notice from validation metadata."""
defects = result.get("defects", {}) or {}
total = int(defects.get("defect_count", 0) or 0)
dropped = int(defects.get("dropped_count", 0) or 0)
duplicate = int(defects.get("duplicate_count", 0) or 0)
meta = result.get("film_metadata", {}) or {}
confidence = str(meta.get("metadata_confidence", "low") or "low").lower()
confidence_values = [
defect.get("confidence")
for defect in defects.get("defects", []) or []
if defect.get("confidence") is not None
]
if total == 0:
message = "No validated boxes were returned. Inspect the scan before assuming a film fault."
tone = "neutral"
elif not confidence_values:
message = (
"Defect boxes passed schema validation. MiniCPM did not emit numeric "
"per-box confidence for this run."
)
tone = "good"
elif confidence == "low":
message = "Metadata is marked low confidence, so the diagnosis is driven mainly by visible defects."
tone = "caution"
elif dropped or duplicate:
message = f"Schema checks removed {dropped} invalid boxes and {duplicate} duplicate boxes."
tone = "caution"
else:
message = "Schema validation passed for the visible defect evidence."
tone = "good"
return f'<div class="halide-notice {tone}">{html.escape(message)}</div>'
def run_state_html(result: dict | None) -> str:
"""Render a top-level status block for the current diagnosis."""
if not result:
return (
'<div class="halide-run-state idle">'
'<span class="halide-run-eyebrow">Ready</span>'
"<strong>Load a scan to begin inspection.</strong>"
"</div>"
)
defects = result.get("defects", {}) or {}
count = int(defects.get("defect_count", 0) or 0)
dropped = int(defects.get("dropped_count", 0) or 0)
duplicates = int(defects.get("duplicate_count", 0) or 0)
if count:
title = f"{count} validated defect{'s' if count != 1 else ''}"
tone = "active"
else:
title = "No validated defects"
tone = "quiet"
detail = (
f"{dropped} invalid removed, {duplicates} duplicate removed, "
f"{float(result.get('total_seconds', 0.0) or 0.0):.2f}s total"
)
return (
f'<div class="halide-run-state {tone}">'
'<span class="halide-run-eyebrow">Current scan</span>'
f"<strong>{html.escape(title)}</strong>"
f"<span>{html.escape(detail)}</span>"
"</div>"
)
def history_label(entry: dict) -> str:
"""Return a compact plain-text label for the history dropdown."""
stamp = time.strftime(
"%Y-%m-%d %H:%M",
time.localtime(float(entry.get("created_at", 0) or 0)),
)
film = str(entry.get("film_type", "Unknown"))
count = int(entry.get("defect_count", 0) or 0)
return f"{stamp} | {film} | {count} defects"
def history_table_rows(entries: Iterable[dict]) -> list[list[str]]:
rows: list[list[str]] = []
for entry in entries:
stamp = time.strftime(
"%Y-%m-%d %H:%M",
time.localtime(float(entry.get("created_at", 0) or 0)),
)
rows.append(
[
stamp,
str(entry.get("film_type", "Unknown")),
str(int(entry.get("defect_count", 0) or 0)),
compact_label_counts(entry.get("label_counts", {}) or {}),
]
)
return rows
def history_choices(entries: Iterable[dict]) -> list[tuple[str, str]]:
return [(history_label(e), str(e.get("id", ""))) for e in entries if e.get("id")]
def history_detail_html(entry: dict | None) -> str:
if not entry:
return '<p class="halide-muted">Select a diagnosis to review details.</p>'
counts = entry.get("label_counts", {}) or {}
raw = entry.get("raw_json", {}) or {}
meta = raw.get("film_metadata", {}) if raw else {}
confidence = meta.get("metadata_confidence", entry.get("metadata_confidence", "low"))
preview = raw.get("preview", {}) if raw else {}
preview_html = ""
overlay_uri = str(preview.get("overlay", "") or "")
original_uri = str(preview.get("original", "") or "")
if overlay_uri.startswith("data:image/") or original_uri.startswith("data:image/"):
hero_uri = overlay_uri if overlay_uri.startswith("data:image/") else original_uri
preview_html = (
'<div class="halide-history-preview">'
f'<img src="{html.escape(hero_uri, quote=True)}" alt="" />'
'<div class="halide-history-preview-actions">'
)
if original_uri.startswith("data:image/"):
preview_html += (
f'<a href="{html.escape(original_uri, quote=True)}" '
'target="_blank" rel="noreferrer">Original</a>'
)
if overlay_uri.startswith("data:image/"):
preview_html += (
f'<a href="{html.escape(overlay_uri, quote=True)}" '
'target="_blank" rel="noreferrer">Overlay</a>'
)
preview_html += "</div></div>"
stamp = time.strftime(
"%Y-%m-%d %H:%M",
time.localtime(float(entry.get("created_at", 0) or 0)),
)
header_rows = [
_stat_row("Saved", stamp),
_stat_row("Film stock", str(entry.get("film_type", "Unknown"))),
_stat_row("Age", f"{entry.get('film_age_years', '?')} years"),
_stat_row("Storage", str(entry.get("storage", "?"))),
_stat_row("Scan DPI", str(entry.get("scan_dpi", "?"))),
_stat_row("Metadata confidence", str(confidence).title()),
_stat_row("Vision model", _truncate(str(entry.get("vision_model", "")), 60)),
_stat_row("Reasoning model", _truncate(str(entry.get("reasoning_model", "")), 60)),
]
return (
'<div class="halide-history-detail">'
f"{preview_html}"
f'<div class="halide-stats compact">{"".join(header_rows)}</div>'
'<div class="halide-subsection">Defects</div>'
f"{defect_pills_html(counts)}"
'<div class="halide-subsection">Diagnosis</div>'
f"{diagnosis_html(str(entry.get('diagnosis_text', '')))}"
"</div>"
)
def raw_json_text(result_or_entry: dict | None) -> str:
if not result_or_entry:
return "{}"
if "raw_json" in result_or_entry:
payload = result_or_entry.get("raw_json") or {}
else:
payload = result_or_entry
payload = _strip_preview(payload)
return json.dumps(payload, indent=2, sort_keys=True)
def _strip_preview(payload: dict) -> dict:
if not isinstance(payload, dict):
return {}
clean = dict(payload)
if "preview" in clean:
preview = clean.get("preview") or {}
if isinstance(preview, dict):
clean["preview"] = {
key: "[image data URI omitted from JSON view]"
for key in preview
}
else:
clean["preview"] = "[image data URI omitted from JSON view]"
return clean
__all__ = [
"HEADER_HTML",
"EMPTY_STATE",
"LIGHTTABLE_EMPTY_STATE",
"REPORT_EMPTY_STATE",
"compact_label_counts",
"comparison_viewer_html",
"confidence_notice_html",
"defect_table_rows",
"defect_pills_html",
"diagnosis_html",
"history_choices",
"history_detail_html",
"history_label",
"history_table_rows",
"metadata_html",
"render_markdown_report",
"run_state_html",
"raw_json_text",
"review_frame_html",
"stats_html",
]