kebson commited on
Commit
f7822d1
·
verified ·
1 Parent(s): 7d8ddd9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +56 -30
app.py CHANGED
@@ -3,11 +3,17 @@ import numpy as np
3
  from paddleocr import PaddleOCR
4
  from sklearn.cluster import KMeans
5
 
 
 
 
6
  ocr = PaddleOCR(
7
  use_textline_orientation=True,
8
  lang="fr"
9
  )
10
 
 
 
 
11
  def extract_column2_9_lines(image):
12
  if image is None:
13
  return "Aucune image fournie."
@@ -31,50 +37,61 @@ def extract_column2_9_lines(image):
31
  elements = []
32
  for text, box in zip(texts, boxes):
33
  text = text.strip()
34
- if len(text) < 2:
35
  continue
 
36
  x_center = np.mean([p[0] for p in box])
37
  y_center = np.mean([p[1] for p in box])
 
38
  elements.append((x_center, y_center, text))
39
 
 
 
 
40
  # -----------------------------
41
- # 2. Clustering vertical (colonnes)
42
  # -----------------------------
43
  X = np.array([[e[0]] for e in elements])
44
- n_cols = 6
45
- kmeans = KMeans(n_clusters=n_cols, random_state=42).fit(X)
46
- labels = kmeans.labels_
 
47
 
48
  columns = {}
49
  for (x, y, text), label in zip(elements, labels):
50
  columns.setdefault(label, []).append((x, y, text))
51
 
52
- sorted_columns = sorted(
53
- columns.values(),
54
- key=lambda col: np.mean([e[0] for e in col])
55
- )
56
-
57
- if len(sorted_columns) < 2:
58
- return "Impossible de détecter la colonne 2."
59
-
60
  # -----------------------------
61
- # 3. Sélection colonne 2
 
62
  # -----------------------------
63
- col = sorted_columns[1]
64
- col.sort(key=lambda e: e[1]) # top → bottom
 
 
 
 
 
 
 
 
 
65
 
66
  # -----------------------------
67
- # 4. FUSION DES LIGNES OCR
68
  # -----------------------------
69
  merged_lines = []
70
  current_text = ""
71
  last_y = None
72
- Y_THRESHOLD = 18
73
 
74
- for _, y, text in col:
75
- if text.upper().startswith((
76
- "DESIGNATION", "UNITE", "QUANT", "PRIX", "TOTAL", "LOT"
77
- )):
 
 
 
78
  continue
79
 
80
  if last_y is None or abs(y - last_y) > Y_THRESHOLD:
@@ -90,24 +107,33 @@ def extract_column2_9_lines(image):
90
  merged_lines.append(current_text.strip())
91
 
92
  # -----------------------------
93
- # 5. 9 premières lignes
94
  # -----------------------------
95
- final_lines = merged_lines[:9]
 
 
 
 
 
 
 
 
96
 
97
  if not final_lines:
98
- return "Colonne détectée mais lignes vides."
99
 
100
- return "\n".join(final_lines)
 
101
 
102
  # -----------------------------
103
  # Interface Gradio
104
  # -----------------------------
105
  demo = gr.Interface(
106
  fn=extract_column2_9_lines,
107
- inputs=gr.Image(type="pil", label="Image du devis"),
108
- outputs=gr.Textbox(label="Colonne 2 9 premières lignes"),
109
- title="Extraction de la colonne DESIGNATIONS",
110
- description="Fusion automatique des lignes OCR (devis & tableaux)"
111
  )
112
 
113
  demo.launch(server_name="0.0.0.0", server_port=7860)
 
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
+ # Fonction principale
16
+ # -----------------------------
17
  def extract_column2_9_lines(image):
18
  if image is None:
19
  return "Aucune image fournie."
 
37
  elements = []
38
  for text, box in zip(texts, boxes):
39
  text = text.strip()
40
+ if len(text) < 3:
41
  continue
42
+
43
  x_center = np.mean([p[0] for p in box])
44
  y_center = np.mean([p[1] for p in box])
45
+
46
  elements.append((x_center, y_center, text))
47
 
48
+ if len(elements) < 5:
49
+ return "Pas assez de texte détecté."
50
+
51
  # -----------------------------
52
+ # 2. Clustering horizontal ADAPTATIF
53
  # -----------------------------
54
  X = np.array([[e[0]] for e in elements])
55
+ n_clusters = min(8, max(3, len(elements) // 8))
56
+
57
+ kmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init=10)
58
+ labels = kmeans.fit_predict(X)
59
 
60
  columns = {}
61
  for (x, y, text), label in zip(elements, labels):
62
  columns.setdefault(label, []).append((x, y, text))
63
 
 
 
 
 
 
 
 
 
64
  # -----------------------------
65
+ # 3. Choisir la colonne "Description"
66
+ # => la plus riche en texte non numérique
67
  # -----------------------------
68
+ def column_score(col):
69
+ score = 0
70
+ for _, _, t in col:
71
+ if not any(char.isdigit() for char in t):
72
+ score += len(t)
73
+ return score
74
+
75
+ best_column = max(columns.values(), key=column_score)
76
+
77
+ # Tri vertical
78
+ best_column.sort(key=lambda e: e[1])
79
 
80
  # -----------------------------
81
+ # 4. Fusion intelligente des lignes
82
  # -----------------------------
83
  merged_lines = []
84
  current_text = ""
85
  last_y = None
86
+ Y_THRESHOLD = 22
87
 
88
+ blacklist = (
89
+ "DESIGNATION", "UNITE", "QUANT", "PRIX", "TOTAL",
90
+ "LOT", "BORDEREAU", "DATE", "NB", "TTC", "HT"
91
+ )
92
+
93
+ for _, y, text in best_column:
94
+ if text.upper().startswith(blacklist):
95
  continue
96
 
97
  if last_y is None or abs(y - last_y) > Y_THRESHOLD:
 
107
  merged_lines.append(current_text.strip())
108
 
109
  # -----------------------------
110
+ # 5. Nettoyage final
111
  # -----------------------------
112
+ cleaned = []
113
+ for line in merged_lines:
114
+ if len(line) < 5:
115
+ continue
116
+ if sum(c.isdigit() for c in line) > len(line) / 2:
117
+ continue
118
+ cleaned.append(line)
119
+
120
+ final_lines = cleaned[:9]
121
 
122
  if not final_lines:
123
+ return "Colonne détectée mais contenu non exploitable."
124
 
125
+ # Numérotation demandée
126
+ return "\n".join([f"{i+1}. {l}" for i, l in enumerate(final_lines)])
127
 
128
  # -----------------------------
129
  # Interface Gradio
130
  # -----------------------------
131
  demo = gr.Interface(
132
  fn=extract_column2_9_lines,
133
+ inputs=gr.Image(type="pil", label="Image du tableau"),
134
+ outputs=gr.Textbox(label="Colonne Description (9 lignes)"),
135
+ title="Extraction robuste de la colonne Description",
136
+ description="Optimisé pour tableaux photographiés (devis, factures, bordereaux)"
137
  )
138
 
139
  demo.launch(server_name="0.0.0.0", server_port=7860)