Skip to content

Commit fbe54d8

Browse files
authored
Merge pull request #395 from Integration-Automation/feat/match-stability-batch
Add match_stability: pre-match settle gating + match persistence
2 parents 8410eab + 6e9f116 commit fbe54d8

15 files changed

Lines changed: 343 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/v180_features_doc.rst`](../docs/source/Zh/doc/new_features/v180_features_doc.rst)
6+
7+
- **`region_stability` / `match_persistence`**(`AC_region_stability``AC_match_persistence`):`smart_waits.wait_until_screen_stable` 以布尔门控实时循环——无法对可注入帧序列评分稳定度,也无法检查某*命中*是否维持。`region_stability` 以相邻帧 SSIM 评分(`{stable, mean_ssim, min_ssim}`);`match_persistence` 确认 template 在*每一*帧都找到且中心于 `agree_px` 内一致(`{persisted, n_hits, jitter}`)。重用 `ssim` + `visual_match` + `grounding_consensus`;帧可注入;不导入 `PySide6`
8+
39
## 本次更新 (2026-06-24) — 色彩感知模板匹配(HSV)
410

511
区分形状相同的红色与绿色状态点。完整参考:[`docs/source/Zh/doc/new_features/v179_features_doc.rst`](../docs/source/Zh/doc/new_features/v179_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/v180_features_doc.rst`](../docs/source/Zh/doc/new_features/v180_features_doc.rst)
6+
7+
- **`region_stability` / `match_persistence`**(`AC_region_stability``AC_match_persistence`):`smart_waits.wait_until_screen_stable` 以布林閘控即時迴圈——無法對可注入幀序列評分穩定度,也無法檢查某*命中*是否維持。`region_stability` 以相鄰幀 SSIM 評分(`{stable, mean_ssim, min_ssim}`);`match_persistence` 確認 template 在*每一*幀都找到且中心於 `agree_px` 內一致(`{persisted, n_hits, jitter}`)。重用 `ssim` + `visual_match` + `grounding_consensus`;幀可注入;不匯入 `PySide6`
8+
39
## 本次更新 (2026-06-24) — 色彩感知樣板比對(HSV)
410

511
區分形狀相同的紅色與綠色狀態點。完整參考:[`docs/source/Zh/doc/new_features/v179_features_doc.rst`](../docs/source/Zh/doc/new_features/v179_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) — Pre-Match Settle Gating + Match Persistence
4+
5+
Avoid matching mid-animation, and confirm a hit holds steady across frames. Full reference: [`docs/source/Eng/doc/new_features/v180_features_doc.rst`](docs/source/Eng/doc/new_features/v180_features_doc.rst).
6+
7+
- **`region_stability` / `match_persistence`** (`AC_region_stability`, `AC_match_persistence`): `smart_waits.wait_until_screen_stable` gates a live loop with a boolean — it can't score stability on an injectable frame sequence or check whether a *match* held steady. `region_stability` scores consecutive-frame SSIM (`{stable, mean_ssim, min_ssim}`); `match_persistence` confirms a template is found in *every* frame with the centres agreeing within `agree_px` (`{persisted, n_hits, jitter}`). Reuses `ssim` + `visual_match` + `grounding_consensus`; injectable frames; no `PySide6`.
8+
39
## What's new (2026-06-24) — Colour-Aware Template Matching (HSV)
410

