Spaces:
Running
Running
| """ | |
| app.py — FastAPI + PaddleOCR zerbitzaria | |
| Hugging Face Spaces-en exekutatzen da (CPU). | |
| Detekzio-estrategia: konfiantza-puntuazio altuena duen scripta hautatu. | |
| """ | |
| import io | |
| import logging | |
| from contextlib import asynccontextmanager | |
| import numpy as np | |
| from fastapi import FastAPI, File, HTTPException, UploadFile | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import JSONResponse | |
| from paddleocr import PaddleOCR | |
| from PIL import Image | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| def _make_ocr(lang: str) -> PaddleOCR: | |
| return PaddleOCR( | |
| use_angle_cls=True, | |
| lang=lang, | |
| use_gpu=False, | |
| show_log=False, | |
| ) | |
| def _extract_with_score(ocr: PaddleOCR, img_array: np.ndarray) -> tuple[str, float]: | |
| """ | |
| Testua eta batez besteko konfiantza-puntuazioa itzultzen ditu. | |
| Puntuazioa 0.0 eta 1.0 artean dago; altuagoa = ziurragoa. | |
| """ | |
| try: | |
| result = ocr.ocr(img_array, cls=True) | |
| if not result or result[0] is None: | |
| return "", 0.0 | |
| lines = [] | |
| scores = [] | |
| for line in result[0]: | |
| if line and line[1]: | |
| text, score = line[1][0], line[1][1] | |
| lines.append(text) | |
| scores.append(score) | |
| if not scores: | |
| return "", 0.0 | |
| avg_score = sum(scores) / len(scores) | |
| return "\n".join(lines), avg_score | |
| except Exception as e: | |
| logger.warning("OCR errorea: %s", e) | |
| return "", 0.0 | |
| ocrs: dict = {} | |
| SCRIPTS = { | |
| "latin": "latin", # es, en, de, fr, lt, pl... | |
| "chinese": "ch", # Txinera sinplifikatua | |
| "chinese_t": "chinese_cht", # Txinera tradizionala | |
| "japanese": "japan", # Japoniera | |
| "korean": "korean", # Koreera | |
| "arabic": "arabic", # Arabiera, pertsiera, urdua | |
| "cyrillic": "cyrillic", # Errusiera, ukrainera... | |
| "devanagari": "devanagari", # Hindiera, nepalera | |
| } | |
| async def lifespan(app: FastAPI): | |
| global ocrs | |
| for name, lang in SCRIPTS.items(): | |
| logger.info("Kargatzen: %s (%s)...", name, lang) | |
| ocrs[name] = _make_ocr(lang) | |
| logger.info("Instantzia guztiak prest.") | |
| yield | |
| ocrs.clear() | |
| app = FastAPI( | |
| title="OCR API", | |
| description="PaddleOCR-en oinarritutako OCR zerbitzua (script anitz)", | |
| version="5.0.0", | |
| lifespan=lifespan, | |
| ) | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_methods=["POST", "GET"], | |
| allow_headers=["*"], | |
| ) | |
| async def health_check(): | |
| return { | |
| "status": "ok", | |
| "engine": "PaddleOCR 2.7.3", | |
| "scripts": list(SCRIPTS.keys()), | |
| } | |
| async def predict(image: UploadFile = File(...)): | |
| """ | |
| Script guztiak probatzen ditu eta konfiantza altueneko emaitza itzultzen du. | |
| Erantzuna: { "text": "...", "script": "...", "confidence": 0.95 } | |
| """ | |
| if not ocrs: | |
| raise HTTPException(status_code=503, detail="OCR ez dago prest.") | |
| contents = await image.read() | |
| try: | |
| pil_image = Image.open(io.BytesIO(contents)).convert("RGB") | |
| except Exception: | |
| raise HTTPException(status_code=400, detail="Irudi baliogabea.") | |
| logger.info("Irudia jasota: %s (%dx%d)", image.filename, *pil_image.size) | |
| img_array = np.array(pil_image) | |
| candidates = { | |
| name: _extract_with_score(ocr, img_array) | |
| for name, ocr in ocrs.items() | |
| } | |
| # Konfiantza altuena duen scripta hautatu | |
| best_script, (best_text, best_score) = max( | |
| candidates.items(), key=lambda x: x[1][1] | |
| ) | |
| logger.info( | |
| "Script hautatu: %s | Konfiantza: %.3f | Karaktereak: %d", | |
| best_script, best_score, len(best_text) | |
| ) | |
| return JSONResponse(content={ | |
| "text": best_text, | |
| "script": best_script, | |
| "confidence": round(best_score, 3), | |
| }) |