Ajout Effi-OCR (Tesseract) + Web Scraper pour OpenWebUI

- Effi-OCR: Service OCR avec Tesseract (multilingue FR/EN)
  * API FastAPI sur port 8300
  * Prétraitement images (contraste, niveaux de gris)
  * Tool OpenWebUI intégré

- Web Scraper: Service Python (BeautifulSoup) sur port 11235
  * Alternative à Crawl4AI (incompatible ARM64/J5040)
  * Scraping HTML basique mais fonctionnel
  * Intégré au docker-compose d'effi-server

- Documentation:
  * ROADMAP-FONCTIONNALITES.md: Plan des 9 fonctionnalités
  * WEB-SCRAPER-CONFIG.md: Configuration OpenWebUI
  * WEB-SCRAPER-TOOL.md: Code du Tool

- Script start-all-services.sh mis à jour (6 services)
This commit is contained in:
Folzo
2026-04-06 16:34:27 +02:00
parent 5f1e19ff91
commit 93f31cdaeb
25 changed files with 2469 additions and 6 deletions
+20
View File
@@ -0,0 +1,20 @@
FROM python:3.11-slim
# Installer Tesseract et les langues
RUN apt-get update && apt-get install -y \
tesseract-ocr \
tesseract-ocr-fra \
tesseract-ocr-eng \
libtesseract-dev \
&& rm -rf /var/lib/apt/lists/*
# Créer le répertoire de travail
WORKDIR /app
# Copier les dépendances
COPY requirements.txt .
# Installer les dépendances Python
RUN pip install --no-cache-dir -r requirements.txt
# Copier le code
COPY main.py .
# Exposer le port
EXPOSE 8300
# Lancer l'application
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8300"]
+232
View File
@@ -0,0 +1,232 @@
# 📝 Effi-OCR
Service OCR (Optical Character Recognition) pour Effi. Extrait le texte des images et photos de documents.
---
## 🎯 Fonctionnalités
- Extraction de texte à partir d'images (JPEG, PNG, GIF, BMP, TIFF, WebP)
- Support multilingue (Français, Anglais, Allemand, Espagnol, Italien)
- Prétraitement automatique pour améliorer la qualité
- API REST simple et rapide
- Intégration OpenWebUI via Tool
---
## 🚀 Démarrage rapide
```bash
# Construire et démarrer
cd ~/Documents/OpenCode/Projets/Effi/effi-ocr
sudo docker compose up -d --build
# Vérifier le service
curl http://localhost:8300/health
```
---
## 📡 API Endpoints
### Health Check
```bash
GET /health
```
### OCR - Upload fichier
```bash
POST /ocr
Content-Type: multipart/form-data
Parameters:
- file: Image à analyser
- lang: Code langue (fra, eng, fra+eng, etc.)
- preprocess: true/false (prétraitement auto)
```
Exemple:
```bash
curl -X POST -F "file=@photo.jpg" -F "lang=fra" http://localhost:8300/ocr
```
### OCR - Base64
```bash
POST /ocr/base64
Content-Type: application/json
{
"image_base64": "...",
"lang": "fra",
"preprocess": true
}
```
### Langues disponibles
```bash
GET /languages
```
---
## 🔧 Configuration
### Variables d'environnement
| Variable | Défaut | Description |
|----------|--------|-------------|
| `OCR_URL` | `http://192.168.1.11:8300` | URL du service OCR |
### Langues supportées
- `fra` - Français
- `eng` - Anglais
- `fra+eng` - Bilingue FR+EN
- `deu` - Allemand
- `spa` - Espagnol
- `ita` - Italien
---
## 🐳 Docker
### Construction
```bash
sudo docker compose build
```
### Démarrage
```bash
sudo docker compose up -d
```
### Logs
```bash
sudo docker logs effi-ocr -f
```
### Arrêt
```bash
sudo docker compose down
```
---
## 🔗 Intégration OpenWebUI
Voir [TOOL-OPENWEBUI.md](TOOL-OPENWEBUI.md) pour la configuration complète du Tool.
**Résumé rapide:**
1. Admin → Tools → Create Tool
2. Copier le code Python fourni
3. Configurer la variable `OCR_URL`
4. Activer le Tool dans les paramètres de conversation
---
## 🧪 Tests
### Test de santé
```bash
curl http://localhost:8300/health
```
### Test OCR simple
```bash
curl -X POST \
-F "file=@/chemin/vers/image.jpg" \
-F "lang=fra" \
http://localhost:8300/ocr
```
### Test avec prétraitement désactivé
```bash
curl -X POST \
-F "file=@image.png" \
-F "lang=eng" \
-F "preprocess=false" \
http://localhost:8300/ocr
```
---
## 📊 Performance
- **Temps de traitement** : ~1-3 secondes par image (dépend de la taille)
- **Formats supportés** : JPEG, PNG, GIF, BMP, TIFF, WebP
- **Taille max recommandée** : 2048x2048 pixels
- **Prétraitement** : Conversion niveaux de gris + contraste (optionnel)
---
## 🐛 Dépannage
### Service ne répond pas
```bash
# Vérifier si le conteneur tourne
sudo docker ps | grep effi-ocr
# Redémarrer
sudo docker restart effi-ocr
# Voir les logs
sudo docker logs effi-ocr
```
### Tesseract non trouvé
L'erreur indique que Tesseract n'est pas installé dans le conteneur. Reconstruire:
```bash
sudo docker compose down
sudo docker compose up -d --build
```
### OCR de mauvaise qualité
- Utiliser `lang=fra+eng` pour les documents mixtes
- S'assurer que l'image est bien éclairée
- Vérifier la résolution (300 DPI recommandé pour documents)
- Activer le prétraitement (par défaut: true)
---
## 📁 Structure
```
effi-ocr/
├── main.py # API FastAPI
├── Dockerfile # Configuration Docker
├── docker-compose.yml # Orchestration
├── requirements.txt # Dépendances Python
├── README.md # Ce fichier
└── TOOL-OPENWEBUI.md # Guide Tool OpenWebUI
```
---
## 📝 Dépendances
- Python 3.11
- FastAPI
- Pillow (traitement d'images)
- pytesseract (wrapper Tesseract)
- Tesseract OCR 5.x
---
## 🎨 Cas d'usage
- **Numérisation de livres** : Extraire le texte de photos de pages
- **Factures** : Récupérer les informations de factures scannées
- **Documents** : OCR sur PDF et images de documents
- **Notes manuscrites** : Reconnaissance de texte manuscrit (limitée)
---
## 🔒 Sécurité
- Le service écoute uniquement sur localhost (127.0.0.1:8300)
- Aucune donnée n'est stockée persistante
- Les images sont traitées en mémoire et supprimées après
---
Créé le 6 Avril 2026
+175
View File
@@ -0,0 +1,175 @@
# Tool OCR pour OpenWebUI
## Configuration
Dans OpenWebUI (http://192.168.1.27:3000) :
1. **Aller dans** : Admin → Tools → Create Tool
2. **Nom** : OCR Extracteur de Texte
3. **ID** : ocr_extractor
4. **Description** : Extrait le texte des images pour les livres et documents
## Code du Tool
```python
from typing import Optional
import requests
import base64
import os
class Tools:
def __init__(self):
"""Initialize OCR Tool"""
self.ocr_url = os.getenv("OCR_URL", "http://192.168.1.11:8300")
def extract_text_from_image(self, image_path: str, language: str = "fra") -> str:
"""
Extract text from an image file using OCR
Args:
image_path: Path to the image file (jpg, png, etc.)
language: Language code (fra, eng, fra+eng, etc.)
Returns:
Extracted text from the image
"""
try:
# Read image file
with open(image_path, 'rb') as f:
files = {'file': f}
data = {'lang': language}
response = requests.post(
f"{self.ocr_url}/ocr",
files=files,
data=data,
timeout=30
)
if response.status_code == 200:
result = response.json()
if result.get('success'):
text = result.get('text', '')
char_count = result.get('characters_count', 0)
word_count = result.get('words_count', 0)
return f"Texte extrait ({char_count} caractères, {word_count} mots):\n\n{text}"
else:
return f"Erreur OCR: {result.get('error', 'Unknown error')}"
else:
return f"Erreur HTTP {response.status_code}: {response.text}"
except FileNotFoundError:
return f"Erreur: Fichier non trouvé: {image_path}"
except Exception as e:
return f"Erreur lors de l'OCR: {str(e)}"
def extract_text_from_base64(self, image_base64: str, language: str = "fra") -> str:
"""
Extract text from a base64 encoded image
Args:
image_base64: Base64 encoded image string
language: Language code (fra, eng, fra+eng, etc.)
Returns:
Extracted text from the image
"""
try:
response = requests.post(
f"{self.ocr_url}/ocr/base64",
json={
'image_base64': image_base64,
'lang': language,
'preprocess': True
},
timeout=30
)
if response.status_code == 200:
result = response.json()
if result.get('success'):
text = result.get('text', '')
char_count = result.get('characters_count', 0)
word_count = result.get('words_count', 0)
return f"Texte extrait ({char_count} caractères, {word_count} mots):\n\n{text}"
else:
return f"Erreur OCR: extraction échouée"
else:
return f"Erreur HTTP {response.status_code}: {response.text}"
except Exception as e:
return f"Erreur lors de l'OCR: {str(e)}"
def get_available_languages(self) -> str:
"""
Get list of available OCR languages
Returns:
List of available language codes
"""
try:
response = requests.get(f"{self.ocr_url}/languages", timeout=10)
if response.status_code == 200:
result = response.json()
languages = result.get('available_languages', [])
return f"Langues disponibles: {', '.join(languages)}"
else:
return f"Erreur: {response.status_code}"
except Exception as e:
return f"Erreur: {str(e)}"
```
## Variables d'environnement
Dans les Settings d'OpenWebUI (Admin → Settings → Environment Variables) :
```
OCR_URL=http://192.168.1.11:8300
```
## Utilisation
Une fois le Tool créé, les agents pourront l'utiliser avec ces fonctions :
- `extract_text_from_image("/chemin/vers/image.jpg", "fra")` - Extraire le texte d'un fichier image
- `extract_text_from_base64("base64encoded...", "fra")` - Extraire d'une image encodée en base64
- `get_available_languages()` - Lister les langues disponibles
## Exemple de prompt
```
"Effi, lis-moi ce texte" [upload d'image]
→ L'agent utilise automatiquement le Tool OCR
```
## Langues disponibles
- `fra` - Français
- `eng` - Anglais
- `fra+eng` - Français + Anglais (bilingue)
- `deu` - Allemand
- `spa` - Espagnol
- `ita` - Italien
## Dépannage
**Erreur de connexion** :
```bash
# Vérifier que le service OCR tourne
curl http://192.168.1.11:8300/health
# Redémarrer si nécessaire
cd ~/Documents/OpenCode/Projets/Effi/effi-ocr
sudo docker compose restart
```
**Texte mal extrait** :
- Essayer `fra+eng` pour les documents bilingues
- Vérifier la qualité de l'image (résolution, contraste)
- L'OCR fonctionne mieux avec des images bien éclairées
---
*Tool créé le 6 Avril 2026*
+22
View File
@@ -0,0 +1,22 @@
services:
effi-ocr:
build: .
container_name: effi-ocr
ports:
- "8300:8300"
restart: unless-stopped
environment:
- PYTHONUNBUFFERED=1
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8300/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
networks:
- effi-network
networks:
effi-network:
external: true
name: effi-network
+208
View File
@@ -0,0 +1,208 @@
"""
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
uvicorn.run(app, host="0.0.0.0", port=8300)
+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