martialroberge commited on
Commit
3cb2c3b
·
1 Parent(s): 5e00169

Mise à jour pour utilisation avec clés API individuelles

Browse files
Files changed (3) hide show
  1. README.md +136 -7
  2. app.py +481 -0
  3. requirements.txt +7 -0
README.md CHANGED
@@ -1,13 +1,142 @@
1
  ---
2
- title: French Visual Dataset Builder V1
3
- emoji: 🦀
4
- colorFrom: indigo
5
- colorTo: red
6
  sdk: gradio
7
- sdk_version: 5.23.3
8
  app_file: app.py
9
  pinned: false
10
- license: apache-2.0
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Mini-VLM Dataset Builder
3
+ emoji: 🎯
4
+ colorFrom: blue
5
+ colorTo: purple
6
  sdk: gradio
7
+ sdk_version: 4.19.2
8
  app_file: app.py
9
  pinned: false
 
10
  ---
11
 
12
+ # Mini-VLM Dataset Builder
13
+
14
+ Cette application permet de créer facilement des datasets de questions/réponses pour l'entraînement de modèles Vision-Langage (VLM).
15
+
16
+ ## ⚠️ Important : Clés API requises
17
+
18
+ Pour utiliser cette application, vous devez avoir :
19
+ 1. Une clé API Google Gemini (obtenir [ici](https://makersuite.google.com/app/apikey))
20
+ 2. Un token Hugging Face avec permissions d'écriture (obtenir [ici](https://huggingface.co/settings/tokens))
21
+
22
+ Ces clés sont à entrer directement dans l'interface de l'application. Elles ne sont jamais stockées et sont uniquement utilisées pour vos propres requêtes.
23
+
24
+ ## Fonctionnalités
25
+
26
+ - 📸 Upload multiple d'images et de PDFs
27
+ - 🤖 Analyse automatique avec Gemini
28
+ - ❓ Génération de questions/réponses
29
+ - 📁 Dataset structuré (train/validation/test)
30
+ - ⬆️ Upload sur votre compte Hugging Face
31
+
32
+ ## Utilisation
33
+
34
+ 1. Entrez votre clé API Google Gemini dans le champ dédié
35
+ 2. Entrez votre token Hugging Face dans le champ dédié
36
+ 3. Spécifiez le nom de votre dataset (format: votre-username/nom-du-dataset)
37
+ 4. Uploadez vos documents (PDF, PNG, JPG, JPEG)
38
+ 5. Cliquez sur "Créer le dataset"
39
+
40
+ ## Structure du dataset généré
41
+
42
+ Le dataset sera créé sur votre compte Hugging Face avec la structure suivante :
43
+
44
+ ```
45
+ votre-username/nom-du-dataset/
46
+ ├── train/
47
+ │ ├── images/
48
+ │ └── metadata.jsonl
49
+ ├── validation/
50
+ │ ├── images/
51
+ │ └── metadata.jsonl
52
+ └── test/
53
+ ├── images/
54
+ └── metadata.jsonl
55
+ ```
56
+
57
+ ## Sécurité
58
+
59
+ - Les clés API sont utilisées uniquement pendant votre session
60
+ - Aucune clé n'est stockée sur le serveur
61
+ - Les données sont transmises de manière sécurisée
62
+ - Chaque utilisateur utilise ses propres identifiants
63
+
64
+ ## Licence
65
+
66
+ Apache License 2.0
67
+
68
+ ## 🎨 Interface utilisateur moderne et intuitive
69
+
70
+ ## 📊 Barre de progression en temps réel
71
+
72
+ ## 📦 Installation
73
+
74
+ 1. Clonez le repository :
75
+ ```bash
76
+ git clone https://huggingface.co/spaces/Marsouuu/mini-vlm-dataset-builder
77
+ cd mini-vlm-dataset-builder
78
+ ```
79
+
80
+ 2. Installez les dépendances :
81
+ ```bash
82
+ pip install -r requirements.txt
83
+ ```
84
+
85
+ ## 🚀 Utilisation
86
+
87
+ 1. Lancez l'application :
88
+ ```bash
89
+ python app.py
90
+ ```
91
+
92
+ 2. Accédez à l'interface web dans votre navigateur (généralement à l'adresse `http://localhost:7860`)
93
+
94
+ 3. Dans l'interface :
95
+ - Entrez votre clé API Google Gemini
96
+ - Entrez votre token Hugging Face
97
+ - Choisissez un nom pour votre dataset
98
+ - Téléchargez vos images de documents
99
+ - Cliquez sur "Créer le dataset"
100
+
101
+ ## 📁 Structure du dataset
102
+
103
+ Le dataset créé aura la structure suivante :
104
+
105
+ ```
106
+ dataset_name/
107
+ ├── train/
108
+ │ ├── images/
109
+ │ │ └── kid-page-{n}.png
110
+ │ └── metadata.jsonl
111
+ ├── validation/
112
+ │ ├── images/
113
+ │ │ └── kid-page-{n}.png
114
+ │ └── metadata.jsonl
115
+ └── test/
116
+ ├── images/
117
+ │ └── kid-page-{n}.png
118
+ └── metadata.jsonl
119
+ ```
120
+
121
+ Chaque fichier `metadata.jsonl` contient des entrées au format :
122
+ ```json
123
+ {
124
+ "image": "images/kid-page-{n}.png",
125
+ "query": "Question générée",
126
+ "answer": "Réponse générée",
127
+ "langue": "fr",
128
+ "page": 1,
129
+ "file_name": "images/kid-page-{n}.png"
130
+ }
131
+ ```
132
+
133
+ ## 🤝 Contribution
134
+
135
+ Les contributions sont les bienvenues ! N'hésitez pas à :
136
+ - Ouvrir une issue pour signaler un bug
137
+ - Proposer une amélioration via une pull request
138
+ - Partager vos idées d'amélioration
139
+
140
+ ## 📝 Licence
141
+
142
+ Ce projet est sous licence MIT. Voir le fichier `LICENSE` pour plus de détails.
app.py ADDED
@@ -0,0 +1,481 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import json
4
+ import shutil
5
+ from pathlib import Path
6
+ import google.generativeai as genai
7
+ from huggingface_hub import HfApi, create_repo
8
+ import tempfile
9
+ import uuid
10
+ from PIL import Image
11
+ import io
12
+ import hashlib
13
+ from typing import List, Dict, Set
14
+ import pdf2image
15
+ import fitz # PyMuPDF
16
+ from dotenv import load_dotenv
17
+
18
+ # Charger les variables d'environnement
19
+ load_dotenv()
20
+
21
+ # Récupérer les clés API depuis les variables d'environnement
22
+ DEFAULT_GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "")
23
+ DEFAULT_HF_TOKEN = os.getenv("HF_TOKEN", "")
24
+
25
+ def generate_unique_id() -> str:
26
+ """Génère un ID unique de 50 caractères"""
27
+ # Générer deux UUID4 et les combiner
28
+ uuid1 = str(uuid.uuid4())
29
+ uuid2 = str(uuid.uuid4())
30
+ # Prendre les 25 premiers caractères de chaque UUID
31
+ return f"{uuid1[:25]}{uuid2[:25]}"
32
+
33
+ def check_duplicates(entries: List[Dict]) -> List[Dict]:
34
+ """Vérifie et supprime les doublons dans les entrées du dataset"""
35
+ seen_questions = set()
36
+ seen_images = set()
37
+ unique_entries = []
38
+
39
+ for entry in entries:
40
+ # Créer une clé unique basée sur la question et l'image
41
+ question_key = entry['query'].lower().strip()
42
+ image_key = entry['image']
43
+
44
+ # Vérifier si la question est similaire à une question existante
45
+ is_duplicate = False
46
+ for seen_question in seen_questions:
47
+ # Calculer la similarité entre les questions
48
+ if question_key in seen_question or seen_question in question_key:
49
+ is_duplicate = True
50
+ break
51
+
52
+ # Vérifier si l'image a déjà été utilisée trop de fois
53
+ image_count = sum(1 for e in unique_entries if e['image'] == image_key)
54
+
55
+ if not is_duplicate and image_count < 5: # Limite à 5 questions par image
56
+ seen_questions.add(question_key)
57
+ seen_images.add(image_key)
58
+ unique_entries.append(entry)
59
+
60
+ return unique_entries
61
+
62
+ def process_files(api_key: str, hf_token: str, files: List[str], dataset_name: str, progress=gr.Progress()) -> str:
63
+ """
64
+ Traite les fichiers (images ou PDFs) et crée le dataset
65
+ """
66
+ try:
67
+ print(f"🔍 Début du traitement avec {len(files)} fichiers")
68
+
69
+ # Dossier temporaire pour toutes les images avant mélange
70
+ with tempfile.TemporaryDirectory() as temp_images_dir:
71
+ print("📂 Création du dossier temporaire pour les images")
72
+
73
+ # Compteur pour numéroter les images
74
+ image_counter = 1
75
+
76
+ # Liste pour stocker tous les chemins d'images
77
+ all_images = []
78
+
79
+ # Traiter d'abord tous les PDFs
80
+ print("📄 Traitement des PDFs...")
81
+ for file in files:
82
+ if file.name.lower().endswith('.pdf'):
83
+ print(f"📄 Conversion du PDF: {file.name}")
84
+ try:
85
+ # Ouvrir le PDF avec PyMuPDF
86
+ pdf_document = fitz.open(file.name)
87
+
88
+ # Convertir chaque page en image
89
+ for page_num in range(len(pdf_document)):
90
+ page = pdf_document[page_num]
91
+ pix = page.get_pixmap()
92
+ image_path = os.path.join(temp_images_dir, f"{image_counter}.png")
93
+ pix.save(image_path)
94
+ all_images.append(image_path)
95
+ print(f" 📄 Page {page_num + 1} convertie en {image_counter}.png")
96
+ image_counter += 1
97
+
98
+ pdf_document.close()
99
+ except Exception as e:
100
+ print(f"❌ Erreur lors de la conversion du PDF {file.name}: {str(e)}")
101
+ continue
102
+
103
+ # Traiter ensuite toutes les images
104
+ print("🖼️ Traitement des images...")
105
+ for file in files:
106
+ file_lower = file.name.lower()
107
+ if file_lower.endswith(('.png', '.jpg', '.jpeg')):
108
+ try:
109
+ # Copier et renommer l'image
110
+ new_path = os.path.join(temp_images_dir, f"{image_counter}.png")
111
+ # Convertir en PNG si nécessaire
112
+ if file_lower.endswith(('.jpg', '.jpeg')):
113
+ img = Image.open(file.name)
114
+ img.save(new_path, 'PNG')
115
+ else:
116
+ shutil.copy2(file.name, new_path)
117
+ all_images.append(new_path)
118
+ print(f"🖼️ Image {file.name} copiée en {image_counter}.png")
119
+ image_counter += 1
120
+ except Exception as e:
121
+ print(f"❌ Erreur lors du traitement de l'image {file.name}: {str(e)}")
122
+ continue
123
+
124
+ if not all_images:
125
+ return "❌ Erreur: Aucune image valide trouvée. Veuillez fournir des fichiers PDF ou des images (PNG, JPG, JPEG)."
126
+
127
+ # Mélanger toutes les images
128
+ print(f"🔄 Mélange des {len(all_images)} images...")
129
+ import random
130
+ random.shuffle(all_images)
131
+
132
+ # Créer un nouveau dossier pour les images mélangées et renumérotées
133
+ with tempfile.TemporaryDirectory() as final_images_dir:
134
+ final_images = []
135
+ for i, image_path in enumerate(all_images, 1):
136
+ new_path = os.path.join(final_images_dir, f"{i}.png")
137
+ shutil.copy2(image_path, new_path)
138
+ final_images.append(new_path)
139
+ print(f"📝 Image {os.path.basename(image_path)} renumérotée en {i}.png")
140
+
141
+ print(f"✅ Total des images à traiter: {len(final_images)}")
142
+
143
+ # Continuer avec le traitement des images
144
+ return process_images(api_key, hf_token, final_images, dataset_name, progress)
145
+
146
+ except Exception as e:
147
+ return f"❌ Erreur lors du traitement des fichiers: {str(e)}"
148
+
149
+ def process_images(api_key, hf_token, images, dataset_name, progress=gr.Progress()):
150
+ """
151
+ Traite les images et crée le dataset
152
+ """
153
+ try:
154
+ print(f"🔍 Début du traitement avec {len(images)} images")
155
+ print(f"🔑 API Key: {api_key[:5]}...")
156
+ print(f"🔑 HF Token: {hf_token[:5]}...")
157
+ print(f"📁 Dataset name: {dataset_name}")
158
+
159
+ if not api_key or not hf_token:
160
+ print("❌ Erreur: API Key ou HF Token manquant")
161
+ return "❌ Erreur: Veuillez entrer votre clé API Google Gemini et votre token Hugging Face."
162
+
163
+ # Configuration de l'API Gemini
164
+ print("⚙️ Configuration de l'API Gemini...")
165
+ genai.configure(api_key=api_key)
166
+ model = genai.GenerativeModel('gemini-1.5-flash')
167
+
168
+ # Créer d'abord le repository sur Hugging Face
169
+ try:
170
+ print("📦 Création du repository sur Hugging Face...")
171
+ api = HfApi(token=hf_token)
172
+ create_repo(dataset_name, repo_type="dataset", token=hf_token, exist_ok=True)
173
+ print("✅ Repository créé avec succès")
174
+ except Exception as e:
175
+ print(f"❌ Erreur lors de la création du repository: {str(e)}")
176
+ return f"❌ Erreur lors de la création du repository sur Hugging Face: {str(e)}"
177
+
178
+ # Création d'un dossier temporaire pour le dataset
179
+ with tempfile.TemporaryDirectory() as temp_dir:
180
+ print(f"📂 Création du dossier temporaire: {temp_dir}")
181
+ # Extraire le nom du dataset sans le nom d'utilisateur
182
+ repo_name = dataset_name.split('/')[-1]
183
+ dataset_path = Path(temp_dir) / repo_name
184
+ dataset_path.mkdir()
185
+
186
+ # Création des dossiers pour les splits
187
+ splits = ['train', 'validation', 'test']
188
+ for split in splits:
189
+ (dataset_path / split / 'images').mkdir(parents=True)
190
+ print(f"📁 Création du dossier {split}")
191
+
192
+ # Mélanger les images aléatoirement
193
+ import random
194
+ random.shuffle(images)
195
+
196
+ total_images = len(images)
197
+ progress(0, desc="Démarrage du traitement...")
198
+
199
+ # Créer un dossier temporaire pour les images renommées
200
+ with tempfile.TemporaryDirectory() as renamed_images_dir:
201
+ renamed_images = []
202
+ print("🔄 Renommage des images...")
203
+
204
+ # Renommer et copier toutes les images d'abord
205
+ for i, image in enumerate(images, 1):
206
+ new_image_path = Path(renamed_images_dir) / f"{i}.png"
207
+ shutil.copy2(image, new_image_path)
208
+ renamed_images.append(str(new_image_path))
209
+ print(f"📝 Image {image} renommée en {i}.png")
210
+
211
+ # Traitement des images renommées
212
+ for i, image in enumerate(renamed_images, 1):
213
+ print(f"\n🖼️ Traitement de l'image {i}/{total_images}")
214
+ progress(i / total_images, desc=f"Traitement de l'image {i}/{total_images}")
215
+
216
+ # Déterminer le split (80% train, 10% validation, 10% test)
217
+ if i <= len(images) * 0.8:
218
+ split = 'train'
219
+ elif i <= len(images) * 0.9:
220
+ split = 'validation'
221
+ else:
222
+ split = 'test'
223
+
224
+ print(f"📂 Split: {split}")
225
+
226
+ # Copier l'image
227
+ image_path = dataset_path / split / 'images' / f"{i}.png"
228
+ print(f"📄 Copie de l'image vers: {image_path}")
229
+ shutil.copy2(image, image_path)
230
+
231
+ # Générer les questions/réponses avec Gemini
232
+ all_qa_pairs = []
233
+
234
+ # Une seule tentative par image
235
+ with open(image, 'rb') as img_file:
236
+ img_data = img_file.read()
237
+
238
+ # Générer un nombre aléatoire de questions à poser
239
+ nb_questions = random.randint(1, 5)
240
+ print(f"❓ Génération de {nb_questions} questions...")
241
+
242
+ prompt = f"""Tu es un expert en analyse financière et en création de datasets de haute qualité. Examine attentivement ce document financier et génère exactement {nb_questions} questions/réponses de qualité professionnelle.
243
+
244
+ Format de réponse requis (JSON) :
245
+ [
246
+ {{
247
+ "query": "Question financière précise et détaillée",
248
+ "answer": "Réponse complète et exacte basée sur le document",
249
+ "langue": "fr",
250
+ "is_negative": false
251
+ }}
252
+ ]
253
+
254
+ Instructions détaillées pour la création du dataset :
255
+
256
+ 1. TYPES DE QUESTIONS FINANCIÈRES (sois créatif et précis) :
257
+ - Analyse quantitative :
258
+ * Montants exacts et variations
259
+ * Pourcentages et ratios financiers
260
+ * Évolutions temporelles
261
+ * Comparaisons chiffrées
262
+
263
+ - Analyse qualitative :
264
+ * Stratégies et objectifs
265
+ * Risques et opportunités
266
+ * Contexte réglementaire
267
+ * Implications business
268
+
269
+ - Dates et échéances :
270
+ * Périodes de reporting
271
+ * Dates clés
272
+ * Échéances importantes
273
+ * Historique des événements
274
+
275
+ 2. QUESTIONS NÉGATIVES (TRÈS IMPORTANT) :
276
+ - Tu DOIS générer au moins 1 question sur {nb_questions} où l'information n'est PAS dans le document
277
+ - Pour ces questions, tu DOIS mettre "is_negative": true
278
+ - La réponse DOIT commencer par "Cette information ne figure pas dans le document"
279
+ - Exemples de questions négatives :
280
+ * "Quel est le montant exact des provisions pour risques ?" -> "Cette information ne figure pas dans le document"
281
+ * "Quelle est la rémunération du directeur financier ?" -> "Cette information ne figure pas dans le document"
282
+ - Les questions négatives doivent être plausibles et pertinentes pour un document financier
283
+
284
+ 3. QUALITÉ DES QUESTIONS :
285
+ - Précision : utilise des chiffres exacts quand possible
286
+ - Clarté : questions non ambiguës
287
+ - Pertinence : focus sur les aspects financiers importants
288
+ - Variété : mélange différents types de questions
289
+ - Profondeur : questions qui nécessitent une analyse approfondie
290
+
291
+ 4. QUALITÉ DES RÉPONSES :
292
+ - Pour les questions normales (is_negative: false) :
293
+ * Exactitude : informations vérifiables dans le document
294
+ * Complétude : réponses détaillées et exhaustives
295
+ * Clarté : formulation professionnelle et précise
296
+ * Contexte : inclure les éléments de contexte pertinents
297
+ - Pour les questions négatives (is_negative: true) :
298
+ * TOUJOURS commencer par "Cette information ne figure pas dans le document"
299
+ * Expliquer brièvement pourquoi cette information serait pertinente
300
+
301
+ 5. RÈGLES STRICTES :
302
+ - Questions et réponses UNIQUEMENT en français
303
+ - Pas de questions vagues ou générales
304
+ - Pas de répétition de questions similaires
305
+ - Pas de devinettes ou d'inférences non documentées
306
+ - Respect strict du format JSON demandé
307
+ - Au moins 1 question négative (is_negative: true) par image
308
+
309
+ La réponse doit être uniquement le JSON, sans texte supplémentaire."""
310
+
311
+ response = model.generate_content([
312
+ prompt,
313
+ {"mime_type": "image/png", "data": img_data}
314
+ ])
315
+
316
+ try:
317
+ # Nettoyer la réponse pour ne garder que le JSON
318
+ response_text = response.text.strip()
319
+ if response_text.startswith("```json"):
320
+ response_text = response_text[7:]
321
+ if response_text.endswith("```"):
322
+ response_text = response_text[:-3]
323
+ response_text = response_text.strip()
324
+
325
+ # Extraire et formater les Q/R
326
+ qa_pairs = json.loads(response_text)
327
+
328
+ # Vérifier que c'est une liste
329
+ if not isinstance(qa_pairs, list):
330
+ raise ValueError("La réponse n'est pas une liste")
331
+
332
+ # Vérifier qu'il y a au moins une question négative
333
+ has_negative = False
334
+ for qa in qa_pairs:
335
+ if qa.get("is_negative", False):
336
+ if not qa["answer"].startswith("Cette information ne figure pas dans le document"):
337
+ qa["answer"] = "Cette information ne figure pas dans le document. " + qa["answer"]
338
+ has_negative = True
339
+
340
+ if not has_negative:
341
+ print("⚠️ Attention: Aucune question négative générée, on réessaie...")
342
+ continue
343
+
344
+ # Vérifier que chaque élément a les bons champs
345
+ for qa in qa_pairs:
346
+ if not all(key in qa for key in ["query", "answer", "langue", "is_negative"]):
347
+ raise ValueError("Un élément ne contient pas tous les champs requis")
348
+
349
+ # Vérifier que la langue est fr
350
+ if qa["langue"] != "fr":
351
+ qa["langue"] = "fr"
352
+
353
+ # Générer un ID unique
354
+ qa["id"] = generate_unique_id()
355
+
356
+ # Ajouter le chemin de l'image avec le nouveau nom
357
+ qa["image"] = f"images/{i}.png"
358
+ qa["file_name"] = f"images/{i}.png"
359
+
360
+ all_qa_pairs.extend(qa_pairs)
361
+
362
+ except json.JSONDecodeError as e:
363
+ print(f"Erreur JSON: {str(e)}")
364
+ continue
365
+ except ValueError as e:
366
+ print(f"Erreur de format: {str(e)}")
367
+ continue
368
+
369
+ # Vérifier et supprimer les doublons
370
+ unique_qa_pairs = check_duplicates(all_qa_pairs)
371
+
372
+ # Créer les entrées pour le JSONL
373
+ for qa in unique_qa_pairs:
374
+ entry = {
375
+ "id": qa["id"],
376
+ "image": qa["image"],
377
+ "query": qa["query"],
378
+ "answer": qa["answer"],
379
+ "langue": qa["langue"],
380
+ "file_name": qa["file_name"],
381
+ "is_negative": qa["is_negative"]
382
+ }
383
+
384
+ # Ajouter au fichier JSONL correspondant
385
+ jsonl_path = dataset_path / split / "metadata.jsonl"
386
+ with open(jsonl_path, 'a', encoding='utf-8') as f:
387
+ f.write(json.dumps(entry, ensure_ascii=False) + '\n')
388
+
389
+ # Créer le fichier LICENSE
390
+ with open(dataset_path / "LICENSE", 'w') as f:
391
+ f.write("""Apache License 2.0
392
+
393
+ Copyright [yyyy] [name of copyright owner]
394
+
395
+ Licensed under the Apache License, Version 2.0 (the "License");
396
+ you may not use this file except in compliance with the License.
397
+ You may obtain a copy of the License at
398
+
399
+ http://www.apache.org/licenses/LICENSE-2.0
400
+
401
+ Unless required by applicable law or agreed to in writing, software
402
+ distributed under the License is distributed on an "AS IS" BASIS,
403
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
404
+ See the License for the specific language governing permissions and
405
+ limitations under the License.
406
+ """)
407
+
408
+ progress(0.9, desc="Upload du dataset sur Hugging Face...")
409
+
410
+ try:
411
+ # Uploader le dataset
412
+ api.upload_folder(
413
+ folder_path=str(dataset_path),
414
+ repo_id=dataset_name,
415
+ repo_type="dataset"
416
+ )
417
+
418
+ progress(1.0, desc="Terminé !")
419
+ return f"✅ Dataset créé avec succès !\n\nAccédez à votre dataset : https://huggingface.co/datasets/{dataset_name}"
420
+
421
+ except Exception as e:
422
+ return f"❌ Erreur lors de l'upload du dataset sur Hugging Face: {str(e)}"
423
+
424
+ except Exception as e:
425
+ return f"❌ Erreur: {str(e)}"
426
+
427
+ # Interface Gradio
428
+ with gr.Blocks() as demo:
429
+ gr.Markdown("""
430
+ # 🎯 Mini-VLM Dataset Builder
431
+ Créez votre propre dataset de questions/réponses pour l'entraînement de modèles Vision-Langage
432
+
433
+ ### ⚠️ Important
434
+ 1. Vous devez avoir une [clé API Gemini](https://makersuite.google.com/app/apikey)
435
+ 2. Vous devez avoir un [token Hugging Face](https://huggingface.co/settings/tokens) avec droits d'écriture
436
+ """)
437
+
438
+ with gr.Row():
439
+ with gr.Column(scale=1):
440
+ api_key = gr.Textbox(
441
+ label="Clé API Google Gemini",
442
+ type="password",
443
+ placeholder="Entrez votre clé API Gemini",
444
+ value=""
445
+ )
446
+ hf_token = gr.Textbox(
447
+ label="Token Hugging Face",
448
+ type="password",
449
+ placeholder="Entrez votre token Hugging Face",
450
+ value=""
451
+ )
452
+ dataset_name = gr.Textbox(
453
+ label="Nom du dataset",
454
+ placeholder="votre-username/nom-du-dataset"
455
+ )
456
+
457
+ with gr.Column(scale=1):
458
+ files = gr.File(
459
+ label="Documents (PDF, PNG, JPG, JPEG)",
460
+ file_count="multiple",
461
+ height=200
462
+ )
463
+
464
+ submit_btn = gr.Button("Créer le dataset", variant="primary")
465
+ output = gr.Textbox(
466
+ label="Résultat",
467
+ lines=3,
468
+ interactive=False
469
+ )
470
+
471
+ submit_btn.click(
472
+ fn=process_files,
473
+ inputs=[api_key, hf_token, files, dataset_name],
474
+ outputs=output
475
+ )
476
+
477
+ if __name__ == "__main__":
478
+ demo.launch()
479
+ else:
480
+ # Pour Hugging Face Spaces
481
+ app = demo
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ gradio>=4.19.2
2
+ google-generativeai
3
+ huggingface_hub
4
+ Pillow
5
+ PyMuPDF
6
+ pdf2image
7
+ python-dotenv