rehan953 commited on
Commit
ddc5a81
·
verified ·
1 Parent(s): bb1b749

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +123 -35
app.py CHANGED
@@ -87,57 +87,140 @@ def _resize_for_inference(img):
87
  return img.resize(new_size, Image.LANCZOS)
88
 
89
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  def _clean_markdown(text: str) -> str:
91
- """Post-process markdown to fix common formatting issues."""
92
-
93
- # Fix table formatting - ensure proper spacing
 
 
94
  lines = text.split('\n')
95
  cleaned = []
96
  in_table = False
97
-
98
- for i, line in enumerate(lines):
99
- # Detect table rows
100
  if '|' in line and line.strip().startswith('|'):
101
  if not in_table:
102
- # First table row - add blank line before if needed
103
  if cleaned and cleaned[-1].strip():
104
  cleaned.append('')
105
  in_table = True
106
-
107
- # Clean up table row spacing
108
  line = re.sub(r'\s*\|\s*', ' | ', line)
109
  line = re.sub(r'\s+', ' ', line)
110
  cleaned.append(line.strip())
111
-
112
  elif in_table and line.strip() == '':
113
- # End of table - add blank line after
114
  in_table = False
115
  cleaned.append('')
116
-
117
  else:
118
  in_table = False
119
- # Remove excessive blank lines
120
  if line.strip() or (cleaned and cleaned[-1].strip()):
121
  cleaned.append(line)
122
-
123
- # Join lines
124
  text = '\n'.join(cleaned)
125
-
126
- # Fix header spacing
127
  text = re.sub(r'(#{1,6})\s*([^\n]+)', r'\1 \2', text)
128
-
129
- # Fix list spacing
130
  text = re.sub(r'\n([•\-\*])\s+', r'\n\1 ', text)
131
-
132
- # Remove trailing whitespace from lines
133
  text = '\n'.join(line.rstrip() for line in text.split('\n'))
134
-
135
- # Normalize multiple blank lines to maximum 2
136
  text = re.sub(r'\n{4,}', '\n\n\n', text)
137
-
138
- # Fix common OCR artifacts in tables
139
- text = re.sub(r'\|\s*:\s*---\s*\|\s*:\s*---\s*\|', '| :--- | :--- |', text)
140
-
141
  return text.strip()
142
 
143
 
@@ -160,7 +243,17 @@ def _infer_image(image_path: str) -> str:
160
  "role": "user",
161
  "content": [
162
  {"type": "image", "url": resized_path},
163
- {"type": "text", "text": "Document Parsing:"},
 
 
 
 
 
 
 
 
 
 
164
  ],
165
  }]
166
 
@@ -173,7 +266,8 @@ def _infer_image(image_path: str) -> str:
173
  ).to(model.device)
174
  inputs.pop("token_type_ids", None)
175
 
176
- torch.cuda.empty_cache()
 
177
 
178
  with torch.no_grad():
179
  ids = model.generate(
@@ -305,12 +399,6 @@ def _create_gradio_demo():
305
  )
306
  run_btn.click(fn=run_ocr, inputs=file_in, outputs=out)
307
 
308
- gr.Markdown("""
309
- ### Tips
310
- - Tables are formatted in markdown format
311
- - Use the copy button to export the markdown
312
- - Each page is separated by `---page-separator---`
313
- """)
314
  return demo
315
 
316
 
 
87
  return img.resize(new_size, Image.LANCZOS)
88
 
89
 
