Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -20,8 +20,10 @@ Explicitly omitted vs the heavy Space build:
|
|
| 20 |
|
| 21 |
Included (data-driven, no institution names):
|
| 22 |
- HTML tables: modal logical width from rowspan-free rows (colspan-aware);
|
| 23 |
-
pad short rows; trim trailing empty cells;
|
| 24 |
-
|
|
|
|
|
|
|
| 25 |
|
| 26 |
Configure GLMOCR_API_KEY (environment variable). Optional: glmocr + gradio +
|
| 27 |
pymupdf + pillow installed.
|
|
@@ -76,7 +78,7 @@ if not GLMOCR_API_KEY:
|
|
| 76 |
|
| 77 |
# Rasterization: higher scale = more pixels per PDF point (helps small type,
|
| 78 |
# boxed headers, and narrow columns). Same constant for all uploads.
|
| 79 |
-
RENDER_SCALE = 3.
|
| 80 |
|
| 81 |
# White margin as a fraction of page width/height after render. Extra right
|
| 82 |
# margin helps right-aligned currency columns that hug the page edge.
|
|
@@ -87,12 +89,12 @@ PAD_BOTTOM_FRAC = 0.018
|
|
| 87 |
|
| 88 |
ENABLE_CONTRAST = True
|
| 89 |
# Slight contrast lift only; same factor for every file.
|
| 90 |
-
CONTRAST_FACTOR = 1.
|
| 91 |
|
| 92 |
# Subtle edge enhancement after contrast (helps hairlines and small digits).
|
| 93 |
ENABLE_UNSHARP = True
|
| 94 |
UNSHARP_RADIUS = 0.78
|
| 95 |
-
UNSHARP_PERCENT =
|
| 96 |
UNSHARP_THRESHOLD = 1
|
| 97 |
|
| 98 |
DEFAULT_ZONE_FRAC = 0.12
|
|
@@ -353,18 +355,92 @@ def _is_whole_cell_currency(text: str) -> bool:
|
|
| 353 |
)
|
| 354 |
|
| 355 |
|
| 356 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 357 |
"""
|
| 358 |
-
|
| 359 |
-
|
| 360 |
"""
|
| 361 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
return cells
|
| 363 |
-
|
|
|
|
|
|
|
| 364 |
return cells
|
| 365 |
-
|
|
|
|
| 366 |
return cells
|
| 367 |
-
return
|
| 368 |
|
| 369 |
|
| 370 |
def _infer_modal_logical_width(tr_inners: List[str]) -> int:
|
|
@@ -405,8 +481,8 @@ def _normalize_one_tr_inner(tr_inner: str, target: int) -> str:
|
|
| 405 |
w -= spans[-1]
|
| 406 |
cells.pop()
|
| 407 |
spans.pop()
|
| 408 |
-
if cells
|
| 409 |
-
cells =
|
| 410 |
return "".join(cells)
|
| 411 |
|
| 412 |
|
|
@@ -417,10 +493,9 @@ def normalize_html_table_row_widths(md: str) -> str:
|
|
| 417 |
empty single-colspan cells from rows that are too wide.
|
| 418 |
|
| 419 |
No column names or fixed N: width comes from per-table row statistics.
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
Tables with rowspan are skipped.
|
| 423 |
-
currency-shaped is not altered.
|
| 424 |
"""
|
| 425 |
if not md or "<table" not in md.lower():
|
| 426 |
return md
|
|
|
|
| 20 |
|
| 21 |
Included (data-driven, no institution names):
|
| 22 |
- HTML tables: modal logical width from rowspan-free rows (colspan-aware);
|
| 23 |
+
pad short rows; trim trailing empty cells; expand each row to a logical
|
| 24 |
+
grid, then if the rightmost non-empty slot is a solitary amount and only
|
| 25 |
+
blanks follow to the edge, slide that amount into the rightmost slot (works
|
| 26 |
+
with merged cells, not only uniform single-span rows).
|
| 27 |
|
| 28 |
Configure GLMOCR_API_KEY (environment variable). Optional: glmocr + gradio +
|
| 29 |
pymupdf + pillow installed.
|
|
|
|
| 78 |
|
| 79 |
# Rasterization: higher scale = more pixels per PDF point (helps small type,
|
| 80 |
# boxed headers, and narrow columns). Same constant for all uploads.
|
| 81 |
+
RENDER_SCALE = 3.12
|
| 82 |
|
| 83 |
# White margin as a fraction of page width/height after render. Extra right
|
| 84 |
# margin helps right-aligned currency columns that hug the page edge.
|
|
|
|
| 89 |
|
| 90 |
ENABLE_CONTRAST = True
|
| 91 |
# Slight contrast lift only; same factor for every file.
|
| 92 |
+
CONTRAST_FACTOR = 1.18
|
| 93 |
|
| 94 |
# Subtle edge enhancement after contrast (helps hairlines and small digits).
|
| 95 |
ENABLE_UNSHARP = True
|
| 96 |
UNSHARP_RADIUS = 0.78
|
| 97 |
+
UNSHARP_PERCENT = 76
|
| 98 |
UNSHARP_THRESHOLD = 1
|
| 99 |
|
| 100 |
DEFAULT_ZONE_FRAC = 0.12
|
|
|
|
| 355 |
)
|
| 356 |
|
| 357 |
|
| 358 |
+
def _replace_cell_plain_body(full_cell: str, new_body_plain: str) -> str:
|
| 359 |
+
"""Rebuild one td/th preserving opening tag attributes; body is plain text (escaped)."""
|
| 360 |
+
m = _CELL.fullmatch(full_cell.strip())
|
| 361 |
+
if not m:
|
| 362 |
+
return full_cell
|
| 363 |
+
tag, attrs = m.group(1), m.group(2)
|
| 364 |
+
return f"<{tag}{attrs}>{html.escape(new_body_plain)}</{tag}>"
|
| 365 |
+
|
| 366 |
+
|
| 367 |
+
def _logical_plain_texts_from_entries(entries: List[Tuple[str, int]]) -> List[str]:
|
| 368 |
"""
|
| 369 |
+
Flatten one table row to one string per logical column: merged spans place
|
| 370 |
+
full visible text on the first slot only, remainder empty strings.
|
| 371 |
"""
|
| 372 |
+
w = sum(s for _, s in entries)
|
| 373 |
+
if w < 1:
|
| 374 |
+
return []
|
| 375 |
+
out = [""] * w
|
| 376 |
+
pos = 0
|
| 377 |
+
for full, span in entries:
|
| 378 |
+
span = max(1, span)
|
| 379 |
+
t = _cell_plain_text(full)
|
| 380 |
+
out[pos] = t
|
| 381 |
+
for k in range(1, span):
|
| 382 |
+
if pos + k < w:
|
| 383 |
+
out[pos + k] = ""
|
| 384 |
+
pos += span
|
| 385 |
+
return out
|
| 386 |
+
|
| 387 |
+
|
| 388 |
+
def _shift_lone_amount_past_trailing_blanks(log: List[str]) -> List[str]:
|
| 389 |
+
"""
|
| 390 |
+
If the rightmost non-blank slot is only a currency token and every slot to
|
| 391 |
+
its right is blank, move that token into the rightmost slot (any column count).
|
| 392 |
+
"""
|
| 393 |
+
w = len(log)
|
| 394 |
+
if w < 2:
|
| 395 |
+
return log
|
| 396 |
+
L = w - 1
|
| 397 |
+
while L >= 0 and not (log[L] or "").strip():
|
| 398 |
+
L -= 1
|
| 399 |
+
if L < 0:
|
| 400 |
+
return log
|
| 401 |
+
token = (log[L] or "").strip()
|
| 402 |
+
if not _is_whole_cell_currency(token):
|
| 403 |
+
return log
|
| 404 |
+
if L == w - 1:
|
| 405 |
+
return log
|
| 406 |
+
if any((log[i] or "").strip() for i in range(L + 1, w)):
|
| 407 |
+
return log
|
| 408 |
+
new_log = list(log)
|
| 409 |
+
new_log[L] = ""
|
| 410 |
+
new_log[w - 1] = token
|
| 411 |
+
return new_log
|
| 412 |
+
|
| 413 |
+
|
| 414 |
+
def _materialize_cells_from_logical(
|
| 415 |
+
entries: List[Tuple[str, int]], new_log: List[str]
|
| 416 |
+
) -> List[str]:
|
| 417 |
+
"""Rebuild physical td/th strings from a logical text row of length sum(span)."""
|
| 418 |
+
w = sum(s for _, s in entries)
|
| 419 |
+
if len(new_log) != w:
|
| 420 |
+
return [e[0] for e in entries]
|
| 421 |
+
pos = 0
|
| 422 |
+
rebuilt: List[str] = []
|
| 423 |
+
for full, span in entries:
|
| 424 |
+
span = max(1, span)
|
| 425 |
+
chunk = [(new_log[pos + k] or "").strip() for k in range(span)]
|
| 426 |
+
pos += span
|
| 427 |
+
body = " ".join(x for x in chunk if x).strip()
|
| 428 |
+
rebuilt.append(_replace_cell_plain_body(full, body))
|
| 429 |
+
return rebuilt
|
| 430 |
+
|
| 431 |
+
|
| 432 |
+
def _apply_row_amount_tail_shift(cells: List[str], spans: List[int]) -> List[str]:
|
| 433 |
+
"""Colspan-aware tail shift; returns possibly unchanged cell HTML list."""
|
| 434 |
+
if not cells or len(cells) != len(spans):
|
| 435 |
return cells
|
| 436 |
+
entries = list(zip(cells, spans))
|
| 437 |
+
old_log = _logical_plain_texts_from_entries(entries)
|
| 438 |
+
if len(old_log) < 2:
|
| 439 |
return cells
|
| 440 |
+
new_log = _shift_lone_amount_past_trailing_blanks(old_log)
|
| 441 |
+
if new_log == old_log:
|
| 442 |
return cells
|
| 443 |
+
return _materialize_cells_from_logical(entries, new_log)
|
| 444 |
|
| 445 |
|
| 446 |
def _infer_modal_logical_width(tr_inners: List[str]) -> int:
|
|
|
|
| 481 |
w -= spans[-1]
|
| 482 |
cells.pop()
|
| 483 |
spans.pop()
|
| 484 |
+
if cells:
|
| 485 |
+
cells = _apply_row_amount_tail_shift(cells, spans)
|
| 486 |
return "".join(cells)
|
| 487 |
|
| 488 |
|
|
|
|
| 493 |
empty single-colspan cells from rows that are too wide.
|
| 494 |
|
| 495 |
No column names or fixed N: width comes from per-table row statistics.
|
| 496 |
+
Solitary amount tokens parked before a run of blank logical slots are slid
|
| 497 |
+
into the rightmost slot so OCR tables stay rectangular for downstream use.
|
| 498 |
+
Tables with rowspan are skipped. Non-currency text is not altered.
|
|
|
|
| 499 |
"""
|
| 500 |
if not md or "<table" not in md.lower():
|
| 501 |
return md
|