feat(effi-ocr): add effi-server deployment configuration

- docker-compose for effi-server (192.168.1.27)
- Uses same network and volumes as OpenWebUI
- Port 8301
This commit is contained in:
Folzo
2026-04-11 01:38:06 +02:00
parent a8e9792412
commit b7af58a2c5
5 changed files with 290 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
FROM python:3.11-slim
WORKDIR /app
RUN apt-get update && apt-get install -y \
tesseract-ocr \
tesseract-ocr-fra \
libgl1 \
libglib2.0-0 \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY main.py .
EXPOSE 8301
CMD ["python", "main.py"]
+33
View File
@@ -0,0 +1,33 @@
# Effi-OCR sur effi-server
# Port: 8301
# OpenWebUI tourne sur le même serveur - accède aux mêmes fichiers uploadés
services:
effi-ocr:
build: .
container_name: effi-ocr
ports:
- "8301:8301"
restart: unless-stopped
environment:
- PORT=8301
- PYTHONUNBUFFERED=1
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8301/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
volumes:
- open-webui-data:/app/backend/data:ro
networks:
- effi-net
networks:
effi-net:
name: effi-net
external: true
volumes:
open-webui-data:
external: true
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
# Installation de effi-ocr sur effi-server (192.168.1.27)
# Port: 8301
set -e
echo "=== Installation effi-ocr sur effi-server ==="
# Créer le dossier
ssh root@192.168.1.27 "mkdir -p /root/effi-ocr"
# Copier les fichiers
scp /home/folzo/effi-ocr-server/* root@192.168.1.27:/root/effi-ocr/
# Build et start
ssh root@192.168.1.27 "cd /root/effi-ocr && docker compose up -d --build"
# Vérifier
sleep 5
ssh root@192.168.1.27 "curl -s http://localhost:8301/health | jq ."
echo ""
echo "=== effi-ocr installé sur http://192.168.1.27:8301 ==="
+210
View File
@@ -0,0 +1,210 @@
"""
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}")
# Vérifier le type de fichier
allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/tiff', 'image/webp']
if file.content_type not in allowed_types:
raise HTTPException(
status_code=400,
detail=f"Type de fichier non supporté: {file.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)
+5
View File
@@ -0,0 +1,5 @@
fastapi==0.109.0
uvicorn[standard]==0.27.0
python-multipart==0.0.6
pillow==10.2.0
pytesseract==0.3.10