Spaces:
Sleeping
Sleeping
| """ | |
| ============================================================= | |
| SURYA OCR API - Reconocimiento de Manuscritos | |
| ============================================================= | |
| API REST para reconocer texto manuscrito usando Surya OCR | |
| Listo para desplegar en Hugging Face Spaces | |
| ============================================================= | |
| """ | |
| import io | |
| from fastapi import FastAPI, UploadFile, File, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from pydantic import BaseModel | |
| from PIL import Image | |
| from typing import Optional, List | |
| # Surya OCR | |
| from surya.detection import DetectionPredictor | |
| from surya.recognition import RecognitionPredictor, FoundationPredictor | |
| # ============================================================= | |
| # CONFIGURACION DE LA API | |
| # ============================================================= | |
| app = FastAPI( | |
| title="Surya OCR API", | |
| description="Reconocimiento de texto manuscrito con Surya OCR - Modelo de alta precision", | |
| version="1.0.0", | |
| docs_url="/docs", | |
| redoc_url="/redoc" | |
| ) | |
| # Permitir CORS (acceso desde cualquier origen) | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # ============================================================= | |
| # MODELOS DE RESPUESTA | |
| # ============================================================= | |
| class LineDetail(BaseModel): | |
| """Detalle de cada linea reconocida""" | |
| texto: str | |
| confianza: float | |
| class OCRResponse(BaseModel): | |
| """Respuesta del reconocimiento OCR""" | |
| exito: bool | |
| texto: str | |
| confianza: float | |
| lineas: List[LineDetail] | |
| mensaje: Optional[str] = None | |
| # ============================================================= | |
| # GESTOR DE MODELOS SURYA (Carga diferida) | |
| # ============================================================= | |
| class SuryaOCR: | |
| """ | |
| Gestor de modelos Surya con carga diferida. | |
| Los modelos se cargan solo cuando se necesitan. | |
| """ | |
| def __init__(self): | |
| self._detector = None | |
| self._recognizer = None | |
| self._foundation = None | |
| self._loaded = False | |
| def _load_models(self): | |
| """Carga los modelos de Surya""" | |
| if not self._loaded: | |
| print("=" * 50) | |
| print("Cargando modelos Surya OCR...") | |
| print("=" * 50) | |
| print("[1/3] Cargando detector de texto...") | |
| self._detector = DetectionPredictor() | |
| print("[2/3] Cargando modelo foundation...") | |
| self._foundation = FoundationPredictor() | |
| print("[3/3] Cargando reconocedor de texto...") | |
| self._recognizer = RecognitionPredictor(self._foundation) | |
| self._loaded = True | |
| print("=" * 50) | |
| print("Modelos cargados correctamente!") | |
| print("=" * 50) | |
| def detector(self): | |
| self._load_models() | |
| return self._detector | |
| def recognizer(self): | |
| self._load_models() | |
| return self._recognizer | |
| def recognize(self, image: Image.Image) -> tuple: | |
| """ | |
| Reconoce texto en una imagen. | |
| Args: | |
| image: Imagen PIL | |
| Returns: | |
| tuple: (texto_completo, confianza_promedio, lista_lineas) | |
| """ | |
| # Asegurar RGB | |
| if image.mode != 'RGB': | |
| image = image.convert('RGB') | |
| # Redimensionar si es muy grande (max 1024px) | |
| max_size = 1024 | |
| if max(image.size) > max_size: | |
| ratio = max_size / max(image.size) | |
| new_size = (int(image.size[0] * ratio), int(image.size[1] * ratio)) | |
| image = image.resize(new_size, Image.Resampling.LANCZOS) | |
| # Procesar con Surya | |
| images = [image] | |
| predictions = self.recognizer( | |
| images, | |
| det_predictor=self.detector | |
| ) | |
| # Extraer resultados | |
| if not predictions or not predictions[0].text_lines: | |
| return "", 0.0, [] | |
| textos = [] | |
| confianzas = [] | |
| lineas = [] | |
| for line in predictions[0].text_lines: | |
| texto = line.text.strip() | |
| confianza = line.confidence | |
| # FILTROS para eliminar ruido: | |
| # 1. Solo texto con confianza >= 50% | |
| if confianza < 0.50: | |
| continue | |
| # 2. Ignorar texto vacío | |
| if not texto: | |
| continue | |
| # 3. Ignorar patrones repetitivos (ruido común) | |
| if "the state" in texto.lower(): | |
| continue | |
| if "and the" in texto.lower() and len(texto) > 50: | |
| continue | |
| # 4. Ignorar líneas muy cortas (1-2 caracteres) con baja confianza | |
| if len(texto) <= 2 and confianza < 0.80: | |
| continue | |
| textos.append(texto) | |
| confianzas.append(confianza) | |
| lineas.append({ | |
| "texto": texto, | |
| "confianza": round(confianza, 3) | |
| }) | |
| texto_completo = ' '.join(textos) | |
| confianza_promedio = sum(confianzas) / len(confianzas) if confianzas else 0.0 | |
| return texto_completo, confianza_promedio, lineas | |
| # Instancia global | |
| surya = SuryaOCR() | |
| # ============================================================= | |
| # ENDPOINTS DE LA API | |
| # ============================================================= | |
| async def inicio(): | |
| """ | |
| Pagina de inicio con informacion de la API | |
| """ | |
| return { | |
| "nombre": "Surya OCR API", | |
| "version": "1.0.0", | |
| "descripcion": "API para reconocimiento de texto manuscrito", | |
| "modelo": "Surya OCR (2024)", | |
| "endpoints": { | |
| "GET /": "Esta informacion", | |
| "GET /health": "Estado del servicio", | |
| "GET /docs": "Documentacion interactiva (Swagger)", | |
| "POST /recognize": "Reconocer texto en imagen" | |
| }, | |
| "uso": "Sube una imagen a /recognize para obtener el texto" | |
| } | |
| async def health(): | |
| """ | |
| Verifica el estado del servicio | |
| """ | |
| return { | |
| "status": "ok", | |
| "modelo": "Surya OCR", | |
| "listo": True | |
| } | |
| async def recognize(file: UploadFile = File(...)): | |
| """ | |
| Reconoce texto manuscrito en una imagen. | |
| Sube una imagen (JPG, PNG, etc.) y obtendras el texto reconocido. | |
| - **file**: Archivo de imagen a procesar | |
| Retorna: | |
| - **exito**: Si el reconocimiento fue exitoso | |
| - **texto**: Texto completo reconocido | |
| - **confianza**: Nivel de confianza (0-1) | |
| - **lineas**: Detalle de cada linea detectada | |
| """ | |
| # Validar que sea imagen | |
| if not file.content_type or not file.content_type.startswith('image/'): | |
| raise HTTPException( | |
| status_code=400, | |
| detail="El archivo debe ser una imagen (JPG, PNG, etc.)" | |
| ) | |
| try: | |
| # Leer imagen | |
| contents = await file.read() | |
| image = Image.open(io.BytesIO(contents)) | |
| # Reconocer texto | |
| texto, confianza, lineas = surya.recognize(image) | |
| # Respuesta | |
| if texto: | |
| return OCRResponse( | |
| exito=True, | |
| texto=texto, | |
| confianza=round(confianza, 3), | |
| lineas=[LineDetail(**l) for l in lineas], | |
| mensaje=f"Se reconocieron {len(lineas)} lineas de texto" | |
| ) | |
| else: | |
| return OCRResponse( | |
| exito=False, | |
| texto="", | |
| confianza=0.0, | |
| lineas=[], | |
| mensaje="No se detecto texto en la imagen" | |
| ) | |
| except Exception as e: | |
| raise HTTPException( | |
| status_code=500, | |
| detail=f"Error procesando imagen: {str(e)}" | |
| ) | |
| # ============================================================= | |
| # PUNTO DE ENTRADA | |
| # ============================================================= | |
| if __name__ == "__main__": | |
| import uvicorn | |
| print("\n" + "=" * 50) | |
| print(" SURYA OCR API") | |
| print(" http://localhost:7860") | |
| print(" Docs: http://localhost:7860/docs") | |
| print("=" * 50 + "\n") | |
| uvicorn.run(app, host="0.0.0.0", port=7860) | |