Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -87,285 +87,111 @@ def _resize_for_inference(img):
|
|
| 87 |
return img.resize(new_size, Image.LANCZOS)
|
| 88 |
|
| 89 |
|
| 90 |
-
def
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
"|------|------------|--------|",
|
| 96 |
-
]
|
| 97 |
-
for row in entries:
|
| 98 |
-
table.append(f"| {row['date']} | {row['description']} | {row['amount']} |")
|
| 99 |
-
return "\n".join(table)
|
| 100 |
|
| 101 |
|
| 102 |
-
def
|
| 103 |
-
return bool(re.match(r"\d{2}/\d{2}", line.strip()))
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
def _ends_with_amount(text: str) -> bool:
|
| 107 |
-
return bool(re.search(r"\d{1,3}(?:,\d{3})*\.\d{2}$", text.strip()))
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
def _is_noise_line(line: str) -> bool:
|
| 111 |
-
s = line.strip().lower()
|
| 112 |
-
if not s:
|
| 113 |
-
return True
|
| 114 |
-
if s == "---page-separator---":
|
| 115 |
-
return True
|
| 116 |
-
if "call 1-800-937-2000" in s:
|
| 117 |
-
return True
|
| 118 |
-
if s in ("<table border=\"1\"><tr><td></td></tr></table>", "<table><tr><td></td></tr></table>"):
|
| 119 |
-
return True
|
| 120 |
-
return False
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
def _merge_transaction_lines(rows: List[str]) -> List[str]:
|
| 124 |
-
"""
|
| 125 |
-
Step 1/2: Merge multiline OCR rows into single transaction rows.
|
| 126 |
-
A new row begins when a line starts with MM/DD.
|
| 127 |
-
"""
|
| 128 |
-
merged: List[str] = []
|
| 129 |
-
current = ""
|
| 130 |
-
|
| 131 |
-
for raw in rows:
|
| 132 |
-
line = " ".join(raw.strip().split())
|
| 133 |
-
if not line or _is_noise_line(line):
|
| 134 |
-
continue
|
| 135 |
-
|
| 136 |
-
if _is_transaction_start(line):
|
| 137 |
-
if current:
|
| 138 |
-
merged.append(current.strip())
|
| 139 |
-
current = line
|
| 140 |
-
else:
|
| 141 |
-
if current:
|
| 142 |
-
current += " " + line
|
| 143 |
-
else:
|
| 144 |
-
current = line
|
| 145 |
-
|
| 146 |
-
if current:
|
| 147 |
-
merged.append(current.strip())
|
| 148 |
-
|
| 149 |
-
return merged
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
def _repair_orphan_amounts(rows: List[str]) -> List[str]:
|
| 153 |
"""
|
| 154 |
-
|
|
|
|
|
|
|
| 155 |
"""
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
|
|
|
| 166 |
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
MM/DD ... amount
|
| 171 |
-
"""
|
| 172 |
-
lines = text.split("\n")
|
| 173 |
-
result: List[str] = []
|
| 174 |
-
table_buffer: List[str] = []
|
| 175 |
-
in_section = False
|
| 176 |
-
pattern = re.compile(r"(\d{2}/\d{2})\s+(.*?)\s+([\d,]+\.\d{2})$")
|
| 177 |
-
|
| 178 |
-
for line in lines:
|
| 179 |
-
if re.search(r"POSTING DATE.*DESCRIPTION.*AMOUNT", line, re.IGNORECASE):
|
| 180 |
-
in_section = True
|
| 181 |
-
table_buffer = []
|
| 182 |
-
continue
|
| 183 |
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
clean_rows = []
|
| 187 |
-
merged_rows = _merge_transaction_lines(table_buffer)
|
| 188 |
-
repaired_rows = _repair_orphan_amounts(merged_rows)
|
| 189 |
-
for normalized in repaired_rows:
|
| 190 |
-
match = pattern.search(normalized)
|
| 191 |
-
if match:
|
| 192 |
-
date, desc, amount = match.groups()
|
| 193 |
-
clean_rows.append(
|
| 194 |
-
{"date": date, "description": desc, "amount": amount}
|
| 195 |
-
)
|
| 196 |
-
if clean_rows:
|
| 197 |
-
result.append(_build_table_from_entries(clean_rows))
|
| 198 |
-
else:
|
| 199 |
-
result.extend(table_buffer)
|
| 200 |
-
table_buffer = []
|
| 201 |
-
in_section = False
|
| 202 |
result.append(line)
|
|
|
|
| 203 |
continue
|
| 204 |
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
width = max(len(r) for r in rows)
|
| 270 |
-
norm = [r + [""] * (width - len(r)) for r in rows]
|
| 271 |
-
md = []
|
| 272 |
-
md.append("| " + " | ".join(norm[0]) + " |")
|
| 273 |
-
md.append("| " + " | ".join(["---"] * width) + " |")
|
| 274 |
-
for r in norm[1:]:
|
| 275 |
-
md.append("| " + " | ".join(r) + " |")
|
| 276 |
-
return "\n".join(md)
|
| 277 |
-
|
| 278 |
-
return table_re.sub(lambda m: _convert(m.group(0)), text)
|
| 279 |
-
|
| 280 |
-
soup = BeautifulSoup(text, "html.parser")
|
| 281 |
-
|
| 282 |
-
for table in soup.find_all("table"):
|
| 283 |
-
rows = []
|
| 284 |
-
for tr in table.find_all("tr"):
|
| 285 |
-
cols = [td.get_text(strip=True) for td in tr.find_all(["td", "th"])]
|
| 286 |
-
if cols:
|
| 287 |
-
rows.append(cols)
|
| 288 |
-
|
| 289 |
-
if rows:
|
| 290 |
-
md = []
|
| 291 |
-
header = rows[0]
|
| 292 |
-
md.append("| " + " | ".join(header) + " |")
|
| 293 |
-
md.append("| " + " | ".join(["---"] * len(header)) + " |")
|
| 294 |
-
|
| 295 |
-
for row in rows[1:]:
|
| 296 |
-
padded = row + [""] * (len(header) - len(row))
|
| 297 |
-
md.append("| " + " | ".join(padded[: len(header)]) + " |")
|
| 298 |
-
|
| 299 |
-
table.replace_with("\n".join(md))
|
| 300 |
-
|
| 301 |
-
return str(soup)
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
def _remove_prompt_echo_artifacts(text: str) -> str:
|
| 305 |
-
"""
|
| 306 |
-
Remove model prompt-echo garbage blocks such as repeated:
|
| 307 |
-
'N) Keep left-most amount values.' / 'N) Keep right-most amount values.'
|
| 308 |
-
"""
|
| 309 |
-
lines = text.split("\n")
|
| 310 |
-
cleaned: List[str] = []
|
| 311 |
-
keep_line_re = re.compile(
|
| 312 |
-
r"^\s*\d+\)\s*Keep\s+(left-most|right-most)\s+amount\s+values\.?\s*$",
|
| 313 |
-
re.IGNORECASE,
|
| 314 |
-
)
|
| 315 |
-
repeat_run = 0
|
| 316 |
-
|
| 317 |
-
for line in lines:
|
| 318 |
-
if keep_line_re.match(line):
|
| 319 |
-
repeat_run += 1
|
| 320 |
continue
|
| 321 |
-
# Also drop explicit prompt/rules echoes if they appear as plain output.
|
| 322 |
-
if re.match(r"^\s*Document Parsing to markdown\.?\s*$", line, re.IGNORECASE):
|
| 323 |
-
continue
|
| 324 |
-
if re.match(r"^\s*Rules:\s*$", line, re.IGNORECASE):
|
| 325 |
-
continue
|
| 326 |
-
cleaned.append(line)
|
| 327 |
-
|
| 328 |
-
# If there was a long repeated run, ensure dangling separator spacing stays clean.
|
| 329 |
-
text = "\n".join(cleaned)
|
| 330 |
-
text = re.sub(r"\n{4,}", "\n\n\n", text)
|
| 331 |
-
return text
|
| 332 |
|
|
|
|
|
|
|
| 333 |
|
| 334 |
-
|
| 335 |
-
"""Post-process markdown to fix table formatting and section structure."""
|
| 336 |
-
text = _remove_prompt_echo_artifacts(text)
|
| 337 |
-
text = _html_to_markdown_tables(text)
|
| 338 |
-
text = _fix_missing_amounts(text)
|
| 339 |
-
text = _normalize_transactions(text)
|
| 340 |
-
|
| 341 |
-
lines = text.split('\n')
|
| 342 |
-
cleaned = []
|
| 343 |
-
in_table = False
|
| 344 |
-
|
| 345 |
-
for line in lines:
|
| 346 |
-
if '|' in line and line.strip().startswith('|'):
|
| 347 |
-
if not in_table:
|
| 348 |
-
if cleaned and cleaned[-1].strip():
|
| 349 |
-
cleaned.append('')
|
| 350 |
-
in_table = True
|
| 351 |
-
|
| 352 |
-
line = re.sub(r'\s*\|\s*', ' | ', line)
|
| 353 |
-
line = re.sub(r'\s+', ' ', line)
|
| 354 |
-
cleaned.append(line.strip())
|
| 355 |
-
elif in_table and line.strip() == '':
|
| 356 |
-
in_table = False
|
| 357 |
-
cleaned.append('')
|
| 358 |
-
else:
|
| 359 |
-
in_table = False
|
| 360 |
-
if line.strip() or (cleaned and cleaned[-1].strip()):
|
| 361 |
-
cleaned.append(line)
|
| 362 |
-
|
| 363 |
-
text = '\n'.join(cleaned)
|
| 364 |
-
text = re.sub(r'(#{1,6})\s*([^\n]+)', r'\1 \2', text)
|
| 365 |
-
text = re.sub(r'\n([•\-\*])\s+', r'\n\1 ', text)
|
| 366 |
-
text = '\n'.join(line.rstrip() for line in text.split('\n'))
|
| 367 |
-
text = re.sub(r'\n{4,}', '\n\n\n', text)
|
| 368 |
-
return text.strip()
|
| 369 |
|
| 370 |
|
| 371 |
def _infer_image(image_path: str) -> str:
|
|
@@ -387,14 +213,7 @@ def _infer_image(image_path: str) -> str:
|
|
| 387 |
"role": "user",
|
| 388 |
"content": [
|
| 389 |
{"type": "image", "url": resized_path},
|
| 390 |
-
{
|
| 391 |
-
"type": "text",
|
| 392 |
-
"text": (
|
| 393 |
-
"Extract document text as markdown only. "
|
| 394 |
-
"Do not include instructions, examples, or template rows. "
|
| 395 |
-
"Preserve transaction lines exactly."
|
| 396 |
-
),
|
| 397 |
-
},
|
| 398 |
],
|
| 399 |
}]
|
| 400 |
|
|
@@ -407,8 +226,7 @@ def _infer_image(image_path: str) -> str:
|
|
| 407 |
).to(model.device)
|
| 408 |
inputs.pop("token_type_ids", None)
|
| 409 |
|
| 410 |
-
|
| 411 |
-
torch.cuda.empty_cache()
|
| 412 |
|
| 413 |
with torch.no_grad():
|
| 414 |
ids = model.generate(
|
|
@@ -422,9 +240,9 @@ def _infer_image(image_path: str) -> str:
|
|
| 422 |
ids[0][inputs["input_ids"].shape[1]:],
|
| 423 |
skip_special_tokens=True,
|
| 424 |
)
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
return
|
| 428 |
|
| 429 |
finally:
|
| 430 |
try:
|
|
@@ -528,18 +346,13 @@ def _create_gradio_demo():
|
|
| 528 |
with gr.Blocks(title="GLM-OCR Fine-tuned") as demo:
|
| 529 |
gr.Markdown("# GLM-OCR (Fine-tuned)")
|
| 530 |
gr.Markdown("Upload a PDF or image to extract structured markdown content.")
|
| 531 |
-
|
| 532 |
file_in = gr.File(
|
| 533 |
label="Upload PDF or image",
|
| 534 |
file_types=[".pdf", ".png", ".jpg", ".jpeg", ".tiff", ".bmp"],
|
| 535 |
)
|
| 536 |
run_btn = gr.Button("Run OCR", variant="primary")
|
| 537 |
-
out = gr.Textbox(
|
| 538 |
-
lines=40,
|
| 539 |
-
label="Output (markdown)"
|
| 540 |
-
)
|
| 541 |
run_btn.click(fn=run_ocr, inputs=file_in, outputs=out)
|
| 542 |
-
|
| 543 |
return demo
|
| 544 |
|
| 545 |
|
|
|
|
| 87 |
return img.resize(new_size, Image.LANCZOS)
|
| 88 |
|
| 89 |
|
| 90 |
+
def _detect_headers(header_line: str) -> List[str]:
|
| 91 |
+
"""Extract column headers from a header line by splitting on 2+ spaces."""
|
| 92 |
+
parts = re.split(r'\s{2,}', header_line.strip())
|
| 93 |
+
headers = [p.strip() for p in parts if p.strip()]
|
| 94 |
+
return headers if headers else ["POSTING DATE", "DESCRIPTION", "AMOUNT"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
|
| 96 |
|
| 97 |
+
def _plaintext_rows_to_table(text: str) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
"""
|
| 99 |
+
Convert plain-text transaction rows (no pipe boundaries) into proper
|
| 100 |
+
markdown tables. Detects headers dynamically from the document.
|
| 101 |
+
Works for any column layout, not just Date/Description/Amount.
|
| 102 |
"""
|
| 103 |
+
lines = text.split('\n')
|
| 104 |
+
result = []
|
| 105 |
+
i = 0
|
| 106 |
+
# Pattern to detect a header line (2+ words separated by 2+ spaces)
|
| 107 |
+
header_re = re.compile(
|
| 108 |
+
r'^[A-Z][A-Z\s]{3,}$',
|
| 109 |
+
)
|
| 110 |
+
# Pattern for a transaction row starting with MM/DD
|
| 111 |
+
txn_re = re.compile(r'^\d{2}/\d{2}\s+\S')
|
| 112 |
+
# Pattern to extract amount at end of line
|
| 113 |
+
amt_re = re.compile(r'^(.*?)\s+([\d,]+\.\d{2})$')
|
| 114 |
|
| 115 |
+
while i < len(lines):
|
| 116 |
+
line = lines[i]
|
| 117 |
+
stripped = line.strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
|
| 119 |
+
# Already a proper markdown table row — pass through unchanged
|
| 120 |
+
if stripped.startswith('|'):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
result.append(line)
|
| 122 |
+
i += 1
|
| 123 |
continue
|
| 124 |
|
| 125 |
+
# Detect start of a plain-text transaction block
|
| 126 |
+
if txn_re.match(stripped):
|
| 127 |
+
# Look back to find the most recent header line before this block
|
| 128 |
+
detected_headers = None
|
| 129 |
+
for j in range(len(result) - 1, max(len(result) - 5, -1), -1):
|
| 130 |
+
prev = result[j].strip()
|
| 131 |
+
if prev and not prev.startswith('|') and not prev.startswith('#'):
|
| 132 |
+
candidate_headers = _detect_headers(prev)
|
| 133 |
+
if len(candidate_headers) >= 2:
|
| 134 |
+
detected_headers = candidate_headers
|
| 135 |
+
# Remove the header line from result since we'll put it in table
|
| 136 |
+
result.pop(j)
|
| 137 |
+
break
|
| 138 |
+
|
| 139 |
+
if not detected_headers:
|
| 140 |
+
detected_headers = ["POSTING DATE", "DESCRIPTION", "AMOUNT"]
|
| 141 |
+
|
| 142 |
+
# Collect all transaction rows in this block
|
| 143 |
+
block = []
|
| 144 |
+
while i < len(lines):
|
| 145 |
+
l = lines[i].strip()
|
| 146 |
+
if not l:
|
| 147 |
+
break
|
| 148 |
+
if l.startswith('|') or l.startswith('#'):
|
| 149 |
+
break
|
| 150 |
+
if re.match(r'^Subtotal', l, re.IGNORECASE):
|
| 151 |
+
break
|
| 152 |
+
if txn_re.match(l):
|
| 153 |
+
block.append(l)
|
| 154 |
+
elif block:
|
| 155 |
+
# Continuation line — merge into previous row
|
| 156 |
+
block[-1] += ' ' + l
|
| 157 |
+
i += 1
|
| 158 |
+
|
| 159 |
+
if block:
|
| 160 |
+
# Build markdown table with detected headers
|
| 161 |
+
num_cols = len(detected_headers)
|
| 162 |
+
result.append('| ' + ' | '.join(detected_headers) + ' |')
|
| 163 |
+
result.append('| ' + ' | '.join(['---'] * num_cols) + ' |')
|
| 164 |
+
|
| 165 |
+
for row in block:
|
| 166 |
+
if num_cols == 3:
|
| 167 |
+
# Try to split into date / description / amount
|
| 168 |
+
m = amt_re.match(row)
|
| 169 |
+
if m:
|
| 170 |
+
rest = m.group(1)
|
| 171 |
+
amount = m.group(2)
|
| 172 |
+
date_m = re.match(r'^(\d{2}/\d{2})\s+(.*)', rest)
|
| 173 |
+
if date_m:
|
| 174 |
+
result.append(
|
| 175 |
+
f'| {date_m.group(1)} | {date_m.group(2)} | {amount} |'
|
| 176 |
+
)
|
| 177 |
+
continue
|
| 178 |
+
# No amount found — put full row in description, leave amount blank
|
| 179 |
+
date_m = re.match(r'^(\d{2}/\d{2})\s+(.*)', row)
|
| 180 |
+
if date_m:
|
| 181 |
+
result.append(
|
| 182 |
+
f'| {date_m.group(1)} | {date_m.group(2)} | |'
|
| 183 |
+
)
|
| 184 |
+
else:
|
| 185 |
+
result.append(f'| | {row} | |')
|
| 186 |
+
else:
|
| 187 |
+
# For non-3-column tables just put full row as single cell
|
| 188 |
+
result.append(f'| {row} |')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
|
| 191 |
+
result.append(line)
|
| 192 |
+
i += 1
|
| 193 |
|
| 194 |
+
return '\n'.join(result)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
|
| 196 |
|
| 197 |
def _infer_image(image_path: str) -> str:
|
|
|
|
| 213 |
"role": "user",
|
| 214 |
"content": [
|
| 215 |
{"type": "image", "url": resized_path},
|
| 216 |
+
{"type": "text", "text": "Document Parsing:"},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
],
|
| 218 |
}]
|
| 219 |
|
|
|
|
| 226 |
).to(model.device)
|
| 227 |
inputs.pop("token_type_ids", None)
|
| 228 |
|
| 229 |
+
torch.cuda.empty_cache()
|
|
|
|
| 230 |
|
| 231 |
with torch.no_grad():
|
| 232 |
ids = model.generate(
|
|
|
|
| 240 |
ids[0][inputs["input_ids"].shape[1]:],
|
| 241 |
skip_special_tokens=True,
|
| 242 |
)
|
| 243 |
+
|
| 244 |
+
result = _plaintext_rows_to_table(result)
|
| 245 |
+
return result.strip()
|
| 246 |
|
| 247 |
finally:
|
| 248 |
try:
|
|
|
|
| 346 |
with gr.Blocks(title="GLM-OCR Fine-tuned") as demo:
|
| 347 |
gr.Markdown("# GLM-OCR (Fine-tuned)")
|
| 348 |
gr.Markdown("Upload a PDF or image to extract structured markdown content.")
|
|
|
|
| 349 |
file_in = gr.File(
|
| 350 |
label="Upload PDF or image",
|
| 351 |
file_types=[".pdf", ".png", ".jpg", ".jpeg", ".tiff", ".bmp"],
|
| 352 |
)
|
| 353 |
run_btn = gr.Button("Run OCR", variant="primary")
|
| 354 |
+
out = gr.Textbox(lines=40, label="Output (markdown)")
|
|
|
|
|
|
|
|
|
|
| 355 |
run_btn.click(fn=run_ocr, inputs=file_in, outputs=out)
|
|
|
|
| 356 |
return demo
|
| 357 |
|
| 358 |
|