Spaces:
Sleeping
Sleeping
Delete image_processing.py
Browse files- image_processing.py +0 -251
image_processing.py
DELETED
|
@@ -1,251 +0,0 @@
|
|
| 1 |
-
# ==========================================
|
| 2 |
-
# image_processing.py - Calcul OCR v3.0 CLEAN
|
| 3 |
-
# ==========================================
|
| 4 |
-
|
| 5 |
-
"""
|
| 6 |
-
Module de traitement d'images pour calculs mathématiques - EasyOCR UNIQUEMENT
|
| 7 |
-
"""
|
| 8 |
-
|
| 9 |
-
from PIL import Image, ImageEnhance
|
| 10 |
-
import numpy as np
|
| 11 |
-
import base64
|
| 12 |
-
from io import BytesIO
|
| 13 |
-
import gc
|
| 14 |
-
import os
|
| 15 |
-
import time
|
| 16 |
-
|
| 17 |
-
# Variables globales pour OCR EasyOCR uniquement
|
| 18 |
-
easyocr_reader = None
|
| 19 |
-
|
| 20 |
-
def init_ocr_model() -> bool:
|
| 21 |
-
"""Initialise EasyOCR (rapide et efficace sur CPU)"""
|
| 22 |
-
global easyocr_reader
|
| 23 |
-
|
| 24 |
-
try:
|
| 25 |
-
print("🔄 Chargement EasyOCR (optimisé CPU)...")
|
| 26 |
-
import easyocr
|
| 27 |
-
easyocr_reader = easyocr.Reader(['en'], gpu=False) # Force CPU pour compatibilité
|
| 28 |
-
print("✅ EasyOCR prêt !")
|
| 29 |
-
return True
|
| 30 |
-
|
| 31 |
-
except Exception as e:
|
| 32 |
-
print(f"❌ Erreur lors du chargement EasyOCR: {e}")
|
| 33 |
-
return False
|
| 34 |
-
|
| 35 |
-
def create_white_canvas(width: int = 300, height: int = 300) -> Image.Image:
|
| 36 |
-
"""Crée un canvas blanc pour le dessin de calculs"""
|
| 37 |
-
return Image.new('RGB', (width, height), 'white')
|
| 38 |
-
|
| 39 |
-
def log_memory_usage(context: str = "") -> None:
|
| 40 |
-
"""Log l'usage mémoire actuel"""
|
| 41 |
-
try:
|
| 42 |
-
import psutil
|
| 43 |
-
process = psutil.Process(os.getpid())
|
| 44 |
-
memory_mb = process.memory_info().rss / 1024 / 1024
|
| 45 |
-
print(f"🔍 Mémoire {context}: {memory_mb:.1f}MB")
|
| 46 |
-
except:
|
| 47 |
-
pass
|
| 48 |
-
|
| 49 |
-
def cleanup_memory() -> None:
|
| 50 |
-
"""Force le nettoyage mémoire"""
|
| 51 |
-
gc.collect()
|
| 52 |
-
|
| 53 |
-
def optimize_image_for_ocr(image_dict: dict | np.ndarray | Image.Image | None) -> Image.Image | None:
|
| 54 |
-
"""Optimisation image pour reconnaissance de nombres avec EasyOCR"""
|
| 55 |
-
if image_dict is None:
|
| 56 |
-
return None
|
| 57 |
-
|
| 58 |
-
try:
|
| 59 |
-
# Gérer les formats Gradio
|
| 60 |
-
if isinstance(image_dict, dict):
|
| 61 |
-
if 'composite' in image_dict and image_dict['composite'] is not None:
|
| 62 |
-
image = image_dict['composite']
|
| 63 |
-
elif 'background' in image_dict and image_dict['background'] is not None:
|
| 64 |
-
image = image_dict['background']
|
| 65 |
-
else:
|
| 66 |
-
return None
|
| 67 |
-
elif isinstance(image_dict, np.ndarray):
|
| 68 |
-
image = image_dict
|
| 69 |
-
elif isinstance(image_dict, Image.Image):
|
| 70 |
-
image = image_dict
|
| 71 |
-
else:
|
| 72 |
-
return None
|
| 73 |
-
|
| 74 |
-
# Conversion
|
| 75 |
-
if isinstance(image, np.ndarray):
|
| 76 |
-
pil_image = Image.fromarray(image).convert('RGB')
|
| 77 |
-
else:
|
| 78 |
-
pil_image = image.convert('RGB')
|
| 79 |
-
|
| 80 |
-
# Redimensionner si nécessaire pour optimiser OCR
|
| 81 |
-
max_size = 300
|
| 82 |
-
if pil_image.size[0] > max_size or pil_image.size[1] > max_size:
|
| 83 |
-
pil_image.thumbnail((max_size, max_size), Image.Resampling.LANCZOS)
|
| 84 |
-
|
| 85 |
-
return pil_image
|
| 86 |
-
|
| 87 |
-
except Exception:
|
| 88 |
-
return None
|
| 89 |
-
|
| 90 |
-
def prepare_image_for_dataset(image: Image.Image, max_size: tuple[int, int] = (100, 100), quality: int = 60) -> dict[str, str | int | float | tuple] | None:
|
| 91 |
-
"""Prépare image pour dataset calcul OCR"""
|
| 92 |
-
try:
|
| 93 |
-
if image is None:
|
| 94 |
-
return None
|
| 95 |
-
|
| 96 |
-
# Copier et redimensionner
|
| 97 |
-
dataset_image = image.copy()
|
| 98 |
-
dataset_image.thumbnail(max_size, Image.Resampling.LANCZOS)
|
| 99 |
-
compressed_size = dataset_image.size
|
| 100 |
-
|
| 101 |
-
# Convertir en base64
|
| 102 |
-
buffer = BytesIO()
|
| 103 |
-
dataset_image.save(buffer, format='PNG', optimize=True, quality=quality)
|
| 104 |
-
|
| 105 |
-
buffer_data = buffer.getvalue()
|
| 106 |
-
image_base64 = base64.b64encode(buffer_data).decode()
|
| 107 |
-
file_size_kb = len(image_base64) / 1024
|
| 108 |
-
|
| 109 |
-
# Structure propre pour dataset
|
| 110 |
-
result = {
|
| 111 |
-
"image_base64": image_base64,
|
| 112 |
-
"compressed_size": compressed_size,
|
| 113 |
-
"file_size_kb": round(file_size_kb, 1),
|
| 114 |
-
"format": "PNG",
|
| 115 |
-
"quality": quality
|
| 116 |
-
}
|
| 117 |
-
|
| 118 |
-
# Nettoyage
|
| 119 |
-
dataset_image.close()
|
| 120 |
-
buffer.close()
|
| 121 |
-
|
| 122 |
-
return result
|
| 123 |
-
|
| 124 |
-
except Exception as e:
|
| 125 |
-
print(f"❌ Erreur préparation image: {e}")
|
| 126 |
-
return None
|
| 127 |
-
|
| 128 |
-
def recognize_number_fast_with_image(image_dict: dict | np.ndarray | Image.Image | None) -> tuple[str, Image.Image | None, dict | None]:
|
| 129 |
-
"""OCR avec EasyOCR uniquement (rapide et efficace)"""
|
| 130 |
-
if image_dict is None:
|
| 131 |
-
print(" ❌ Image manquante")
|
| 132 |
-
return "0", None, None
|
| 133 |
-
|
| 134 |
-
if easyocr_reader is None:
|
| 135 |
-
print(" ❌ EasyOCR non initialisé")
|
| 136 |
-
return "0", None, None
|
| 137 |
-
|
| 138 |
-
try:
|
| 139 |
-
print(" 🔄 Début OCR EasyOCR...")
|
| 140 |
-
start_time = time.time()
|
| 141 |
-
|
| 142 |
-
# Optimiser image
|
| 143 |
-
print(" 📐 Optimisation image...")
|
| 144 |
-
opt_start = time.time()
|
| 145 |
-
optimized_image = optimize_image_for_ocr(image_dict)
|
| 146 |
-
if optimized_image is None:
|
| 147 |
-
print(" ❌ Échec optimisation image")
|
| 148 |
-
return "0", None, None
|
| 149 |
-
print(f" ✅ Image optimisée en {time.time() - opt_start:.1f}s")
|
| 150 |
-
|
| 151 |
-
# EasyOCR - simple et rapide
|
| 152 |
-
print(" ⚡ Lancement EasyOCR...")
|
| 153 |
-
easy_start = time.time()
|
| 154 |
-
|
| 155 |
-
# Convertir PIL vers numpy pour EasyOCR
|
| 156 |
-
img_array = np.array(optimized_image)
|
| 157 |
-
results = easyocr_reader.readtext(img_array, detail=0, paragraph=False)
|
| 158 |
-
|
| 159 |
-
if results:
|
| 160 |
-
# Extraire les chiffres de tous les résultats
|
| 161 |
-
all_text = ' '.join(str(r) for r in results)
|
| 162 |
-
cleaned_result = ''.join(filter(str.isdigit, all_text))
|
| 163 |
-
|
| 164 |
-
# Validation longueur
|
| 165 |
-
if cleaned_result and len(cleaned_result) <= 4:
|
| 166 |
-
final_result = cleaned_result
|
| 167 |
-
else:
|
| 168 |
-
# Si trop long, prendre les premiers chiffres
|
| 169 |
-
final_result = cleaned_result[:4] if cleaned_result else "0"
|
| 170 |
-
else:
|
| 171 |
-
final_result = "0"
|
| 172 |
-
|
| 173 |
-
print(f" ✅ EasyOCR en {time.time() - easy_start:.1f}s → '{final_result}'")
|
| 174 |
-
|
| 175 |
-
# Préparer pour dataset
|
| 176 |
-
print(" 📦 Préparation dataset...")
|
| 177 |
-
dataset_start = time.time()
|
| 178 |
-
dataset_image_data = prepare_image_for_dataset(optimized_image)
|
| 179 |
-
print(f" ✅ Dataset prep en {time.time() - dataset_start:.1f}s")
|
| 180 |
-
|
| 181 |
-
total_time = time.time() - start_time
|
| 182 |
-
print(f" 🏁 OCR total: {total_time:.1f}s")
|
| 183 |
-
|
| 184 |
-
return final_result, optimized_image, dataset_image_data
|
| 185 |
-
|
| 186 |
-
except Exception as e:
|
| 187 |
-
print(f"❌ Erreur OCR EasyOCR: {e}")
|
| 188 |
-
import traceback
|
| 189 |
-
traceback.print_exc()
|
| 190 |
-
return "0", None, None
|
| 191 |
-
|
| 192 |
-
def recognize_number_fast(image_dict: dict | np.ndarray | Image.Image | None) -> tuple[str, Image.Image | None]:
|
| 193 |
-
"""Version rapide standard pour calculs avec EasyOCR"""
|
| 194 |
-
result, optimized_image, _ = recognize_number_fast_with_image(image_dict)
|
| 195 |
-
return result, optimized_image
|
| 196 |
-
|
| 197 |
-
def recognize_number(image_dict: dict | np.ndarray | Image.Image | None) -> str:
|
| 198 |
-
"""Interface standard pour reconnaissance de calculs avec EasyOCR"""
|
| 199 |
-
result, _ = recognize_number_fast(image_dict)
|
| 200 |
-
return result
|
| 201 |
-
|
| 202 |
-
def create_thumbnail_fast(optimized_image: Image.Image | None, size: tuple[int, int] = (40, 40)) -> str:
|
| 203 |
-
"""Création miniature rapide pour calculs"""
|
| 204 |
-
try:
|
| 205 |
-
if optimized_image is None:
|
| 206 |
-
return "📝"
|
| 207 |
-
|
| 208 |
-
thumbnail = optimized_image.copy()
|
| 209 |
-
thumbnail.thumbnail(size, Image.Resampling.LANCZOS)
|
| 210 |
-
|
| 211 |
-
buffer = BytesIO()
|
| 212 |
-
thumbnail.save(buffer, format='PNG', optimize=True, quality=70)
|
| 213 |
-
img_str = base64.b64encode(buffer.getvalue()).decode()
|
| 214 |
-
|
| 215 |
-
thumbnail.close()
|
| 216 |
-
buffer.close()
|
| 217 |
-
|
| 218 |
-
return f'<img src="data:image/png;base64,{img_str}" width="{size[0]}" height="{size[1]}" style="border: 1px solid #ccc; border-radius: 3px;" alt="Réponse calcul">'
|
| 219 |
-
|
| 220 |
-
except Exception:
|
| 221 |
-
return "📝"
|
| 222 |
-
|
| 223 |
-
# Fonction utilitaire pour fine-tuning calculs
|
| 224 |
-
def decode_image_from_dataset(base64_string: str) -> Image.Image | None:
|
| 225 |
-
"""Décode une image depuis le dataset pour fine-tuning calculs avec EasyOCR"""
|
| 226 |
-
try:
|
| 227 |
-
image_bytes = base64.b64decode(base64_string)
|
| 228 |
-
image = Image.open(BytesIO(image_bytes))
|
| 229 |
-
return image
|
| 230 |
-
except Exception as e:
|
| 231 |
-
print(f"Erreur décodage calcul: {e}")
|
| 232 |
-
return None
|
| 233 |
-
|
| 234 |
-
# Fonctions d'analyse pour différents types de calculs
|
| 235 |
-
def analyze_calculation_complexity(operand_a: int, operand_b: int, operation: str) -> dict:
|
| 236 |
-
"""Analyse la complexité d'un calcul pour le dataset"""
|
| 237 |
-
complexity_score = 0
|
| 238 |
-
|
| 239 |
-
if operation == "×":
|
| 240 |
-
complexity_score = max(operand_a, operand_b)
|
| 241 |
-
elif operation == "+":
|
| 242 |
-
complexity_score = (operand_a + operand_b) / 20
|
| 243 |
-
elif operation == "-":
|
| 244 |
-
complexity_score = max(operand_a, operand_b) / 10
|
| 245 |
-
elif operation == "÷":
|
| 246 |
-
complexity_score = operand_a / 10
|
| 247 |
-
|
| 248 |
-
return {
|
| 249 |
-
"complexity_score": round(complexity_score, 2),
|
| 250 |
-
"difficulty_category": "easy" if complexity_score < 5 else "medium" if complexity_score < 10 else "hard"
|
| 251 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|