90
+ def _build_table(rows: List[str]) -> str:
91
+ parsed = []
92
+
93
+ for row in rows:
94
+ parts = re.split(r"\s{2,}", row.strip())
95
+ if len(parts) >= 3:
96
+ date = parts[0]
97
+ amount = parts[-1] if re.search(r"\d+\.\d{2}", parts[-1]) else ""
98
+ desc = " ".join(parts[1:-1]) if amount else " ".join(parts[1:])
99
+ parsed.append((date, desc, amount))
100
+
101
+ if not parsed:
102
+ return "\n".join(rows)
103
+
104
+ table = [
105
+ "| POSTING DATE | DESCRIPTION | AMOUNT |",
106
+ "| :--- | :--- | ---: |",
107
+ ]
108
+ for date, desc, amount in parsed:
109
+ table.append(f"| {date} | {desc} | {amount} |")
110
+ return "\n".join(table)
111
+
112
+
113
+ def _normalize_transactions(text: str) -> str:
114
+ """
115
+ Convert loose transaction lines into structured markdown tables.
116
+ """
117
+ lines = text.split("\n")
118
+ result: List[str] = []
119
+ table_buffer: List[str] = []
120
+ in_section = False
121
+
122
+ for line in lines:
123
+ if re.search(r"POSTING DATE.*DESCRIPTION.*AMOUNT", line, re.IGNORECASE):
124
+ in_section = True
125
+ table_buffer = []
126
+ continue
127
+
128
+ if in_section and (line.strip() == "" or line.strip().startswith("Subtotal")):
129
+ if table_buffer:
130
+ result.append(_build_table(table_buffer))
131
+ table_buffer = []
132
+ in_section = False
133
+ result.append(line)
134
+ continue
135
+
136
+ if in_section:
137
+ table_buffer.append(line)
138
+ else:
139
+ result.append(line)
140
+
141
+ if table_buffer:
142
+ result.append(_build_table(table_buffer))
143
+
144
+ return "\n".join(result)
145
+
146
+
147
+ def _fix_missing_amounts(text: str) -> str:
148
+ lines = text.split("\n")
149
+ fixed: List[str] = []
150
+
151
+ for line in lines:
152
+ if re.search(r"\d{2}/\d{2}", line) and not re.search(r"\d+\.\d{2}", line):
153
+ match = re.search(r"(\d{1,3}(?:,\d{3})*\.\d{2})$", line)
154
+ if match:
155
+ line += f" {match.group(1)}"
156
+ fixed.append(line)
157
+
158
+ return "\n".join(fixed)
159
+
160
+
161
+ def _html_to_markdown_tables(text: str) -> str:
162
+ try:
163
+ from bs4 import BeautifulSoup
164
+ except Exception:
165
+ return text
166
+
167
+ soup = BeautifulSoup(text, "html.parser")
168
+
169
+ for table in soup.find_all("table"):
170
+ rows = []
171
+ for tr in table.find_all("tr"):
172
+ cols = [td.get_text(strip=True) for td in tr.find_all(["td", "th"])]
173
+ if cols:
174
+ rows.append(cols)
175
+
176
+ if rows:
177
+ md = []
178
+ header = rows[0]
179
+ md.append("| " + " | ".join(header) + " |")
180
+ md.append("| " + " | ".join(["---"] * len(header)) + " |")
181
+
182
+ for row in rows[1:]:
183
+ padded = row + [""] * (len(header) - len(row))
184
+ md.append("| " + " | ".join(padded[: len(header)]) + " |")
185
+
186
+ table.replace_with("\n".join(md))
187
+
188
+ return str(soup)
189
+
190
+
191
  def _clean_markdown(text: str) -> str:
192
+ """Post-process markdown to fix table formatting and section structure."""
193
+ text = _html_to_markdown_tables(text)
194
+ text = _fix_missing_amounts(text)
195
+ text = _normalize_transactions(text)
196
+
197
  lines = text.split('\n')
198
  cleaned = []
199
  in_table = False
200
+
201
+ for line in lines:
 
202
  if '|' in line and line.strip().startswith('|'):
203
  if not in_table:
 
204
  if cleaned and cleaned[-1].strip():
205
  cleaned.append('')
206
  in_table = True
207
+
 
208
  line = re.sub(r'\s*\|\s*', ' | ', line)
209
  line = re.sub(r'\s+', ' ', line)
210
  cleaned.append(line.strip())
 
211
  elif in_table and line.strip() == '':
 
212
  in_table = False
213
  cleaned.append('')
 
214
  else:
215
  in_table = False
 
216
  if line.strip() or (cleaned and cleaned[-1].strip()):
217
  cleaned.append(line)
218
+
 
219
  text = '\n'.join(cleaned)
 
 
220
  text = re.sub(r'(#{1,6})\s*([^\n]+)', r'\1 \2', text)
 
 
221
  text = re.sub(r'\n([•\-\*])\s+', r'\n\1 ', text)
 
 
222
  text = '\n'.join(line.rstrip() for line in text.split('\n'))
 
 
223
  text = re.sub(r'\n{4,}', '\n\n\n', text)
 
 
 
 
224
  return text.strip()
225
 
226
 
 
243
  "role": "user",
244
  "content": [
245
  {"type": "image", "url": resized_path},
246
+ {
247
+ "type": "text",
248
+ "text": (
249
+ "Document Parsing to markdown.\n"
250
+ "Rules:\n"
251
+ "1) Preserve rows exactly in reading order.\n"
252
+ "2) Keep transaction blocks as markdown tables.\n"
253
+ "3) Do not invent rows or sample/template data.\n"
254
+ "4) Keep right-most amount values."
255
+ ),
256
+ },
257
  ],
258
  }]
259
 
 
266
  ).to(model.device)
267
  inputs.pop("token_type_ids", None)
268
 
269
+ if torch.cuda.is_available():
270
+ torch.cuda.empty_cache()
271
 
272
  with torch.no_grad():
273
  ids = model.generate(
 
399
  )
400
  run_btn.click(fn=run_ocr, inputs=file_in, outputs=out)
401
 
 
 
 
 
 
 
402
  return demo
403
 
404