kebson commited on
Commit
5d96033
·
verified ·
1 Parent(s): ce6a96f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +109 -67
app.py CHANGED
@@ -1,19 +1,47 @@
1
  import gradio as gr
2
  import numpy as np
 
3
  from paddleocr import PaddleOCR
4
  from sklearn.cluster import KMeans
5
 
6
- # -----------------------------
7
  # OCR
8
- # -----------------------------
9
  ocr = PaddleOCR(
10
- use_textline_orientation=True,
11
- lang="fr"
12
  )
13
 
14
- # -----------------------------
15
- # Extraction de la 2e colonne
16
- # -----------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  def extract_second_column(image):
18
  if image is None:
19
  return "Aucune image fournie."
@@ -21,66 +49,70 @@ def extract_second_column(image):
21
  img = np.array(image)
22
  result = ocr.predict(img)
23
 
24
- if not result or len(result[0]["rec_texts"]) == 0:
25
- return "OCR exécuté mais aucun texte détecté."
26
 
27
  data = result[0]
28
- texts = data["rec_texts"]
29
- boxes = data["dt_polys"]
30
 
31
- elements = []
32
  for text, box in zip(texts, boxes):
33
- text = text.strip()
34
- if len(text) < 3:
35
  continue
36
 
37
- x_center = np.mean([p[0] for p in box])
38
- y_center = np.mean([p[1] for p in box])
39
-
40
- elements.append((x_center, y_center, text))
41
-
42
- if len(elements) < 5:
43
- return "Pas assez de texte détecté."
44
-
45
- # -----------------------------
46
- # 1. Regroupement en colonnes (par X)
47
- # -----------------------------
48
- X = np.array([[e[0]] for e in elements])
49
-
50
- # Nombre de colonnes estimé automatiquement
51
- n_cols = min(6, max(2, len(elements) // 6))
52
-
53
- kmeans = KMeans(n_clusters=n_cols, random_state=42, n_init=10)
54
- labels = kmeans.fit_predict(X)
55
-
56
- columns = {}
57
- for (x, y, text), label in zip(elements, labels):
58
- columns.setdefault(label, []).append((x, y, text))
59
-
60
- # Trier les colonnes de gauche à droite
61
- sorted_columns = sorted(
62
- columns.values(),
63
- key=lambda col: np.mean([e[0] for e in col])
64
- )
65
 
66
- if len(sorted_columns) < 2:
67
- return "Impossible de détecter la 2e colonne."
68
 
69
- # -----------------------------
70
- # 2. Sélection de la 2e colonne
71
- # -----------------------------
72
- col = sorted_columns[1]
73
- col.sort(key=lambda e: e[1]) # top → bottom
74
 
75
- # -----------------------------
76
- # 3. Fusion verticale (cellules)
77
- # -----------------------------
78
  merged = []
79
  current = ""
80
  last_y = None
81
  Y_THRESHOLD = 22
82
 
83
- for _, y, text in col:
 
 
 
 
 
84
  if last_y is None or abs(y - last_y) > Y_THRESHOLD:
85
  if current:
86
  merged.append(current.strip())
@@ -93,28 +125,38 @@ def extract_second_column(image):
93
  if current:
94
  merged.append(current.strip())
95
 
96
- # -----------------------------
97
- # 4. Nettoyage léger
98
- # -----------------------------
99
- final = [
100
- line for line in merged
101
- if len(line) > 5
102
- ]
 
 
 
 
103
 
104
  if not final:
105
- return "Colonne détectée mais vide."
106
 
107
- return "\n".join(f"{i+1}. {l}" for i, l in enumerate(final))
 
 
 
108
 
109
- # -----------------------------
110
  # Interface Gradio
111
- # -----------------------------
112
  demo = gr.Interface(
113
  fn=extract_second_column,
114
  inputs=gr.Image(type="pil", label="Image du tableau"),
115
- outputs=gr.Textbox(label="Contenu de la 2e colonne"),
116
- title="Extraction de la 2e colonne du tableau",
117
- description="La colonne cible est toujours la deuxième (texte uniquement)"
 
 
 
118
  )
119
 
120
  demo.launch(server_name="0.0.0.0", server_port=7860)
 
1
  import gradio as gr
2
  import numpy as np
3
+ import unicodedata
4
  from paddleocr import PaddleOCR
5
  from sklearn.cluster import KMeans
6
 
7
+ # -------------------------------------------------
8
  # OCR
9
+ # -------------------------------------------------
10
  ocr = PaddleOCR(
11
+ lang="fr",
12
+ use_textline_orientation=True
13
  )
14
 
15
+ # -------------------------------------------------
16
+ # Normalisation texte (casse + accents)
17
+ # -------------------------------------------------
18
+ def normalize(text: str) -> str:
19
+ text = text.lower()
20
+ text = unicodedata.normalize("NFD", text)
21
+ text = "".join(c for c in text if unicodedata.category(c) != "Mn")
22
+ return " ".join(text.split())
23
+
24
+ # -------------------------------------------------
25
+ # Titres valides de la colonne 2
26
+ # -------------------------------------------------
27
+ COL_TITLES = {
28
+ "designation",
29
+ "designations",
30
+ "description",
31
+ "description des services"
32
+ }
33
+
34
+ # -------------------------------------------------
35
+ # Mots / lignes à ignorer
36
+ # -------------------------------------------------
37
+ IGNORE_KEYWORDS = {
38
+ "prix", "total", "ht", "htva", "tva",
39
+ "ttc", "general", "generale"
40
+ }
41
+
42
+ # -------------------------------------------------
43
+ # Fonction principale
44
+ # -------------------------------------------------
45
  def extract_second_column(image):
46
  if image is None:
47
  return "Aucune image fournie."
 
49
  img = np.array(image)
50
  result = ocr.predict(img)
51
 
52
+ if not result:
53
+ return "OCR : aucun texte détecté."
54
 
55
  data = result[0]
56
+ texts = data.get("rec_texts", [])
57
+ boxes = data.get("dt_polys", [])
58
 
59
+ blocks = []
60
  for text, box in zip(texts, boxes):
61
+ t = text.strip()
62
+ if len(t) < 2:
63
  continue
64
 
65
+ x = np.mean([p[0] for p in box])
66
+ y = np.mean([p[1] for p in box])
67
+
68
+ blocks.append((t, x, y))
69
+
70
+ if len(blocks) < 5:
71
+ return "Pas assez de texte exploitable."
72
+
73
+ # -------------------------------------------------
74
+ # 1. Détection du X de la colonne cible via son titre
75
+ # -------------------------------------------------
76
+ col_x = None
77
+ for text, x, y in blocks:
78
+ if normalize(text) in COL_TITLES:
79
+ col_x = x
80
+ break
81
+
82
+ if col_x is None:
83
+ return "Titre de la colonne cible non détecté."
84
+
85
+ # -------------------------------------------------
86
+ # 2. Sélection des blocs proches du X détecté
87
+ # -------------------------------------------------
88
+ X_THRESHOLD = 45
89
+ column_blocks = [
90
+ (t, x, y) for t, x, y in blocks
91
+ if abs(x - col_x) < X_THRESHOLD
92
+ ]
93
 
94
+ if not column_blocks:
95
+ return "Colonne détectée mais vide."
96
 
97
+ # -------------------------------------------------
98
+ # 3. Tri vertical (haut bas)
99
+ # -------------------------------------------------
100
+ column_blocks.sort(key=lambda e: e[2])
 
101
 
102
+ # -------------------------------------------------
103
+ # 4. Fusion intelligente des lignes OCR
104
+ # -------------------------------------------------
105
  merged = []
106
  current = ""
107
  last_y = None
108
  Y_THRESHOLD = 22
109
 
110
+ for text, x, y in column_blocks:
111
+ nt = normalize(text)
112
+
113
+ if any(k in nt for k in IGNORE_KEYWORDS):
114
+ continue
115
+
116
  if last_y is None or abs(y - last_y) > Y_THRESHOLD:
117
  if current:
118
  merged.append(current.strip())
 
125
  if current:
126
  merged.append(current.strip())
127
 
128
+ # -------------------------------------------------
129
+ # 5. Nettoyage final (cellules texte uniquement)
130
+ # -------------------------------------------------
131
+ final = []
132
+ for line in merged:
133
+ nt = normalize(line)
134
+ if len(nt) < 4:
135
+ continue
136
+ if sum(c.isdigit() for c in line) > len(line) / 2:
137
+ continue
138
+ final.append(line)
139
 
140
  if not final:
141
+ return "Aucune cellule texte valide trouvée."
142
 
143
+ # -------------------------------------------------
144
+ # 6. Résultat numéroté
145
+ # -------------------------------------------------
146
+ return "\n".join(f"{i+1}. {line}" for i, line in enumerate(final))
147
 
148
+ # -------------------------------------------------
149
  # Interface Gradio
150
+ # -------------------------------------------------
151
  demo = gr.Interface(
152
  fn=extract_second_column,
153
  inputs=gr.Image(type="pil", label="Image du tableau"),
154
+ outputs=gr.Textbox(label="Contenu de la colonne 2"),
155
+ title="Extraction fiable de la colonne 2 (Désignation / Description)",
156
+ description=(
157
+ "Extraction robuste de la deuxième colonne des tableaux scannés "
158
+ "(Désignation, DESIGNATIONS, Description, Description des services)."
159
+ )
160
  )
161
 
162
  demo.launch(server_name="0.0.0.0", server_port=7860)