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

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +17 -16
  2. game_engine.py +73 -230
app.py CHANGED
@@ -1,17 +1,15 @@
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
10
  import warnings
11
  import os
12
- import gc
13
- import numpy as np
14
- from PIL import Image
15
 
16
  warnings.filterwarnings("ignore")
17
 
@@ -19,7 +17,7 @@ warnings.filterwarnings("ignore")
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")
@@ -29,24 +27,24 @@ def create_new_game_session():
29
  return MathGame()
30
 
31
  def start_game_wrapper(duration: str, operation: str, difficulty: str, game_state) -> tuple:
32
- """Wrapper avec isolation de session"""
33
  if game_state is None:
34
  game_state = create_new_game_session()
35
 
36
  cleanup_memory()
37
  result = game_state.start_game(duration, operation, difficulty)
38
- return result + (game_state, "") # Retourner l'état mis à jour
39
 
40
  def next_question_wrapper(image_data, game_state) -> tuple:
41
- """Wrapper avec isolation de session"""
42
  if game_state is None:
43
  game_state = create_new_game_session()
44
 
45
  result = game_state.next_question(image_data)
46
- return result + (game_state,) # Retourner l'état mis à jour
47
 
48
  def export_current_session(game_state) -> str:
49
- """Export avec isolation de session"""
50
  if game_state is None:
51
  return "❌ Aucune session active"
52
 
@@ -81,7 +79,7 @@ def export_current_session(game_state) -> str:
81
  game_state.export_status = "not_exported"
82
  return f"❌ Erreur export: {str(e)}"
83
 
