Skip to content

Commit a878e12

Browse files
authored
Merge pull request #377 from Integration-Automation/feat/column-layout-batch
Add column_layout: infer columns from whitespace (borderless tables)
2 parents c97a23b + 6159eab commit a878e12

15 files changed

Lines changed: 427 additions & 0 deletions

File tree

README/WHATS_NEW_zh-CN.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# 本次更新 — AutoControl
22

3+
## 本次更新 (2026-06-24) — 留白投影列推断(无框线表格)
4+
5+
靠留白间隙推断列来读取无框线表格。完整参考:[`docs/source/Zh/doc/new_features/v165_features_doc.rst`](../docs/source/Zh/doc/new_features/v165_features_doc.rst)
6+
7+
- **`detect_borderless_table` / `column_gutters` / `assign_columns` / `vertical_projection`**(`AC_detect_borderless_table``AC_column_gutters`):`ocr/structure` 只有在每一行单元格左缘 x 都相符时才检测得到表格——对 ragged / 无框线 / 右对齐列都失败;`edge_lines.find_grid` 需要框线,而留白表格没有。本功能靠*间隙*找列:把 OCR 框投影到 x 轴,读出持续为空的垂直带作为 gutter,指派列索引,依间距分组成行,输出 `{n_rows, n_cols, rows, columns}`。纯标准库差分数组投影(不需 numpy);重用 `table_grid_fill` 的框读取器。不导入 `PySide6`
8+
39
## 本次更新 (2026-06-24) — 自动门槛模板匹配(对分数图做 Otsu)
410

511
不再手调 `min_score`——由分数图推导匹配门槛。完整参考:[`docs/source/Zh/doc/new_features/v164_features_doc.rst`](../docs/source/Zh/doc/new_features/v164_features_doc.rst)

README/WHATS_NEW_zh-TW.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# 本次更新 — AutoControl
22

3+
## 本次更新 (2026-06-24) — 留白投影欄位偵測(無框線表格)
4+
5+
靠留白間隙推導欄位來讀取無框線表格。完整參考:[`docs/source/Zh/doc/new_features/v165_features_doc.rst`](../docs/source/Zh/doc/new_features/v165_features_doc.rst)
6+
7+
- **`detect_borderless_table` / `column_gutters` / `assign_columns` / `vertical_projection`**(`AC_detect_borderless_table``AC_column_gutters`):`ocr/structure` 只有在每一列儲存格左緣 x 都相符時才偵測得到表格——對 ragged / 無框線 / 右對齊欄都失敗;`edge_lines.find_grid` 需要框線,而留白表格沒有。本功能靠*間隙*找欄位:把 OCR 框投影到 x 軸,讀出持續為空的垂直帶作為 gutter,指派欄索引,依間距分群成列,輸出 `{n_rows, n_cols, rows, columns}`。純標準函式庫差分陣列投影(不需 numpy);重用 `table_grid_fill` 的框讀取器。不匯入 `PySide6`
8+
39
## 本次更新 (2026-06-24) — 自動門檻樣板比對(對分數圖做 Otsu)
410

511
不再手調 `min_score`——由分數圖推導比對門檻。完整參考:[`docs/source/Zh/doc/new_features/v164_features_doc.rst`](../docs/source/Zh/doc/new_features/v164_features_doc.rst)

WHATS_NEW.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# What's New — AutoControl
22

3+
## What's new (2026-06-24) — Whitespace-Projection Columns (Borderless Tables)
4+
5+
Read borderless tables by inferring columns from the whitespace gaps. Full reference: [`docs/source/Eng/doc/new_features/v165_features_doc.rst`](docs/source/Eng/doc/new_features/v165_features_doc.rst).
6+
7+
- **`detect_borderless_table` / `column_gutters` / `assign_columns` / `vertical_projection`** (`AC_detect_borderless_table`, `AC_column_gutters`): `ocr/structure` only detects a table when every row's cell-left-x matches — it fails on ragged / borderless / right-aligned columns; `edge_lines.find_grid` needs ruling lines a whitespace table doesn't have. This finds columns by the *gaps*: project OCR boxes onto the x-axis, read the persistent empty vertical bands as gutters, assign column indices, bucket rows by spacing, and emit `{n_rows, n_cols, rows, columns}`. Pure-stdlib difference-array projection (no numpy); reuses `table_grid_fill`'s box reader. No `PySide6`.
8+
39
## What's new (2026-06-24) — Auto-Thresholded Template Matching (Otsu on the Score Map)
410

