<h1><b>📊 Table Structure Recognition</b></h1>
💖 This repository serves as an inference library for structured recognition of tables within documents, including models for wired and wireless table recognition from Alibaba DulaLight, a wired table model from llaipython (WeChat), and a built-in table classification model from NetEase Qanything.
Quick Start Model Evaluation Char Rec Usage Recommendations Document Distortion Correction Table Rotation & Perspective Correction Input Parameters Frequently Asked Questions Update Plan
⚡ Fast: Uses ONNXRuntime as the inference engine, achieving 1-7 seconds per image on CPU.
🎯 Accurate: Combines a table type classification model to distinguish between wired and wireless tables, providing more refined tasks and higher accuracy.
🛡️ Stable: Does not depend on any third-party training frameworks; relies only on essential base libraries, avoiding package conflicts.
<img src="https://github.com/RapidAI/TableStructureRec/releases/download/v0.0.0/demo_img_output.gif" alt="Demo" width="100%" height="100%">
TableRecognitionMetric Evaluation Tool
huggingface Dataset
modelscope Dataset
Rapid OCR
Test Environment: Ubuntu 20.04, Python 3.10.10, opencv-python 4.10.0.84
Note: StructEqTable outputs in LaTeX format.测评仅选取成功转换为 HTML and stripped of style tags.
Surya-Tabled uses its built-in OCR module, which is a row-column recognition model and cannot identify cell merges, resulting in lower scores.
| Method | TEDS | TEDS-only-structure |
|---|---|---|
| surya-tabled(--skip-detect) | 0.33437 | 0.65865 |
| surya-tabled | 0.33940 | 0.67103 |
| deepdoctection(table-transformer) | 0.59975 | 0.69918 |
| ppstructure_table_master | 0.61606 | 0.73892 |
| ppsturcture_table_engine | 0.67924 | 0.78653 |
| StructEqTable | 0.67310 | 0.81210 |
| RapidTable(SLANet) | 0.71654 | 0.81067 |
| table_cls + wired_table_rec v1 + lineless_table_rec | 0.75288 | 0.82574 |
| table_cls + wired_table_rec v2 + lineless_table_rec | 0.77676 | 0.84580 |
| PaddleX(SLANetXt+RT-DERT) | 0.79900 | 0.92222 |
| RapidTable(SLANet-plus) | 0.84481 | 0.91369 |
| RapidTable(unitable) | 0.86200 | 0.91813 |
wired_table_rec_v2 (highest precision for wired tables): General scenes for wired tables (papers, magazines, journals, receipts, invoices, bills)
paddlex-SLANet-plus (highest overall precision): Document scene tables (tables in papers, magazines, and journals)
pip install wired_table_rec lineless_table_rec table_cls
pip install rapidocr
⚠️:
wired_table_rec/table_cls>=1.2.0lineless_table_rec> 0.1.0 ,the input and output format are same withRapidTable`
``` python {linenos=table} from pathlib import Path
from wired_table_rec.utils.utils import VisTable from table_cls import TableCls from wired_table_rec.main import WiredTableInput, WiredTableRecognition from lineless_table_rec.main import LinelessTableInput, LinelessTableRecognition from rapidocr import RapidOCR
if name == "main": # Init wired_input = WiredTableInput() lineless_input = LinelessTableInput() wired_engine = WiredTableRecognition(wired_input) lineless_engine = LinelessTableRecognition(lineless_input) viser = VisTable() # 默认小yolo模型(0.1s),可切换为精度更高yolox(0.25s),更快的qanything(0.07s)模型或paddle模型(0.03s) table_cls = TableCls() img_path = f"tests/test_files/table.jpg"
cls, elasp = table_cls(img_path)
if cls == "wired":
table_engine = wired_engine
else:
table_engine = lineless_engine
# 使用RapidOCR输入
ocr_engine = RapidOCR()
rapid_ocr_output = ocr_engine(img_path, return_word_box=True)
ocr_result = list(
zip(rapid_ocr_output.boxes, rapid_ocr_output.txts, rapid_ocr_output.scores)
)
table_results = table_engine(
img_path, ocr_result=ocr_result
)
# 使用单字识别
# word_results = rapid_ocr_output.word_results
# ocr_result = [
# [word_result[2], word_result[0], word_result[1]] for word_result in word_results
# ]
# table_results = table_engine(
# img_path, ocr_result=ocr_result, enhance_box_line=False
# )
# Save
#save_dir = Path("outputs")
#save_dir.mkdir(parents=True, exist_ok=True)
#save_html_path = f"outputs/{Path(img_path).stem}.html"
#save_drawed_path = f"outputs/{Path(img_path).stem}_table_vis{Path(img_path).suffix}"
#save_logic_path = (
# f"outputs/{Path(img_path).stem}_table_vis_logic{Path(img_path).suffix}"
#)
# Visualize table rec result
#vis_imged = viser(
# img_path, table_results, save_html_path, save_drawed_path, save_logic_path
#)
#### Single Character OCR Matching
```python
# Convert single character boxes to the same structure as line recognition
from rapidocr import RapidOCR
img_path = "tests/test_files/wired/table4.jpg"
ocr_engine = RapidOCR()
rapid_ocr_output = ocr_engine(img_path, return_word_box=True)
word_results = rapid_ocr_output.word_results
ocr_result = [
[word_result[2], word_result[0], word_result[1]] for word_result in word_results
]
import cv2
img_path = f'tests/test_files/wired/squeeze_error.jpeg'
from wired_table_rec.utils import ImageOrientationCorrector
img_orientation_corrector = ImageOrientationCorrector()
img = cv2.imread(img_path)
img = img_orientation_corrector(img)
cv2.imwrite(f'img_rotated.jpg', img)
For GPU or higher precision scenarios, please refer to the RapidTableDet project.
pip install rapid-table-det
import os
import cv2
from rapid_table_det.utils import img_loader, visuallize, extract_table_img
from rapid_table_det.inference import TableDetector
table_det = TableDetector()
img_path = f"tests/test_files/chip.jpg"
result, elapse = table_det(img_path)
img = img_loader(img_path)
extract_img = img.copy()
#There may be multiple tables
for i, res in enumerate(result):
box = res["box"]
lt, rt, rb, lb = res["lt"], res["rt"], res["rb"], res["lb"]
# Recognition box and top-left corner position
img = visuallize(img, box, lt, rt, rb, lb)
# Perspective transformation to extract table image
wrapped_img = extract_table_img(extract_img.copy(), lt, rt, rb, lb)
# cv2.imwrite(f"{out_dir}/{file_name}-extract-{i}.jpg", wrapped_img)
# cv2.imwrite(f"{out_dir}/{file_name}-visualize.jpg", img)
@dataclass
class WiredTableInput:
model_type: Optional[str] = "unet" #unet/cycle_center_net
model_path: Union[str, Path, None, Dict[str, str]] = None
use_cuda: bool = False
device: str = "cpu"
@dataclass
class LinelessTableInput:
model_type: Optional[str] = "lore" #lore
model_path: Union[str, Path, None, Dict[str, str]] = None
use_cuda: bool = False
device: str = "cpu"
@dataclass
class WiredTableOutput:
pred_html: Optional[str] = None
cell_bboxes: Optional[np.ndarray] = None
logic_points: Optional[np.ndarray] = None
elapse: Optional[float] = None
@dataclass
class LinelessTableOutput:
pred_html: Optional[str] = None
cell_bboxes: Optional[np.ndarray] = None
logic_points: Optional[np.ndarray] = None
elapse: Optional[float] = None
wired_table_rec = WiredTableRecognition()
html, elasp, polygons, logic_points, ocr_res = wired_table_rec(
img, # Image Union[str, np.ndarray, bytes, Path, PIL.Image.Image]
ocr_result, # Input rapidOCR recognition result, use internal rapidocr model by default if not provided
enhance_box_line=True, # Enhance box line find (turn off to avoid excessive cutting, turn on to reduce missed cuts), default is True
need_ocr=True, # Whether to perform OCR recognition, default is True
rec_again=True, # Whether to re-recognize table boxes without detected text by cropping them separately, default is True
)
lineless_table_rec = LinelessTableRecognition()
html, elasp, polygons, logic_points, ocr_res = lineless_table_rec(
img, # Image Union[str, np.ndarray, bytes, Path, PIL.Image.Image]
ocr_result, # Input rapidOCR recognition result, use internal rapidocr model by default if not provided
need_ocr=True, # Whether to perform OCR recognition, default is True
rec_again=True, # Whether to re-recognize table boxes without detected text by cropping them separately, default is True
)
flowchart TD
A[/table image/] --> B([table cls table_cls])
B --> C([wired_table_rec]) & D([lineless_table_rec]) --> E([rapidocr])
E --> F[/html output/]
[PaddleX Table Recognition](https://github.com/PaddlePaddle/PaddleX
$ claude mcp add TableStructureRec \
-- python -m otcore.mcp_server <graph>