j-r-b commited on
Commit
547e622
·
verified ·
1 Parent(s): 60cd4c3

Upload 8 files

Browse files
Files changed (8) hide show
  1. .gitattributes +3 -35
  2. Dockerfile +36 -0
  3. README.md +47 -12
  4. app.py +176 -0
  5. download_model.py +57 -0
  6. models/Qwen3-0.6B-Q8_0.gguf +3 -0
  7. ocr_module.py +108 -0
  8. requirements.txt +9 -0
.gitattributes CHANGED
@@ -1,35 +1,3 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.gguf filter=lfs diff=lfs merge=lfs -text
2
+ *.traineddata filter=lfs diff=lfs merge=lfs -text
3
+ models/ filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Dockerfile ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Installation des dépendances système
6
+ RUN apt-get update && apt-get install -y \
7
+ tesseract-ocr \
8
+ tesseract-ocr-fra \
9
+ libgl1-mesa-glx \
10
+ libglib2.0-0 \
11
+ && apt-get clean \
12
+ && rm -rf /var/lib/apt/lists/*
13
+
14
+ # Copier les fichiers nécessaires
15
+ COPY requirements.txt .
16
+ COPY app.py .
17
+ COPY ocr_module.py .
18
+ COPY README.md .
19
+
20
+ # Créer le dossier pour les modèles et les fichiers temporaires
21
+ RUN mkdir -p /app/models /app/temp
22
+
23
+ # Installer les dépendances Python
24
+ RUN pip install --no-cache-dir -r requirements.txt
25
+
26
+ # Définir les variables d'environnement
27
+ ENV PYTHONUNBUFFERED=1
28
+ ENV PYTHONIOENCODING=UTF-8
29
+ ENV GRADIO_SERVER_NAME=0.0.0.0
30
+ ENV GRADIO_SERVER_PORT=7860
31
+
32
+ # Exposer le port
33
+ EXPOSE 7860
34
+
35
+ # Commande de démarrage
36
+ CMD ["python", "app.py"]
README.md CHANGED
@@ -1,12 +1,47 @@
1
- ---
2
- title: ImageVersTexte
3
- emoji: 🐨
4
- colorFrom: pink
5
- colorTo: yellow
6
- sdk: docker
7
- pinned: false
8
- license: apache-2.0
9
- short_description: Transformation de vieux documents en texte
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # OCR et Modernisation de Texte
2
+
3
+ Cette application permet d'extraire du texte à partir d'images (OCR) et de le moderniser en utilisant un modèle Mistral.
4
+
5
+ ## Fonctionnalités
6
+
7
+ - Extraction de texte à partir d'images avec OCR
8
+ - Reconnaissance de formules mathématiques (LaTeX)
9
+ - Modernisation du texte ancien en français contemporain
10
+ - Interface web simple et intuitive
11
+
12
+ ## Comment utiliser
13
+
14
+ 1. Téléchargez une image contenant du texte
15
+ 2. L'application extraira automatiquement le texte
16
+ 3. Le modèle Mistral modernisera le texte
17
+ 4. Les résultats seront affichés avec le texte original et sa version modernisée
18
+
19
+ ## Technique
20
+
21
+ Cette application utilise:
22
+ - Tesseract OCR pour l'extraction de texte
23
+ - Mathpix (si disponible) pour les formules mathématiques
24
+ - Un modèle Mistral quantifié (GGUF) pour la modernisation du texte
25
+ - Gradio pour l'interface utilisateur
26
+ - Flask pour l'API
27
+
28
+ ## API
29
+
30
+ L'application expose également une API REST:
31
+
32
+ ```
33
+ POST /api/ocr
34
+ ```
35
+
36
+ Avec un fichier image dans le corps de la requête (multipart/form-data) sous le nom "image".
37
+
38
+ ## Installation locale
39
+
40
+ ```bash
41
+ pip install -r requirements.txt
42
+ python app.py
43
+ ```
44
+
45
+ ---
46
+
47
+ Développé pour Hugging Face Spaces
app.py ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import gradio as gr
4
+ from PIL import Image
5
+ import tempfile
6
+ import json
7
+ from flask import Flask, request, jsonify
8
+ from ctransformers import AutoModelForCausalLM
9
+
10
+ # Import du module OCR
11
+ from ocr_module import process_image as ocr_process
12
+
13
+ # Chemins
14
+ CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
15
+ MODEL_PATH = os.path.join(CURRENT_DIR, "models", "Mistral-7B-Instruct-v0.3.Q4_K_M.gguf")
16
+ TEMP_DIR = os.path.join(CURRENT_DIR, "temp")
17
+
18
+ # Créer le dossier temp s'il n'existe pas
19
+ if not os.path.exists(TEMP_DIR):
20
+ os.makedirs(TEMP_DIR)
21
+
22
+ # Vérifier l'existence du modèle
23
+ if not os.path.exists(MODEL_PATH):
24
+ print(f"ATTENTION: Le modèle n'existe pas à {MODEL_PATH}")
25
+ print("Exécutez d'abord le script download_model.py pour copier le modèle")
26
+ # Tenter de trouver le modèle à l'emplacement alternatif (pour le développement)
27
+ alt_path = os.path.join(os.path.dirname(CURRENT_DIR), "models", "Mistral-7B-Instruct-v0.3.Q4_K_M.gguf")
28
+ if os.path.exists(alt_path):
29
+ print(f"Modèle trouvé à l'emplacement alternatif: {alt_path}")
30
+ MODEL_PATH = alt_path
31
+ else:
32
+ print("Aucun modèle trouvé. L'application risque de ne pas fonctionner correctement.")
33
+
34
+ # Chargement du modèle Mistral
35
+ try:
36
+ print(f"Chargement du modèle depuis: {MODEL_PATH}")
37
+ model = AutoModelForCausalLM.from_pretrained(
38
+ MODEL_PATH,
39
+ model_type="mistral",
40
+ local_files_only=True,
41
+ gpu_layers=1
42
+ )
43
+ print("Modèle chargé avec succès")
44
+ except Exception as e:
45
+ print(f"Erreur lors du chargement du modèle: {e}")
46
+ model = None
47
+
48
+ # Fonction pour prétraiter et OCR une image
49
+ def process_image(image):
50
+ try:
51
+ # Sauvegarder l'image temporairement
52
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.png', dir=TEMP_DIR) as temp_file:
53
+ temp_path = temp_file.name
54
+ if isinstance(image, str): # Si c'est un chemin
55
+ Image.open(image).save(temp_path)
56
+ else: # Si c'est un objet image
57
+ image.save(temp_path)
58
+
59
+ # Appeler la fonction OCR du module
60
+ ocr_result = ocr_process(temp_path)
61
+
62
+ # Supprimer le fichier temporaire
63
+ if os.path.exists(temp_path):
64
+ os.remove(temp_path)
65
+
66
+ return ocr_result
67
+ except Exception as e:
68
+ return {"error": str(e)}
69
+
70
+ # Fonction pour moderniser le texte avec Mistral
71
+ def modernize_text(text):
72
+ try:
73
+ if model is None:
74
+ return {
75
+ "status": "error",
76
+ "error": "Modèle non disponible"
77
+ }
78
+
79
+ # Créer le prompt pour le modèle
80
+ prompt = f"""<s>[INST] Tu es un assistant spécialisé dans la modernisation de textes anciens.
81
+ Modernise le texte suivant en français contemporain tout en préservant son sens:
82
+
83
+ {text} [/INST]"""
84
+
85
+ # Générer la réponse
86
+ response = model(prompt, max_new_tokens=1024, temperature=0.7)
87
+
88
+ return {
89
+ "status": "success",
90
+ "modern_text": response.strip()
91
+ }
92
+ except Exception as e:
93
+ return {
94
+ "status": "error",
95
+ "error": str(e)
96
+ }
97
+
98
+ # Fonction principale pour l'interface Gradio
99
+ def ocr_and_modernize(image):
100
+ # Extraire le texte de l'image
101
+ ocr_result = process_image(image)
102
+
103
+ if "text" in ocr_result and ocr_result["text"]:
104
+ # Moderniser le texte
105
+ modernization_result = modernize_text(ocr_result["text"])
106
+
107
+ if modernization_result["status"] == "success":
108
+ result = {
109
+ "Texte original": ocr_result["text"],
110
+ "Texte modernisé": modernization_result["modern_text"]
111
+ }
112
+
113
+ # Si LaTeX est disponible
114
+ if "latex" in ocr_result and ocr_result["latex"]:
115
+ result["Formules LaTeX"] = ocr_result["latex"]
116
+
117
+ return result
118
+ else:
119
+ return {
120
+ "Texte original": ocr_result["text"],
121
+ "Erreur": f"Échec de la modernisation: {modernization_result.get('error', 'Erreur inconnue')}"
122
+ }
123
+ else:
124
+ return {
125
+ "Erreur": f"Échec de l'OCR: {ocr_result.get('error', 'Aucun texte détecté')}"
126
+ }
127
+
128
+ # Interface Gradio
129
+ interface = gr.Interface(
130
+ fn=ocr_and_modernize,
131
+ inputs=gr.Image(type="pil"),
132
+ outputs=gr.JSON(),
133
+ title="OCR et Modernisation de Texte",
134
+ description="Téléchargez une image contenant du texte pour extraire et moderniser son contenu.",
135
+ examples=["exemple1.jpg", "exemple2.jpg"] if os.path.exists("exemple1.jpg") else None
136
+ )
137
+
138
+ # API Flask pour intégration
139
+ app = Flask(__name__)
140
+
141
+ @app.route("/api/ocr", methods=["POST"])
142
+ def api_ocr():
143
+ if "image" not in request.files:
144
+ return jsonify({"error": "Aucune image n'a été fournie"}), 400
145
+
146
+ file = request.files["image"]
147
+ temp_path = os.path.join(TEMP_DIR, "temp_" + str(hash(file.filename)) + ".png")
148
+ file.save(temp_path)
149
+
150
+ try:
151
+ # Extraire le texte
152
+ ocr_result = process_image(temp_path)
153
+
154
+ # Moderniser le texte
155
+ if "text" in ocr_result and ocr_result["text"]:
156
+ modernization_result = modernize_text(ocr_result["text"])
157
+ if modernization_result["status"] == "success":
158
+ ocr_result["original_text"] = ocr_result["text"]
159
+ ocr_result["text"] = modernization_result["modern_text"]
160
+
161
+ return jsonify(ocr_result)
162
+ except Exception as e:
163
+ return jsonify({"error": str(e)}), 500
164
+ finally:
165
+ # Nettoyer
166
+ if os.path.exists(temp_path):
167
+ os.remove(temp_path)
168
+
169
+ # Point d'entrée
170
+ if __name__ == "__main__":
171
+ # Si exécuté sur HF Spaces, utiliser Gradio
172
+ if os.environ.get("SPACE_ID"):
173
+ interface.launch()
174
+ else:
175
+ # En local, démarrer le serveur Flask
176
+ app.run(host="0.0.0.0", port=7860, debug=True)
download_model.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import shutil
4
+ import argparse
5
+
6
+ def copy_model(source_path, destination_dir):
7
+ """Copier le modèle GGUF depuis le répertoire source vers le répertoire destination"""
8
+ try:
9
+ # Vérifier si le fichier source existe
10
+ if not os.path.exists(source_path):
11
+ print(f"Erreur: Le fichier source '{source_path}' n'existe pas.")
12
+ return False
13
+
14
+ # Créer le répertoire de destination s'il n'existe pas
15
+ if not os.path.exists(destination_dir):
16
+ os.makedirs(destination_dir)
17
+
18
+ # Construire le chemin complet de destination
19
+ destination_path = os.path.join(destination_dir, os.path.basename(source_path))
20
+
21
+ # Copier le fichier
22
+ print(f"Copie du modèle de {source_path} vers {destination_path}...")
23
+ shutil.copy2(source_path, destination_path)
24
+
25
+ print(f"Le modèle a été copié avec succès vers {destination_path}")
26
+ return True
27
+
28
+ except Exception as e:
29
+ print(f"Erreur lors de la copie du modèle: {str(e)}")
30
+ return False
31
+
32
+ def main():
33
+ parser = argparse.ArgumentParser(description="Copie un modèle GGUF depuis un répertoire source")
34
+ parser.add_argument("--source", default="../models/Mistral-7B-Instruct-v0.3.Q4_K_M.gguf",
35
+ help="Chemin vers le fichier du modèle source")
36
+ parser.add_argument("--dest", default="./models",
37
+ help="Répertoire de destination pour le modèle")
38
+
39
+ args = parser.parse_args()
40
+
41
+ # Obtenir les chemins absolus
42
+ # Obtenir le chemin absolu du répertoire courant
43
+ current_dir = os.path.dirname(os.path.abspath(__file__))
44
+ # Construire le chemin source absolu
45
+ source_path = os.path.abspath(os.path.join(current_dir, "..", "models", "Mistral-7B-Instruct-v0.3.Q4_K_M.gguf"))
46
+ destination_dir = os.path.join(current_dir, "models")
47
+
48
+ print(f"Recherche du modèle à: {source_path}")
49
+
50
+ # Copier le modèle
51
+ success = copy_model(source_path, destination_dir)
52
+
53
+ # Terminer avec le code approprié
54
+ sys.exit(0 if success else 1)
55
+
56
+ if __name__ == "__main__":
57
+ main()
models/Qwen3-0.6B-Q8_0.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9465e63a22add5354d9bb4b99e90117043c7124007664907259bd16d043bb031
3
+ size 639446688
ocr_module.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import pytesseract
4
+ import cv2
5
+ import numpy as np
6
+ from PIL import Image
7
+ import json
8
+ import logging
9
+ import tempfile
10
+
11
+ # Configuration du logging
12
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # Chemin vers Tesseract (à ajuster selon l'environnement)
16
+ if sys.platform.startswith('win'):
17
+ pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
18
+
19
+ # Répertoire temporaire
20
+ TEMP_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "temp")
21
+ if not os.path.exists(TEMP_DIR):
22
+ os.makedirs(TEMP_DIR)
23
+
24
+ def preprocess_image(image_path):
25
+ """Prétraitement de l'image pour améliorer la reconnaissance OCR"""
26
+ try:
27
+ # Lire l'image avec OpenCV
28
+ img = cv2.imread(image_path)
29
+ if img is None:
30
+ logger.error(f"Impossible de lire l'image: {image_path}")
31
+ return None
32
+
33
+ # Convertir en niveaux de gris
34
+ gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
35
+
36
+ # Appliquer un filtre bilatéral pour réduire le bruit tout en préservant les bords
37
+ blur = cv2.bilateralFilter(gray, 9, 75, 75)
38
+
39
+ # Normaliser la luminosité et le contraste
40
+ normalized = cv2.normalize(blur, None, 0, 255, cv2.NORM_MINMAX)
41
+
42
+ # Seuillage adaptatif pour améliorer le contraste entre le texte et l'arrière-plan
43
+ thresh = cv2.adaptiveThreshold(normalized, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
44
+ cv2.THRESH_BINARY, 11, 2)
45
+
46
+ # Enregistrer l'image prétraitée
47
+ processed_image_path = os.path.join(TEMP_DIR, f"processed_{os.path.basename(image_path)}")
48
+ cv2.imwrite(processed_image_path, thresh)
49
+
50
+ return processed_image_path
51
+ except Exception as e:
52
+ logger.error(f"Erreur lors du prétraitement de l'image: {str(e)}")
53
+ return None
54
+
55
+ def perform_ocr(image_path, lang='fra'):
56
+ """Extraire le texte d'une image en utilisant Tesseract OCR"""
57
+ try:
58
+ # Prétraiter l'image
59
+ processed_image_path = preprocess_image(image_path)
60
+ if not processed_image_path:
61
+ processed_image_path = image_path # Utiliser l'image originale si le prétraitement échoue
62
+
63
+ # Configuration OCR
64
+ custom_config = r'--oem 3 --psm 6'
65
+
66
+ # Effectuer l'OCR
67
+ text = pytesseract.image_to_string(Image.open(processed_image_path), lang=lang, config=custom_config)
68
+
69
+ # Nettoyer le texte
70
+ text = text.strip()
71
+
72
+ # Supprimer l'image prétraitée si elle existe
73
+ if processed_image_path != image_path and os.path.exists(processed_image_path):
74
+ os.remove(processed_image_path)
75
+
76
+ return text
77
+ except Exception as e:
78
+ logger.error(f"Erreur OCR: {str(e)}")
79
+ return None
80
+
81
+ def process_image(image_path, lang='fra'):
82
+ """Traiter une image et extraire son texte"""
83
+ try:
84
+ # Extraire le texte
85
+ extracted_text = perform_ocr(image_path, lang)
86
+
87
+ if not extracted_text:
88
+ return {"error": "Aucun texte n'a pu être extrait de l'image"}
89
+
90
+ # Préparer le résultat
91
+ result = {
92
+ "text": extracted_text,
93
+ "confidence": 0.9 # Valeur fictive car Tesseract ne fournit pas de score de confiance simple
94
+ }
95
+
96
+ return result
97
+ except Exception as e:
98
+ logger.error(f"Erreur lors du traitement de l'image: {str(e)}")
99
+ return {"error": str(e)}
100
+
101
+ # Test direct du module
102
+ if __name__ == "__main__":
103
+ if len(sys.argv) > 1:
104
+ image_path = sys.argv[1]
105
+ result = process_image(image_path)
106
+ print(json.dumps(result, ensure_ascii=False, indent=2))
107
+ else:
108
+ print("Usage: python ocr_module.py <chemin_image>")
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ gradio>=3.50.2
2
+ pillow>=10.0.0
3
+ flask>=2.0.0
4
+ ctransformers>=0.2.24
5
+ pytesseract>=0.3.10
6
+ numpy>=1.24.0
7
+ python-dotenv>=1.0.0
8
+ transformers>=4.35.0
9
+ opencv-python>=4.8.0