Markuspierre commited on
Commit
7b05692
·
verified ·
1 Parent(s): 78528ac

Upload 3 files

Browse files
Files changed (3) hide show
  1. README.md +58 -12
  2. app.py +232 -0
  3. requirements.txt +5 -0
README.md CHANGED
@@ -1,12 +1,58 @@
1
- ---
2
- title: Décompte des tableaux
3
- emoji: 📊
4
- colorFrom: blue
5
- colorTo: indigo
6
- sdk: gradio
7
- sdk_version: 5.29.0
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Décompte de PASF, IPM (= ASS), PR, MUT, RESULTAT et CONTROL dans la 2e colonne de tableaux photographiés (OCR Tesseract + OpenCV).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Décompte PASF / IPM(ASS) / PR / MUT / RESULTAT / CONTROL
2
+
3
+ Petite interface web pour compter automatiquement ces catégories dans des **images de tableaux**.
4
+
5
+ ## Catégories comptées
6
+
7
+ | Catégorie | Remarques |
8
+ |-------------|------------------------------------|
9
+ | **PASF** | |
10
+ | **IPM/ASS** | `IPM` et `ASS` sont équivalents |
11
+ | **PR** | |
12
+ | **MUT** | |
13
+ | **RESULTAT**| |
14
+ | **CONTROL** | |
15
+
16
+ ## Installation rapide
17
+
18
+ ```bash
19
+ # 1. Installer Tesseract OCR (si pas déjà fait)
20
+ # Ubuntu/Debian :
21
+ sudo apt update && sudo apt install -y tesseract-ocr tesseract-ocr-fra tesseract-ocr-eng
22
+
23
+ # macOS (Homebrew) :
24
+ brew install tesseract tesseract-lang
25
+
26
+ # Windows : télécharger depuis https://github.com/UB-Mannheim/tesseract/wiki
27
+ # et ajouter le chemin dans les variables d'environnement
28
+
29
+ # 2. Installer les dépendances Python
30
+ pip install -r requirements.txt
31
+ ```
32
+
33
+ ## Lancement
34
+
35
+ ```bash
36
+ python app.py
37
+ ```
38
+
39
+ Puis ouvrir dans le navigateur l’adresse affichée (généralement http://127.0.0.1:7860).
40
+
41
+ ## Utilisation
42
+
43
+ 1. Cliquez sur la zone d’upload et sélectionnez une ou plusieurs images de tableaux.
44
+ 2. Cliquez sur **Analyser les images**.
45
+ 3. Le rapport de décompte apparaît (totaux + détail par image).
46
+ 4. Vous pouvez ouvrir l’accordéon « Texte OCR brut » pour vérifier ce que le système a lu.
47
+
48
+ ## Conseils
49
+
50
+ - Images nettes, bien cadrées, contraste correct.
51
+ - Si l’OCR se trompe (ex. « 1PM » au lieu de « IPM »), regardez le texte OCR et corrigez manuellement le compte si besoin.
52
+ - Le prétraitement d’image (CLAHE + binarisation) aide beaucoup sur les photos de tableaux.
53
+
54
+ ## Notes techniques
55
+
56
+ - OCR : Tesseract (français + anglais)
57
+ - Interface : Gradio
58
+ - IPM et ASS sont fusionnés dans une seule catégorie « IPM/ASS »
app.py ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Interface de décompte PASF / IPM(ASS) / PR / MUT / RESULTAT / CONTROL
4
+ à partir d'images de tableaux (OCR).
5
+ """
6
+
7
+ import re
8
+ import io
9
+ from collections import Counter, defaultdict
10
+ from typing import List, Dict, Tuple
11
+
12
+ import gradio as gr
13
+ import numpy as np
14
+ from PIL import Image
15
+ import cv2
16
+ import pytesseract
17
+
18
+ # Catégories à compter (IPM et ASS sont équivalents)
19
+ CATEGORIES = ["PASF", "IPM/ASS", "PR", "MUT", "RESULTAT", "CONTROL"]
20
+
21
+ # Patterns regex (insensible à la casse, mots entiers)
22
+ PATTERNS = {
23
+ "PASF": re.compile(r"\bPASF\b", re.IGNORECASE),
24
+ "IPM/ASS": re.compile(r"\b(?:IPM|ASS)\b", re.IGNORECASE),
25
+ "PR": re.compile(r"\bPR\b", re.IGNORECASE),
26
+ "MUT": re.compile(r"\bMUT\b", re.IGNORECASE),
27
+ "RESULTAT": re.compile(r"\bRESULTAT\b", re.IGNORECASE),
28
+ "CONTROL": re.compile(r"\bCONTROL\b", re.IGNORECASE),
29
+ }
30
+
31
+
32
+ def preprocess_image(img: Image.Image) -> Image.Image:
33
+ """Améliore l'image pour un meilleur OCR (tableaux)."""
34
+ # Convertir en OpenCV
35
+ arr = np.array(img.convert("RGB"))
36
+ gray = cv2.cvtColor(arr, cv2.COLOR_RGB2GRAY)
37
+
38
+ # Amélioration du contraste (CLAHE)
39
+ clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
40
+ gray = clahe.apply(gray)
41
+
42
+ # Binarisation adaptative (bon pour les tableaux)
43
+ binary = cv2.adaptiveThreshold(
44
+ gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2
45
+ )
46
+
47
+ # Léger débruitage
48
+ binary = cv2.medianBlur(binary, 3)
49
+
50
+ return Image.fromarray(binary)
51
+
52
+
53
+ def extract_text(img: Image.Image, lang: str = "fra+eng") -> str:
54
+ """OCR avec configuration adaptée aux tableaux."""
55
+ # Essayer plusieurs modes de segmentation
56
+ configs = [
57
+ "--psm 6 --oem 3", # Bloc uniforme de texte
58
+ "--psm 4 --oem 3", # Colonne unique
59
+ "--psm 11 --oem 3", # Texte dispersé
60
+ ]
61
+ best_text = ""
62
+ max_len = 0
63
+
64
+ processed = preprocess_image(img)
65
+
66
+ for config in configs:
67
+ try:
68
+ text = pytesseract.image_to_string(processed, lang=lang, config=config)
69
+ if len(text) > max_len:
70
+ max_len = len(text)
71
+ best_text = text
72
+ except Exception:
73
+ continue
74
+
75
+ # Fallback sans prétraitement si rien
76
+ if not best_text.strip():
77
+ best_text = pytesseract.image_to_string(img, lang=lang, config="--psm 6")
78
+
79
+ return best_text
80
+
81
+
82
+ def count_categories(text: str) -> Dict[str, int]:
83
+ """Compte les occurrences de chaque catégorie."""
84
+ counts = {cat: 0 for cat in CATEGORIES}
85
+ for cat, pattern in PATTERNS.items():
86
+ matches = pattern.findall(text)
87
+ counts[cat] = len(matches)
88
+ return counts
89
+
90
+
91
+ def process_images(files: List) -> Tuple[str, str, str]:
92
+ """
93
+ Traite une ou plusieurs images et génère le rapport.
94
+ Retourne : (rapport markdown, détail OCR, résumé JSON-like)
95
+ """
96
+ if not files:
97
+ return "Aucune image fournie.", "", ""
98
+
99
+ total_counts = Counter()
100
+ per_image = []
101
+ all_ocr = []
102
+
103
+ for i, file in enumerate(files, 1):
104
+ try:
105
+ # Gradio donne un objet fichier ou un chemin
106
+ if hasattr(file, "name"):
107
+ img = Image.open(file.name)
108
+ name = file.name.split("/")[-1]
109
+ else:
110
+ img = Image.open(file)
111
+ name = f"image_{i}"
112
+
113
+ text = extract_text(img)
114
+ counts = count_categories(text)
115
+ total_counts.update(counts)
116
+
117
+ per_image.append({
118
+ "nom": name,
119
+ "counts": counts,
120
+ "texte": text
121
+ })
122
+ all_ocr.append(f"=== {name} ===\n{text}\n")
123
+ except Exception as e:
124
+ per_image.append({
125
+ "nom": f"image_{i}",
126
+ "counts": {cat: 0 for cat in CATEGORIES},
127
+ "texte": f"ERREUR: {str(e)}"
128
+ })
129
+ all_ocr.append(f"=== image_{i} ===\nERREUR: {e}\n")
130
+
131
+ # Construction du rapport
132
+ lines = []
133
+ lines.append("# Rapport de décompte")
134
+ lines.append("")
135
+ lines.append(f"**Nombre d'images analysées :** {len(files)}")
136
+ lines.append("")
137
+ lines.append("## Totaux globaux")
138
+ lines.append("")
139
+ lines.append("| Catégorie | Nombre |")
140
+ lines.append("|-----------|--------|")
141
+ for cat in CATEGORIES:
142
+ lines.append(f"| **{cat}** | **{total_counts[cat]}** |")
143
+ lines.append("")
144
+ total_all = sum(total_counts.values())
145
+ lines.append(f"**Total des occurrences :** {total_all}")
146
+ lines.append("")
147
+ lines.append("---")
148
+ lines.append("")
149
+ lines.append("## Détail par image")
150
+ lines.append("")
151
+
152
+ for item in per_image:
153
+ lines.append(f"### {item['nom']}")
154
+ lines.append("")
155
+ lines.append("| Catégorie | Nombre |")
156
+ lines.append("|-----------|--------|")
157
+ for cat in CATEGORIES:
158
+ lines.append(f"| {cat} | {item['counts'][cat]} |")
159
+ lines.append("")
160
+
161
+ report_md = "\n".join(lines)
162
+ ocr_text = "\n".join(all_ocr)
163
+
164
+ # Résumé simple pour copie
165
+ summary = "RÉSUMÉ\n" + "\n".join(f"{cat}: {total_counts[cat]}" for cat in CATEGORIES)
166
+
167
+ return report_md, ocr_text, summary
168
+
169
+
170
+ # Interface Gradio
171
+ with gr.Blocks(
172
+ title="Décompte PASF / IPM / PR / MUT / RESULTAT / CONTROL",
173
+ ) as demo:
174
+ gr.Markdown(
175
+ """
176
+ # 📊 Décompte de catégories dans des tableaux (images)
177
+
178
+ **Catégories comptées :** PASF · IPM/ASS · PR · MUT · RESULTAT · CONTROL
179
+
180
+ > **Note :** `IPM` et `ASS` sont considérés comme **équivalents** (même catégorie).
181
+
182
+ Uploadez une ou plusieurs images de tableaux, puis cliquez sur **Analyser**.
183
+ """
184
+ )
185
+
186
+ with gr.Row():
187
+ with gr.Column(scale=1):
188
+ files = gr.File(
189
+ label="Images du/des tableau(x)",
190
+ file_count="multiple",
191
+ file_types=["image"],
192
+ type="filepath"
193
+ )
194
+ btn = gr.Button("🔍 Analyser les images", variant="primary", size="lg")
195
+ clear_btn = gr.Button("Effacer", variant="secondary")
196
+
197
+ with gr.Column(scale=2):
198
+ report = gr.Markdown(label="Rapport")
199
+ with gr.Accordion("Texte OCR brut (pour vérification)", open=False):
200
+ ocr_out = gr.Textbox(label="OCR", lines=15, max_lines=30)
201
+ summary = gr.Textbox(label="Résumé rapide (copiable)", lines=8)
202
+
203
+ btn.click(
204
+ fn=process_images,
205
+ inputs=[files],
206
+ outputs=[report, ocr_out, summary]
207
+ )
208
+ clear_btn.click(
209
+ fn=lambda: (None, "", "", ""),
210
+ inputs=None,
211
+ outputs=[files, report, ocr_out, summary]
212
+ )
213
+
214
+ gr.Markdown(
215
+ """
216
+ ---
217
+ **Conseils pour de meilleurs résultats :**
218
+ - Images nettes, bien éclairées, tableau centré
219
+ - Évitez les photos trop inclinées ou floues
220
+ - Les colonnes/lignes doivent être lisibles
221
+ - Si un mot est mal reconnu, vérifiez le texte OCR et corrigez manuellement si besoin
222
+ """
223
+ )
224
+
225
+
226
+ if __name__ == "__main__":
227
+ demo.launch(
228
+ server_name="0.0.0.0",
229
+ server_port=7860,
230
+ share=False,
231
+ theme=gr.themes.Soft()
232
+ )
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ gradio>=4.0
2
+ pytesseract>=0.3.10
3
+ Pillow>=9.0
4
+ opencv-python-headless>=4.5
5
+ numpy>=1.20