511
Tell a red status dot from a green one of identical shape. Full reference: [`docs/source/Eng/doc/new_features/v179_features_doc.rst`](docs/source/Eng/doc/new_features/v179_features_doc.rst).
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
Pre-Match Settle Gating + Match Persistence
2+
===========================================
3+
4+
Matching mid-animation is a top flakiness source: the spinner is still spinning, a transition
5+
is half-done, and the matcher fires on a transient. ``smart_waits.wait_until_screen_stable``
6+
gates a *live polling loop* and returns a boolean — it cannot score stability on an *injectable*
7+
sequence of frames, nor tell you whether a *match* held steady across them. ``match_stability``
8+
adds both, on injected frames so it is unit-testable: ``region_stability`` scores how settled a
9+
frame sequence is (consecutive-frame SSIM), and ``match_persistence`` checks that a template's
10+
hit holds at the same place across the frames (low jitter), not just in one lucky frame.
11+
12+
It reuses ``ssim.ssim_compare``, ``visual_match.match_template`` and
13+
``grounding_consensus.consensus_point`` — no new matching code. The frames are injectable
14+
(ndarray / path / PIL). Imports no ``PySide6``.
15+
16+
Headless API
17+
------------
18+
19+
.. code-block:: python
20+
21+
from je_auto_control import region_stability, match_persistence
22+
23+
frames = [grab(), grab(), grab()] # a few snapshots of the same region
24+
if region_stability(frames, settle_threshold=0.99)["stable"]:
25+
do_the_match()
26+
27+
result = match_persistence("button.png", frames, min_score=0.8, agree_px=8)
28+
if result["persisted"]:
29+
print("steady hit, jitter", result["jitter"])
30+
31+
``region_stability`` returns ``{stable, mean_ssim, min_ssim}`` — ``stable`` when the lowest
32+
consecutive-frame SSIM clears ``settle_threshold``. ``match_persistence`` returns
33+
``{persisted, n_hits, jitter}`` — ``persisted`` when the template is found in *every* frame and
34+
the hit centres agree within ``agree_px`` (``jitter`` is their spread; 0 = rock steady).
35+
36+
Executor commands
37+
-----------------
38+
39+
``AC_region_stability`` (``frames`` / ``settle_threshold`` → ``{stable, mean_ssim, min_ssim}``)
40+
and ``AC_match_persistence`` (``template`` / ``frames`` / ``min_score`` / ``agree_px`` →
41+
``{persisted, n_hits, jitter}``). They are exposed as the MCP tools ``ac_region_stability`` /
42+
``ac_match_persistence`` (read-only) and as the Script Builder commands **Region Stability
43+
(frames)** / **Match Persistence (frames)** under **Image**.

docs/source/Eng/eng_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ Comprehensive guides for all AutoControl features.
202202
doc/new_features/v177_features_doc
203203
doc/new_features/v178_features_doc
204204
doc/new_features/v179_features_doc
205+
doc/new_features/v180_features_doc
205206
doc/ocr_backends/ocr_backends_doc
206207
doc/observability/observability_doc
207208
doc/operations_layer/operations_layer_doc
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
比對前安定閘 + 命中穩定性
2+
==========================
3+
4+
在動畫進行中比對是主要的不穩定來源:轉圈圖示還在轉、轉場做到一半,比對器命中暫態。
5+
``smart_waits.wait_until_screen_stable`` 閘控*即時輪詢迴圈*並回傳布林——它無法對*可注入*的
6+
幀序列評分穩定度,也無法告訴你某個*命中*是否跨幀維持。``match_stability`` 補上兩者,且作用於
7+
注入幀因此可單元測試:``region_stability`` 以相鄰幀 SSIM 評分幀序列的安定度,``match_persistence``
8+
檢查某 template 的命中是否跨幀維持在同一處(jitter 小),而非只在某一幸運幀。
9+
10+
本功能重用 ``ssim.ssim_compare``、``visual_match.match_template`` 與
11+
``grounding_consensus.consensus_point``——不新增比對程式。幀可注入(ndarray / 路徑 / PIL)。
12+
不匯入 ``PySide6``。
13+
14+
無頭 API
15+
--------
16+
17+
.. code-block:: python
18+
19+
from je_auto_control import region_stability, match_persistence
20+
21+
frames = [grab(), grab(), grab()] # 同一區域的幾張快照
22+
if region_stability(frames, settle_threshold=0.99)["stable"]:
23+
do_the_match()
24+
25+
result = match_persistence("button.png", frames, min_score=0.8, agree_px=8)
26+
if result["persisted"]:
27+
print("穩定命中, jitter", result["jitter"])
28+
29+
``region_stability`` 回傳 ``{stable, mean_ssim, min_ssim}``——當最低相鄰幀 SSIM 超過
30+
``settle_threshold`` 時 ``stable``。``match_persistence`` 回傳 ``{persisted, n_hits, jitter}``
31+
——當 template 在*每一*幀都找到且命中中心於 ``agree_px`` 內一致時 ``persisted``(``jitter``
32+
為其離散度;0 = 穩如磐石)。
33+
34+
執行器指令
35+
----------
36+
37+
``AC_region_stability``(``frames`` / ``settle_threshold`` → ``{stable, mean_ssim, min_ssim}``)
38+
與 ``AC_match_persistence``(``template`` / ``frames`` / ``min_score`` / ``agree_px`` →
39+
``{persisted, n_hits, jitter}``)。兩者以 MCP 工具 ``ac_region_stability`` /
40+
``ac_match_persistence``(唯讀)及 Script Builder 指令 **Region Stability (frames)** /
41+
**Match Persistence (frames)**(位於 **Image** 分類下)形式提供。

