项目文件夹

文件
wehub-resource-sync 925e56bb5f
Unit tests / build (t4_gpu) (push) Has been cancelled
Unit tests / build (ubuntu-latest) (push) Has been cancelled
Unit tests / build (windows-latest) (push) Has been cancelled
Test CLI scripts / build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:24:56 +08:00

218 行
7.1 KiB
Python

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
"""TableRecPredictor: dual-path table structure recognition.
- predict_simple: TABLE_REC_PROMPT → rows + columns only, cells derived
geometrically (row × column intersections).
- predict_full: BLOCK_PROMPT on the table crop → full <table> HTML with
colspan / rowspan / <th>. The HTML lives on TableResult.html for marker to
consume directly.
"""
from __future__ import annotations
from typing import List, Optional
from PIL import Image
from surya.inference import SuryaInferenceManager, get_default_manager
from surya.inference.parsers import clean_block_html, denorm_bbox, parse_table_rec
from surya.inference.prompts import (
PROMPT_TYPE_BLOCK,
PROMPT_TYPE_TABLE_REC,
TABLE_REC_JSON_SCHEMA,
)
from surya.inference.schema import BatchInputItem
from surya.inference.util import image_token_budget
from surya.logging import get_logger
from surya.settings import settings
from surya.table_rec.schema import TableCell, TableCol, TableResult, TableRow
logger = get_logger()
def _polygon_from_bbox(bbox):
x0, y0, x1, y1 = bbox
return [[x0, y0], [x1, y0], [x1, y1], [x0, y1]]
def _intersect_bbox(a, b):
x0 = max(a[0], b[0])
y0 = max(a[1], b[1])
x1 = min(a[2], b[2])
y1 = min(a[3], b[3])
if x1 <= x0 or y1 <= y0:
return None
return (x0, y0, x1, y1)
class TableRecPredictor:
def __init__(self, manager: Optional[SuryaInferenceManager] = None):
self.manager = manager
self._disable_tqdm = settings.DISABLE_TQDM
@property
def disable_tqdm(self) -> bool:
return self._disable_tqdm
@disable_tqdm.setter
def disable_tqdm(self, value: bool) -> None:
self._disable_tqdm = bool(value)
def to(self, *args, **kwargs):
return
def __call__(
self, images: List[Image.Image], mode: str = "simple"
) -> List[TableResult]:
if mode == "full":
return self.predict_full(images)
return self.predict_simple(images)
def predict_simple(self, images: List[Image.Image]) -> List[TableResult]:
if not images:
return []
manager = self.manager or get_default_manager()
guided = TABLE_REC_JSON_SCHEMA if settings.SURYA_GUIDED_TABLE_REC else None
batch = [
BatchInputItem(
image=img,
prompt_type=PROMPT_TYPE_TABLE_REC,
max_tokens=settings.SURYA_MAX_TOKENS_TABLE_REC,
guided_json=guided,
)
for img in images
]
outputs = manager.generate(batch)
results: List[TableResult] = []
for img, out in zip(images, outputs):
w, h = img.size
page_bbox = [0, 0, float(w), float(h)]
if out.error or not out.raw:
results.append(
TableResult(
rows=[],
cols=[],
cells=[],
image_bbox=page_bbox,
raw=out.raw,
mode="simple",
error=True,
)
)
continue
try:
elements = parse_table_rec(out.raw)
except Exception as e:
logger.warning(
f"Table rec parse failed: {e}; raw[:200]={out.raw[:200]!r}"
)
results.append(
TableResult(
rows=[],
cols=[],
cells=[],
image_bbox=page_bbox,
raw=out.raw,
mode="simple",
error=True,
)
)
continue
rows: List[TableRow] = []
cols: List[TableCol] = []
for el in elements:
pixel_bbox = denorm_bbox(el.bbox, w, h, scale=settings.BBOX_SCALE)
poly = _polygon_from_bbox(pixel_bbox)
if el.label == "Row":
rows.append(TableRow(polygon=poly, row_id=len(rows)))
else:
cols.append(TableCol(polygon=poly, col_id=len(cols)))
# Derive cells geometrically (row × column intersections)
cells: List[TableCell] = []
cell_id = 0
for row in rows:
for col in cols:
inter = _intersect_bbox(row.bbox, col.bbox)
if inter is None:
continue
cells.append(
TableCell(
polygon=_polygon_from_bbox(inter),
row_id=row.row_id,
col_id=col.col_id,
cell_id=cell_id,
)
)
cell_id += 1
results.append(
TableResult(
rows=rows,
cols=cols,
cells=cells,
image_bbox=page_bbox,
raw=out.raw,
mode="simple",
error=False,
)
)
return results
def predict_full(
self, images: List[Image.Image], counts: Optional[List[int]] = None
) -> List[TableResult]:
"""Full-HTML path: BLOCK_PROMPT on table crops. Use when complex
structure (spanning cells, headers) matters and ground-truth-style
HTML is preferred. `counts` (one per image) shapes max_tokens."""
if not images:
return []
manager = self.manager or get_default_manager()
if counts is None:
counts = [0] * len(images)
batch = []
for img, count in zip(images, counts):
batch.append(
BatchInputItem(
image=img,
prompt_type=PROMPT_TYPE_BLOCK,
max_tokens=image_token_budget(
count,
ceiling=settings.SURYA_MAX_TOKENS_BLOCK_CEILING,
floor=1024,
),
)
)
outputs = manager.generate(batch)
results: List[TableResult] = []
for img, out in zip(images, outputs):
w, h = img.size
page_bbox = [0, 0, float(w), float(h)]
if out.error:
results.append(
TableResult(
rows=[],
cols=[],
cells=[],
image_bbox=page_bbox,
raw=out.raw,
mode="full",
error=True,
)
)
continue
html = clean_block_html(out.raw)
results.append(
TableResult(
rows=[],
cols=[],
cells=[],
image_bbox=page_bbox,
raw=out.raw,
html=html,
mode="full",
error=False,
)
)
return results