Conversation
* Opt: using pHash and template matching for commission suffix recognition * Refactor: improve suffix image processing and hash calculation
This reverts commit c852cff.
* Opt: using template matching for commission suffix recognition (#5731) * Opt: using pHash and template matching for commission suffix recognition * Refactor: improve suffix image processing and hash calculation * Revert "Upd: [JP] asset GET_ITEMS_X (#5718)" (#5751) This reverts commit c852cff. * Chore: move hashlib to local import * Upd: [TW] Event entrance of Revelations of Dust Rerun (event_20230223_cn) --------- Co-authored-by: guoh064 <50830808+guoh064@users.noreply.github.com>
- 更新 ROUTE_PORT_BUSINESS_COMPLETE 资源图,匹配当前港口商区路线完成入口 - 同步按钮识别区域与平均颜色,避免沿用旧位置导致误判
最新活动"美梦巡演:奇妙夜"sp关卡配置 `AzurPilot/campaign/event_20260625_cn/sp.py` 中`MAP_HAS_MODE_SWITCH`被错误设置为`True`,导致进入关卡时AzurPilot会寻找不存在的切换困难模式按钮,从而卡死在进入关卡界面。对照此前活动的 `sp.py` 文件将其设置为`True` ## Summary by Sourcery Bug Fixes: - 修正 20260625 CN SP 活动配置中的 `MAP_HAS_MODE_SWITCH` 标志,以防止客户端在进入 SP 关卡时发生卡死。 <details> <summary>Original summary in English</summary> ## Summary by Sourcery Bug Fixes: - Correct the MAP_HAS_MODE_SWITCH flag in the 20260625 CN SP campaign config so the client no longer hangs when entering the SP stage. </details>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Reviewer's Guide本次 PR 将佣金后缀识别从「基于 OCR 的 commission suffix」替换为「基于图像裁剪的后缀区域 + 模板匹配」,同时强化 ws-scrcpy 服务器与 websocket 的处理逻辑,并根据近期活动与 UI 变动调整部分资源模板和战役配置。 ws-scrcpy 服务器启动与连接的时序图sequenceDiagram
participant Client
participant WsScrcpySession as session
participant AdbConnection as connection
participant Device
Client->>session: start_server()
loop start_server
session->>connection: adb_push(WS_SCRCPY_FILEPATH_LOCAL, WS_SCRCPY_FILEPATH_REMOTE)
session->>connection: adb_forward("tcp:"+WS_SCRCPY_PORT)
connection-->>session: local_port
session->>session: _server_running()
session->>connection: adb_shell("test -f " + WS_SCRCPY_PID_FILE_REMOTE + " && cat " + WS_SCRCPY_PID_FILE_REMOTE)
connection-->>session: pid
session->>connection: adb_shell("cat /proc/"+pid+"/cmdline")
connection-->>session: cmdline
alt server_running
session-->>Client: reuse existing server
else server_not_running
session->>connection: adb_shell(_server_command())
connection-->>session: output
end
end
Client->>session: connect()
session->>Device: connect(url, max_size=None, ping_interval=None, close_timeout=1)
Device-->>session: websocket
session-->>Client: remote_ws established
文件级改动
Tips and commandsInteracting with Sourcery
Customizing Your Experience访问你的 dashboard 以:
Getting HelpOriginal review guide in EnglishReviewer's GuideThis PR replaces commission suffix OCR with image-based suffix cropping and template matching, hardens ws-scrcpy server and websocket handling, and adjusts several asset templates and campaign configuration for recent events and UI changes. Sequence diagram for ws-scrcpy server startup and connectionsequenceDiagram
participant Client
participant WsScrcpySession as session
participant AdbConnection as connection
participant Device
Client->>session: start_server()
loop start_server
session->>connection: adb_push(WS_SCRCPY_FILEPATH_LOCAL, WS_SCRCPY_FILEPATH_REMOTE)
session->>connection: adb_forward("tcp:"+WS_SCRCPY_PORT)
connection-->>session: local_port
session->>session: _server_running()
session->>connection: adb_shell("test -f " + WS_SCRCPY_PID_FILE_REMOTE + " && cat " + WS_SCRCPY_PID_FILE_REMOTE)
connection-->>session: pid
session->>connection: adb_shell("cat /proc/"+pid+"/cmdline")
connection-->>session: cmdline
alt server_running
session-->>Client: reuse existing server
else server_not_running
session->>connection: adb_shell(_server_command())
connection-->>session: output
end
end
Client->>session: connect()
session->>Device: connect(url, max_size=None, ping_interval=None, close_timeout=1)
Device-->>session: websocket
session-->>Client: remote_ws established
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - 我发现了 1 个问题,并给出了一些总体性的反馈:
- 在
crop_suffix_image和suffix_match中,建议把当前散落在各处的“魔法数字”(阈值、look_back 距离、3 像素边距、相似度默认值)集中抽取成具名常量或配置,这样可以让后缀匹配行为在不同服务器之间更易于调优和理解。 - 新增的
image_hash辅助函数在每次调用时都会在函数内部导入hashlib;将该导入移动到模块级,可以避免重复导入,并在紧密的佣金解析循环中降低该辅助函数的调用开销。
面向 AI Agent 的提示词
Please address the comments from this code review:
## Overall Comments
- In `crop_suffix_image` and `suffix_match`, consider centralizing the magic numbers (thresholds, look_back distance, 3‑pixel margins, similarity default) into named constants or config to make the suffix matching behavior easier to tune and reason about across servers.
- The new `image_hash` helper imports `hashlib` inside the function on every call; moving this import to module scope would avoid repeated imports and make the helper cheaper when used in tight commission parsing loops.
## Individual Comments
### Comment 1
<location path="module/commission/project.py" line_range="35-38" />
<code_context>
+ Returns:
+ 后缀裁剪图,黑字白底;未检测到文字时返回 None。
+ """
+ name_image = crop(image, area)
+ name_image = extract_letters(name_image, letter=(255, 255, 255), threshold=128).astype(np.uint8)
+
+ line = cv2.reduce(name_image[5:-5, :], 0, cv2.REDUCE_AVG).flatten()
+ columns = np.where(line < 250)[0]
+ if not len(columns):
</code_context>
<issue_to_address>
**issue (bug_risk):** Guard against very small or empty name_image before slicing and reducing.
If the cropped region is very small (e.g., height ≤ 10 or width == 0), `name_image[5:-5, :]` may be empty and `cv2.reduce` will error. Consider checking `name_image.shape` first and returning None or using a fallback when the region is too small for the 5‑pixel trim.
</issue_to_address>帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进之后的评审。
Original comment in English
Hey - I've found 1 issue, and left some high level feedback:
- In
crop_suffix_imageandsuffix_match, consider centralizing the magic numbers (thresholds, look_back distance, 3‑pixel margins, similarity default) into named constants or config to make the suffix matching behavior easier to tune and reason about across servers. - The new
image_hashhelper importshashlibinside the function on every call; moving this import to module scope would avoid repeated imports and make the helper cheaper when used in tight commission parsing loops.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `crop_suffix_image` and `suffix_match`, consider centralizing the magic numbers (thresholds, look_back distance, 3‑pixel margins, similarity default) into named constants or config to make the suffix matching behavior easier to tune and reason about across servers.
- The new `image_hash` helper imports `hashlib` inside the function on every call; moving this import to module scope would avoid repeated imports and make the helper cheaper when used in tight commission parsing loops.
## Individual Comments
### Comment 1
<location path="module/commission/project.py" line_range="35-38" />
<code_context>
+ Returns:
+ 后缀裁剪图,黑字白底;未检测到文字时返回 None。
+ """
+ name_image = crop(image, area)
+ name_image = extract_letters(name_image, letter=(255, 255, 255), threshold=128).astype(np.uint8)
+
+ line = cv2.reduce(name_image[5:-5, :], 0, cv2.REDUCE_AVG).flatten()
+ columns = np.where(line < 250)[0]
+ if not len(columns):
</code_context>
<issue_to_address>
**issue (bug_risk):** Guard against very small or empty name_image before slicing and reducing.
If the cropped region is very small (e.g., height ≤ 10 or width == 0), `name_image[5:-5, :]` may be empty and `cv2.reduce` will error. Consider checking `name_image.shape` first and returning None or using a fallback when the region is too small for the 5‑pixel trim.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| name_image = crop(image, area) | ||
| name_image = extract_letters(name_image, letter=(255, 255, 255), threshold=128).astype(np.uint8) | ||
|
|
||
| line = cv2.reduce(name_image[5:-5, :], 0, cv2.REDUCE_AVG).flatten() |
There was a problem hiding this comment.
issue (bug_risk): 在进行切片和归约之前,需要防范 name_image 过小或为空的情况。
如果裁剪区域非常小(例如高度 ≤ 10 或宽度 == 0),name_image[5:-5, :] 可能为空,而此时调用 cv2.reduce 会报错。建议先检查 name_image.shape,在区域太小不适合做 5 像素裁剪时直接返回 None,或采用其他回退处理方式。
Original comment in English
issue (bug_risk): Guard against very small or empty name_image before slicing and reducing.
If the cropped region is very small (e.g., height ≤ 10 or width == 0), name_image[5:-5, :] may be empty and cv2.reduce will error. Consider checking name_image.shape first and returning None or using a fallback when the region is too small for the 5‑pixel trim.
There was a problem hiding this comment.
Code Review
This pull request updates event translations, adjusts UI button assets across different servers, and refactors the commission suffix recognition from OCR to image-based template matching. It also improves ws-scrcpy server detection in the WebUI by reading PID files and verifying process command lines. Feedback on the changes highlights potential crashes in the new image processing logic: slicing and cropping could fail on small images in crop_suffix_image and suffix_match, and an IndexError could occur when parsing an empty PID file.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| line = cv2.reduce(name_image[5:-5, :], 0, cv2.REDUCE_AVG).flatten() | ||
| columns = np.where(line < 250)[0] | ||
| if not len(columns): | ||
| return None | ||
|
|
||
| # 从最右侧文字向左回看,尽量完整包含罗马数字后缀。 | ||
| threshold = 250 | ||
| look_back = 10 | ||
| for i in range(columns[-1], 0, -1): | ||
| if line[i] > threshold: | ||
| if columns[-1] - i > look_back: | ||
| look_back = columns[-1] - i | ||
| break | ||
|
|
||
| left = columns[-1] - look_back | ||
| right = columns[-1] + 1 |
There was a problem hiding this comment.
If name_image has a height of 10 or less, slicing name_image[5:-5, :] will result in an empty array, causing cv2.reduce to crash. Additionally, if columns[-1] is less than look_back (10), left will become negative, which can lead to out-of-bounds cropping or unexpected behavior.
We should add a guard for the image height and ensure that left and right are clamped within the valid boundaries of name_image.
| line = cv2.reduce(name_image[5:-5, :], 0, cv2.REDUCE_AVG).flatten() | |
| columns = np.where(line < 250)[0] | |
| if not len(columns): | |
| return None | |
| # 从最右侧文字向左回看,尽量完整包含罗马数字后缀。 | |
| threshold = 250 | |
| look_back = 10 | |
| for i in range(columns[-1], 0, -1): | |
| if line[i] > threshold: | |
| if columns[-1] - i > look_back: | |
| look_back = columns[-1] - i | |
| break | |
| left = columns[-1] - look_back | |
| right = columns[-1] + 1 | |
| h, w = name_image.shape[:2] | |
| if h > 10: | |
| line = cv2.reduce(name_image[5:-5, :], 0, cv2.REDUCE_AVG).flatten() | |
| else: | |
| line = cv2.reduce(name_image, 0, cv2.REDUCE_AVG).flatten() | |
| columns = np.where(line < 250)[0] | |
| if not len(columns): | |
| return None | |
| # 从最右侧文字向左回看,尽量完整包含罗马数字后缀。 | |
| threshold = 250 | |
| look_back = 10 | |
| for i in range(columns[-1], 0, -1): | |
| if line[i] > threshold: | |
| if columns[-1] - i > look_back: | |
| look_back = columns[-1] - i | |
| break | |
| left = max(0, columns[-1] - look_back) | |
| right = min(w, columns[-1] + 1) |
| def match(image, template): | ||
| template = crop(template, (3, 3, template.shape[1] - 3, template.shape[0] - 3), copy=False) | ||
| if image.shape[0] < template.shape[0] or image.shape[1] < template.shape[1]: | ||
| return 0.0 | ||
|
|
||
| res = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED) | ||
| _, sim, _, _ = cv2.minMaxLoc(res) | ||
| return sim |
There was a problem hiding this comment.
If the template width or height is 6 or less (which can happen for narrow characters like "I" or due to tight cropping), template.shape[1] - 3 will be 3 or less, resulting in an invalid crop area where x1 >= x2 or y1 >= y2. This causes crop to return an empty image, which will crash cv2.matchTemplate with an assertion error.
We should check if the template dimensions are too small and return 0.0 early to prevent crashes.
def match(image, template):
h, w = template.shape[:2]
if h <= 6 or w <= 6:
return 0.0
template = crop(template, (3, 3, w - 3, h - 3), copy=False)
if image.shape[0] < template.shape[0] or image.shape[1] < template.shape[1]:
return 0.0
res = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED)
_, sim, _, _ = cv2.minMaxLoc(res)
return sim| try: | ||
| pid = self.connection.adb_shell(f"test -f {WS_SCRCPY_PID_FILE_REMOTE} && cat {WS_SCRCPY_PID_FILE_REMOTE}", timeout=2) | ||
| pid = str(pid or "").strip().split()[0] | ||
| except Exception: | ||
| pid = "" |
There was a problem hiding this comment.
If the adb_shell command returns an empty string or only whitespace (e.g., if the PID file is empty or doesn't exist), split() will return an empty list, and accessing [0] will raise an IndexError. Although this is caught by the except Exception: block, relying on exceptions for expected control flow is an anti-pattern and can make debugging harder.
We should use a safe list access to retrieve the PID.
| try: | |
| pid = self.connection.adb_shell(f"test -f {WS_SCRCPY_PID_FILE_REMOTE} && cat {WS_SCRCPY_PID_FILE_REMOTE}", timeout=2) | |
| pid = str(pid or "").strip().split()[0] | |
| except Exception: | |
| pid = "" | |
| try: | |
| pid_out = self.connection.adb_shell(f"test -f {WS_SCRCPY_PID_FILE_REMOTE} && cat {WS_SCRCPY_PID_FILE_REMOTE}", timeout=2) | |
| pids = str(pid_out or "").strip().split() | |
| pid = pids[0] if pids else "" | |
| except Exception: | |
| pid = "" |
Summary by Sourcery
将佣金后缀文本的 OCR 替换为基于图像裁剪和相似度匹配的方案,并调整相关日志记录、实时预览稳定性、活动配置以及多处 UI 素材模板。
New Features:
Bug Fixes:
None和空字节帧,避免过早结束或误解析。cmdline检测正在运行的 ws-scrcpy 服务,而不是仅依赖本地端口可用性。event_20260625_cn的 SP 地图配置。Enhancements:
Documentation:
Original summary in English
Summary by Sourcery
Replace commission suffix text OCR with image-based cropping and similarity matching, and adjust related logging, live preview stability, campaign configuration, and various UI asset templates.
New Features:
Bug Fixes:
Enhancements:
Documentation: