hoololi commited on
Commit
36961d0
·
verified ·
1 Parent(s): aac442a

Upload 6 files

Browse files
Files changed (5) hide show
  1. app.py +11 -23
  2. game_engine.py +78 -132
  3. image_processing_gpu.py +12 -40
  4. requirements.txt +6 -9
  5. utils.py +12 -46
app.py CHANGED
@@ -1,9 +1,9 @@
1
  # ==========================================
2
- # app_fixed.py - Calcul OCR v3.0 - Sessions isolées
3
  # ==========================================
4
 
5
  """
6
- Application principale avec isolation des sessions utilisateurs
7
  """
8
 
9
  import gradio as gr
@@ -12,29 +12,17 @@ import os
12
  import gc
13
  import numpy as np
14
  from PIL import Image
15
- import simple_gpu
16
 
17
  warnings.filterwarnings("ignore")
18
 
19
- # Import avec structure claire : GPU ou CPU uniquement
20
- try:
21
- import torch
22
- if torch.cuda.is_available():
23
- from image_processing_gpu import init_ocr_model, create_white_canvas, cleanup_memory
24
- print("📱 Interface: Mode GPU détecté - TrOCR")
25
- else:
26
- from image_processing_cpu import init_ocr_model, create_white_canvas, cleanup_memory
27
- print("📱 Interface: Mode CPU détecté - EasyOCR")
28
- except ImportError:
29
- from image_processing_cpu import init_ocr_model, create_white_canvas, cleanup_memory
30
- print("📱 Interface: Mode CPU détecté - EasyOCR")
31
-
32
  from game_engine import MathGame, export_to_clean_dataset
33
 
34
- print("🚀 Initialisation Calcul OCR v3.0...")
35
- print("🔄 Chargement modèle OCR...")
36
  init_ocr_model()
37
- print("✅ Modèle OCR prêt")
38
 
39
  def create_new_game_session():
40
  """Crée une nouvelle instance de jeu pour chaque session"""
@@ -123,7 +111,7 @@ with gr.Blocks(
123
  head="<meta name='viewport' content='width=device-width, initial-scale=1.0'>"
124
  ) as demo:
125
 
126
- # STATE MANAGEMENT - Chaque utilisateur a sa propre instance
127
  game_state = gr.State(value=None)
128
 