84
- # Interface Gradio avec State management
85
  with gr.Blocks(
86
  title="🧮 Calcul OCR - Entraînement mathématiques",
87
  theme=gr.themes.Soft(),
@@ -111,7 +109,7 @@ with gr.Blocks(
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(
@@ -125,6 +123,8 @@ with gr.Blocks(
125
  4. Cliquez sur **➡️ NEXT !** pour la question suivante
126
 
127
  À la fin, vous pourrez consulter vos résultats et contribuer au dataset ouvert !
 
 
128
  """
129
  )
130
 
@@ -180,11 +180,11 @@ with gr.Blocks(
180
  # Résultats
181
  results_display = gr.HTML("")
182
 
183
- # Export vers dataset dédié
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,8 +204,9 @@ with gr.Blocks(
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")
 
1
  # ==========================================
2
+ # app.py - Calcul OCR v3.0 - Version ultra-simplifiée
3
  # ==========================================
4
 
5
  """
6
+ Application principale ultra-simplifiée pour ZeroGPU HuggingFace Spaces
7
+ OCR en fin de session uniquement - Performance optimale
8
  """
9
 
10
  import gradio as gr
11
  import warnings
12
  import os
 
 
 
13
 
14
  warnings.filterwarnings("ignore")
15
 
 
17
  from image_processing_gpu import init_ocr_model, create_white_canvas, cleanup_memory
18
  from game_engine import MathGame, export_to_clean_dataset
19
 
20
+ print("🚀 Initialisation Calcul OCR v3.0 (Ultra-simplifié)...")
21
  print("🔄 Chargement modèle TrOCR...")
22
  init_ocr_model()
23
  print("✅ Modèle TrOCR prêt")
 
27
  return MathGame()
28
 
29
  def start_game_wrapper(duration: str, operation: str, difficulty: str, game_state) -> tuple:
30
+ """Démarre une nouvelle session de jeu"""
31
  if game_state is None:
32
  game_state = create_new_game_session()
33
 
34
  cleanup_memory()
35
  result = game_state.start_game(duration, operation, difficulty)
36
+ return result + (game_state, "")
37
 
38
  def next_question_wrapper(image_data, game_state) -> tuple:
39
+ """Passe à la question suivante - Stockage simple, pas d'OCR"""
40
  if game_state is None:
41
  game_state = create_new_game_session()
42
 
43
  result = game_state.next_question(image_data)
44
+ return result + (game_state,)
45
 
46
  def export_current_session(game_state) -> str:
47
+ """Export de la session vers le dataset"""
48
  if game_state is None:
49
  return "❌ Aucune session active"
50
 
 
79
  game_state.export_status = "not_exported"
80
  return f"❌ Erreur export: {str(e)}"
81
 
82
+ # Interface Gradio
83
  with gr.Blocks(
84
  title="🧮 Calcul OCR - Entraînement mathématiques",
85
  theme=gr.themes.Soft(),
 
109
  head="<meta name='viewport' content='width=device-width, initial-scale=1.0'>"
110
  ) as demo:
111
 
112
+ # State management - Isolation par utilisateur
113
  game_state = gr.State(value=None)
114
 
115
  gr.Markdown(
 
123
  4. Cliquez sur **➡️ NEXT !** pour la question suivante
124
 
125
  À la fin, vous pourrez consulter vos résultats et contribuer au dataset ouvert !
126
+
127
+ 🚀 **Version ultra-optimisée** : OCR en fin de session pour une fluidité maximale !
128
  """
129
  )
130
 
 
180
  # Résultats
181
  results_display = gr.HTML("")
182
 
183
+ # Export vers dataset
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
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 - Ultra-simplifié")
208
  print("🎯 ZeroGPU HuggingFace Spaces")
209
+ print("⚡ Performance optimale : OCR en fin de session")
210
  print("🎯 Dataset: calcul_ocr_dataset")
211
  print("📊 Opérations: ×, +, -, ÷, Aléatoire")
212
  print("⚙️ Durées: 30s, 60s")
game_engine.py CHANGED
@@ -1,9 +1,10 @@
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
@@ -15,9 +16,6 @@ import uuid
15
  import gc
16
  import numpy as np
17
  from PIL import Image
18
- import threading
19
- import queue
20
- from typing import Dict, Tuple, Optional
21
 
22
  # Import GPU uniquement
23
  from image_processing_gpu import (
@@ -25,19 +23,10 @@ from image_processing_gpu import (
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:
@@ -60,22 +49,17 @@ DIFFICULTY_RANGES = {
60
  }
61
 
62
  def create_result_row_with_images(i: int, image: dict | np.ndarray | Image.Image, expected: int, operation_data: tuple[int, int, str, int]) -> dict:
 
63
 
64
- print(f"🔍 create_result_row_with_images #{i}")
65
- print(f"🔍 Expected: {expected}")
66
- print(f"🔍 Image type: {type(image)}")
67
 
68
- # OCR optimisé avec debug
69
  recognized, optimized_image, dataset_image_data = recognize_number_fast_with_image(image, debug=True)
70
 
71
- print(f"🔍 OCR recognized: '{recognized}' (type: {type(recognized)})")
72
-
73
  try:
74
  recognized_num = int(recognized) if recognized.isdigit() else 0
75
  except:
76
  recognized_num = 0
77
-
78
- print(f"🔍 OCR parsed num: {recognized_num}")
79
 
80
  is_correct = recognized_num == expected
81
  a, b, operation, correct_result = operation_data
@@ -84,10 +68,10 @@ def create_result_row_with_images(i: int, image: dict | np.ndarray | Image.Image
84
  status_text = "Correct" if is_correct else "Incorrect"
85
  row_color = "#e8f5e8" if is_correct else "#ffe8e8"
86
 
87
- # Miniature
88
  image_thumbnail = create_thumbnail_fast(optimized_image, size=(50, 50))
89
 
90
- # Libérer mémoire
91
  if optimized_image and hasattr(optimized_image, 'close'):
92
  try:
93
  optimized_image.close()
@@ -115,7 +99,7 @@ def create_result_row_with_images(i: int, image: dict | np.ndarray | Image.Image
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
@@ -138,12 +122,6 @@ class MathGame:
138
  self.export_status = "not_exported"
139
  self.export_timestamp = None
140
  self.export_result = None
141
-
142
- # Traitement parallèle
143
- self.processing_queue = queue.Queue()
144
- self.results_cache: Dict[int, dict] = {}
145
- self.worker_thread: Optional[threading.Thread] = None
146
- self.processing_active = False
147
 
148
  def get_export_status(self) -> dict[str, str | bool | None]:
149
  return {
@@ -161,54 +139,6 @@ class MathGame:
161
  self.export_status = "exported"
162
  self.export_result = result
163
 
164
- def _start_background_processing(self) -> None:
165
- """Démarre le thread de traitement en arrière-plan"""
166
- if self.worker_thread is None or not self.worker_thread.is_alive():
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)
189
- processing_time = time.time() - start_time
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)
197
-
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"""
214
  min_val, max_val = DIFFICULTY_RANGES["×"][difficulty]
@@ -265,7 +195,7 @@ class MathGame:
265
  self.operation_type = operation
266
  self.difficulty = difficulty
267
 
268
- # Nettoyage
269
  if hasattr(self, 'user_images') and self.user_images:
270
  for img in self.user_images:
271
  if hasattr(img, 'close'):
@@ -274,21 +204,7 @@ class MathGame:
274
  except:
275
  pass
276
 
277
- if hasattr(self, 'session_data') and self.session_data:
278
- for entry in self.session_data:
279
- if 'user_drawing' in entry and entry['user_drawing']:
280
- entry['user_drawing'] = None
281
- self.session_data.clear()
282
-
283
- # Réinit avec nettoyage parallèle
284
- self._stop_background_processing()
285
- self.results_cache.clear()
286
- while not self.processing_queue.empty():
287
- try:
288
- self.processing_queue.get_nowait()
289
- except queue.Empty:
290
- break
291
-
292
  self.is_running = True
293
  self.start_time = time.time()
294
  self.user_images = []
@@ -303,9 +219,6 @@ class MathGame:
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()
310
 
311
  # Première opération
@@ -318,7 +231,7 @@ class MathGame:
318
  a, op, b = int(parts[0]), parts[1], int(parts[2])
319
  self.operations_history.append((a, b, op, answer))
320
 
321
- # Affichage adapté selon l'opération
322
  operation_emoji = {
323
  "×": "✖️", "+": "➕", "-": "➖", "÷": "➗", "Aléatoire": "🎲"
324
  }
@@ -335,6 +248,8 @@ class MathGame:
335
  )
336
 
337
  def next_question(self, image_data: dict | np.ndarray | Image.Image | None) -> tuple[str, Image.Image, str, str, gr.update, gr.update, str]:
 
 
338
  if not self.is_running:
339
  return (
340
  f'<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;">{self.current_operation}</div>',
@@ -350,20 +265,12 @@ class MathGame:
350
  if elapsed_time >= self.duration:
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
-
358
- # Parser l'opération actuelle pour le traitement
359
- parts = self.current_operation.split()
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
 
367
 
368
  # Nouvelle opération
369
  operation_str, answer = self.generate_operation(self.operation_type, self.difficulty)
@@ -398,47 +305,27 @@ class MathGame:
398
  )
399
 
400
  def end_game(self, final_image: dict | np.ndarray | Image.Image | None) -> tuple[str, Image.Image, str, str, gr.update, gr.update, str]:
 
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)
411
  self.expected_answers.append(self.correct_answer)
412
-
413
- # Traitement de la dernière image
414
- parts = self.current_operation.split()
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
-
423
  self.question_count += 1
 
 
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
441
  total_questions = len(self.user_images)
 
442
  table_rows_html = ""
443
 
444
  session_timestamp = datetime.datetime.now().isoformat()
@@ -447,36 +334,30 @@ class MathGame:
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:
461
- row_data = {
462
- 'html_row': f'<tr><td>{i+1}</td><td colspan="7">Erreur traitement</td></tr>',
463
- 'is_correct': False,
464
- 'recognized': "0",
465
- 'recognized_num': 0,
466
- 'dataset_image_data': None
467
- }
468
 
469
  table_rows_html += row_data['html_row']
470
 
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"}
@@ -491,21 +372,19 @@ class MathGame:
491
  "operand_a": a,
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"])
510
  entry["image_height"] = int(row_data['dataset_image_data']["height"])
511
  entry["has_image"] = True
@@ -517,10 +396,11 @@ class MathGame:
517
 
518
  accuracy = (correct_answers / total_questions * 100) if total_questions > 0 else 0
519
 
 
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:
@@ -528,7 +408,9 @@ class MathGame:
528
  except:
529
  pass
530
 
531
- gc.collect()
 
 
532
 
533
  # HTML résultats
534
  table_html = f"""
@@ -542,7 +424,7 @@ class MathGame:
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>
@@ -555,20 +437,16 @@ class MathGame:
555
 
556
  # Configuration session pour affichage
557
  config_display = f"{self.operation_type} • {self.difficulty} • {self.duration}s"
558
- operation_emoji = {
559
- "×": "✖️", "+": "➕", "-": "➖", "÷": "➗", "Aléatoire": "🎲"
560
- }
561
- emoji = operation_emoji.get(self.operation_type, "🔢")
562
-
563
  export_info = self.get_export_status()
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>
@@ -579,7 +457,7 @@ class MathGame:
579
  final_results = f"""
580
  <div style="margin: 20px 0;">
581
  <div style="background: linear-gradient(45deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 10px; margin: 20px 0;">
582
- <h2 style="text-align: center; color: #4a90e2;">🎉 Session terminée !</h2>
583
  <div style="display: flex; justify-content: space-around; flex-wrap: wrap;">
584
  <div style="text-align: center; margin: 10px;">
585
  <div style="font-size: 2em; font-weight: bold;">{total_questions}</div>
@@ -599,7 +477,7 @@ class MathGame:
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,7 +486,7 @@ class MathGame:
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,7 +495,7 @@ class MathGame:
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
 
@@ -626,71 +504,40 @@ def export_to_clean_dataset(session_data: list[dict], dataset_name: str = None)
626
 
627
  hf_token = os.getenv("HF_TOKEN") or os.getenv("tk_calcul_ocr")
628
  if not hf_token:
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()
669
- print(f"📊 {len(existing_data)} entrées existantes trouvées")
670
 
671
- # Combiner ancien + nouveau
672
  combined_data = existing_data + clean_entries
673
  clean_dataset = Dataset.from_list(combined_data)
674
- print(f"📊 Dataset combiné: {len(existing_data)} existantes + {len(clean_entries)} nouvelles = {len(combined_data)} total")
675
 
676
  except Exception as e:
677
- print(f"📊 Dataset non trouvé, création nouveau: {e}")
678
- # Si le dataset n'existe pas, créer depuis les nouvelles entrées
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
 
693
- # Statistiques par opération
694
  operations_count = {}
695
  for entry in clean_entries:
696
  op = entry.get('operation_type', 'unknown')
@@ -699,30 +546,26 @@ def export_to_clean_dataset(session_data: list[dict], dataset_name: str = None)
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
 
720
- 🔗 **Le dataset est consultable ici :** <a href="https://huggingface.co/datasets/{dataset_name}" target="_blank" rel="noopener noreferrer">{dataset_name}</a>
721
  """
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)}"
 
1
  # ==========================================
2
+ # game_engine.py - Calcul OCR v3.0 ULTRA SIMPLIFIÉ
3
  # ==========================================
4
 
5
  """
6
+ Moteur de jeu mathématique ultra-simplifié pour ZeroGPU
7
+ OCR en fin de session uniquement - Performance optimale
8
  """
9
 
10
  import random
 
16
  import gc
17
  import numpy as np
18
  from PIL import Image
 
 
 
19
 
20
  # Import GPU uniquement
21
  from image_processing_gpu import (
 
23
  create_thumbnail_fast,
24
  create_white_canvas,
25
  cleanup_memory,
 
26
  get_ocr_model_info
27
  )
28
 
29
+ print("✅ Game Engine: Mode GPU ultra-simplifié")
 
 
 
 
 
 
 
 
30
 
31
  # Imports dataset
32
  try:
 
49
  }
50
 
51
  def create_result_row_with_images(i: int, image: dict | np.ndarray | Image.Image, expected: int, operation_data: tuple[int, int, str, int]) -> dict:
52
+ """Traite une image avec OCR et génère la ligne de résultat"""
53
 
54
+ print(f"🔍 Traitement OCR image #{i+1}")
 
 
55
 
56
+ # OCR avec TrOCR
57
  recognized, optimized_image, dataset_image_data = recognize_number_fast_with_image(image, debug=True)
58
 
 
 
59
  try:
60
  recognized_num = int(recognized) if recognized.isdigit() else 0
61
  except:
62
  recognized_num = 0
 
 
63
 
64
  is_correct = recognized_num == expected
65
  a, b, operation, correct_result = operation_data
 
68
  status_text = "Correct" if is_correct else "Incorrect"
69
  row_color = "#e8f5e8" if is_correct else "#ffe8e8"
70
 
71
+ # Miniature pour affichage
72
  image_thumbnail = create_thumbnail_fast(optimized_image, size=(50, 50))
73
 
74
+ # Libérer mémoire optimisée
75
  if optimized_image and hasattr(optimized_image, 'close'):
76
  try:
77
  optimized_image.close()
 
99
 
100
 
101
  class MathGame:
102
+ """Moteur de jeu mathématique ultra-simplifié"""
103
 
104
  def __init__(self):
105
  self.is_running = False
 
122
  self.export_status = "not_exported"
123
  self.export_timestamp = None
124
  self.export_result = None
 
 
 
 
 
 
125
 
126
  def get_export_status(self) -> dict[str, str | bool | None]:
127
  return {
 
139
  self.export_status = "exported"
140
  self.export_result = result
141
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  def generate_multiplication(self, difficulty: str) -> tuple[str, int]:
143
  """Génère une multiplication"""
144
  min_val, max_val = DIFFICULTY_RANGES["×"][difficulty]
 
195
  self.operation_type = operation
196
  self.difficulty = difficulty
197
 
198
+ # Nettoyage simple
199
  if hasattr(self, 'user_images') and self.user_images:
200
  for img in self.user_images:
201
  if hasattr(img, 'close'):
 
204
  except:
205
  pass
206
 
207
+ # Réinitialisation complète
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  self.is_running = True
209
  self.start_time = time.time()
210
  self.user_images = []
 
219
  self.export_timestamp = None
220
  self.export_result = None
221
 
 
 
 
222
  gc.collect()
223
 
224
  # Première opération
 
231
  a, op, b = int(parts[0]), parts[1], int(parts[2])
232
  self.operations_history.append((a, b, op, answer))
233
 
234
+ # Affichage
235
  operation_emoji = {
236
  "×": "✖️", "+": "➕", "-": "➖", "÷": "➗", "Aléatoire": "🎲"
237
  }
 
248
  )
249
 
250
  def next_question(self, image_data: dict | np.ndarray | Image.Image | None) -> tuple[str, Image.Image, str, str, gr.update, gr.update, str]:
251
+ """Passe à la question suivante - STOCKAGE SIMPLE, PAS D'OCR"""
252
+
253
  if not self.is_running:
254
  return (
255
  f'<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;">{self.current_operation}</div>',
 
265
  if elapsed_time >= self.duration:
266
  return self.end_game(image_data)
267
 
268
+ # STOCKAGE SIMPLE - PAS D'OCR pendant le jeu !
269
  if image_data is not None:
 
270
  self.user_images.append(image_data)
271
  self.expected_answers.append(self.correct_answer)
 
 
 
 
 
 
 
 
 
272
  self.question_count += 1
273
+ print(f"📝 Image {self.question_count} stockée (pas d'OCR pendant le jeu)")
274
 
275
  # Nouvelle opération
276
  operation_str, answer = self.generate_operation(self.operation_type, self.difficulty)
 
305
  )
306
 
307
  def end_game(self, final_image: dict | np.ndarray | Image.Image | None) -> tuple[str, Image.Image, str, str, gr.update, gr.update, str]:
308
+ """Fin de jeu - OCR DE TOUTES LES IMAGES EN SÉQUENTIEL"""
309
 
310
  self.is_running = False
311
 
312
+ print("🏁 Fin de jeu - Début OCR séquentiel de toutes les images...")
 
 
 
313
 
314
+ # Ajouter la dernière image si présente
315
  if final_image is not None:
316
  self.user_images.append(final_image)
317
  self.expected_answers.append(self.correct_answer)
 
 
 
 
 
 
 
 
 
 
 
318
  self.question_count += 1
319
+
320
+ # Ajouter l'opération finale à l'historique si nécessaire
321
  if len(self.operations_history) < len(self.user_images):
322
+ parts = self.current_operation.split()
323
+ a, op, b = int(parts[0]), parts[1], int(parts[2])
324
  self.operations_history.append((a, b, op, self.correct_answer))
325
 
326
+ # OCR SÉQUENTIEL SIMPLE de toutes les images
 
 
 
 
 
 
 
 
 
 
 
 
 
327
  total_questions = len(self.user_images)
328
+ correct_answers = 0
329
  table_rows_html = ""
330
 
331
  session_timestamp = datetime.datetime.now().isoformat()
 
334
  self.session_data = []
335
  images_saved = 0
336
 
337
+ print(f"🔄 Traitement OCR séquentiel de {total_questions} images...")
338
 
339
+ # Boucle simple - une image à la fois
340
  for i in range(total_questions):
341
+ print(f"📷 OCR image {i+1}/{total_questions}...")
342
+
343
+ # OCR de cette image
344
+ row_data = create_result_row_with_images(
345
+ i,
346
+ self.user_images[i],
347
+ self.expected_answers[i],
348
+ self.operations_history[i] if i < len(self.operations_history) else (0, 0, "×", 0)
349
+ )
 
 
 
 
 
 
350
 
351
  table_rows_html += row_data['html_row']
352
 
353
  if row_data['is_correct']:
354
  correct_answers += 1
355
 
356
+ # Structure pour dataset
357
  a, b, operation, correct_result = self.operations_history[i] if i < len(self.operations_history) else (0, 0, "×", 0)
358
 
359
  try:
360
  ocr_info_data = get_ocr_model_info()
 
361
  except Exception as e:
362
  print(f"❌ Erreur get_ocr_model_info: {e}")
363
  ocr_info_data = {"model_name": "TrOCR", "device": "ZeroGPU"}
 
372
  "operand_a": a,
373
  "operand_b": b,
374
  "operation": operation,
375
+ "correct_answer": self.expected_answers[i],
376
  "ocr_model": ocr_info_data.get("model_name", "TrOCR"),
377
  "ocr_device": ocr_info_data.get("device", "ZeroGPU"),
378
  "user_answer_ocr": row_data['recognized'],
379
  "user_answer_parsed": row_data['recognized_num'],
380
  "is_correct": row_data['is_correct'],
381
  "total_questions": total_questions,
382
+ "app_version": "3.0_calcul_ocr_ultra_simplified"
383
  }
384
 
 
 
385
  # Image PIL native pour dataset
386
  if row_data['dataset_image_data']:
387
+ entry["handwriting_image"] = row_data['dataset_image_data']["handwriting_image"]
388
  entry["image_width"] = int(row_data['dataset_image_data']["width"])
389
  entry["image_height"] = int(row_data['dataset_image_data']["height"])
390
  entry["has_image"] = True
 
396
 
397
  accuracy = (correct_answers / total_questions * 100) if total_questions > 0 else 0
398
 
399
+ # Ajouter accuracy à toutes les entrées
400
  for entry in self.session_data:
401
  entry["session_accuracy"] = accuracy
402
 
403
+ # Nettoyage mémoire
404
  for img in self.user_images:
405
  if hasattr(img, 'close'):
406
  try:
 
408
  except:
409
  pass
410
 
411
+ cleanup_memory()
412
+
413
+ print(f"✅ OCR terminé: {correct_answers}/{total_questions} correct ({accuracy:.1f}%)")
414
 
415
  # HTML résultats
416
  table_html = f"""
 
424
  <th style="padding: 8px;">B</th>
425
  <th style="padding: 8px;">Réponse</th>
426
  <th style="padding: 8px;">Votre dessin</th>
427
+ <th style="padding: 8px;">OCR</th>
428
  <th style="padding: 8px;">Statut</th>
429
  </tr>
430
  </thead>
 
437
 
438
  # Configuration session pour affichage
439
  config_display = f"{self.operation_type} • {self.difficulty} • {self.duration}s"
440
+
 
 
 
 
441
  export_info = self.get_export_status()
442
  if export_info["can_export"]:
443
  export_section = f"""
444
  <div style="margin-top: 20px; padding: 15px; background-color: #e8f5e8; border-radius: 8px;">
445
+ <h3 style="color: #2e7d32;">📊 Résumé de la série</h3>
446
  <p style="color: #2e7d32;">
447
  ✅ {total_questions} réponses • 📊 {accuracy:.1f}% de précision<br>
448
+ 🖼️ {images_saved} images sauvegardées<br>
449
+ 🤖 OCR: TrOCR ZeroGPU (séquentiel)<br>
450
  ⚙️ Configuration: {config_display}
451
  </p>
452
  </div>
 
457
  final_results = f"""
458
  <div style="margin: 20px 0;">
459
  <div style="background: linear-gradient(45deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 10px; margin: 20px 0;">
460
+ <h2 style="text-align: center;">🎉 Session terminée !</h2>
461
  <div style="display: flex; justify-content: space-around; flex-wrap: wrap;">
462
  <div style="text-align: center; margin: 10px;">
463
  <div style="font-size: 2em; font-weight: bold;">{total_questions}</div>
 
477
  </div>
478
  </div>
479
  </div>
480
+ <h2 style="color: #4a90e2;">📊 Détail des Réponses (TrOCR séquentiel)</h2>
481
  {table_html}
482
  {export_section}
483
  </div>
 
486
  return (
487
  """<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>""",
488
  create_white_canvas(),
489
+ f"✨ Session {config_display} terminée !",
490
  "⏱️ Temps écoulé !",
491
  gr.update(interactive=True),
492
  gr.update(interactive=False),
 
495
 
496
 
497
  def export_to_clean_dataset(session_data: list[dict], dataset_name: str = None) -> str:
498
+ """Export vers le dataset - Version simplifiée"""
499
  if dataset_name is None:
500
  dataset_name = DATASET_NAME
501
 
 
504
 
505
  hf_token = os.getenv("HF_TOKEN") or os.getenv("tk_calcul_ocr")
506
  if not hf_token:
507
+ return "❌ Token HuggingFace manquant"
508
 
509
  try:
510
+ print(f"\n🚀 === EXPORT DATASET ULTRA-SIMPLIFIÉ ===")
511
  print(f"📊 Dataset: {dataset_name}")
512
 
513
+ # Filtrer les entrées avec images
514
+ clean_entries = [entry for entry in session_data if entry.get('has_image', False)]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
515
 
516
  if len(clean_entries) == 0:
517
  return "❌ Aucune entrée avec image à exporter"
518
 
 
 
 
 
 
519
  # Charger dataset existant et combiner
520
  try:
521
  existing_dataset = load_dataset(dataset_name, split="train")
522
  existing_data = existing_dataset.to_list()
523
+ print(f"📊 {len(existing_data)} entrées existantes")
524
 
 
525
  combined_data = existing_data + clean_entries
526
  clean_dataset = Dataset.from_list(combined_data)
527
+ print(f"📊 Dataset combiné: {len(combined_data)} total")
528
 
529
  except Exception as e:
530
+ print(f"📊 Nouveau dataset: {e}")
 
531
  clean_dataset = Dataset.from_list(clean_entries)
 
532
 
533
+ # Conversion colonne image
534
  try:
535
  clean_dataset = clean_dataset.cast_column("handwriting_image", DatasetImage())
536
+ print("✅ Colonne image convertie")
537
  except Exception as e:
538
+ print(f"⚠️ Conversion image: {e}")
 
 
 
 
539
 
540
+ # Statistiques
541
  operations_count = {}
542
  for entry in clean_entries:
543
  op = entry.get('operation_type', 'unknown')
 
546
  operations_summary = ", ".join([f"{op}: {count}" for op, count in operations_count.items()])
547
 
548
  # Push vers HuggingFace
549
+ print(f"📤 Push vers {dataset_name}...")
550
  clean_dataset.push_to_hub(
551
  dataset_name,
552
  private=False,
553
  token=hf_token,
554
+ commit_message=f"Add {len(clean_entries)} ultra-simplified samples ({operations_summary})"
555
  )
556
 
557
  cleanup_memory()
558
 
559
+ return f"""### ✅ Session ajoutée avec succès !
560
 
561
  📊 **Dataset:** {dataset_name}
562
+ 🖼️ **Images:** {len(clean_entries)}
 
563
  🔢 **Opérations:** {operations_summary}
564
  📈 **Total:** {len(clean_dataset)}
565
 
566
+ 🔗 <a href="https://huggingface.co/datasets/{dataset_name}" target="_blank">{dataset_name}</a>
567
  """
 
568
 
569
  except Exception as e:
570
+ print(f"❌ ERREUR: {e}")
571
+ return f"❌ Erreur: {str(e)}"