import os import sys import time from pathlib import Path import numpy as np import streamlit as st from PIL import Image sys.path.insert(0, str(Path(__file__).resolve().parent)) try: from .ocr_engine import DEFAULT_MODEL_ID, OCREngine from .storage import is_model_cached, storage_info from .utils import clean_ocr_markdown except ImportError: from ocr_engine import DEFAULT_MODEL_ID, OCREngine from storage import is_model_cached, storage_info from utils import clean_ocr_markdown st.set_page_config( page_title="LightOnOCR Handwriting POC", page_icon="📝", layout="wide", initial_sidebar_state="expanded", ) st.markdown( """ """, unsafe_allow_html=True, ) st.markdown( """

LightOnOCR Handwriting POC

Ekstraksi teks dokumen, nota, dan handwriting dengan model PetaniHandal berbasis LightOnOCR. Output dikembalikan sebagai Markdown agar mudah diaudit, disalin, atau diproses lanjut.

""", unsafe_allow_html=True, ) PRESET_LABELS = { "handwriting": "Handwriting", "document": "Dokumen umum", "receipt": "Nota / invoice", } with st.sidebar: st.header("Pengaturan POC") preset = st.segmented_control( "Mode ekstraksi", options=list(PRESET_LABELS.keys()), format_func=lambda key: PRESET_LABELS[key], default="handwriting", ) max_size = st.slider( "Resolusi sisi terpanjang", 960, 2200, 1540, step=100, help="Rekomendasi LightOnOCR sekitar 1540px untuk menjaga geometri teks.", ) max_new_tokens = st.slider("Batas token output", 512, 8192, 4096, step=512) temperature = st.slider("Temperature", 0.0, 0.7, 0.1, step=0.05) st.divider() st.caption("Model aktif") st.code(DEFAULT_MODEL_ID, language=None) info = storage_info() cached = is_model_cached(DEFAULT_MODEL_ID) with st.expander("Storage & cache", expanded=False): st.write(f"Environment: {info['environment']}") st.write(f"Base path: `{info['base_path']}`") st.write("Model cache:", "tersedia" if cached else "belum tersedia") if "disk" in info: disk = info["disk"] used_pct = disk["used_gb"] / disk["total_gb"] if disk["total_gb"] else 0 st.progress(used_pct, text=f"{disk['used_gb']} GB / {disk['total_gb']} GB digunakan") @st.cache_resource(show_spinner="Menyiapkan LightOnOCR. Download hanya terjadi jika model belum ada di cache.") def get_ocr_engine(model_preset: str, output_tokens: int, temp: float): return OCREngine( preset=model_preset, max_new_tokens=output_tokens, temperature=temp, ) runtime_label = "vLLM endpoint" if os.getenv("LIGHTONOCR_ENDPOINT_URL") else "Transformers local" model_id = os.getenv("LIGHTONOCR_MODEL_ID", DEFAULT_MODEL_ID) cache_label = "Warm cache" if is_model_cached(model_id) else "Cache pending" st.markdown( f"""
Runtime{runtime_label}
Model{model_id}
Cache{cache_label}
""", unsafe_allow_html=True, ) uploaded_file = st.file_uploader( "Unggah gambar dokumen", type=["png", "jpg", "jpeg"], accept_multiple_files=False, ) if "ocr_result" not in st.session_state: st.session_state.ocr_result = None if "ocr_elapsed" not in st.session_state: st.session_state.ocr_elapsed = None if "uploaded_name" not in st.session_state: st.session_state.uploaded_name = None if uploaded_file is None: st.session_state.uploaded_name = None st.info("Unggah satu gambar untuk memulai ekstraksi.") else: if st.session_state.uploaded_name != uploaded_file.name: st.session_state.uploaded_name = uploaded_file.name st.session_state.ocr_result = None st.session_state.ocr_elapsed = None image = Image.open(uploaded_file).convert("RGB") img_array_bgr = np.array(image)[:, :, ::-1].copy() h, w = img_array_bgr.shape[:2] left, right = st.columns([1, 1], gap="medium") with left: st.subheader("Preview Dokumen") st.image(image, use_container_width=True) st.caption(f"Ukuran asli: {w} x {h}px") with right: st.subheader("Hasil OCR") run = st.button("Jalankan Ekstraksi", type="primary", use_container_width=True) if run: with st.spinner("Membaca dokumen dan menyusun Markdown..."): engine = get_ocr_engine(preset, max_new_tokens, temperature) t0 = time.time() result = engine.process_image(img_array_bgr, max_size=max_size) elapsed = time.time() - t0 md_text = clean_ocr_markdown(result.get("markdown_text", "")) st.session_state.ocr_result = md_text st.session_state.ocr_elapsed = elapsed if st.session_state.ocr_elapsed is not None: st.success(f"Selesai dalam {st.session_state.ocr_elapsed:.1f} detik") with st.container(border=True): if st.session_state.ocr_result: tab_rendered, tab_raw = st.tabs(["Rendered", "Markdown"]) with tab_rendered: st.markdown(st.session_state.ocr_result, unsafe_allow_html=True) with tab_raw: st.code(st.session_state.ocr_result, language="markdown") elif st.session_state.ocr_elapsed is not None: st.warning("Model tidak mengembalikan teks untuk gambar ini.") else: st.markdown( '
Hasil OCR akan muncul di sini setelah ekstraksi dijalankan.
', unsafe_allow_html=True, )