hoololi commited on
Commit
3db8788
·
verified ·
1 Parent(s): 0040761

Delete game_engine.py

Browse files
Files changed (1) hide show
  1. game_engine.py +0 -724
game_engine.py DELETED
@@ -1,724 +0,0 @@
1
- # ==========================================
2
- # game_engine.py - Calcul OCR v3.0
3
- # ==========================================
4
-
5
- """
6
- Moteur de jeu mathématique complet
7
- """
8
-
9
- import random
10
- import time
11
- import datetime
12
- import gradio as gr
13
- import os
14
- import uuid
15
- import gc
16
- import base64
17
- from io import BytesIO
18
- import numpy as np
19
- from PIL import Image
20
- import threading
21
- import queue
22
- from typing import Dict, Tuple, Optional
23
-
24
- # Auto-détection intelligente CPU/GPU avec fallbacks
25
- ocr_module = None
26
- ocr_info = {"model_name": "Unknown", "device": "Unknown"}
27
-
28
- # Tentative 1: GPU/TrOCR (si disponible et packages installés)
29
- try:
30
- import torch
31
- if torch.cuda.is_available():
32
- from image_processing_gpu import (
33
- recognize_number_fast_with_image,
34
- create_thumbnail_fast,
35
- create_white_canvas,
36
- cleanup_memory,
37
- log_memory_usage,
38
- get_ocr_model_info
39
- )
40
- ocr_module = "gpu"
41
- print("✅ Mode GPU détecté - TrOCR activé")
42
- else:
43
- raise ImportError("GPU non disponible")
44
- except (ImportError, Exception) as e:
45
- print(f"⚠️ GPU/TrOCR non disponible: {e}")
46
-
47
- # Tentative 2: CPU/EasyOCR (fallback)
48
- try:
49
- from image_processing_cpu import (
50
- recognize_number_fast_with_image,
51
- create_thumbnail_fast,
52
- create_white_canvas,
53
- cleanup_memory,
54
- log_memory_usage,
55
- get_ocr_model_info
56
- )
57
- ocr_module = "cpu"
58
- print("✅ Mode CPU détecté - EasyOCR activé")
59
- except ImportError:
60
-
61
- # Tentative 3: Fallback vers ancien fichier (sécurité)
62
- try:
63
- from image_processing import (
64
- recognize_number_fast_with_image,
65
- create_thumbnail_fast,
66
- create_white_canvas,
67
- cleanup_memory,
68
- log_memory_usage
69
- )
70
- def get_ocr_model_info():
71
- return {"model_name": "Legacy", "device": "Unknown"}
72
- ocr_module = "legacy"
73
- print("⚠️ Fallback vers image_processing.py legacy")
74
- except ImportError:
75
- print("❌ ERREUR: Aucun module OCR disponible!")
76
- raise
77
-
78
- # Récupérer les infos du modèle sélectionné
79
- try:
80
- ocr_info = get_ocr_model_info()
81
- print(f"🎯 OCR sélectionné: {ocr_info['model_name']} sur {ocr_info['device']}")
82
- except:
83
- print("⚠️ Impossible de récupérer les infos OCR")
84
-
85
- # Imports dataset avec gestion d'erreur
86
- try:
87
- from datasets import Dataset, load_dataset
88
- DATASET_AVAILABLE = True
89
- print("✅ Modules dataset disponibles")
90
- except ImportError as e:
91
- DATASET_AVAILABLE = False
92
- print(f"⚠️ Modules dataset non disponibles: {e}")
93
-
94
- # Nom du nouveau dataset
95
- DATASET_NAME = "hoololi/calcul_ocr_dataset"
96
-
97
- # Configuration des difficultés par opération
98
- DIFFICULTY_RANGES = {
99
- "×": {
100
- "Facile": (2, 9),
101
- "Difficile": (4, 12)
102
- },
103
- "+": {
104
- "Facile": (1, 50),
105
- "Difficile": (10, 100)
106
- },
107
- "-": {
108
- "Facile": (1, 50),
109
- "Difficile": (10, 100)
110
- },
111
- "÷": {
112
- "Facile": (1, 10), # Pour les résultats
113
- "Difficile": (2, 12) # Pour les résultats
114
- }
115
- }
116
-
117
- def create_result_row_with_images(i: int, image: dict | np.ndarray | Image.Image, expected: int, operation_data: tuple[int, int, str, int]) -> dict:
118
-
119
- # OCR optimisé
120
- recognized, optimized_image, dataset_image_data = recognize_number_fast_with_image(image)
121
-
122
- try:
123
- recognized_num = int(recognized) if recognized.isdigit() else 0
124
- except:
125
- recognized_num = 0
126
-
127
- is_correct = recognized_num == expected
128
- a, b, operation, correct_result = operation_data
129
-
130
- status_icon = "✅" if is_correct else "❌"
131
- status_text = "Correct" if is_correct else "Incorrect"
132
- row_color = "#e8f5e8" if is_correct else "#ffe8e8"
133
-
134
- # Miniature
135
- image_thumbnail = create_thumbnail_fast(optimized_image, size=(50, 50))
136
-
137
- # Libérer mémoire
138
- if optimized_image and hasattr(optimized_image, 'close'):
139
- try:
140
- optimized_image.close()
141
- except:
142
- pass
143
-
144
- return {
145
- 'html_row': f"""
146
- <tr style="background-color: {row_color};">
147
- <td style="text-align: center; padding: 8px; border: 1px solid #ddd; color: #333;">{i+1}</td>
148
- <td style="text-align: center; padding: 8px; border: 1px solid #ddd; font-weight: bold; color: #333;">{a}</td>
149
- <td style="text-align: center; padding: 8px; border: 1px solid #ddd; font-weight: bold; color: #333;">{operation}</td>
150
- <td style="text-align: center; padding: 8px; border: 1px solid #ddd; font-weight: bold; color: #333;">{b}</td>
151
- <td style="text-align: center; padding: 8px; border: 1px solid #ddd; font-weight: bold; color: #333;">{expected}</td>
152
- <td style="text-align: center; padding: 8px; border: 1px solid #ddd;">{image_thumbnail}</td>
153
- <td style="text-align: center; padding: 8px; border: 1px solid #ddd; font-weight: bold; color: #333;">{recognized_num}</td>
154
- <td style="text-align: center; padding: 8px; border: 1px solid #ddd; color: #333;">{status_icon} {status_text}</td>
155
- </tr>
156
- """,
157
- 'is_correct': is_correct,
158
- 'recognized': recognized,
159
- 'recognized_num': recognized_num,
160
- 'dataset_image_data': dataset_image_data
161
- }
162
-
163
-
164
- class MathGame:
165
- """Moteur de jeu mathématique avec traitement parallèle"""
166
-
167
- def __init__(self):
168
- self.is_running = False
169
- self.start_time = 0
170
- self.current_operation = ""
171
- self.correct_answer = 0
172
- self.user_images = []
173
- self.expected_answers = []
174
- self.operations_history = []
175
- self.question_count = 0
176
- self.time_remaining = 30
177
- self.session_data = []
178
-
179
- # Configuration session
180
- self.duration = 30
181
- self.operation_type = "×"
182
- self.difficulty = "Facile"
183
-
184
- # Gestion export
185
- self.export_status = "not_exported"
186
- self.export_timestamp = None
187
- self.export_result = None
188
-
189
- # NOUVEAU: Traitement parallèle
190
- self.processing_queue = queue.Queue()
191
- self.results_cache: Dict[int, dict] = {} # {question_number: result_data}
192
- self.worker_thread: Optional[threading.Thread] = None
193
- self.processing_active = False
194
-
195
- def _start_background_processing(self) -> None:
196
- """Démarre le thread de traitement en arrière-plan"""
197
- if self.worker_thread is None or not self.worker_thread.is_alive():
198
- self.processing_active = True
199
- self.worker_thread = threading.Thread(target=self._process_images_worker, daemon=True)
200
- self.worker_thread.start()
201
- print("🔄 Thread de traitement parallèle démarré")
202
-
203
- def _stop_background_processing(self) -> None:
204
- """Arrête le thread de traitement"""
205
- self.processing_active = False
206
- if self.worker_thread and self.worker_thread.is_alive():
207
- print("⏹️ Arrêt du thread de traitement parallèle")
208
-
209
- def _process_images_worker(self) -> None:
210
- """Worker thread qui traite les images en arrière-plan"""
211
- print("🚀 Worker thread démarré")
212
- while self.processing_active:
213
- try:
214
- if not self.processing_queue.empty():
215
- question_num, image, expected, operation_data = self.processing_queue.get(timeout=1)
216
- print(f"🔄 Traitement parallèle image {question_num}...")
217
-
218
- start_time = time.time()
219
- result_data = create_result_row_with_images(question_num, image, expected, operation_data)
220
- processing_time = time.time() - start_time
221
-
222
- # Stocker le résultat
223
- self.results_cache[question_num] = result_data
224
- print(f"✅ Image {question_num} traitée en {processing_time:.1f}s (parallèle)")
225
-
226
- else:
227
- time.sleep(0.1) # Éviter la consommation CPU excessive
228
-
229
- except queue.Empty:
230
- continue
231
- except Exception as e:
232
- print(f"❌ Erreur traitement parallèle: {e}")
233
-
234
- print("🛑 Worker thread terminé")
235
-
236
- def _add_image_to_processing_queue(self, question_num: int, image: dict | np.ndarray | Image.Image,
237
- expected: int, operation_data: tuple) -> None:
238
- """Ajoute une image à la queue de traitement"""
239
- if image is not None:
240
- self.processing_queue.put((question_num, image, expected, operation_data))
241
- print(f"📝 Image {question_num} ajoutée à la queue de traitement")
242
- return {
243
- "status": self.export_status,
244
- "timestamp": self.export_timestamp,
245
- "result": self.export_result,
246
- "can_export": self.export_status == "not_exported" and len(self.session_data) > 0
247
- }
248
-
249
- def mark_export_in_progress(self) -> None:
250
- self.export_status = "exporting"
251
- self.export_timestamp = datetime.datetime.now().isoformat()
252
-
253
- def mark_export_completed(self, result: str) -> None:
254
- self.export_status = "exported"
255
- self.export_result = result
256
-
257
- def generate_multiplication(self, difficulty: str) -> tuple[str, int]:
258
- """Génère une multiplication"""
259
- min_val, max_val = DIFFICULTY_RANGES["×"][difficulty]
260
- a = random.randint(min_val, max_val)
261
- b = random.randint(min_val, max_val)
262
- return f"{a} × {b}", a * b
263
-
264
- def generate_addition(self, difficulty: str) -> tuple[str, int]:
265
- """Génère une addition"""
266
- min_val, max_val = DIFFICULTY_RANGES["+"][difficulty]
267
- a = random.randint(min_val, max_val)
268
- b = random.randint(min_val, max_val)
269
- return f"{a} + {b}", a + b
270
-
271
- def generate_subtraction(self, difficulty: str) -> tuple[str, int]:
272
- """Génère une soustraction (résultat toujours positif)"""
273
- min_val, max_val = DIFFICULTY_RANGES["-"][difficulty]
274
- a = random.randint(min_val, max_val)
275
- b = random.randint(min_val, a) # b <= a pour éviter les négatifs
276
- return f"{a} - {b}", a - b
277
-
278
- def generate_division(self, difficulty: str) -> tuple[str, int]:
279
- """Génère une division exacte"""
280
- min_result, max_result = DIFFICULTY_RANGES["÷"][difficulty]
281
- result = random.randint(min_result, max_result)
282
- if difficulty == "Facile":
283
- divisor = random.randint(2, 9)
284
- else:
285
- divisor = random.randint(2, 12)
286
- dividend = result * divisor
287
- return f"{dividend} ÷ {divisor}", result
288
-
289
- def generate_operation(self, operation_type: str, difficulty: str) -> tuple[str, int]:
290
- """Génère une opération selon le type et la difficulté"""
291
- if operation_type == "×":
292
- return self.generate_multiplication(difficulty)
293
- elif operation_type == "+":
294
- return self.generate_addition(difficulty)
295
- elif operation_type == "-":
296
- return self.generate_subtraction(difficulty)
297
- elif operation_type == "÷":
298
- return self.generate_division(difficulty)
299
- elif operation_type == "Aléatoire":
300
- # Choisir aléatoirement une opération
301
- random_op = random.choice(["×", "+", "-", "÷"])
302
- return self.generate_operation(random_op, difficulty)
303
- else:
304
- # Par défaut, multiplication
305
- return self.generate_multiplication(difficulty)
306
-
307
- def start_game(self, duration: str, operation: str, difficulty: str) -> tuple[str, Image.Image, str, str, gr.update, gr.update, str]:
308
- """Démarre le jeu avec la configuration choisie"""
309
-
310
- # log_memory_usage("avant nettoyage start_game") # DEBUG: Désactivé
311
-
312
- # Configuration
313
- self.duration = 60 if duration == "60 secondes" else 30
314
- self.operation_type = operation
315
- self.difficulty = difficulty
316
-
317
- # Nettoyage
318
- if hasattr(self, 'user_images') and self.user_images:
319
- for img in self.user_images:
320
- if hasattr(img, 'close'):
321
- try:
322
- img.close()
323
- except:
324
- pass
325
-
326
- if hasattr(self, 'session_data') and self.session_data:
327
- for entry in self.session_data:
328
- if 'user_drawing' in entry and entry['user_drawing']:
329
- entry['user_drawing'] = None
330
- self.session_data.clear()
331
-
332
- # Réinit avec nettoyage parallèle
333
- self._stop_background_processing()
334
- self.results_cache.clear()
335
- while not self.processing_queue.empty():
336
- try:
337
- self.processing_queue.get_nowait()
338
- except queue.Empty:
339
- break
340
-
341
- self.is_running = True
342
- self.start_time = time.time()
343
- self.user_images = []
344
- self.expected_answers = []
345
- self.operations_history = []
346
- self.question_count = 0
347
- self.time_remaining = self.duration
348
- self.session_data = []
349
-
350
- # Reset export
351
- self.export_status = "not_exported"
352
- self.export_timestamp = None
353
- self.export_result = None
354
-
355
- # Démarrer le traitement parallèle
356
- self._start_background_processing()
357
-
358
- gc.collect()
359
- # log_memory_usage("après nettoyage start_game") # DEBUG: Désactivé
360
-
361
- # Première opération
362
- operation_str, answer = self.generate_operation(self.operation_type, self.difficulty)
363
- self.current_operation = operation_str
364
- self.correct_answer = answer
365
-
366
- # Parser l'opération pour l'historique
367
- parts = operation_str.split()
368
- a, op, b = int(parts[0]), parts[1], int(parts[2])
369
- self.operations_history.append((a, b, op, answer))
370
-
371
- # Affichage adapté selon l'opération
372
- operation_emoji = {
373
- "×": "✖️", "+": "➕", "-": "➖", "÷": "➗", "Aléatoire": "🎲"
374
- }
375
- emoji = operation_emoji.get(self.operation_type, "🔢")
376
-
377
- return (
378
- 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;">{operation_str}</div>',
379
- create_white_canvas(),
380
- f"🎯 {emoji} {self.operation_type} • {self.difficulty} • Écrivez votre réponse !",
381
- f"⏱️ Temps restant: {self.time_remaining}s",
382
- gr.update(interactive=False),
383
- gr.update(interactive=True),
384
- ""
385
- )
386
-
387
- def next_question(self, image_data: dict | np.ndarray | Image.Image | None) -> tuple[str, Image.Image, str, str, gr.update, gr.update, str]:
388
- if not self.is_running:
389
- return (
390
- 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>',
391
- image_data,
392
- "❌ Le jeu n'est pas en cours !",
393
- "⏱️ Temps: 0s",
394
- gr.update(interactive=True),
395
- gr.update(interactive=False),
396
- ""
397
- )
398
-
399
- elapsed_time = time.time() - self.start_time
400
- if elapsed_time >= self.duration:
401
- return self.end_game(image_data)
402
-
403
- if image_data is not None:
404
- # Ajouter l'image à la liste ET au traitement parallèle
405
- self.user_images.append(image_data)
406
- self.expected_answers.append(self.correct_answer)
407
-
408
- # Parser l'opération actuelle pour le traitement
409
- parts = self.current_operation.split()
410
- a, op, b = int(parts[0]), parts[1], int(parts[2])
411
- current_operation_data = (a, b, op, self.correct_answer)
412
-
413
- # Lancer le traitement en parallèle de l'image qu'on vient de recevoir
414
- self._add_image_to_processing_queue(self.question_count, image_data, self.correct_answer, current_operation_data)
415
-
416
- self.question_count += 1
417
-
418
- # Nouvelle opération
419
- operation_str, answer = self.generate_operation(self.operation_type, self.difficulty)
420
- self.current_operation = operation_str
421
- self.correct_answer = answer
422
-
423
- # Parser pour l'historique
424
- parts = operation_str.split()
425
- a, op, b = int(parts[0]), parts[1], int(parts[2])
426
- self.operations_history.append((a, b, op, answer))
427
-
428
- time_remaining = max(0, self.duration - int(elapsed_time))
429
- self.time_remaining = time_remaining
430
-
431
- if time_remaining <= 0:
432
- return self.end_game(image_data)
433
-
434
- # Emoji pour l'opération
435
- operation_emoji = {
436
- "×": "✖️", "+": "➕", "-": "➖", "÷": "➗", "Aléatoire": "🎲"
437
- }
438
- emoji = operation_emoji.get(self.operation_type, "🔢")
439
-
440
- return (
441
- 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;">{operation_str}</div>',
442
- create_white_canvas(),
443
- f"🎯 {emoji} Question {self.question_count + 1} • {self.difficulty}",
444
- f"⏱️ Temps restant: {time_remaining}s",
445
- gr.update(interactive=False),
446
- gr.update(interactive=True),
447
- ""
448
- )
449
-
450
- def end_game(self, final_image: dict | np.ndarray | Image.Image | None) -> tuple[str, Image.Image, str, str, gr.update, gr.update, str]:
451
-
452
- self.is_running = False
453
-
454
- # log_memory_usage("début end_game") # DEBUG: Désactivé
455
-
456
- if final_image is not None:
457
- self.user_images.append(final_image)
458
- self.expected_answers.append(self.correct_answer)
459
- self.question_count += 1
460
- if len(self.operations_history) < len(self.user_images):
461
- parts = self.current_operation.split()
462
- a, op, b = int(parts[0]), parts[1], int(parts[2])
463
- self.operations_history.append((a, b, op, self.correct_answer))
464
-
465
- correct_answers = 0
466
- total_questions = len(self.user_images)
467
- table_rows_html = ""
468
-
469
- session_timestamp = datetime.datetime.now().isoformat()
470
- session_id = f"session_{int(datetime.datetime.now().timestamp())}_{str(uuid.uuid4())[:8]}"
471
-
472
- self.session_data = []
473
- images_saved = 0
474
- total_image_size_kb = 0
475
-
476
- # Traitement optimisé avec DEBUG
477
- print(f"🔄 Traitement de {total_questions} images...")
478
- start_processing = time.time()
479
-
480
- for i, (image, expected, operation_data) in enumerate(zip(self.user_images, self.expected_answers, self.operations_history)):
481
- print(f" → Image {i+1}/{total_questions}...")
482
- img_start = time.time()
483
-
484
- row_data = create_result_row_with_images(i, image, expected, operation_data)
485
- table_rows_html += row_data['html_row']
486
-
487
- img_time = time.time() - img_start
488
- print(f" ✅ Traitée en {img_time:.1f}s")
489
-
490
- if row_data['is_correct']:
491
- correct_answers += 1
492
-
493
- # Structure pour NOUVEAU DATASET CALCUL OCR
494
- a, b, operation, correct_result = operation_data
495
-
496
- entry = {
497
- "session_id": session_id,
498
- "timestamp": session_timestamp,
499
- "question_number": i + 1,
500
-
501
- # Configuration session
502
- "session_duration": self.duration,
503
- "operation_type": self.operation_type,
504
- "difficulty_level": self.difficulty,
505
-
506
- # Mathématiques
507
- "operand_a": a,
508
- "operand_b": b,
509
- "operation": operation,
510
- "correct_answer": expected,
511
-
512
- # OCR & Résultats avec détection automatique du modèle
513
- ocr_info = get_ocr_model_info()
514
- "ocr_model": ocr_info.get("model_name", "Unknown"),
515
- "ocr_device": ocr_info.get("device", "Unknown"),
516
- "user_answer_ocr": row_data['recognized'],
517
- "user_answer_parsed": row_data['recognized_num'],
518
- "is_correct": row_data['is_correct'],
519
-
520
- # Métadonnées
521
- "total_questions": total_questions,
522
- "app_version": "3.0_calcul_ocr_parallel" # Mis à jour pour le parallélisme
523
- }
524
-
525
- # Ajouter image si disponible
526
- if row_data['dataset_image_data']:
527
- entry["handwriting_image"] = row_data['dataset_image_data']["image_base64"]
528
- entry["image_width"] = int(row_data['dataset_image_data']["compressed_size"][0])
529
- entry["image_height"] = int(row_data['dataset_image_data']["compressed_size"][1])
530
- entry["image_size_kb"] = float(row_data['dataset_image_data']["file_size_kb"])
531
- entry["has_image"] = True
532
- images_saved += 1
533
- total_image_size_kb += row_data['dataset_image_data']["file_size_kb"]
534
- else:
535
- entry["has_image"] = False
536
-
537
- self.session_data.append(entry)
538
-
539
- processing_time = time.time() - start_processing
540
- print(f"⏱️ Traitement total: {processing_time:.1f}s")
541
-
542
- accuracy = (correct_answers / total_questions * 100) if total_questions > 0 else 0
543
-
544
- for entry in self.session_data:
545
- entry["session_accuracy"] = accuracy
546
-
547
- # Nettoyage mémoire
548
- for img in self.user_images:
549
- if hasattr(img, 'close'):
550
- try:
551
- img.close()
552
- except:
553
- pass
554
-
555
- gc.collect()
556
- # log_memory_usage("après nettoyage end_game") # DEBUG: Désactivé
557
-
558
- # HTML résultats
559
- table_html = f"""
560
- <div style="overflow-x: auto; margin: 20px 0;">
561
- <table style="width: 100%; border-collapse: collapse; border: 2px solid #4a90e2;">
562
- <thead>
563
- <tr style="background: #4a90e2; color: white;">
564
- <th style="padding: 8px;">Question</th>
565
- <th style="padding: 8px;">A</th>
566
- <th style="padding: 8px;">Op</th>
567
- <th style="padding: 8px;">B</th>
568
- <th style="padding: 8px;">Réponse</th>
569
- <th style="padding: 8px;">Votre dessin</th>
570
- <th style="padding: 8px;">OCR</th>
571
- <th style="padding: 8px;">Statut</th>
572
- </tr>
573
- </thead>
574
- <tbody>
575
- {table_rows_html}
576
- </tbody>
577
- </table>
578
- </div>
579
- """
580
-
581
- # Configuration session pour affichage
582
- config_display = f"{self.operation_type} • {self.difficulty} • {self.duration}s"
583
- operation_emoji = {
584
- "×": "✖️", "+": "➕", "-": "➖", "÷": "➗", "Aléatoire": "🎲"
585
- }
586
- emoji = operation_emoji.get(self.operation_type, "🔢")
587
-
588
- export_info = self.get_export_status()
589
- if export_info["can_export"]:
590
- export_section = f"""
591
- <div style="margin-top: 20px; padding: 15px; background-color: #e8f5e8; border-radius: 8px;">
592
- <h3 style="color: #2e7d32;">📤 Ajouter cette série au dataset ?</h3>
593
- <p style="color: #2e7d32;">
594
- ✅ {total_questions} réponses • 📊 {accuracy:.1f}% de précision<br>
595
- 📸 {images_saved} opérations et images sauvegardées ({total_image_size_kb:.1f}KB)<br>
596
- ⚙️ Configuration: {config_display}
597
- </p>
598
- </div>
599
- """
600
- else:
601
- export_section = ""
602
-
603
- final_results = f"""
604
- <div style="margin: 20px 0;">
605
- <h1 style="text-align: center; color: #4a90e2;">🎉 Session terminée !</h1>
606
- <div style="background: linear-gradient(45deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 10px; margin: 20px 0;">
607
- <h2>📈 Résultats</h2>
608
- <div style="text-align: center; margin-bottom: 15px;">
609
- <strong>{emoji} {config_display}</strong>
610
- </div>
611
- <div style="display: flex; justify-content: space-around; flex-wrap: wrap;">
612
- <div style="text-align: center; margin: 10px;">
613
- <div style="font-size: 2em; font-weight: bold;">{total_questions}</div>
614
- <div>Questions</div>
615
- </div>
616
- <div style="text-align: center; margin: 10px;">
617
- <div style="font-size: 2em; font-weight: bold; color: #90EE90;">{correct_answers}</div>
618
- <div>Correctes</div>
619
- </div>
620
- <div style="text-align: center; margin: 10px;">
621
- <div style="font-size: 2em; font-weight: bold; color: #FFB6C1;">{total_questions - correct_answers}</div>
622
- <div>Incorrectes</div>
623
- </div>
624
- <div style="text-align: center; margin: 10px;">
625
- <div style="font-size: 2em; font-weight: bold;">{accuracy:.1f}%</div>
626
- <div>Précision</div>
627
- </div>
628
- </div>
629
- </div>
630
- <h2 style="color: #4a90e2;">📊 Détail des Réponses</h2>
631
- {table_html}
632
- {export_section}
633
- </div>
634
- """
635
-
636
- return (
637
- """<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>""",
638
- create_white_canvas(),
639
- f"✨ Session {config_display} terminée !",
640
- "⏱️ Temps écoulé !",
641
- gr.update(interactive=True),
642
- gr.update(interactive=False),
643
- final_results
644
- )
645
-
646
-
647
- def export_to_clean_dataset(session_data: list[dict], dataset_name: str = DATASET_NAME) -> str:
648
- """Export vers le nouveau dataset calcul_ocr_dataset"""
649
- if not DATASET_AVAILABLE:
650
- return "❌ Modules dataset non disponibles"
651
-
652
- hf_token = os.getenv("HF_TOKEN") or os.getenv("tk_calcul_ocr") # Support des deux noms
653
- if not hf_token:
654
- return "❌ Token HuggingFace manquant (HF_TOKEN ou tk_calcul_ocr)"
655
-
656
- try:
657
- print(f"\n🚀 === EXPORT VERS DATASET CALCUL OCR ===")
658
- print(f"📊 Dataset: {dataset_name}")
659
-
660
- # Filtrer les entrées avec images
661
- clean_entries = []
662
-
663
- for entry in session_data:
664
- if entry.get('has_image', False):
665
- clean_entries.append(entry)
666
-
667
- print(f"✅ {len(clean_entries)} entrées avec images converties")
668
-
669
- if len(clean_entries) == 0:
670
- return "❌ Aucune entrée avec image à exporter"
671
-
672
- # Charger dataset existant OU créer nouveau
673
- try:
674
- existing_dataset = load_dataset(dataset_name, split="train")
675
- existing_data = existing_dataset.to_list()
676
- print(f"📊 {len(existing_data)} entrées existantes")
677
- except:
678
- existing_data = []
679
- print("📊 Création nouveau dataset calcul_ocr")
680
-
681
- # Combiner
682
- combined_data = existing_data + clean_entries
683
- clean_dataset = Dataset.from_list(combined_data)
684
-
685
- print(f"✅ Dataset créé - Features:")
686
- for feature_name in clean_dataset.features:
687
- print(f" - {feature_name}: {clean_dataset.features[feature_name]}")
688
-
689
- # Statistiques par opération
690
- operations_count = {}
691
- for entry in clean_entries:
692
- op = entry.get('operation_type', 'unknown')
693
- operations_count[op] = operations_count.get(op, 0) + 1
694
-
695
- operations_summary = ", ".join([f"{op}: {count}" for op, count in operations_count.items()])
696
-
697
- # Push vers HuggingFace
698
- print(f"📤 Push vers {dataset_name}...")
699
- clean_dataset.push_to_hub(
700
- dataset_name,
701
- private=False,
702
- token=hf_token,
703
- commit_message=f"Add {len(clean_entries)} handwriting samples for math OCR ({operations_summary})"
704
- )
705
-
706
- cleanup_memory()
707
-
708
- success_message = f"""✅ Session ajoutée au dataset avec succès !
709
-
710
- 📊 Dataset: {dataset_name}
711
- 📸 Images: {len(clean_entries)}
712
- 🔢 Opérations: {operations_summary}
713
- 📈 Total: {len(clean_dataset)}
714
-
715
- 🔗 Le dataset est consultable ici : https://huggingface.co/datasets/{dataset_name}"""
716
-
717
- return success_message
718
-
719
- except Exception as e:
720
- print(f"❌ ERREUR: {e}")
721
- import traceback
722
- traceback.print_exc()
723
- error_message = f"❌ Erreur: {str(e)}"
724
- return error_message