511
No more hand-tuned `min_score` — derive the match threshold from the score map. Full reference: [`docs/source/Eng/doc/new_features/v164_features_doc.rst`](docs/source/Eng/doc/new_features/v164_features_doc.rst).
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
Whitespace-Projection Columns (Borderless Tables)
2+
=================================================
3+
4+
``ocr/structure`` detects tables only when *every* row's cell-left-x matches within a
5+
tolerance — it collapses on ragged or borderless tables, right-aligned numeric columns, or
6+
any row with a missing cell. ``edge_lines.find_grid`` needs ruling lines, so a table drawn
7+
purely with whitespace has no grid at all. ``column_layout`` finds columns the robust way the
8+
layout-analysis literature uses: by the *gaps*. It projects the OCR boxes onto the x-axis (an
9+
ink-density profile), reads off the persistent empty vertical bands as column gutters, assigns
10+
each box a column index, and buckets rows by vertical spacing to emit a borderless table.
11+
12+
Pure-stdlib over plain box dicts (a difference-array projection — no numpy), so it is fully
13+
unit-testable with no image and no OCR engine. Reuses ``table_grid_fill``'s box-bounds reader.
14+
Imports no ``PySide6``.
15+
16+
Headless API
17+
------------
18+
19+
.. code-block:: python
20+
21+
from je_auto_control import (detect_borderless_table, column_gutters,
22+
assign_columns, vertical_projection)
23+
24+
table = detect_borderless_table(ocr_boxes)
25+
# {"n_rows": 3, "n_cols": 2, "rows": [["Name","Age"],["Ann","30"],["Bob","25"]],
26+
# "columns": [{"start": 70, "end": 120, "width": 50}]}
27+
28+
gutters = column_gutters(ocr_boxes, min_gap=8) # empty vertical bands
29+
tagged = assign_columns(ocr_boxes) # each box + "column" index
30+
profile = vertical_projection(ocr_boxes) # ink density per x
31+
32+
``vertical_projection`` returns the per-x ink-density profile; ``column_gutters`` returns the
33+
interior empty bands ``[{start, end, width}]`` at least ``min_gap`` wide; ``assign_columns``
34+
tags every box with a 0-based ``column``; ``detect_borderless_table`` combines columns (from
35+
gutters) with rows (from vertical spacing) into ``{n_rows, n_cols, rows, columns}``, or
36+
``None`` when fewer than ``min_cols`` columns / ``min_rows`` rows are found. Boxes accept
37+
``{x, y, width, height}`` or ``{left, top, right, bottom}`` plus an optional ``text``.
38+
39+
Executor commands
40+
-----------------
41+
42+
``AC_detect_borderless_table`` (``boxes`` / ``page_width`` / ``min_gap`` / ``min_cols`` /
43+
``min_rows`` → ``{found, table}``) and ``AC_column_gutters`` (``boxes`` / ``page_width`` /
44+
``min_gap`` → ``{count, gutters}``). They are exposed as the MCP tools
45+
``ac_detect_borderless_table`` / ``ac_column_gutters`` (read-only) and as the Script Builder
46+
commands **Detect Borderless Table** / **Column Gutters (whitespace)** under **OCR**.

docs/source/Eng/eng_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ Comprehensive guides for all AutoControl features.
187187
doc/new_features/v162_features_doc
188188
doc/new_features/v163_features_doc
189189
doc/new_features/v164_features_doc
190+
doc/new_features/v165_features_doc
190191
doc/ocr_backends/ocr_backends_doc
191192
doc/observability/observability_doc
192193
doc/operations_layer/operations_layer_doc
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
留白投影欄位偵測(無框線表格)
2+
==============================
3+
4+
``ocr/structure`` 只有在*每一列*的儲存格左緣 x 都在容差內相符時才偵測得到表格——對 ragged
5+
或無框線表格、右對齊數字欄、或任何缺格的列都會失敗。``edge_lines.find_grid`` 需要框線,
6+
因此純以留白繪製的表格根本沒有網格。``column_layout`` 以版面分析文獻常用的穩健方法找欄位:
7+
靠*間隙*。它把 OCR 框投影到 x 軸(墨水密度剖面),讀出持續為空的垂直帶作為欄間隙(gutter),
8+
為每個框指派欄索引,並依垂直間距分群成列,輸出無框線表格。
9+
10+
純標準函式庫,作用於純框字典(差分陣列投影——不需 numpy),因此可在無影像、無 OCR 引擎下
11+
完整單元測試。重用 ``table_grid_fill`` 的框邊界讀取器。不匯入 ``PySide6``。
12+
13+
無頭 API
14+
--------
15+
16+
.. code-block:: python
17+
18+
from je_auto_control import (detect_borderless_table, column_gutters,
19+
assign_columns, vertical_projection)
20+
21+
table = detect_borderless_table(ocr_boxes)
22+
# {"n_rows": 3, "n_cols": 2, "rows": [["Name","Age"],["Ann","30"],["Bob","25"]],
23+
# "columns": [{"start": 70, "end": 120, "width": 50}]}
24+
25+
gutters = column_gutters(ocr_boxes, min_gap=8) # 空白垂直帶
26+
tagged = assign_columns(ocr_boxes) # 每個框 + "column" 索引
27+
profile = vertical_projection(ocr_boxes) # 每個 x 的墨水密度
28+
29+
``vertical_projection`` 回傳每個 x 的墨水密度剖面;``column_gutters`` 回傳至少 ``min_gap`` 寬
30+
的內部空白帶 ``[{start, end, width}]``;``assign_columns`` 為每個框標上 0 起算的 ``column``;
31+
``detect_borderless_table`` 將欄(來自 gutter)與列(來自垂直間距)組合成
32+
``{n_rows, n_cols, rows, columns}``,或在欄數少於 ``min_cols`` / 列數少於 ``min_rows`` 時回傳
33+
``None``。框接受 ``{x, y, width, height}`` 或 ``{left, top, right, bottom}`` 加上可選 ``text``。
34+
35+
執行器指令
36+
----------
37+
38+
``AC_detect_borderless_table``(``boxes`` / ``page_width`` / ``min_gap`` / ``min_cols`` /
39+
``min_rows`` → ``{found, table}``)與 ``AC_column_gutters``(``boxes`` / ``page_width`` /
40+
``min_gap`` → ``{count, gutters}``)。兩者以 MCP 工具 ``ac_detect_borderless_table`` /
41+
``ac_column_gutters``(唯讀)及 Script Builder 指令 **Detect Borderless Table** /
42+
**Column Gutters (whitespace)**(位於 **OCR** 分類下)形式提供。

