217 lines
6.5 KiB
Python
217 lines
6.5 KiB
Python
"""
|
|
Effi-OCR : Service OCR pour Effi
|
|
API FastAPI qui expose Tesseract via une interface REST
|
|
"""
|
|
|
|
from fastapi import FastAPI, File, UploadFile, HTTPException
|
|
from fastapi.responses import JSONResponse
|
|
from PIL import Image
|
|
import pytesseract
|
|
import io
|
|
import logging
|
|
from typing import Optional
|
|
|
|
# Configuration logging
|
|
logging.basicConfig(level=logging.INFO)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
app = FastAPI(
|
|
title="Effi-OCR",
|
|
description="Service OCR pour extraction de texte d'images",
|
|
version="1.0.0"
|
|
)
|
|
|
|
|
|
@app.get("/health")
|
|
async def health_check():
|
|
"""Endpoint de santé pour monitoring"""
|
|
try:
|
|
# Vérifier que Tesseract est accessible
|
|
import subprocess
|
|
result = subprocess.run(['tesseract', '--version'], capture_output=True, text=True)
|
|
version_line = result.stdout.split('\n')[0] if result.stdout else "unknown"
|
|
tesseract_version = str(version_line) # Forcer en string
|
|
except Exception as e:
|
|
tesseract_version = f"error: {str(e)}"
|
|
|
|
return {
|
|
"status": "healthy",
|
|
"service": "effi-ocr",
|
|
"version": "1.0.0",
|
|
"tesseract_version": tesseract_version
|
|
}
|
|
|
|
|
|
@app.post("/ocr")
|
|
async def ocr_image(
|
|
file: UploadFile = File(...),
|
|
lang: str = "fra",
|
|
preprocess: bool = True
|
|
):
|
|
"""
|
|
Extraire le texte d'une image
|
|
|
|
Args:
|
|
file: Image à analyser (jpg, png, etc.)
|
|
lang: Code langue (fra, eng, fra+eng, etc.)
|
|
preprocess: Appliquer un prétraitement pour améliorer la qualité
|
|
|
|
Returns:
|
|
JSON avec le texte extrait et les métadonnées
|
|
"""
|
|
logger.info(f"OCR demandé - lang: {lang}, fichier: {file.filename}")
|
|
|
|
# Déterminer le type MIME (avec fallback depuis l'extension)
|
|
content_type = file.content_type
|
|
if content_type is None:
|
|
import mimetypes
|
|
content_type, _ = mimetypes.guess_type(file.filename)
|
|
content_type = content_type or 'application/octet-stream'
|
|
|
|
allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/tiff', 'image/webp']
|
|
if content_type not in allowed_types:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Type de fichier non supporté: {content_type}. Types acceptés: {', '.join(allowed_types)}"
|
|
)
|
|
|
|
try:
|
|
# Lire l'image
|
|
contents = await file.read()
|
|
image = Image.open(io.BytesIO(contents))
|
|
|
|
# Convertir en RGB si nécessaire (pour les images avec transparence)
|
|
if image.mode in ('RGBA', 'LA', 'P'):
|
|
image = image.convert('RGB')
|
|
|
|
# Prétraitement optionnel pour améliorer la qualité OCR
|
|
if preprocess:
|
|
# Convertir en niveaux de gris pour meilleure détection texte
|
|
image = image.convert('L')
|
|
# Augmenter le contraste
|
|
from PIL import ImageEnhance
|
|
enhancer = ImageEnhance.Contrast(image)
|
|
image = enhancer.enhance(2.0)
|
|
|
|
# OCR avec Tesseract
|
|
text = pytesseract.image_to_string(image, lang=lang)
|
|
|
|
# Nettoyer le texte
|
|
text = text.strip()
|
|
|
|
# Informations sur l'image
|
|
width, height = image.size
|
|
|
|
logger.info(f"OCR terminé - {len(text)} caractères extraits")
|
|
|
|
return JSONResponse(content={
|
|
"success": True,
|
|
"text": text,
|
|
"language": lang,
|
|
"filename": file.filename,
|
|
"image_size": {"width": width, "height": height},
|
|
"characters_count": len(text),
|
|
"words_count": len(text.split()) if text else 0
|
|
})
|
|
|
|
except Exception as e:
|
|
logger.error(f"Erreur OCR: {str(e)}")
|
|
raise HTTPException(status_code=500, detail=f"Erreur lors de l'OCR: {str(e)}")
|
|
|
|
|
|
@app.post("/ocr/base64")
|
|
async def ocr_base64(
|
|
image_base64: str,
|
|
lang: str = "fra",
|
|
preprocess: bool = True
|
|
):
|
|
"""
|
|
Extraire le texte d'une image en base64
|
|
|
|
Args:
|
|
image_base64: Image encodée en base64
|
|
lang: Code langue (fra, eng, fra+eng, etc.)
|
|
preprocess: Appliquer un prétraitement
|
|
|
|
Returns:
|
|
JSON avec le texte extrait
|
|
"""
|
|
import base64
|
|
|
|
logger.info(f"OCR base64 demandé - lang: {lang}")
|
|
|
|
try:
|
|
# Décoder le base64
|
|
image_data = base64.b64decode(image_base64)
|
|
image = Image.open(io.BytesIO(image_data))
|
|
|
|
# Convertir en RGB si nécessaire
|
|
if image.mode in ('RGBA', 'LA', 'P'):
|
|
image = image.convert('RGB')
|
|
|
|
# Prétraitement
|
|
if preprocess:
|
|
image = image.convert('L')
|
|
from PIL import ImageEnhance
|
|
enhancer = ImageEnhance.Contrast(image)
|
|
image = enhancer.enhance(2.0)
|
|
|
|
# OCR
|
|
text = pytesseract.image_to_string(image, lang=lang)
|
|
text = text.strip()
|
|
|
|
width, height = image.size
|
|
|
|
logger.info(f"OCR base64 terminé - {len(text)} caractères")
|
|
|
|
return {
|
|
"success": True,
|
|
"text": text,
|
|
"language": lang,
|
|
"image_size": {"width": width, "height": height},
|
|
"characters_count": len(text),
|
|
"words_count": len(text.split()) if text else 0
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Erreur OCR base64: {str(e)}")
|
|
raise HTTPException(status_code=500, detail=f"Erreur lors de l'OCR: {str(e)}")
|
|
|
|
|
|
@app.get("/languages")
|
|
async def list_languages():
|
|
"""Lister les langues disponibles pour l'OCR"""
|
|
try:
|
|
# Commande tesseract pour lister les langues
|
|
import subprocess
|
|
result = subprocess.run(
|
|
['tesseract', '--list-langs'],
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
# Parser la sortie
|
|
lines = result.stdout.strip().split('\n')
|
|
# La première ligne est "List of available languages (xxx):"
|
|
languages = lines[1:] if len(lines) > 1 else []
|
|
|
|
return {
|
|
"available_languages": languages,
|
|
"count": len(languages)
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"Erreur listage langues: {str(e)}")
|
|
# Fallback avec les langues communes
|
|
return {
|
|
"available_languages": ["fra", "eng", "fra+eng", "deu", "spa", "ita"],
|
|
"count": 6,
|
|
"note": "Liste par défaut, erreur lors de la récupération"
|
|
}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
import os
|
|
port = int(os.getenv("PORT", "8301"))
|
|
uvicorn.run(app, host="0.0.0.0", port=port)
|