docs/source/Zh/zh_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ AutoControl 所有功能的完整使用指南。
202202
doc/new_features/v177_features_doc
203203
doc/new_features/v178_features_doc
204204
doc/new_features/v179_features_doc
205+
doc/new_features/v180_features_doc
205206
doc/ocr_backends/ocr_backends_doc
206207
doc/observability/observability_doc
207208
doc/operations_layer/operations_layer_doc

je_auto_control/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,10 @@
299299
from je_auto_control.utils.color_match import (
300300
match_color, match_color_all,
301301
)
302+
# Pre-match settle gating + match persistence over a frame sequence
303+
from je_auto_control.utils.match_stability import (
304+
match_persistence, region_stability,
305+
)
302306
# Otsu auto-thresholding for template matching (no hand-tuned min_score)
303307
from je_auto_control.utils.match_autothresh import (
304308
auto_threshold, match_auto,
@@ -1286,6 +1290,8 @@ def start_autocontrol_gui(*args, **kwargs):
12861290
"vote_centers",
12871291
"match_color",
12881292
"match_color_all",
1293+
"region_stability",
1294+
"match_persistence",
12891295
"SubPixelMatch",
12901296
"match_subpixel",
12911297
"refine_peak",

je_auto_control/gui/script_builder/command_schema.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,28 @@ def _add_image_specs(specs: List[CommandSpec]) -> None:
458458
),
459459
description="Find every colour (HSV) match of a template (NMS-deduped).",
460460
))
461+
specs.append(CommandSpec(
462+
"AC_region_stability", "Image", "Region Stability (frames)",
463+
fields=(
464+
FieldSpec("frames", FieldType.STRING,
465+
placeholder='["f1.png", "f2.png", "f3.png"]'),
466+
FieldSpec("settle_threshold", FieldType.FLOAT, optional=True,
467+
default=0.99, min_value=0.0, max_value=1.0),
468+
),
469+
description="Is a frame sequence settled? (consecutive-frame SSIM).",
470+
))
471+
specs.append(CommandSpec(
472+
"AC_match_persistence", "Image", "Match Persistence (frames)",
473+
fields=(
474+
FieldSpec("template", FieldType.FILE_PATH),
475+
FieldSpec("frames", FieldType.STRING,
476+
placeholder='["f1.png", "f2.png", "f3.png"]'),
477+
FieldSpec("min_score", FieldType.FLOAT, optional=True, default=0.8,
478+
min_value=0.0, max_value=1.0),
479+
FieldSpec("agree_px", FieldType.INT, optional=True, default=8),
480+
),
481+
description="Does a template match hold steady across frames? (jitter).",
482+
))
461483
specs.append(CommandSpec(
462484
"AC_grid_cells", "Image", "Grid Cells (coarse grounding)",
463485
fields=(

je_auto_control/utils/executor/action_executor.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3459,6 +3459,26 @@ def _match_color_all(template: str, channels: Any = None, min_score: Any = 0.7,
34593459
return {"count": len(matches), "matches": [m.to_dict() for m in matches]}
34603460

34613461

3462+
def _region_stability(frames: Any, settle_threshold: Any = 0.99) -> Dict[str, Any]:
3463+
"""Adapter: how settled an injected frame sequence is (consecutive SSIM)."""
3464+
import json
3465+
from je_auto_control.utils.match_stability import region_stability
3466+
if isinstance(frames, str):
3467+
frames = json.loads(frames)
3468+
return region_stability(frames, settle_threshold=float(settle_threshold))
3469+
3470+
3471+
def _match_persistence(template: str, frames: Any, min_score: Any = 0.8,
3472+
agree_px: Any = 8) -> Dict[str, Any]:
3473+
"""Adapter: whether a template match holds steady across frames."""
3474+
import json
3475+
from je_auto_control.utils.match_stability import match_persistence
3476+
if isinstance(frames, str):
3477+
frames = json.loads(frames)
3478+
return match_persistence(template, frames, min_score=float(min_score),
3479+
agree_px=float(agree_px))
3480+
3481+
34623482
def _region_arg(value: Any) -> Optional[List[int]]:
34633483
"""Coerce a JSON-string / list region arg into a list of ints, or None."""
34643484
import json
@@ -6186,6 +6206,8 @@ def __init__(self):
61866206
"AC_vote_centers": _vote_centers,
61876207
"AC_match_color": _match_color,
61886208
"AC_match_color_all": _match_color_all,
6209+
"AC_region_stability": _region_stability,
6210+
"AC_match_persistence": _match_persistence,
61896211
"AC_grid_cells": _grid_cells,
61906212
"AC_cell_for_point": _cell_for_point,
61916213
"AC_point_for_cell": _point_for_cell,

0 commit comments

Comments
 (0)