docs/source/Zh/zh_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ AutoControl 所有功能的完整使用指南。
187187
doc/new_features/v162_features_doc
188188
doc/new_features/v163_features_doc
189189
doc/new_features/v164_features_doc
190+
doc/new_features/v165_features_doc
190191
doc/ocr_backends/ocr_backends_doc
191192
doc/observability/observability_doc
192193
doc/operations_layer/operations_layer_doc

je_auto_control/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,10 @@
303303
from je_auto_control.utils.observation_delta import (
304304
delta_index, delta_observation, summarize_delta,
305305
)
306+
# Infer columns from vertical whitespace (borderless tables)
307+
from je_auto_control.utils.column_layout import (
308+
assign_columns, column_gutters, detect_borderless_table, vertical_projection,
309+
)
306310
# Locate on-screen regions by colour (mask + connected components)
307311
from je_auto_control.utils.color_region import (
308312
find_color_region, find_color_regions,
@@ -1230,6 +1234,10 @@ def start_autocontrol_gui(*args, **kwargs):
12301234
"delta_index",
12311235
"delta_observation",
12321236
"summarize_delta",
1237+
"vertical_projection",
1238+
"column_gutters",
1239+
"assign_columns",
1240+
"detect_borderless_table",
12331241
"find_color_region",
12341242
"find_color_regions",
12351243
"ssim_compare",

je_auto_control/gui/script_builder/command_schema.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,27 @@ def _add_ocr_specs(specs: List[CommandSpec]) -> None:
708708
),
709709
description="Drop OCR text boxes into a ruling-line grid → addressable table.",
710710
))
711+
specs.append(CommandSpec(
712+
"AC_detect_borderless_table", "OCR", "Detect Borderless Table",
713+
fields=(
714+
FieldSpec("boxes", FieldType.STRING,
715+
placeholder='[{"x":10,"y":0,"width":60,"height":18,'
716+
'"text":"Name"}]'),
717+
FieldSpec("min_gap", FieldType.INT, optional=True, default=8),
718+
FieldSpec("page_width", FieldType.INT, optional=True),
719+
),
720+
description="Infer a borderless table from OCR boxes via whitespace columns.",
721+
))
722+
specs.append(CommandSpec(
723+
"AC_column_gutters", "OCR", "Column Gutters (whitespace)",
724+
fields=(
725+
FieldSpec("boxes", FieldType.STRING,
726+
placeholder='[{"x":10,"y":0,"width":60,"height":18}]'),
727+
FieldSpec("min_gap", FieldType.INT, optional=True, default=8),
728+
FieldSpec("page_width", FieldType.INT, optional=True),
729+
),
730+
description="Find borderless-table column separators by whitespace projection.",
731+
))
711732
specs.append(CommandSpec(
712733
"AC_scroll_to_find", "OCR", "Scroll Until Visible",
713734
fields=(
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"""Infer columns from vertical whitespace, for borderless tables."""
2+
from je_auto_control.utils.column_layout.column_layout import (
3+
assign_columns, column_gutters, detect_borderless_table, vertical_projection,
4+
)
5+
6+
__all__ = [
7+
"vertical_projection", "column_gutters",
8+
"assign_columns", "detect_borderless_table",
9+
]

0 commit comments

Comments
 (0)