kebson commited on
Commit
ed975bc
·
verified ·
1 Parent(s): daa9804

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +53 -51
app.py CHANGED
@@ -3,26 +3,26 @@ import numpy as np
3
  import unicodedata
4
  from paddleocr import PaddleOCR
5
 
6
- # =================================================
7
- # OCR Paddle (HF compatible)
8
- # =================================================
9
  ocr = PaddleOCR(
10
  lang="fr",
11
  use_textline_orientation=True
12
  )
13
 
14
- # =================================================
15
  # Normalisation texte (casse + accents)
16
- # =================================================
17
  def normalize(text: str) -> str:
18
  text = text.lower()
19
  text = unicodedata.normalize("NFD", text)
20
  text = "".join(c for c in text if unicodedata.category(c) != "Mn")
21
  return " ".join(text.split())
22
 
23
- # =================================================
24
- # Titres possibles de la colonne 2
25
- # =================================================
26
  COL_TITLES = {
27
  "designation",
28
  "designations",
@@ -30,34 +30,32 @@ COL_TITLES = {
30
  "description des services"
31
  }
32
 
33
- # =================================================
34
- # Mots à ignorer
35
- # =================================================
36
  IGNORE_KEYWORDS = {
37
- "prix", "ht", "htva", "tva", "ttc",
38
- "total", "generale", "general"
39
  }
40
 
41
- # =================================================
 
 
 
 
 
 
 
 
42
  # Fonction principale
43
- # =================================================
44
  def extract_second_column(image):
45
  if image is None:
46
  return "Aucune image fournie."
47
 
48
  img = np.array(image)
49
-
50
- # -------------------------------------------------
51
- # Rotation automatique si image couchée
52
- # -------------------------------------------------
53
- h, w = img.shape[:2]
54
- if w > h:
55
- img = np.rot90(img, 1)
56
-
57
- # -------------------------------------------------
58
- # OCR
59
- # -------------------------------------------------
60
  result = ocr.predict(img)
 
61
  if not result:
62
  return "OCR : aucun texte détecté."
63
 
@@ -76,56 +74,60 @@ def extract_second_column(image):
76
 
77
  blocks.append((t, x, y))
78
 
79
- if len(blocks) < 8:
80
  return "Pas assez de texte exploitable."
81
 
82
  # -------------------------------------------------
83
- # Détection du X de la colonne 2 via le titre
84
  # -------------------------------------------------
85
  col_x = None
 
 
86
  for text, x, y in blocks:
87
- nt = normalize(text)
88
- for title in COL_TITLES:
89
- if title in nt:
90
- col_x = x
91
- break
92
- if col_x is not None:
93
  break
94
 
95
  if col_x is None:
96
  return "Titre de la colonne cible non détecté."
97
 
98
  # -------------------------------------------------
99
- # Sélection des blocs de la colonne
100
  # -------------------------------------------------
101
- X_THRESHOLD = 55
102
  column_blocks = [
103
  (t, x, y) for t, x, y in blocks
104
- if abs(x - col_x) < X_THRESHOLD
105
  ]
106
 
107
  if not column_blocks:
108
  return "Colonne détectée mais vide."
109
 
110
  # -------------------------------------------------
111
- # Tri vertical
112
  # -------------------------------------------------
113
  column_blocks.sort(key=lambda e: e[2])
114
 
115
  # -------------------------------------------------
116
- # Fusion intelligente des lignes OCR
117
  # -------------------------------------------------
118
  merged = []
119
  current = ""
120
  last_y = None
121
- Y_THRESHOLD = 28
122
 
123
  for text, x, y in column_blocks:
124
  nt = normalize(text)
125
 
 
126
  if any(k in nt for k in IGNORE_KEYWORDS):
127
  continue
128
 
 
 
 
 
129
  if last_y is None or abs(y - last_y) > Y_THRESHOLD:
130
  if current:
131
  merged.append(current.strip())
@@ -139,19 +141,16 @@ def extract_second_column(image):
139
  merged.append(current.strip())
140
 
141
  # -------------------------------------------------
142
- # Nettoyage final
143
  # -------------------------------------------------
144
  final = []
145
  for line in merged:
146
  nt = normalize(line)
147
 
148
- if nt in COL_TITLES:
149
- continue
150
- if len(nt) < 5:
151
  continue
152
- if sum(c.isdigit() for c in line) > len(line) / 3:
153
- continue
154
- if not line[0].isupper():
155
  continue
156
 
157
  final.append(line)
@@ -159,18 +158,21 @@ def extract_second_column(image):
159
  if not final:
160
  return "Aucune cellule texte valide trouvée."
161
 
 
 
 
162
  return "\n".join(f"{i+1}. {line}" for i, line in enumerate(final))
163
 
164
- # =================================================
165
- # Interface Gradio
166
- # =================================================
167
  demo = gr.Interface(
168
  fn=extract_second_column,
169
  inputs=gr.Image(type="pil", label="Image du tableau"),
170
  outputs=gr.Textbox(label="Contenu de la colonne 2"),
171
  title="Extraction fiable de la colonne 2",
172
  description=(
173
- "Extraction adaptative de la deuxième colonne "
174
  "(Désignation, DESIGNATIONS, Description, Description des services)."
175
  )
176
  )
 
3
  import unicodedata
4
  from paddleocr import PaddleOCR
5
 
6
+ # -------------------------------------------------
7
+ # OCR (compatible Hugging Face)
8
+ # -------------------------------------------------
9
  ocr = PaddleOCR(
10
  lang="fr",
11
  use_textline_orientation=True
12
  )
13
 
14
+ # -------------------------------------------------
15
  # Normalisation texte (casse + accents)
16
+ # -------------------------------------------------
17
  def normalize(text: str) -> str:
18
  text = text.lower()
19
  text = unicodedata.normalize("NFD", text)
20
  text = "".join(c for c in text if unicodedata.category(c) != "Mn")
21
  return " ".join(text.split())
22
 
23
+ # -------------------------------------------------
24
+ # Titres valides de la colonne 2
25
+ # -------------------------------------------------
26
  COL_TITLES = {
27
  "designation",
28
  "designations",
 
30
  "description des services"
31
  }
32
 
33
+ # -------------------------------------------------
34
+ # Mots / lignes à ignorer
35
+ # -------------------------------------------------
36
  IGNORE_KEYWORDS = {
37
+ "prix", "total", "ht", "htva", "tva",
38
+ "ttc", "general", "generale"
39
  }
40
 
41
+ # -------------------------------------------------
42
+ # Métadonnées à exclure (hors tableau)
43
+ # -------------------------------------------------
44
+ META_KEYWORDS = {
45
+ "dpo", "dao", "ref", "reference",
46
+ "date", "nme", ":"
47
+ }
48
+
49
+ # -------------------------------------------------
50
  # Fonction principale
51
+ # -------------------------------------------------
52
  def extract_second_column(image):
53
  if image is None:
54
  return "Aucune image fournie."
55
 
56
  img = np.array(image)
 
 
 
 
 
 
 
 
 
 
 
57
  result = ocr.predict(img)
58
+
59
  if not result:
60
  return "OCR : aucun texte détecté."
61
 
 
74
 
75
  blocks.append((t, x, y))
76
 
77
+ if len(blocks) < 5:
78
  return "Pas assez de texte exploitable."
79
 
80
  # -------------------------------------------------
81
+ # 1. Détection du X de la colonne cible (par le titre)
82
  # -------------------------------------------------
83
  col_x = None
84
+ title_y = None
85
+
86
  for text, x, y in blocks:
87
+ if normalize(text) in COL_TITLES:
88
+ col_x = x
89
+ title_y = y
 
 
 
90
  break
91
 
92
  if col_x is None:
93
  return "Titre de la colonne cible non détecté."
94
 
95
  # -------------------------------------------------
96
+ # 2. Sélection des blocs de la colonne (SOUS le titre)
97
  # -------------------------------------------------
98
+ X_THRESHOLD = 45
99
  column_blocks = [
100
  (t, x, y) for t, x, y in blocks
101
+ if abs(x - col_x) < X_THRESHOLD and y > title_y
102
  ]
103
 
104
  if not column_blocks:
105
  return "Colonne détectée mais vide."
106
 
107
  # -------------------------------------------------
108
+ # 3. Tri vertical (haut → bas)
109
  # -------------------------------------------------
110
  column_blocks.sort(key=lambda e: e[2])
111
 
112
  # -------------------------------------------------
113
+ # 4. Fusion contrôlée des lignes OCR
114
  # -------------------------------------------------
115
  merged = []
116
  current = ""
117
  last_y = None
118
+ Y_THRESHOLD = 22
119
 
120
  for text, x, y in column_blocks:
121
  nt = normalize(text)
122
 
123
+ # Ignore lignes de totaux / prix
124
  if any(k in nt for k in IGNORE_KEYWORDS):
125
  continue
126
 
127
+ # Ignore métadonnées résiduelles
128
+ if any(k in nt for k in META_KEYWORDS):
129
+ continue
130
+
131
  if last_y is None or abs(y - last_y) > Y_THRESHOLD:
132
  if current:
133
  merged.append(current.strip())
 
141
  merged.append(current.strip())
142
 
143
  # -------------------------------------------------
144
+ # 5. Nettoyage final (cellules texte métier uniquement)
145
  # -------------------------------------------------
146
  final = []
147
  for line in merged:
148
  nt = normalize(line)
149
 
150
+ if len(nt) < 4:
 
 
151
  continue
152
+
153
+ if sum(c.isdigit() for c in line) > len(line) / 2:
 
154
  continue
155
 
156
  final.append(line)
 
158
  if not final:
159
  return "Aucune cellule texte valide trouvée."
160
 
161
+ # -------------------------------------------------
162
+ # 6. Résultat numéroté
163
+ # -------------------------------------------------
164
  return "\n".join(f"{i+1}. {line}" for i, line in enumerate(final))
165
 
166
+ # -------------------------------------------------
167
+ # Interface Gradio (Hugging Face)
168
+ # -------------------------------------------------
169
  demo = gr.Interface(
170
  fn=extract_second_column,
171
  inputs=gr.Image(type="pil", label="Image du tableau"),
172
  outputs=gr.Textbox(label="Contenu de la colonne 2"),
173
  title="Extraction fiable de la colonne 2",
174
  description=(
175
+ "Extraction robuste de la deuxième colonne des tableaux scannés "
176
  "(Désignation, DESIGNATIONS, Description, Description des services)."
177
  )
178
  )