hoololi commited on
Commit
e467692
·
verified ·
1 Parent(s): 271b2e1

Upload utils.py

Browse files
Files changed (1) hide show
  1. utils.py +239 -0
utils.py ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ==========================================
2
+ # utils.py - Fonctions communes CPU/GPU
3
+ # ==========================================
4
+
5
+ """
6
+ Utilitaires partagés pour le traitement d'images OCR
7
+ Fonctions communes aux versions CPU et GPU
8
+ """
9
+
10
+ from PIL import Image, ImageEnhance
11
+ import numpy as np
12
+ import base64
13
+ from io import BytesIO
14
+ import gc
15
+ import os
16
+ import time
17
+
18
+ def create_white_canvas(width: int = 300, height: int = 300) -> Image.Image:
19
+ """Crée un canvas blanc pour le dessin de calculs"""
20
+ return Image.new('RGB', (width, height), 'white')
21
+
22
+ def log_memory_usage(context: str = "") -> None:
23
+ """Log l'usage mémoire actuel"""
24
+ try:
25
+ import psutil
26
+ process = psutil.Process(os.getpid())
27
+ memory_mb = process.memory_info().rss / 1024 / 1024
28
+ print(f"🔍 Mémoire {context}: {memory_mb:.1f}MB")
29
+ except:
30
+ pass
31
+
32
+ def cleanup_memory() -> None:
33
+ """Force le nettoyage mémoire"""
34
+ gc.collect()
35
+
36
+ def optimize_image_for_ocr(image_dict: dict | np.ndarray | Image.Image | None, max_size: int = 300) -> Image.Image | None:
37
+ """
38
+ Optimisation image commune pour tous types d'OCR
39
+
40
+ Args:
41
+ image_dict: Image d'entrée (format Gradio, numpy ou PIL)
42
+ max_size: Taille maximale pour le redimensionnement
43
+
44
+ Returns:
45
+ Image PIL optimisée ou None si erreur
46
+ """
47
+ if image_dict is None:
48
+ return None
49
+
50
+ try:
51
+ # Gérer les formats Gradio
52
+ if isinstance(image_dict, dict):
53
+ if 'composite' in image_dict and image_dict['composite'] is not None:
54
+ image = image_dict['composite']
55
+ elif 'background' in image_dict and image_dict['background'] is not None:
56
+ image = image_dict['background']
57
+ else:
58
+ return None
59
+ elif isinstance(image_dict, np.ndarray):
60
+ image = image_dict
61
+ elif isinstance(image_dict, Image.Image):
62
+ image = image_dict
63
+ else:
64
+ return None
65
+
66
+ # Conversion vers PIL
67
+ if isinstance(image, np.ndarray):
68
+ pil_image = Image.fromarray(image).convert('RGB')
69
+ else:
70
+ pil_image = image.convert('RGB')
71
+
72
+ # Redimensionnement si nécessaire
73
+ if pil_image.size[0] > max_size or pil_image.size[1] > max_size:
74
+ pil_image.thumbnail((max_size, max_size), Image.Resampling.LANCZOS)
75
+
76
+ return pil_image
77
+
78
+ except Exception as e:
79
+ print(f"❌ Erreur optimisation image: {e}")
80
+ return None
81
+
82
+ def prepare_image_for_dataset(image: Image.Image, max_size: tuple[int, int] = (100, 100), quality: int = 60) -> dict[str, str | int | float | tuple] | None:
83
+ """
84
+ Prépare une image pour l'inclusion dans le dataset
85
+
86
+ Args:
87
+ image: Image PIL à traiter
88
+ max_size: Taille maximale (largeur, hauteur)
89
+ quality: Qualité de compression PNG
90
+
91
+ Returns:
92
+ Dictionnaire avec image_base64, taille, etc. ou None
93
+ """
94
+ try:
95
+ if image is None:
96
+ return None
97
+
98
+ # Copier et redimensionner
99
+ dataset_image = image.copy()
100
+ dataset_image.thumbnail(max_size, Image.Resampling.LANCZOS)
101
+ compressed_size = dataset_image.size
102
+
103
+ # Convertir en base64
104
+ buffer = BytesIO()
105
+ dataset_image.save(buffer, format='PNG', optimize=True, quality=quality)
106
+
107
+ buffer_data = buffer.getvalue()
108
+ image_base64 = base64.b64encode(buffer_data).decode()
109
+ file_size_kb = len(image_base64) / 1024
110
+
111
+ # Structure propre pour dataset
112
+ result = {
113
+ "image_base64": image_base64,
114
+ "compressed_size": compressed_size,
115
+ "file_size_kb": round(file_size_kb, 1),
116
+ "format": "PNG",
117
+ "quality": quality
118
+ }
119
+
120
+ # Nettoyage
121
+ dataset_image.close()
122
+ buffer.close()
123
+
124
+ return result
125
+
126
+ except Exception as e:
127
+ print(f"❌ Erreur préparation image dataset: {e}")
128
+ return None
129
+
130
+ def create_thumbnail_fast(optimized_image: Image.Image | None, size: tuple[int, int] = (40, 40)) -> str:
131
+ """
132
+ Création miniature rapide pour affichage dans les résultats
133
+
134
+ Args:
135
+ optimized_image: Image PIL source
136
+ size: Taille de la miniature (largeur, hauteur)
137
+
138
+ Returns:
139
+ HTML img tag avec image base64 ou icône par défaut
140
+ """
141
+ try:
142
+ if optimized_image is None:
143
+ return "📝"
144
+
145
+ thumbnail = optimized_image.copy()
146
+ thumbnail.thumbnail(size, Image.Resampling.LANCZOS)
147
+
148
+ buffer = BytesIO()
149
+ thumbnail.save(buffer, format='PNG', optimize=True, quality=70)
150
+ img_str = base64.b64encode(buffer.getvalue()).decode()
151
+
152
+ thumbnail.close()
153
+ buffer.close()
154
+
155
+ return f'<img src="data:image/png;base64,{img_str}" width="{size[0]}" height="{size[1]}" style="border: 1px solid #ccc; border-radius: 3px;" alt="Réponse calcul">'
156
+
157
+ except Exception:
158
+ return "📝"
159
+
160
+ def decode_image_from_dataset(base64_string: str) -> Image.Image | None:
161
+ """
162
+ Décode une image depuis le dataset pour fine-tuning ou analyse
163
+
164
+ Args:
165
+ base64_string: String base64 de l'image
166
+
167
+ Returns:
168
+ Image PIL ou None si erreur
169
+ """
170
+ try:
171
+ image_bytes = base64.b64decode(base64_string)
172
+ image = Image.open(BytesIO(image_bytes))
173
+ return image
174
+ except Exception as e:
175
+ print(f"❌ Erreur décodage image dataset: {e}")
176
+ return None
177
+
178
+ def validate_ocr_result(raw_result: str, max_length: int = 4) -> str:
179
+ """
180
+ Valide et nettoie un résultat OCR
181
+
182
+ Args:
183
+ raw_result: Résultat brut de l'OCR
184
+ max_length: Longueur maximale autorisée
185
+
186
+ Returns:
187
+ Résultat nettoyé (chiffres uniquement)
188
+ """
189
+ if not raw_result:
190
+ return "0"
191
+
192
+ # Extraire uniquement les chiffres
193
+ cleaned_result = ''.join(filter(str.isdigit, str(raw_result)))
194
+
195
+ # Valider la longueur
196
+ if cleaned_result and len(cleaned_result) <= max_length:
197
+ return cleaned_result
198
+ elif cleaned_result:
199
+ # Si trop long, prendre les premiers chiffres
200
+ return cleaned_result[:max_length]
201
+ else:
202
+ return "0"
203
+
204
+ def analyze_calculation_complexity(operand_a: int, operand_b: int, operation: str) -> dict:
205
+ """
206
+ Analyse la complexité d'un calcul pour enrichir les métadonnées dataset
207
+
208
+ Args:
209
+ operand_a: Premier opérande
210
+ operand_b: Deuxième opérande
211
+ operation: Type d'opération (×, +, -, ÷)
212
+
213
+ Returns:
214
+ Dictionnaire avec score de complexité et catégorie
215
+ """
216
+ complexity_score = 0
217
+
218
+ if operation == "×":
219
+ complexity_score = max(operand_a, operand_b)
220
+ elif operation == "+":
221
+ complexity_score = (operand_a + operand_b) / 20
222
+ elif operation == "-":
223
+ complexity_score = max(operand_a, operand_b) / 10
224
+ elif operation == "÷":
225
+ complexity_score = operand_a / 10
226
+
227
+ # Catégorisation
228
+ if complexity_score < 5:
229
+ category = "easy"
230
+ elif complexity_score < 10:
231
+ category = "medium"
232
+ else:
233
+ category = "hard"
234
+
235
+ return {
236
+ "complexity_score": round(complexity_score, 2),
237
+ "difficulty_category": category,
238
+ "operation_type": operation
239
+ }