CoolFace
Apppublic

lorensation/doc-benchmark-deepseek-ocr

sourceHugging Faceapache-2.0updated 10mo agoView on Hugging Face
0likes
app.py361 linesDownload Raw Back to root
1"""Streamlit UI for benchmarking DeepSeek-OCR vs Tesseract.2 3Features:4- Upload a single image with optional ground truth to run a benchmark.5- Run a quick batch benchmark on the bundled SROIE2019 dataset.6- Browse summaries of previous benchmark runs.7"""8 9from __future__ import annotations10 11import json12import os13import sys14import tempfile15import uuid16from datetime import datetime17from pathlib import Path18from typing import Dict, List, Optional, Tuple19 20import streamlit as st21 22# Ensure we can import the worker package when running in HF Spaces23REPO_ROOT = Path(__file__).resolve().parents[1]24if str(REPO_ROOT) not in sys.path:25    sys.path.append(str(REPO_ROOT))26 27from services.benchmark_worker import worker  # noqa: E40228 29 30# ---------------------------------------------------------------------------31# Paths and configuration helpers32# ---------------------------------------------------------------------------33DATA_DIR = worker.DATA_DIR34UPLOAD_DIR = worker.UPLOAD_DIR35RESULTS_DIR = worker.RESULTS_DIR36SROIE_PATH = worker.SROIE_DATASET_PATH37 38 39def _ensure_dirs() -> None:40    UPLOAD_DIR.mkdir(parents=True, exist_ok=True)41    RESULTS_DIR.mkdir(parents=True, exist_ok=True)42 43 44def _save_upload(file) -> Path:45    """Persist an uploaded file to the uploads directory and return its path."""46    suffix = Path(file.name).suffix or ".png"47    dest = UPLOAD_DIR / f"{uuid.uuid4()}{suffix}"48    with open(dest, "wb") as fh:49        fh.write(file.getbuffer())50    return dest51 52 53def _parse_ground_truth(gt_text: str, gt_json_file) -> Tuple[Optional[str], Optional[List[str]], Optional[Dict]]:54    """Normalize ground truth inputs from the UI."""55    ground_truth_text = gt_text.strip() if gt_text and gt_text.strip() else None56    ground_truth_lines = ground_truth_text.splitlines() if ground_truth_text else None57 58    ground_truth_fields = None59    if gt_json_file is not None:60        try:61            ground_truth_fields = json.loads(gt_json_file.getvalue().decode("utf-8"))62        except Exception as exc:  # pragma: no cover - UI feedback path63            st.error(f"Could not parse ground truth JSON: {exc}")64 65    return ground_truth_text, ground_truth_lines, ground_truth_fields66 67 68# ---------------------------------------------------------------------------69# Rendering helpers70# ---------------------------------------------------------------------------71def _render_result_details(result: dict) -> None:72    """Pretty-print a single benchmark result."""73    st.success("Benchmark complete")74    meta_cols = st.columns(3)75    meta_cols[0].metric("Run ID", result.get("run_id", "") or "-")76    meta_cols[1].metric("File", result.get("filename", "") or "-")77    meta_cols[2].metric("Created", result.get("created_at", "") or "-")78 79    st.caption(f"Saved to: {result.get('result_file', 'not saved')}")80 81    metrics = result.get("metrics", {})82    lengths = metrics.get("lengths") or {83        "deepseek": len(result.get("deepseek_text", "")),84        "tesseract": len(result.get("tesseract_text", "")),85    }86    char_diff = metrics.get("char_difference") or abs(lengths["deepseek"] - lengths["tesseract"])87 88    with st.container():89        cols = st.columns(3)90        cols[0].metric("DeepSeek length", lengths.get("deepseek", 0))91        cols[1].metric("Tesseract length", lengths.get("tesseract", 0))92        cols[2].metric("Char diff", char_diff)93 94    st.divider()95 96    deepseek_metrics = metrics.get("deepseek", {})97    tesseract_metrics = metrics.get("tesseract", {})98 99    if deepseek_metrics or tesseract_metrics:100        st.subheader("Quality metrics")101        mcols = st.columns(2)102        _render_model_metrics("DeepSeek-OCR", deepseek_metrics, mcols[0])103        _render_model_metrics("Tesseract", tesseract_metrics, mcols[1])104 105    st.subheader("Extracted text")106    text_cols = st.columns(2)107    text_cols[0].write("DeepSeek-OCR")108    text_cols[0].code(result.get("deepseek_text", ""), language="markdown")109 110    text_cols[1].write("Tesseract")111    text_cols[1].code(result.get("tesseract_text", ""), language="markdown")112 113    st.download_button(114        "Download result JSON",115        data=json.dumps(result, ensure_ascii=False, indent=2),116        file_name=f"{result.get('run_id', 'benchmark')}.json",117        mime="application/json",118        use_container_width=True,119    )120 121 122def _render_model_metrics(name: str, payload: dict, container) -> None:123    """Render key metrics for a single model."""124    if not payload:125        container.info("No metrics captured.")126        return127 128    text_metrics = payload.get("text", {})129    llm_metrics = payload.get("llm", {})130 131    if text_metrics:132        cer = text_metrics.get("cer", {}).get("cer")133        wer = text_metrics.get("wer", {}).get("wer")134        ser = text_metrics.get("ser", {}).get("ser")135        if cer is not None or wer is not None or ser is not None:136            container.metric(f"{name} CER", f"{cer:.2f}%" if cer is not None else "-")137            container.metric(f"{name} WER", f"{wer:.2f}%" if wer is not None else "-")138            container.metric(f"{name} SER", f"{ser:.2f}%" if ser is not None else "-")139 140    if llm_metrics:141        token_eff = llm_metrics.get("token_efficiency", {})142        fext = llm_metrics.get("field_extraction", {})143        rows = []144        if token_eff:145            rows.append(f"Token density: {token_eff.get('token_density', 0):.2f}")146            rows.append(f"Chars/token: {token_eff.get('chars_per_token', 0):.2f}")147        if fext:148            rows.append(f"Field F1: {fext.get('field_f1', 0):.2f}")149            rows.append(f"Field recall: {fext.get('field_recall', 0):.2f}")150        if rows:151            container.write("\n".join(rows))152 153 154def _render_batch_summary(summary: dict, results: List[dict]) -> None:155    st.success("SROIE benchmark finished")156    st.write(157        f"Batch ID: `{summary.get('batch_run_id', '-')}`  | "158        f"Split: `{summary.get('split', '-')}`  | "159        f"Samples: `{summary.get('count', 0)}`"160    )161    if summary.get("result_files"):162        st.caption(f"Stored in: {Path(summary['result_files'][0]).parent}")163 164    if results:165        st.subheader("Individual results")166        for item in results:167            with st.expander(f"{item.get('filename', 'sample')} — {item.get('run_id', '')}"):168                _render_result_details(item)169    else:170        st.info("No individual results were returned.")171 172 173# ---------------------------------------------------------------------------174# Page renderers175# ---------------------------------------------------------------------------176def page_single_run() -> None:177    st.header("Upload and Benchmark")178    st.write(179        "Upload an image and optional ground truth. The worker will call DeepSeek-OCR "180        "and Tesseract services, compute metrics, and save the result to the workspace."181    )182 183    upload = st.file_uploader(184        "Image file",185        type=["png", "jpg", "jpeg", "tiff", "bmp", "webp"],186    )187    gt_text = st.text_area(188        "Ground truth text (optional)",189        placeholder="Paste expected text. Leave empty to auto-use SROIE ground truth when the file belongs to the dataset.",190        height=120,191    )192    gt_json = st.file_uploader(193        "Ground truth fields JSON (optional)",194        type=["json"],195        key="gt_json_single",196        help='Optional key-value ground truth (e.g. {"total": "123.00", "date": "2024-01-01"}).',197    )198 199    if upload and st.button("Run benchmark", type="primary", use_container_width=True):200        _ensure_dirs()201        with st.spinner("Running benchmark..."):202            img_path = _save_upload(upload)203            gt_text_val, gt_lines, gt_fields = _parse_ground_truth(gt_text, gt_json)204            result = worker.run_benchmark(205                str(img_path),206                ground_truth_text=gt_text_val,207                ground_truth_lines=gt_lines,208                ground_truth_fields=gt_fields,209            )210        _render_result_details(result)211    elif not upload:212        st.info("Upload an image to begin.")213 214 215def _count_sroie_samples(split: str) -> int:216    patterns = ("*.jpg", "*.jpeg", "*.png")217    img_dir = Path(SROIE_PATH) / split / "img"218    total = 0219    for pattern in patterns:220        total += len(list(img_dir.glob(pattern)))221    return total222 223 224def page_sroie_batch() -> None:225    st.header("SROIE2019 Batch Benchmark")226    st.write(227        "Run a quick benchmark on the bundled SROIE2019 dataset. "228        "Ground truth is auto-loaded from the dataset annotations."229    )230 231    if not SROIE_PATH.exists():232        st.error(f"SROIE dataset not found at {SROIE_PATH}. Add the dataset before running.")233        return234 235    split = st.selectbox("Split", ["train", "test"], index=0)236    total = _count_sroie_samples(split)237    st.caption(f"Found {total} images in {SROIE_PATH / split / 'img'}")238    limit = st.slider("Number of samples to run", min_value=1, max_value=max(1, total) if total else 1, value=min(3, total or 1))239 240    if st.button("Run SROIE benchmark", type="primary", use_container_width=True):241        _ensure_dirs()242        with st.spinner("Processing SROIE samples..."):243            payload = worker.run_sroie_samples(split=split, limit=limit)244        summary = payload.get("summary", {})245        results = payload.get("results", [])246        _render_batch_summary(summary, results)247 248 249def _is_batch_summary(data: dict) -> bool:250    return "batch_run_id" in data or ("summary" in data and isinstance(data["summary"], dict))251 252 253def _load_result_file(path: Path) -> Optional[dict]:254    try:255        with path.open("r", encoding="utf-8") as fh:256            return json.load(fh)257    except Exception:258        return None259 260 261def _collect_history(max_files: int = 200) -> Tuple[List[dict], List[dict]]:262    run_items: List[dict] = []263    batch_items: List[dict] = []264 265    files = sorted(RESULTS_DIR.rglob("*.json"), key=os.path.getmtime, reverse=True)266    for path in files[:max_files]:267        data = _load_result_file(path)268        if not data:269            continue270 271        if _is_batch_summary(data):272            summary = data.get("summary", data)273            batch_items.append(274                {275                    "path": str(path),276                    "batch_run_id": summary.get("batch_run_id"),277                    "split": summary.get("split"),278                    "count": summary.get("count"),279                    "saved": summary.get("result_files", []),280                }281            )282        elif "run_id" in data:283            run_items.append(284                {285                    "path": str(path),286                    "run_id": data.get("run_id"),287                    "filename": data.get("filename"),288                    "created_at": data.get("created_at"),289                    "ground_truth": data.get("ground_truth_available", bool(data.get("metrics", {}).get("deepseek", {}).get("text"))),290                    "char_diff": data.get("metrics", {}).get("char_difference"),291                }292            )293 294    return run_items, batch_items295 296 297def page_history() -> None:298    st.header("Benchmark History")299    st.write("Browse results saved under the workspace `data/results` directory.")300 301    run_items, batch_items = _collect_history()302 303    if batch_items:304        st.subheader("Batch summaries")305        st.table(batch_items)306    else:307        st.info("No batch summaries found.")308 309    st.subheader("Individual runs")310    if not run_items:311        st.info("No individual runs found.")312        return313 314    st.dataframe(run_items, use_container_width=True)315 316    selected = st.selectbox(317        "Inspect a run",318        options=run_items,319        format_func=lambda item: f"{item['run_id']} — {item['filename']}",320    )321 322    if selected:323        data = _load_result_file(Path(selected["path"]))324        if data:325            _render_result_details(data)326        else:327            st.error("Could not load the selected result file.")328 329 330# ---------------------------------------------------------------------------331# App entrypoint332# ---------------------------------------------------------------------------333def main() -> None:334    st.set_page_config(page_title="DeepSeek OCR Benchmark", layout="wide")335    st.title("DeepSeek-OCR Benchmark Space")336    st.caption("Compare DeepSeek-OCR against Tesseract, run SROIE batches, and review saved results.")337 338    page = st.sidebar.radio(339        "Navigation",340        options=[341            "Upload benchmark",342            "SROIE dataset benchmark",343            "Benchmark history",344        ],345    )346    st.sidebar.markdown(f"**Data dir:** `{DATA_DIR}`")347    st.sidebar.markdown(f"**Results dir:** `{RESULTS_DIR}`")348    st.sidebar.markdown(f"**Uploads dir:** `{UPLOAD_DIR}`")349    st.sidebar.markdown(f"**SROIE path:** `{SROIE_PATH}`")350 351    if page == "Upload benchmark":352        page_single_run()353    elif page == "SROIE dataset benchmark":354        page_sroie_batch()355    else:356        page_history()357 358 359if __name__ == "__main__":360    main()361