-
Notifications
You must be signed in to change notification settings - Fork 9
Description
在基于该数据集开展合并单元格相关研究时,我依据表格结构的基本逻辑进行了系统性校验:表格可抽象为 R*C 的网格(grid)结构,合并单元格本质是网格的聚合,其核心约束为 “单元格所占网格无重叠” 且 “所有网格均归属唯一单元格”。基于此标准,我对数据集进行了全面核查,发现训练集(train)中有 5441/30633 条数据、验证集(val)中有 1252/7548 条数据存在不符合上述约束的情况,主要表现为网格重叠或存在未分配网格的异常标注。
考虑到 TabRecSet 作为首个大规模双语端到端表格识别数据集,其标注质量直接影响模型训练的有效性 —— 此类结构异常可能导致模型对合并单元格的空间关系学习产生偏差,尤其在处理您团队重点关注的旋转、扭曲等不规则表格时,可能进一步放大识别误差。
因此想向您请教:
上述标注异常是否与数据集的特定标注逻辑相关?例如多边形空间标注与传统网格划分的适配性处理,或 TableMe 标注工具的自动化标注机制影响。
团队是否已关注到此类问题?未来是否有针对表格结构标注的修正计划,如推出更新版本或异常数据修正补丁?
若暂未开展修正工作,是否可公开标注规范细则或异常数据判断标准,以便研究者在使用时进行针对性预处理?
TabRecSet 在表格识别领域的影响力正持续扩大,若能完善标注一致性,相信将进一步提升其作为基准数据集的权威性与易用性。我已整理了异常数据的统计明细与典型案例,若您需要可随时提供。
我是用的代码脚本如下所示
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from PIL import Image
import numpy as np
import re
import os
from io import BytesIO
import requests
import json
import shutil
import numpy as np
import re
from typing import List, Dict, Tuple, Optional
import tqdm
class TableLayoutValidator:
def init(self):
"""初始化表格布局验证器"""
self.cells = [] # 存储解析后的单元格信息
self.layout = None # 表格布局网格
self.rows = 0 # 表格总行数
self.cols = 0 # 表格总列数
self.is_valid = False # 布局是否有效
self.errors = [] # 错误信息列表
def parse_label(self, label: str) -> Optional[Dict]:
"""
解析标签内容,提取行、列、行跨度、列跨度和文本内容
Args:
label: 标签字符串,格式为"Row-Column-Rowspan-Colspan-Text content"
Returns:
包含解析结果的字典,解析失败返回None
"""
pattern = r'^(\d+)-(\d+)-(\d+)-(\d+)-(.*)$'
match = re.match(pattern, label, re.DOTALL)
if match:
return {
'row': int(match.group(1)),
'column': int(match.group(2)),
'rowspan': int(match.group(3)),
'colspan': int(match.group(4)),
'text': match.group(5).replace('\n', ' ').strip(),
'id': len(self.cells) + 1 # 单元格ID,按顺序分配
}
else:
self.errors.append(f"无法解析标签格式: {label}")
return None
def load_cells_from_annotations(self, shapes: List[Dict]) -> None:
"""
从注释数据加载并解析单元格信息
Args:
shapes: 注释数据中的shapes列表
"""
self.cells = []
self.errors = []
for shape in shapes:
if 'label' not in shape:
self.errors.append("发现没有标签的形状")
continue
cell_info = self.parse_label(shape['label'])
if cell_info:
self.cells.append(cell_info)
# 确定表格的最大行和列,用于构建网格
if self.cells:
max_row = max(cell['row'] + cell['rowspan'] - 1 for cell in self.cells)
max_col = max(cell['column'] + cell['colspan'] - 1 for cell in self.cells)
self.rows = max_row
self.cols = max_col
def build_layout(self) -> None:
"""构建表格布局网格,检查单元格重叠"""
if not self.cells or self.rows == 0 or self.cols == 0:
self.layout = None
return
# 初始化布局网格,0表示未填充
self.layout = np.zeros((self.rows, self.cols), dtype=int)
self.is_valid = True
# 按顺序填充单元格到网格
for cell in self.cells:
row = cell['row'] - 1 # 转换为0索引
col = cell['column'] - 1
rowspan = cell['rowspan']
colspan = cell['colspan']
cell_id = cell['id']
# 检查单元格是否超出表格范围
if (row + rowspan > self.rows) or (col + colspan > self.cols):
self.errors.append(f"单元格ID {cell_id} 超出表格范围")
self.is_valid = False
continue
# 检查单元格是否与已有单元格重叠
for i in range(row, row + rowspan):
for j in range(col, col + colspan):
if self.layout[i, j] != 0:
self.errors.append(
f"单元格ID {cell_id} 与单元格ID {self.layout[i, j]} 在位置 ({i+1},{j+1}) 重叠"
)
self.is_valid = False
# 将单元格ID填充到网格
for i in range(row, row + rowspan):
for j in range(col, col + colspan):
self.layout[i, j] = cell_id
def check_completeness(self) -> bool:
"""
检查表格布局是否完整
Returns:
如果布局完整(无重叠且全部填充)返回True,否则返回False
"""
if self.layout is None:
self.errors.append("布局未构建,请先调用build_layout方法")
return False
# 检查是否有未填充的单元格
if np.any(self.layout == 0):
empty_cells = np.where(self.layout == 0)
for i, j in zip(empty_cells[0], empty_cells[1]):
self.errors.append(f"表格位置 ({i+1},{j+1}) 未被任何单元格填充")
self.is_valid = False
return self.is_valid
def validate_layout(self, shapes: List[Dict]) -> Tuple[bool, np.ndarray, List[str]]:
"""
验证表格布局的完整性
Args:
shapes: 注释数据中的shapes列表
Returns:
包含三个元素的元组:
- 布局是否完整有效
- 表格布局网格
- 错误信息列表
"""
self.load_cells_from_annotations(shapes)
self.build_layout()
self.check_completeness()
return self.is_valid, self.layout, self.errors
def print_layout_info(self) -> None:
"""打印布局信息和检查结果"""
print(f"表格尺寸: {self.rows}行 × {self.cols}列")
print(f"单元格总数: {len(self.cells)}")
print(f"布局是否完整有效: {'是' if self.is_valid else '否'}\n")
if self.layout is not None:
print("表格布局网格 (数字表示单元格ID):")
print(self.layout)
print()
if self.errors:
print("错误信息:")
for i, error in enumerate(self.errors, 1):
print(f"{i}. {error}")
示例用法
if name == "main":
# 示例1: 完整有效的表格布局
data_dir = "E:\datasets\TabRecSet\cropped_datasets_1Tab_per_img\train"
out_dir = "E:\datasets\TabRecSet\vis"
try:
shutil.rmtree(out_dir)
except:
pass
os.makedirs(out_dir)
a = 0
c = 0
for i in tqdm.tqdm(os.listdir(data_dir)):
if "json" in i:
continue
img_path=f"{data_dir}{i}"
json_path=f"{data_dir}{i[:-4]}.json"
try:
with open(json_path , 'r', encoding="utf-8") as f:
annotation_data = json.loads(f.read())
a+=1
# print("=== 测试完整有效的布局 ===")
validator = TableLayoutValidator()
# validator.load_cells_from_annotations(annotation_data)
is_valid, layout, errors = validator.validate_layout(annotation_data['shapes'])
if not is_valid:
c+=1
# validator.print_layout_info()
except:
pass
print(a,c)