129
  gr.Markdown(
@@ -196,7 +184,7 @@ with gr.Blocks(
196
  export_button = gr.Button("📤 Exporter la série vers le dataset", variant="primary", size="lg")
197
  export_status = gr.Markdown("")
198
 
199
- # ÉVÉNEMENTS avec State management
200
  go_button.click(
201
  fn=start_game_wrapper,
202
  inputs=[duration_choice, operation_choice, difficulty_choice, game_state],
@@ -216,8 +204,8 @@ with gr.Blocks(
216
  )
217
 
218
  if __name__ == "__main__":
219
- print("🚀 Lancement Calcul OCR v3.0 - Sessions isolées...")
220
- print("🔒 Isolation: Chaque utilisateur a sa propre instance")
221
  print("🎯 Dataset: calcul_ocr_dataset")
222
  print("📊 Opérations: ×, +, -, ÷, Aléatoire")
223
  print("⚙️ Durées: 30s, 60s")
 
1
  # ==========================================
2
+ # app.py - Calcul OCR v3.0 - Version GPU simplifiée
3
  # ==========================================
4
 
5
  """
6
+ Application principale optimisée pour ZeroGPU HuggingFace Spaces
7
  """
8
 
9
  import gradio as gr
 
12
  import gc
13
  import numpy as np
14
  from PIL import Image
 
15
 
16
  warnings.filterwarnings("ignore")
17
 
18
+ # Import GPU uniquement
19
+ from image_processing_gpu import init_ocr_model, create_white_canvas, cleanup_memory
 
 
 
 
 
 
 
 
 
 
 
20
  from game_engine import MathGame, export_to_clean_dataset
21
 
22
+ print("🚀 Initialisation Calcul OCR v3.0 (GPU)...")
23
+ print("🔄 Chargement modèle TrOCR...")
24
  init_ocr_model()
25
+ print("✅ Modèle TrOCR prêt")
26
 
27
  def create_new_game_session():
28
  """Crée une nouvelle instance de jeu pour chaque session"""
 
111
  head="<meta name='viewport' content='width=device-width, initial-scale=1.0'>"
112
  ) as demo:
113
 
114
+ # State management - Chaque utilisateur a sa propre instance
115
  game_state = gr.State(value=None)
116
 
117
  gr.Markdown(
 
184
  export_button = gr.Button("📤 Exporter la série vers le dataset", variant="primary", size="lg")
185
  export_status = gr.Markdown("")
186
 
187
+ # Événements avec State management
188
  go_button.click(
189
  fn=start_game_wrapper,
190
  inputs=[duration_choice, operation_choice, difficulty_choice, game_state],
 
204
  )
205
 
206
  if __name__ == "__main__":
207
+ print("🚀 Lancement Calcul OCR v3.0 - Version GPU simplifiée")
208
+ print("🎯 ZeroGPU HuggingFace Spaces")
209
  print("🎯 Dataset: calcul_ocr_dataset")
210
  print("📊 Opérations: ×, +, -, ÷, Aléatoire")
211
  print("⚙️ Durées: 30s, 60s")
game_engine.py CHANGED
@@ -1,10 +1,9 @@
1
  # ==========================================
2
- # game_engine.py - Calcul OCR v3.0 CLEAN (VERSION SANS BASE64)
3
  # ==========================================
4
 
5
  """
6
- Moteur de jeu mathématique avec traitement parallèle et auto-détection OCR
7
- VERSION MODIFIÉE: Utilise le format Image natif au lieu de Base64
8
  """
9
 
10
  import random
@@ -20,81 +19,27 @@ import threading
20
  import queue
21
  from typing import Dict, Tuple, Optional
22
 
23
- # Auto-détection adaptée ZeroGPU
24
- ocr_module = None
25
- ocr_info = {"model_name": "Unknown", "device": "Unknown"}
 
 
 
 
 
 
26
 
27
- # Debug des variables d'environnement HF
28
- space_id = os.getenv("SPACE_ID")
29
- space_hardware = os.getenv("SPACE_HARDWARE")
30
- hf_space = os.getenv("HF_SPACE")
31
- space_author = os.getenv("SPACE_AUTHOR_NAME")
32
- zero_gpu = os.getenv("ZERO_GPU")
33
 
34
- print(f"🔍 Debug HF Env:")
35
- print(f" SPACE_ID: {space_id}")
36
- print(f" SPACE_HARDWARE: {space_hardware}")
37
- print(f" HF_SPACE: {hf_space}")
38
- print(f" SPACE_AUTHOR_NAME: {space_author}")
39
- print(f" ZERO_GPU: {zero_gpu}")
40
-
41
- # Détecter ZeroGPU (GPU dynamique HF)
42
- is_zerogpu = space_id and ("hoololi" in str(space_id))
43
-
44
- print(f"🎯 ZeroGPU détecté: {is_zerogpu}")
45
-
46
- if is_zerogpu:
47
- try:
48
- print("🚀 Force mode ZeroGPU - Import GPU...")
49
- from simple_gpu import gpu_dummy_function
50
- print("✅ Simple GPU importé")
51
-
52
- from image_processing_gpu import (
53
- recognize_number_fast_with_image as gpu_recognize,
54
- create_thumbnail_fast,
55
- create_white_canvas,
56
- cleanup_memory,
57
- log_memory_usage,
58
- get_ocr_model_info
59
- )
60
-
61
- recognize_number_fast_with_image = gpu_recognize
62
- ocr_module = "zerogpu_trocr"
63
- print("✅ Game Engine: Mode ZeroGPU - TrOCR directement utilisé")
64
-
65
- except Exception as e:
66
- print(f"❌ Erreur ZeroGPU: {e}")
67
- from image_processing_cpu import (
68
- recognize_number_fast_with_image,
69
- create_thumbnail_fast,
70
- create_white_canvas,
71
- cleanup_memory,
72
- log_memory_usage,
73
- get_ocr_model_info
74
- )
75
- ocr_module = "cpu"
76
- print("✅ Game Engine: Mode CPU - EasyOCR (fallback)")
77
- else:
78
- from image_processing_cpu import (
79
- recognize_number_fast_with_image,
80
- create_thumbnail_fast,
81
- create_white_canvas,
82
- cleanup_memory,
83
- log_memory_usage,
84
- get_ocr_model_info
85
- )
86
- ocr_module = "cpu"
87
- print("✅ Game Engine: Mode CPU - EasyOCR standard")
88
-
89
- # Récupérer les infos du modèle sélectionné
90
  try:
91
  ocr_info = get_ocr_model_info()
92
  print(f"🎯 OCR sélectionné: {ocr_info['model_name']} sur {ocr_info['device']}")
93
  except Exception as e:
94
  print(f"⚠️ Impossible de récupérer les infos OCR: {e}")
95
- ocr_info = {"model_name": "Error", "device": "Unknown"}
96
 
97
- # Imports dataset avec gestion d'erreur
98
  try:
99
  from datasets import Dataset, Image as DatasetImage, load_dataset
100
  DATASET_AVAILABLE = True
@@ -103,7 +48,7 @@ except ImportError as e:
103
  DATASET_AVAILABLE = False
104
  print(f"⚠️ Modules dataset non disponibles: {e}")
105
 
106
- # Nom du dataset modifié pour la version avec images natives
107
  DATASET_NAME = "hoololi/calcul_ocr_dataset_with_images_v2"
108
 
109
  # Configuration des difficultés par opération
@@ -170,7 +115,7 @@ def create_result_row_with_images(i: int, image: dict | np.ndarray | Image.Image
170
 
171
 
172
  class MathGame:
173
- """Moteur de jeu mathématique avec traitement parallèle"""
174
 
175
  def __init__(self):
176
  self.is_running = False
@@ -222,22 +167,22 @@ class MathGame:
222
  self.processing_active = True
223
  self.worker_thread = threading.Thread(target=self._process_images_worker, daemon=True)
224
  self.worker_thread.start()
225
- print("🔄 Thread de traitement parallèle démarré")
226
 
227
  def _stop_background_processing(self) -> None:
228
  """Arrête le thread de traitement"""
229
  self.processing_active = False
230
  if self.worker_thread and self.worker_thread.is_alive():
231
- print("⏹️ Arrêt du thread de traitement parallèle")
232
 
233
  def _process_images_worker(self) -> None:
234
- """Worker thread qui traite les images en arrière-plan"""
235
- print("🚀 Worker thread démarré")
236
  while self.processing_active:
237
  try:
238
  if not self.processing_queue.empty():
239
  question_num, image, expected, operation_data = self.processing_queue.get(timeout=1)
240
- print(f"🔄 Traitement parallèle image {question_num}...")
241
 
242
  start_time = time.time()
243
  result_data = create_result_row_with_images(question_num, image, expected, operation_data)
@@ -245,7 +190,7 @@ class MathGame:
245
 
246
  # Stocker le résultat
247
  self.results_cache[question_num] = result_data
248
- print(f"✅ Image {question_num} traitée en {processing_time:.1f}s (parallèle)")
249
 
250
  else:
251
  time.sleep(0.1)
@@ -253,16 +198,16 @@ class MathGame:
253
  except queue.Empty:
254
  continue
255
  except Exception as e:
256
- print(f"❌ Erreur traitement parallèle: {e}")
257
 
258
- print("🛑 Worker thread terminé")
259
 
260
  def _add_image_to_processing_queue(self, question_num: int, image: dict | np.ndarray | Image.Image,
261
  expected: int, operation_data: tuple) -> None:
262
- """Ajoute une image à la queue de traitement"""
263
  if image is not None:
264
  self.processing_queue.put((question_num, image, expected, operation_data))
265
- print(f"📝 Image {question_num} ajoutée à la queue de traitement")
266
 
267
  def generate_multiplication(self, difficulty: str) -> tuple[str, int]:
268
  """Génère une multiplication"""
@@ -358,7 +303,7 @@ class MathGame:
358
  self.export_timestamp = None
359
  self.export_result = None
360
 
361
- # Démarrer le traitement parallèle
362
  self._start_background_processing()
363
 
364
  gc.collect()
@@ -406,7 +351,7 @@ class MathGame:
406
  return self.end_game(image_data)
407
 
408
  if image_data is not None:
409
- # Ajouter l'image à la liste ET au traitement parallèle
410
  self.user_images.append(image_data)
411
  self.expected_answers.append(self.correct_answer)
412
 
@@ -415,7 +360,7 @@ class MathGame:
415
  a, op, b = int(parts[0]), parts[1], int(parts[2])
416
  current_operation_data = (a, b, op, self.correct_answer)
417
 
418
- # Lancer le traitement en parallèle de l'image qu'on vient de recevoir
419
  self._add_image_to_processing_queue(self.question_count, image_data, self.correct_answer, current_operation_data)
420
 
421
  self.question_count += 1
@@ -456,10 +401,10 @@ class MathGame:
456
 
457
  self.is_running = False
458
 
459
- # Arrêter le traitement parallèle
460
  self._stop_background_processing()
461
 
462
- print("🏁 Fin de jeu - Assemblage des résultats...")
463
 
464
  if final_image is not None:
465
  self.user_images.append(final_image)
@@ -470,8 +415,8 @@ class MathGame:
470
  a, op, b = int(parts[0]), parts[1], int(parts[2])
471
  final_operation_data = (a, b, op, self.correct_answer)
472
 
473
- # Traiter la dernière image immédiatement (pas en parallèle)
474
- print(f"🔄 Traitement final de l'image {self.question_count}...")
475
  final_result = create_result_row_with_images(self.question_count, final_image, self.correct_answer, final_operation_data)
476
  self.results_cache[self.question_count] = final_result
477
 
@@ -479,17 +424,17 @@ class MathGame:
479
  if len(self.operations_history) < len(self.user_images):
480
  self.operations_history.append((a, b, op, self.correct_answer))
481
 
482
- # Attendre que toutes les images soient traitées
483
  max_wait = 10
484
  wait_start = time.time()
485
  expected_results = len(self.user_images)
486
 
487
- print(f"⏳ Attente de {expected_results} résultats...")
488
  while len(self.results_cache) < expected_results and (time.time() - wait_start) < max_wait:
489
  time.sleep(0.1)
490
 
491
  results_ready = len(self.results_cache)
492
- print(f"✅ {results_ready}/{expected_results} résultats prêts")
493
 
494
  # Assembler les résultats dans l'ordre
495
  correct_answers = 0
@@ -502,14 +447,14 @@ class MathGame:
502
  self.session_data = []
503
  images_saved = 0
504
 
505
- print(f"📊 Assemblage de {total_questions} résultats...")
506
 
507
  for i in range(total_questions):
508
  if i in self.results_cache:
509
  row_data = self.results_cache[i]
510
- print(f" ✅ Résultat {i} du cache parallèle")
511
  else:
512
- print(f" 🔄 Traitement fallback pour résultat {i}...")
513
  if i < len(self.operations_history):
514
  row_data = create_result_row_with_images(i, self.user_images[i], self.expected_answers[i], self.operations_history[i])
515
  else:
@@ -526,15 +471,15 @@ class MathGame:
526
  if row_data['is_correct']:
527
  correct_answers += 1
528
 
529
- # Structure pour dataset avec debug OCR
530
  a, b, operation, correct_result = self.operations_history[i] if i < len(self.operations_history) else (0, 0, "×", 0)
531
 
532
  try:
533
  ocr_info_data = get_ocr_model_info()
534
- print(f"🔍 Debug OCR info: {ocr_info_data}")
535
  except Exception as e:
536
  print(f"❌ Erreur get_ocr_model_info: {e}")
537
- ocr_info_data = {"model_name": "Error", "device": "Unknown"}
538
 
539
  entry = {
540
  "session_id": session_id,
@@ -547,18 +492,18 @@ class MathGame:
547
  "operand_b": b,
548
  "operation": operation,
549
  "correct_answer": self.expected_answers[i] if i < len(self.expected_answers) else 0,
550
- "ocr_model": ocr_info_data.get("model_name", "Unknown"),
551
- "ocr_device": ocr_info_data.get("device", "Unknown"),
552
  "user_answer_ocr": row_data['recognized'],
553
  "user_answer_parsed": row_data['recognized_num'],
554
  "is_correct": row_data['is_correct'],
555
  "total_questions": total_questions,
556
- "app_version": "3.0_calcul_ocr_native_images"
557
  }
558
 
559
- print(f"🔍 Debug entry OCR fields: ocr_model={entry['ocr_model']}, ocr_device={entry['ocr_device']}")
560
 
561
- # MODIFICATION CLÉE: Utiliser l'image PIL native au lieu de Base64
562
  if row_data['dataset_image_data']:
563
  entry["handwriting_image"] = row_data['dataset_image_data']["handwriting_image"] # Image PIL native
564
  entry["image_width"] = int(row_data['dataset_image_data']["width"])
@@ -575,7 +520,7 @@ class MathGame:
575
  for entry in self.session_data:
576
  entry["session_accuracy"] = accuracy
577
 
578
- # Nettoyage mémoire
579
  for img in self.user_images:
580
  if hasattr(img, 'close'):
581
  try:
@@ -597,7 +542,7 @@ class MathGame:
597
  <th style="padding: 8px;">B</th>
598
  <th style="padding: 8px;">Réponse</th>
599
  <th style="padding: 8px;">Votre dessin</th>
600
- <th style="padding: 8px;">OCR</th>
601
  <th style="padding: 8px;">Statut</th>
602
  </tr>
603
  </thead>
@@ -619,10 +564,11 @@ class MathGame:
619
  if export_info["can_export"]:
620
  export_section = f"""
621
  <div style="margin-top: 20px; padding: 15px; background-color: #e8f5e8; border-radius: 8px;">
622
- <h3 style="color: #2e7d32;">📊 Résumé de la série</h3>
623
  <p style="color: #2e7d32;">
624
  ✅ {total_questions} réponses • 📊 {accuracy:.1f}% de précision<br>
625
  🖼️ {images_saved} images natives sauvegardées<br>
 
626
  ⚙️ Configuration: {config_display}
627
  </p>
628
  </div>
@@ -653,7 +599,7 @@ class MathGame:
653
  </div>
654
  </div>
655
  </div>
656
- <h2 style="color: #4a90e2;">📊 Détail des Réponses</h2>
657
  {table_html}
658
  {export_section}
659
  </div>
@@ -662,7 +608,7 @@ class MathGame:
662
  return (
663
  """<div style="font-size: 3em; font-weight: bold; text-align: center; padding: 20px; background: linear-gradient(45deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px;">🏁 C'est fini !</div>""",
664
  create_white_canvas(),
665
- f"✨ Session {config_display} terminée !",
666
  "⏱️ Temps écoulé !",
667
  gr.update(interactive=True),
668
  gr.update(interactive=False),
@@ -671,9 +617,9 @@ class MathGame:
671
 
672
 
673
  def export_to_clean_dataset(session_data: list[dict], dataset_name: str = None) -> str:
674
- """Export vers le nouveau dataset avec images natives (PAS DE BASE64)"""
675
  if dataset_name is None:
676
- dataset_name = DATASET_NAME # Utiliser le dataset avec images natives
677
 
678
  if not DATASET_AVAILABLE:
679
  return "❌ Modules dataset non disponibles"
@@ -683,41 +629,40 @@ def export_to_clean_dataset(session_data: list[dict], dataset_name: str = None)
683
  return "❌ Token HuggingFace manquant (HF_TOKEN ou tk_calcul_ocr)"
684
 
685
  try:
686
- print(f"\n🚀 === EXPORT VERS DATASET CALCUL OCR (IMAGES NATIVES) ===")
687
  print(f"📊 Dataset: {dataset_name}")
688
 
689
- # Filtrer les entrées avec images et ajouter les infos OCR globalement
690
  clean_entries = []
691
 
692
- # Récupérer une seule fois les infos OCR pour toute la session
693
  try:
694
  global_ocr_info = get_ocr_model_info()
695
- print(f"🔍 Infos OCR globales: {global_ocr_info}")
696
  except Exception as e:
697
- print(f"❌ Erreur infos OCR globales: {e}")
698
- global_ocr_info = {"model_name": "Unknown", "device": "Unknown"}
699
 
700
  for entry in session_data:
701
  if entry.get('has_image', False):
702
- # Ajouter explicitement les champs OCR manquants
703
  entry_with_ocr = entry.copy()
704
- entry_with_ocr["ocr_model"] = global_ocr_info.get("model_name", "Unknown")
705
- entry_with_ocr["ocr_device"] = global_ocr_info.get("device", "Unknown")
706
 
707
- print(f"🔍 Entry avec OCR: ocr_model={entry_with_ocr['ocr_model']}, ocr_device={entry_with_ocr['ocr_device']}")
708
  print(f"🖼️ Image type: {type(entry_with_ocr.get('handwriting_image', 'None'))}")
709
  clean_entries.append(entry_with_ocr)
710
 
711
- # Créer un dataset de test avec structure forcée
712
  if len(clean_entries) == 0:
713
  return "❌ Aucune entrée avec image à exporter"
714
 
715
  # Vérifier la structure de la première entrée
716
  sample_entry = clean_entries[0]
717
- print(f"🔍 Structure première entrée: {list(sample_entry.keys())}")
718
- print(f"🔍 OCR dans entrée: ocr_model={sample_entry.get('ocr_model', 'MISSING')}, ocr_device={sample_entry.get('ocr_device', 'MISSING')}")
719
 
720
- # Charger dataset existant et combiner (IMPORTANT!)
721
  try:
722
  existing_dataset = load_dataset(dataset_name, split="train")
723
  existing_data = existing_dataset.to_list()
@@ -734,14 +679,14 @@ def export_to_clean_dataset(session_data: list[dict], dataset_name: str = None)
734
  clean_dataset = Dataset.from_list(clean_entries)
735
  print(f"📊 Nouveau dataset créé avec {len(clean_entries)} entrées")
736
 
737
- # MODIFICATION CLÉE: Spécifier que la colonne handwriting_image est de type Image
738
  try:
739
  clean_dataset = clean_dataset.cast_column("handwriting_image", DatasetImage())
740
  print("✅ Colonne handwriting_image convertie au type Image natif")
741
  except Exception as e:
742
  print(f"⚠️ Impossible de convertir au type Image: {e}")
743
 
744
- print(f"✅ Dataset créé - Features:")
745
  for feature_name in clean_dataset.features:
746
  print(f" - {feature_name}: {clean_dataset.features[feature_name]}")
747
 
@@ -754,20 +699,21 @@ def export_to_clean_dataset(session_data: list[dict], dataset_name: str = None)
754
  operations_summary = ", ".join([f"{op}: {count}" for op, count in operations_count.items()])
755
 
756
  # Push vers HuggingFace
757
- print(f"📤 Push vers {dataset_name}...")
758
  clean_dataset.push_to_hub(
759
  dataset_name,
760
  private=False,
761
  token=hf_token,
762
- commit_message=f"Add {len(clean_entries)} native image samples for math OCR ({operations_summary})"
763
  )
764
 
765
  cleanup_memory()
766
 
767
- return f"""### ✅ Session ajoutée au dataset avec succès !
768
 
769
  📊 **Dataset:** {dataset_name}
770
- 🖼️ **Images natives:** {len(clean_entries)}
 
771
  🔢 **Opérations:** {operations_summary}
772
  📈 **Total:** {len(clean_dataset)}
773
 
@@ -776,7 +722,7 @@ def export_to_clean_dataset(session_data: list[dict], dataset_name: str = None)
776
 
777
 
778
  except Exception as e:
779
- print(f"❌ ERREUR: {e}")
780
  import traceback
781
  traceback.print_exc()
782
- return f"❌ Erreur: {str(e)}"
 
1
  # ==========================================
2
+ # game_engine.py - Calcul OCR v3.0 GPU simplifié
3
  # ==========================================
4
 
5
  """
6
+ Moteur de jeu mathématique optimisé pour ZeroGPU HuggingFace Spaces
 
7
  """
8
 
9
  import random
 
19
  import queue
20
  from typing import Dict, Tuple, Optional
21
 
22
+ # Import GPU uniquement
23
+ from image_processing_gpu import (
24
+ recognize_number_fast_with_image,
25
+ create_thumbnail_fast,
26
+ create_white_canvas,
27
+ cleanup_memory,
28
+ log_memory_usage,
29
+ get_ocr_model_info
30
+ )
31
 
32
+ print("✅ Game Engine: Mode GPU TrOCR")
 
 
 
 
 
33
 
34
+ # Récupérer les infos du modèle OCR
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  try:
36
  ocr_info = get_ocr_model_info()
37
  print(f"🎯 OCR sélectionné: {ocr_info['model_name']} sur {ocr_info['device']}")
38
  except Exception as e:
39
  print(f"⚠️ Impossible de récupérer les infos OCR: {e}")
40
+ ocr_info = {"model_name": "TrOCR", "device": "ZeroGPU"}
41
 
42
+ # Imports dataset
43
  try:
44
  from datasets import Dataset, Image as DatasetImage, load_dataset
45
  DATASET_AVAILABLE = True
 
48
  DATASET_AVAILABLE = False
49
  print(f"⚠️ Modules dataset non disponibles: {e}")
50
 
51
+ # Dataset name
52
  DATASET_NAME = "hoololi/calcul_ocr_dataset_with_images_v2"
53
 
54
  # Configuration des difficultés par opération
 
115
 
116
 
117
  class MathGame:
118
+ """Moteur de jeu mathématique avec traitement parallèle GPU"""
119
 
120
  def __init__(self):
121
  self.is_running = False
 
167
  self.processing_active = True
168
  self.worker_thread = threading.Thread(target=self._process_images_worker, daemon=True)
169
  self.worker_thread.start()
170
+ print("🔄 Thread de traitement GPU parallèle démarré")
171
 
172
  def _stop_background_processing(self) -> None:
173
  """Arrête le thread de traitement"""
174
  self.processing_active = False
175
  if self.worker_thread and self.worker_thread.is_alive():
176
+ print("⏹️ Arrêt du thread de traitement GPU parallèle")
177
 
178
  def _process_images_worker(self) -> None:
179
+ """Worker thread qui traite les images en arrière-plan avec GPU"""
180
+ print("🚀 Worker thread GPU démarré")
181
  while self.processing_active:
182
  try:
183
  if not self.processing_queue.empty():
184
  question_num, image, expected, operation_data = self.processing_queue.get(timeout=1)
185
+ print(f"🔄 Traitement GPU parallèle image {question_num}...")
186
 
187
  start_time = time.time()
188
  result_data = create_result_row_with_images(question_num, image, expected, operation_data)
 
190
 
191
  # Stocker le résultat
192
  self.results_cache[question_num] = result_data
193
+ print(f"✅ Image {question_num} traitée en {processing_time:.1f}s (GPU parallèle)")
194
 
195
  else:
196
  time.sleep(0.1)
 
198
  except queue.Empty:
199
  continue
200
  except Exception as e:
201
+ print(f"❌ Erreur traitement GPU parallèle: {e}")
202
 
203
+ print("🛑 Worker thread GPU terminé")
204
 
205
  def _add_image_to_processing_queue(self, question_num: int, image: dict | np.ndarray | Image.Image,
206
  expected: int, operation_data: tuple) -> None:
207
+ """Ajoute une image à la queue de traitement GPU"""
208
  if image is not None:
209
  self.processing_queue.put((question_num, image, expected, operation_data))
210
+ print(f"📝 Image {question_num} ajoutée à la queue de traitement GPU")
211
 
212
  def generate_multiplication(self, difficulty: str) -> tuple[str, int]:
213
  """Génère une multiplication"""
 
303
  self.export_timestamp = None
304
  self.export_result = None
305
 
306
+ # Démarrer le traitement parallèle GPU
307
  self._start_background_processing()
308
 
309
  gc.collect()
 
351
  return self.end_game(image_data)
352
 
353
  if image_data is not None:
354
+ # Ajouter l'image à la liste ET au traitement parallèle GPU
355
  self.user_images.append(image_data)
356
  self.expected_answers.append(self.correct_answer)
357
 
 
360
  a, op, b = int(parts[0]), parts[1], int(parts[2])
361
  current_operation_data = (a, b, op, self.correct_answer)
362
 
363
+ # Lancer le traitement GPU en parallèle de l'image qu'on vient de recevoir
364
  self._add_image_to_processing_queue(self.question_count, image_data, self.correct_answer, current_operation_data)
365
 
366
  self.question_count += 1
 
401
 
402
  self.is_running = False
403
 
404
+ # Arrêter le traitement parallèle GPU
405
  self._stop_background_processing()
406
 
407
+ print("🏁 Fin de jeu - Assemblage des résultats GPU...")
408
 
409
  if final_image is not None:
410
  self.user_images.append(final_image)
 
415
  a, op, b = int(parts[0]), parts[1], int(parts[2])
416
  final_operation_data = (a, b, op, self.correct_answer)
417
 
418
+ # Traiter la dernière image immédiatement avec GPU
419
+ print(f"🔄 Traitement GPU final de l'image {self.question_count}...")
420
  final_result = create_result_row_with_images(self.question_count, final_image, self.correct_answer, final_operation_data)
421
  self.results_cache[self.question_count] = final_result
422
 
 
424
  if len(self.operations_history) < len(self.user_images):
425
  self.operations_history.append((a, b, op, self.correct_answer))
426
 
427
+ # Attendre que toutes les images soient traitées par GPU
428
  max_wait = 10
429
  wait_start = time.time()
430
  expected_results = len(self.user_images)
431
 
432
+ print(f"⏳ Attente de {expected_results} résultats GPU...")
433
  while len(self.results_cache) < expected_results and (time.time() - wait_start) < max_wait:
434
  time.sleep(0.1)
435
 
436
  results_ready = len(self.results_cache)
437
+ print(f"✅ {results_ready}/{expected_results} résultats GPU prêts")
438
 
439
  # Assembler les résultats dans l'ordre
440
  correct_answers = 0
 
447
  self.session_data = []
448
  images_saved = 0
449
 
450
+ print(f"📊 Assemblage de {total_questions} résultats GPU...")
451
 
452
  for i in range(total_questions):
453
  if i in self.results_cache:
454
  row_data = self.results_cache[i]
455
+ print(f" ✅ Résultat {i} du cache GPU parallèle")
456
  else:
457
+ print(f" 🔄 Traitement GPU fallback pour résultat {i}...")
458
  if i < len(self.operations_history):
459
  row_data = create_result_row_with_images(i, self.user_images[i], self.expected_answers[i], self.operations_history[i])
460
  else:
 
471
  if row_data['is_correct']:
472
  correct_answers += 1
473
 
474
+ # Structure pour dataset avec info OCR GPU
475
  a, b, operation, correct_result = self.operations_history[i] if i < len(self.operations_history) else (0, 0, "×", 0)
476
 
477
  try:
478
  ocr_info_data = get_ocr_model_info()
479
+ print(f"🔍 Debug OCR info GPU: {ocr_info_data}")
480
  except Exception as e:
481
  print(f"❌ Erreur get_ocr_model_info: {e}")
482
+ ocr_info_data = {"model_name": "TrOCR", "device": "ZeroGPU"}
483
 
484
  entry = {
485
  "session_id": session_id,
 
492
  "operand_b": b,
493
  "operation": operation,
494
  "correct_answer": self.expected_answers[i] if i < len(self.expected_answers) else 0,
495
+ "ocr_model": ocr_info_data.get("model_name", "TrOCR"),
496
+ "ocr_device": ocr_info_data.get("device", "ZeroGPU"),
497
  "user_answer_ocr": row_data['recognized'],
498
  "user_answer_parsed": row_data['recognized_num'],
499
  "is_correct": row_data['is_correct'],
500
  "total_questions": total_questions,
501
+ "app_version": "3.0_calcul_ocr_gpu_simplified"
502
  }
503
 
504
+ print(f"🔍 Debug entry OCR GPU: ocr_model={entry['ocr_model']}, ocr_device={entry['ocr_device']}")
505
 
506
+ # Image PIL native pour dataset
507
  if row_data['dataset_image_data']:
508
  entry["handwriting_image"] = row_data['dataset_image_data']["handwriting_image"] # Image PIL native
509
  entry["image_width"] = int(row_data['dataset_image_data']["width"])
 
520
  for entry in self.session_data:
521
  entry["session_accuracy"] = accuracy
522
 
523
+ # Nettoyage mémoire GPU
524
  for img in self.user_images:
525
  if hasattr(img, 'close'):
526
  try:
 
542
  <th style="padding: 8px;">B</th>
543
  <th style="padding: 8px;">Réponse</th>
544
  <th style="padding: 8px;">Votre dessin</th>
545
+ <th style="padding: 8px;">OCR GPU</th>
546
  <th style="padding: 8px;">Statut</th>
547
  </tr>
548
  </thead>
 
564
  if export_info["can_export"]:
565
  export_section = f"""
566
  <div style="margin-top: 20px; padding: 15px; background-color: #e8f5e8; border-radius: 8px;">
567
+ <h3 style="color: #2e7d32;">📊 Résumé de la série (GPU)</h3>
568
  <p style="color: #2e7d32;">
569
  ✅ {total_questions} réponses • 📊 {accuracy:.1f}% de précision<br>
570
  🖼️ {images_saved} images natives sauvegardées<br>
571
+ 🎯 OCR: TrOCR ZeroGPU<br>
572
  ⚙️ Configuration: {config_display}
573
  </p>
574
  </div>
 
599
  </div>
600
  </div>
601
  </div>
602
+ <h2 style="color: #4a90e2;">📊 Détail des Réponses (TrOCR GPU)</h2>
603
  {table_html}
604
  {export_section}
605
  </div>
 
608
  return (
609
  """<div style="font-size: 3em; font-weight: bold; text-align: center; padding: 20px; background: linear-gradient(45deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px;">🏁 C'est fini !</div>""",
610
  create_white_canvas(),
611
+ f"✨ Session GPU {config_display} terminée !",
612
  "⏱️ Temps écoulé !",
613
  gr.update(interactive=True),
614
  gr.update(interactive=False),
 
617
 
618
 
619
  def export_to_clean_dataset(session_data: list[dict], dataset_name: str = None) -> str:
620
+ """Export vers le dataset avec images natives optimisé GPU"""
621
  if dataset_name is None:
622
+ dataset_name = DATASET_NAME
623
 
624
  if not DATASET_AVAILABLE:
625
  return "❌ Modules dataset non disponibles"
 
629
  return "❌ Token HuggingFace manquant (HF_TOKEN ou tk_calcul_ocr)"
630
 
631
  try:
632
+ print(f"\n🚀 === EXPORT VERS DATASET CALCUL OCR GPU ===")
633
  print(f"📊 Dataset: {dataset_name}")
634
 
635
+ # Filtrer les entrées avec images et ajouter les infos OCR GPU
636
  clean_entries = []
637
 
638
+ # Récupérer les infos OCR GPU pour toute la session
639
  try:
640
  global_ocr_info = get_ocr_model_info()
641
+ print(f"🔍 Infos OCR GPU globales: {global_ocr_info}")
642
  except Exception as e:
643
+ print(f"❌ Erreur infos OCR GPU: {e}")
644
+ global_ocr_info = {"model_name": "TrOCR", "device": "ZeroGPU"}
645
 
646
  for entry in session_data:
647
  if entry.get('has_image', False):
648
+ # Ajouter explicitement les champs OCR GPU
649
  entry_with_ocr = entry.copy()
650
+ entry_with_ocr["ocr_model"] = global_ocr_info.get("model_name", "TrOCR")
651
+ entry_with_ocr["ocr_device"] = global_ocr_info.get("device", "ZeroGPU")
652
 
653
+ print(f"🔍 Entry GPU avec OCR: ocr_model={entry_with_ocr['ocr_model']}, ocr_device={entry_with_ocr['ocr_device']}")
654
  print(f"🖼️ Image type: {type(entry_with_ocr.get('handwriting_image', 'None'))}")
655
  clean_entries.append(entry_with_ocr)
656
 
 
657
  if len(clean_entries) == 0:
658
  return "❌ Aucune entrée avec image à exporter"
659
 
660
  # Vérifier la structure de la première entrée
661
  sample_entry = clean_entries[0]
662
+ print(f"🔍 Structure première entrée GPU: {list(sample_entry.keys())}")
663
+ print(f"🔍 OCR GPU dans entrée: ocr_model={sample_entry.get('ocr_model', 'MISSING')}, ocr_device={sample_entry.get('ocr_device', 'MISSING')}")
664
 
665
+ # Charger dataset existant et combiner
666
  try:
667
  existing_dataset = load_dataset(dataset_name, split="train")
668
  existing_data = existing_dataset.to_list()
 
679
  clean_dataset = Dataset.from_list(clean_entries)
680
  print(f"📊 Nouveau dataset créé avec {len(clean_entries)} entrées")
681
 
682
+ # Spécifier que la colonne handwriting_image est de type Image
683
  try:
684
  clean_dataset = clean_dataset.cast_column("handwriting_image", DatasetImage())
685
  print("✅ Colonne handwriting_image convertie au type Image natif")
686
  except Exception as e:
687
  print(f"⚠️ Impossible de convertir au type Image: {e}")
688
 
689
+ print(f"✅ Dataset GPU créé - Features:")
690
  for feature_name in clean_dataset.features:
691
  print(f" - {feature_name}: {clean_dataset.features[feature_name]}")
692
 
 
699
  operations_summary = ", ".join([f"{op}: {count}" for op, count in operations_count.items()])
700
 
701
  # Push vers HuggingFace
702
+ print(f"📤 Push GPU vers {dataset_name}...")
703
  clean_dataset.push_to_hub(
704
  dataset_name,
705
  private=False,
706
  token=hf_token,
707
+ commit_message=f"Add {len(clean_entries)} GPU TrOCR native image samples for math OCR ({operations_summary})"
708
  )
709
 
710
  cleanup_memory()
711
 
712
+ return f"""### ✅ Session GPU ajoutée au dataset avec succès !
713
 
714
  📊 **Dataset:** {dataset_name}
715
+ 🖼️ **Images natives (GPU):** {len(clean_entries)}
716
+ 🎯 **OCR:** TrOCR ZeroGPU
717
  🔢 **Opérations:** {operations_summary}
718
  📈 **Total:** {len(clean_dataset)}
719
 
 
722
 
723
 
724
  except Exception as e:
725
+ print(f"❌ ERREUR GPU: {e}")
726
  import traceback
727
  traceback.print_exc()
728
+ return f"❌ Erreur GPU: {str(e)}"
image_processing_gpu.py CHANGED
@@ -1,37 +1,15 @@
1
  # ==========================================
2
- # image_processing_gpu.py - Version ZeroGPU compatible (SANS BASE64)
3
  # ==========================================
4
 
5
  """
6
- Module de traitement d'images GPU-optimisé pour calculs mathématiques
7
- Compatible ZeroGPU HuggingFace Spaces
8
- VERSION MODIFIÉE: Format Image natif au lieu de Base64
9
  """
10
 
11
  import time
12
-
13
- # Import spaces avec gestion d'erreur complète
14
- try:
15
- import spaces
16
- print("✅ Import spaces réussi dans image_processing_gpu")
17
- SPACES_AVAILABLE = True
18
- except ImportError as e:
19
- print(f"❌ Import spaces échoué: {e}")
20
- # Créer un mock si spaces n'est pas disponible
21
- class MockSpaces:
22
- @staticmethod
23
- def GPU(func):
24
- print(f"MockSpaces.GPU décorateur appliqué à {func.__name__}")
25
- return func
26
- spaces = MockSpaces()
27
- SPACES_AVAILABLE = False
28
-
29
- try:
30
- import torch
31
- TORCH_AVAILABLE = True
32
- except ImportError:
33
- print("❌ Torch non disponible")
34
- TORCH_AVAILABLE = False
35
 
36
  from utils import (
37
  optimize_image_for_ocr,
@@ -49,17 +27,11 @@ model = None
49
  OCR_MODEL_NAME = "TrOCR-base-handwritten"
50
 
51
  def init_ocr_model() -> bool:
52
- """Initialise TrOCR (ZeroGPU compatible)"""
53
  global processor, model
54
 
55
  try:
56
- print("🔄 Chargement TrOCR (ZeroGPU optimisé)...")
57
-
58
- if not TORCH_AVAILABLE:
59
- print("❌ Torch non disponible, impossible de charger TrOCR")
60
- return False
61
-
62
- from transformers import TrOCRProcessor, VisionEncoderDecoderModel
63
 
64
  processor = TrOCRProcessor.from_pretrained('microsoft/trocr-base-handwritten')
65
  model = VisionEncoderDecoderModel.from_pretrained('microsoft/trocr-base-handwritten')
@@ -83,9 +55,9 @@ def init_ocr_model() -> bool:
83
 
84
  def get_ocr_model_info() -> dict:
85
  """Retourne les informations du modèle OCR utilisé"""
86
- if TORCH_AVAILABLE and torch.cuda.is_available():
87
  device = "ZeroGPU"
88
- gpu_name = torch.cuda.get_device_name() if torch.cuda.is_available() else "N/A"
89
  else:
90
  device = "CPU"
91
  gpu_name = "N/A"
@@ -99,10 +71,10 @@ def get_ocr_model_info() -> dict:
99
  "version": "microsoft/trocr-base-handwritten"
100
  }
101
 
102
- @spaces.GPU # Décorateur ZeroGPU
103
  def recognize_number_fast_with_image(image_dict, debug: bool = False) -> tuple[str, any, dict | None]:
104
  """
105
- OCR avec TrOCR (ZeroGPU optimisé) - VERSION SANS BASE64
106
  """
107
  if image_dict is None:
108
  if debug:
@@ -152,7 +124,7 @@ def recognize_number_fast_with_image(image_dict, debug: bool = False) -> tuple[s
152
  result = processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
153
  final_result = validate_ocr_result(result, max_length=4)
154
 
155
- # Préparer pour dataset (NOUVELLE VERSION SANS BASE64)
156
  dataset_image_data = prepare_image_for_dataset(optimized_image)
157
 
158
  if debug:
 
1
  # ==========================================
2
+ # image_processing_gpu.py - Version ZeroGPU simplifiée
3
  # ==========================================
4
 
5
  """
6
+ Module de traitement d'images GPU-optimisé pour ZeroGPU HuggingFace Spaces
 
 
7
  """
8
 
9
  import time
10
+ import torch
11
+ import spaces
12
+ from transformers import TrOCRProcessor, VisionEncoderDecoderModel
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  from utils import (
15
  optimize_image_for_ocr,
 
27
  OCR_MODEL_NAME = "TrOCR-base-handwritten"
28
 
29
  def init_ocr_model() -> bool:
30
+ """Initialise TrOCR pour ZeroGPU"""
31
  global processor, model
32
 
33
  try:
34
+ print("🔄 Chargement TrOCR (ZeroGPU)...")
 
 
 
 
 
 
35
 
36
  processor = TrOCRProcessor.from_pretrained('microsoft/trocr-base-handwritten')
37
  model = VisionEncoderDecoderModel.from_pretrained('microsoft/trocr-base-handwritten')
 
55
 
56
  def get_ocr_model_info() -> dict:
57
  """Retourne les informations du modèle OCR utilisé"""
58
+ if torch.cuda.is_available():
59
  device = "ZeroGPU"
60
+ gpu_name = torch.cuda.get_device_name()
61
  else:
62
  device = "CPU"
63
  gpu_name = "N/A"
 
71
  "version": "microsoft/trocr-base-handwritten"
72
  }
73
 
74
+ @spaces.GPU
75
  def recognize_number_fast_with_image(image_dict, debug: bool = False) -> tuple[str, any, dict | None]:
76
  """
77
+ OCR avec TrOCR ZeroGPU - Version simplifiée
78
  """
79
  if image_dict is None:
80
  if debug:
 
124
  result = processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
125
  final_result = validate_ocr_result(result, max_length=4)
126
 
127
+ # Préparer pour dataset
128
  dataset_image_data = prepare_image_for_dataset(optimized_image)
129
 
130
  if debug:
requirements.txt CHANGED
@@ -1,4 +1,4 @@
1
- # Requirements unifiés avec fallbacks intelligents
2
  gradio>=4.0.0
3
  pillow>=9.0.0
4
  numpy>=1.21.0
@@ -7,11 +7,8 @@ huggingface_hub>=0.16.0
7
  pandas>=1.5.0
8
  psutil>=5.8.0
9
 
10
- # EasyOCR (toujours installé - fonctionne partout)
11
- easyocr>=1.7.0
12
-
13
- # GPU/TrOCR (optionnel - installé si GPU disponible)
14
- # Ces packages seront installés automatiquement sur les spaces GPU
15
- torch>=2.0.0; sys_platform != "emscripten"
16
- torchvision>=0.15.0; sys_platform != "emscripten"
17
- transformers>=4.30.0; sys_platform != "emscripten"
 
1
+ # Requirements simplifiés pour GPU/ZeroGPU uniquement
2
  gradio>=4.0.0
3
  pillow>=9.0.0
4
  numpy>=1.21.0
 
7
  pandas>=1.5.0
8
  psutil>=5.8.0
9
 
10
+ # GPU/TrOCR pour ZeroGPU HuggingFace Spaces
11
+ torch>=2.0.0
12
+ torchvision>=0.15.0
13
+ transformers>=4.30.0
14
+ spaces
 
 
 
utils.py CHANGED
@@ -1,17 +1,15 @@
1
  # ==========================================
2
- # utils.py - Fonctions communes CPU/GPU (VERSION SANS BASE64)
3
  # ==========================================
4
 
5
  """
6
- Utilitaires partagés pour le traitement d'images OCR
7
- Fonctions communes aux versions CPU et GPU - Format Image natif
8
  """
9
 
10
  from PIL import Image, ImageEnhance
11
  import numpy as np
12
  import gc
13
  import os
14
- import time
15
 
16
  def create_white_canvas(width: int = 300, height: int = 300) -> Image.Image:
17
  """Crée un canvas blanc pour le dessin de calculs"""
@@ -28,12 +26,18 @@ def log_memory_usage(context: str = "") -> None:
28
  pass
29
 
30
  def cleanup_memory() -> None:
31
- """Force le nettoyage mémoire"""
32
  gc.collect()
 
 
 
 
 
 
33
 
34
  def optimize_image_for_ocr(image_dict: dict | np.ndarray | Image.Image | None, max_size: int = 300) -> Image.Image | None:
35
  """
36
- Optimisation image commune pour tous types d'OCR
37
 
38
  Args:
39
  image_dict: Image d'entrée (format Gradio, numpy ou PIL)
@@ -121,14 +125,13 @@ def create_thumbnail_fast(optimized_image: Image.Image | None, size: tuple[int,
121
  size: Taille de la miniature (largeur, hauteur)
122
 
123
  Returns:
124
- HTML img tag avec image base64 ou icône par défaut (pour affichage uniquement)
125
  """
126
  try:
127
  if optimized_image is None:
128
  return "📝"
129
 
130
  # Pour l'affichage dans l'interface, on garde le base64 temporairement
131
- # mais seulement pour les miniatures d'affichage
132
  import base64
133
  from io import BytesIO
134
 
@@ -171,41 +174,4 @@ def validate_ocr_result(raw_result: str, max_length: int = 4) -> str:
171
  # Si trop long, prendre les premiers chiffres
172
  return cleaned_result[:max_length]
173
  else:
174
- return "0"
175
-
176
- def analyze_calculation_complexity(operand_a: int, operand_b: int, operation: str) -> dict:
177
- """
178
- Analyse la complexité d'un calcul pour enrichir les métadonnées dataset
179
-
180
- Args:
181
- operand_a: Premier opérande
182
- operand_b: Deuxième opérande
183
- operation: Type d'opération (×, +, -, ÷)
184
-
185
- Returns:
186
- Dictionnaire avec score de complexité et catégorie
187
- """
188
- complexity_score = 0
189
-
190
- if operation == "×":
191
- complexity_score = max(operand_a, operand_b)
192
- elif operation == "+":
193
- complexity_score = (operand_a + operand_b) / 20
194
- elif operation == "-":
195
- complexity_score = max(operand_a, operand_b) / 10
196
- elif operation == "÷":
197
- complexity_score = operand_a / 10
198
-
199
- # Catégorisation
200
- if complexity_score < 5:
201
- category = "easy"
202
- elif complexity_score < 10:
203
- category = "medium"
204
- else:
205
- category = "hard"
206
-
207
- return {
208
- "complexity_score": round(complexity_score, 2),
209
- "difficulty_category": category,
210
- "operation_type": operation
211
- }
 
1
  # ==========================================
2
+ # utils.py - Fonctions communes GPU simplifiées
3
  # ==========================================
4
 
5
  """
6
+ Utilitaires partagés pour le traitement d'images OCR GPU
 
7
  """
8
 
9
  from PIL import Image, ImageEnhance
10
  import numpy as np
11
  import gc
12
  import os
 
13
 
14
  def create_white_canvas(width: int = 300, height: int = 300) -> Image.Image:
15
  """Crée un canvas blanc pour le dessin de calculs"""
 
26
  pass
27
 
28
  def cleanup_memory() -> None:
29
+ """Force le nettoyage mémoire GPU"""
30
  gc.collect()
31
+ try:
32
+ import torch
33
+ if torch.cuda.is_available():
34
+ torch.cuda.empty_cache()
35
+ except:
36
+ pass
37
 
38
  def optimize_image_for_ocr(image_dict: dict | np.ndarray | Image.Image | None, max_size: int = 300) -> Image.Image | None:
39
  """
40
+ Optimisation image pour OCR GPU
41
 
42
  Args:
43
  image_dict: Image d'entrée (format Gradio, numpy ou PIL)
 
125
  size: Taille de la miniature (largeur, hauteur)
126
 
127
  Returns:
128
+ HTML img tag avec image base64 ou icône par défaut
129
  """
130
  try:
131
  if optimized_image is None:
132
  return "📝"
133
 
134
  # Pour l'affichage dans l'interface, on garde le base64 temporairement
 
135
  import base64
136
  from io import BytesIO
137
 
 
174
  # Si trop long, prendre les premiers chiffres
175
  return cleaned_result[:max_length]
176
  else:
177
+ return "0"