## get_html_result(并引入 bisect)
你现在在 get_html_result 里用 matched_list_index/td_index 手动推进行列指针,遇到 table_cells_flag 出现重复(0 长度行)或结构标注与检测边界错位时很容易“对不齐”。虽然此时 matched_index 已经包含 {0:[0], 1:[6], 2:[57]},但行列指针没有严格按全局 td 序号映射到“第几行第几列”,导致没有拿到 57。
把指针推进方式改为“用全局单元格序号 td_global,通过二分在 table_cells_flag 中定位行,再计算列”。这样就不会受 0 长度行、重复 flag 等影响,保证能取到 ocr_texts_res[57]。
请按下面修改 get_html_result(并引入 bisect),其余逻辑保持不变。
````python
# ...existing code...
import math
+import bisect
# ...existing code...
-def get_html_result(
- all_matched_index: dict, ocr_contents: dict, pred_structures: list, table_cells_flag
-) -> str:
+def get_html_result(
+ all_matched_index: dict, ocr_contents: dict, pred_structures: list, table_cells_flag
+) -> str:
"""
Generates HTML content based on the matched index, OCR contents, and predicted structures.
"""
- pred_html = []
- td_index = 0
- td_count = 0
- matched_list_index = 0
+ pred_html = []
+ # 全局单元格序号,从 0 开始,和 table_cells_flag 使用同一坐标系
+ td_global = 0
head_structure = pred_structures[0:3]
html = "".join(head_structure)
table_structure = pred_structures[3:-3]
for tag in table_structure:
- if "" in tag:
- # 预对齐:处理 table_cells_flag 中可能出现的“0 长度行”
- while (
- matched_list_index < len(all_matched_index) - 1
- and td_count >= table_cells_flag[matched_list_index + 1]
- ):
- matched_list_index += 1
- td_index = 0
-
- matched_index = all_matched_index[matched_list_index]
+ if "" in tag:
+ # 通过全局 td 序号定位当前“行索引”和“列索引”
+ # table_cells_flag 是每行起始单元格的前缀计数(已 append 了末尾)
+ row_idx = max(0, bisect.bisect_right(table_cells_flag, td_global) - 1)
+ col_idx = td_global - table_cells_flag[row_idx]
+ matched_index = all_matched_index[row_idx] if row_idx < len(all_matched_index) else {}
if "
| " == tag:
- pred_html.extend("")
- if td_index in matched_index.keys():
- if len(matched_index[td_index]) == 0:
+ pred_html.append(" | ")
+ if col_idx in matched_index.keys():
+ if len(matched_index[col_idx]) == 0:
continue
b_with = False
if (
- "" in ocr_contents[matched_index[td_index][0]]
- and len(matched_index[td_index]) > 1
+ "" in ocr_contents[matched_index[col_idx][0]]
+ and len(matched_index[col_idx]) > 1
):
b_with = True
- pred_html.extend("")
- for i, td_index_index in enumerate(matched_index[td_index]):
+ pred_html.append("")
+ for i, td_index_index in enumerate(matched_index[col_idx]):
content = ocr_contents[td_index_index]
- if len(matched_index[td_index]) > 1:
+ if len(matched_index[col_idx]) > 1:
if len(content) == 0:
continue
if content[0] == " ":
content = content[1:]
if "" in content:
content = content[3:]
if "" in content:
content = content[:-4]
if len(content) == 0:
continue
- if i != len(matched_index[td_index]) - 1 and " " != content[-1]:
+ if i != len(matched_index[col_idx]) - 1 and " " != content[-1]:
content += " "
- pred_html.extend(content)
+ pred_html.append(content)
if b_with:
- pred_html.extend("")
+ pred_html.append("")
if " | | " == tag:
pred_html.append("")
else:
pred_html.append(tag)
- td_index += 1
- td_count += 1
- # 事后对齐:连续 0 长度行时继续前进
- while (
- matched_list_index < len(all_matched_index) - 1
- and td_count >= table_cells_flag[matched_list_index + 1]
- ):
- matched_list_index += 1
- td_index = 0
+ # 推进到下一个全局单元格
+ td_global += 1
else:
pred_html.append(tag)
html += "".join(pred_html)
end_structure = pred_structures[-3:]
html += "".join(end_structure)
return html
````
要点
- 用 bisect_right 在 table_cells_flag 中定位当前 td_global 属于哪一行,再用 col_idx = td_global - table_cells_flag[row_idx] 得到列索引。不会再受 0 长度行、重复 flag 的影响。
- 将原先对字符串用 extend 的地方改为 append,避免把字符串拆成字符列表(功能不受影响,但更稳)。
- 这样在你给出的状态 row_idx=2、matched_index={0:[0],1:[6],2:[57]}、col_idx=2 时,会稳定取到 ocr_texts_res[57]“上期发生额”。
另外建议
- 若单元格内有折行,请在调用管线时把 use_table_cells_split_ocr 关掉,使用整格 OCR,避免漏行问题:
- outputs = pipeline.predict(..., use_ocr_results_with_table_cells=True, use_table_cells_split_ocr=False)
- 如果是有线表格且单元格检测很准,可保留 use_wired_table_cells_trans_to_html=True;否则可先置为 False 看对齐是否更稳。
## 对齐检测列数与结构列数,若不一致则回退到基于cells的结构骨架
**build_structure_from_cells bugfix**
你现在看到 row_idx=3、matched_index={0:[0],1:[6],2:[57]},但 col_idx 只有 1,没有走到 2,根因是“行内单元格数量不一致”:
- matched_index 是按“单元格检测”得到的分组,这一行有 3 个格(第 2 个里是 57→‘上期发生额’)。
- 而 pred_structures(表格结构预测)在该行只产出了 2 个 ,get_html_result 是沿着 pred_structures 逐个 | 写内容的,所以第三个格没有“落位”,自然取不到 57。
解决思路
- 当“结构预测的列数”与“单元格检测的列数”不一致时,改用“按单元格检测结果生成 HTML 骨架”,再把 matched_index 内容填进去。这样每个检测到的格都有对应的 | 槽位。
提交补丁
- 在 table_recognition_post_processing_v2.py 中增加一个用 cells 构建表格骨架的函数,并在不一致时回退到该骨架。
````python
# ...existing code...
import math
import bisect
import numpy as np
# ...existing code...
+def build_structure_from_cells(table_cells_flag: list) -> list:
+ """
+ 用单元格检测的行起始标记(前缀和)构造一个简单的表格结构tokens:
+ head(3项) + [ | , | *n,
]*R + end(3项)
+ """
+ head = ["", "", ""]
+ body = []
+ for r in range(len(table_cells_flag) - 1):
+ body.append("")
+ cols = table_cells_flag[r + 1] - table_cells_flag[r]
+ for _ in range(cols):
+ body.append(" | ")
+ body.append("
")
+ end = ["
", "", ""]
+ return head + body + end
# ...existing get_html_result stays as you pasted (用 bisect + td_global 的版本)...
def get_table_recognition_res(
table_box: list,
table_structure_result: list,
table_cells_result: list,
overall_ocr_res: OCRResult,
table_ocr_pred: dict,
cells_texts_list: list,
use_table_cells_ocr_results: bool,
use_table_cells_split_ocr: bool,
) -> SingleTableRecognitionResult:
# ...existing code above...
table_cells_result, table_cells_flag = sort_table_cells_boxes(table_cells_result)
row_start_index = find_row_start_index(table_structure_result)
table_cells_flag = map_and_get_max(table_cells_flag, row_start_index)
table_cells_flag.append(len(table_cells_result))
row_start_index.append(len(table_cells_result))
matched_index = match_table_and_ocr(
table_cells_result, ocr_dt_boxes, table_cells_flag, table_cells_flag
)
- pred_html = get_html_result(
- matched_index, ocr_texts_res, table_structure_result, row_start_index
- )
+ # 对齐检测列数与结构列数,若不一致则回退到基于cells的结构骨架
+ use_cells_skeleton = False
+ for i in range(len(table_cells_flag) - 1):
+ cols_cells = table_cells_flag[i + 1] - table_cells_flag[i]
+ cols_struct = row_start_index[i + 1] - row_start_index[i]
+ if cols_cells != cols_struct:
+ use_cells_skeleton = True
+ break
+
+ if use_cells_skeleton:
+ skeleton = build_structure_from_cells(table_cells_flag)
+ pred_html = get_html_result(
+ matched_index, ocr_texts_res, skeleton, table_cells_flag
+ )
+ else:
+ pred_html = get_html_result(
+ matched_index, ocr_texts_res, table_structure_result, row_start_index
+ )
# ...existing code below...
````
要点
- 当结构预测的 数量比单元格检测少时(你当前的情况),用 cells 构造的骨架就会生成足够的 | ,此时 col_idx 会走到 2,自然能取到 ocr_texts_res[57]‘上期发生额’。
- get_html_result 里使用 bisect 在行起始数组里快速定位行索引 row_idx 和列索引 col_idx,避免 0 长度行或重复标记引起的错位。
运行建议
- 若页面有折行文本,建议在调用管线时关闭单元格内拆分重识别,避免丢字:
- outputs = pipeline.predict(..., use_ocr_results_with_table_cells=True, use_table_cells_split_ocr=False)
- 有线表格且单元格检测很准时,可开启 use_wired_table_cells_trans_to_html=True;若仍有不一致,可先设为 False 观察。
这样改后,即便结构预测与单元格检测不完全一致,也能保证每个检测到的单元格都能被填充到 HTML 中,不会再出现“第三格的 57 没有位置写入”的问题。
## 下面给出在本地生成补丁、推送到远端并提交 PR 的完整流程(Mac 终端命令)。任选其一方式:生成 .patch 文件交付,或直接走 GitHub PR。
一、在本地创建修复分支并提交代码
````bash
# 1) 在仓库根目录
cd /Users/zhch158/workspace/repository.git/PaddleX
# 2) 取最新主分支
git checkout main
git pull
# 3) 新建修复分支
git checkout -b fix/table_rec_v2-bisect-skeleton
# 4) 编辑并保存你的修改
# - paddlex/inference/pipelines/table_recognition/table_recognition_post_processing_v2.py
# - zhch/tabble_recognition_post_processing_V2 bugfix.md(可选:作为说明)
# 保存后查看改动
git status
git diff
# 5) 添加到暂存区并提交
git add paddlex/inference/pipelines/table_recognition/table_recognition_post_processing_v2.py \
"zhch/tabble_recognition_post_processing_V2 bugfix.md"
git commit -m "table_recognition_v2: fix cell-to-HTML alignment
- use bisect on table_cells_flag to locate row/col
- fallback to cells-based skeleton when cols mismatch
- keep multi-line text by avoiding split OCR loss"
# 6) 本地验证(可选)
python -m pip install -e .
python zhch/test_table_recognition_v2.py
````
二、生成补丁文件(用于邮件/离线提交)
- 简单 diff 补丁:
````bash
git diff main...fix/table_rec_v2-bisect-skeleton > table_rec_v2_fix.patch
````
- 邮件式补丁(带提交信息,适合上游用 git am 合并):
````bash
git format-patch -1 HEAD -o ./patches
# 生成的 ./patches/0001-table_recognition_v2-xxx.patch 即可提交
````
- 接收方应用补丁:
````bash
# 简单补丁
git apply table_rec_v2_fix.patch
# 邮件式补丁
git am ./patches/0001-table_recognition_v2-xxx.patch
````
三、推送分支并提交 GitHub PR(推荐)
````bash
# 1) 确认 origin 指向你的 Fork;若没有 Fork,请先在 GitHub 页面 fork 官方仓库
git remote -v
# 2) 推送分支到你的远端
git push -u origin fix/table_rec_v2-bisect-skeleton
# 3) 打开 GitHub,针对该分支创建 Pull Request 到上游的 main 分支
# PR 描述建议包含:
# - 问题现象与复现步骤(含示例图片/JSON、td_global/row_idx/col_idx日志)
# - 解决方案概要(bisect 定位行列 + cells skeleton 回退)
# - 关键代码位置 diff 链接
# - 前后对比截图(HTML/Markdown中“上期发生额”能正确落位)
# - 风险评估与开关说明(use_table_cells_split_ocr 建议)
````
四、PR 内容建议
- 标题:table_recognition_v2: fix get_html_result row/col mismatch with bisect; fallback to cells skeleton
- 关联合并的说明文档:zhch/tabble_recognition_post_processing_V2 bugfix.md
- 附加材料:
- 最小复现图与对应 table_cells_flag、matched_index、td_global 日志
- 修复前后 HTML 片段对比(包含“上期发生额”示例)
- 若加了新参数或回退逻辑,写明默认行为不变
五、若仅在你本仓使用(不对外 PR)
- 直接把 patch 存在 zhch/patches 目录,并在 README 中记录应用方法;
- 或将分支合并回你仓库 main:
````bash
git checkout main
git merge --no-ff fix/table_rec_v2-bisect-skeleton -m "Merge: table_recognition_v2 bugfix"
git push
````
如需我帮你把当前修改生成规范的 patch/PR 模板描述,或补上对应的代码块文件,请告知你最终决定的提交方式(.patch 还是 PR)。我也可以按你的提交信息直接生成 format-patch 文件内容。 |