Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions Storage/customs/gitlihang/output-ocr-result-log/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# PR Send Data wps

在UI日志界面上显示OCR结果

## 功能

- 通过logger日志模块,将ocr结果输出到UI,方便用户查看关键OCR节点结果。


## 文件说明

- `logger.py`:日志输出模块。
- `returnOCR.py`:Python脚本。


## 使用方式
### 输出及click动作(暂时只写了click动作,其他动作暂未写)。
"""
"接受进入的副本名称": {
"recognition": "OCR",
"expected": [
""
],
"roi": [
441,
25,
411,
70
]
},

"活动-三界奇缘-结束": {
"action": "Custom",
"custom_action": "returnOCR",
"custom_action_param": {
"recognition_name": "接受进入的副本名称",
"action_key": "Click",
"return_text": "同意进入副本名称:",
"click_target": [
727,
621,
190,
64
]
}
}
自定义返回ocr识别结果,根据action_key执行不同的动作,
action_key: 动作名称,用于判断动作类型,如Click、Move等
recognition_name: task任务名称,用于指定识别任务名称,返回该节点的结果。
return_text: 输出的描述,用于指定返回的描述
click_target: 点击坐标,格式为[x1, y1, x2, y2],仅在action_key为Click时使用。如果不提供click_target,则默认点击识别结果的中心位置。

"""
### 只输出,无动作
```
"活动-三界奇缘-正确率": {
"recognition": "OCR",
"expected": [],
"roi": [
320,
69,
116,
51
]
},
"活动-三界奇缘-结束": {

"action": "Custom",
"custom_action": "returnOCR",
"custom_action_param": {
"recognition_name": "活动-三界奇缘-正确率",
"action_key": "",
"return_text": "三界奇缘结果正确率:"
}
},
```
## 依赖

- json
- loguru



## 注意事项

- 注意的值"custom_action_param"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (typo): "注意的值" 这一表述有些不清晰,建议重新表述以提高可读性。

`注意的值

Suggested change
- 注意的值"custom_action_param"
- 请注意配置项 `custom_action_param` 的取值
Original comment in English

issue (typo): The phrase "注意的值" is a bit unclear; consider rephrasing for clarity.

`注意的值

Suggested change
- 注意的值"custom_action_param"
- 请注意配置项 `custom_action_param` 的取值



feat(customs): 新增在UI日志界面上显示OCR结果。
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .returnOCR import *
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from maa.agent.agent_server import AgentServer
from maa.custom_action import CustomAction
from maa.context import Context
import json

from utils import logger

@AgentServer.custom_action("returnOCR")
class ReturnOCR(CustomAction):
"""
自定义返回ocr识别结果,根据action_key执行不同的动作
"custom_action_param": {
"action_key": "Click",
"recognition_name": "识别输出测试",
"return_text": "输出的描述"
"click_target": [] # 点击坐标,格式为[x1, y1, x2, y2],仅在action_key为Click时使用
}
action_key: 动作名称,用于判断动作类型,如Click、Move等
recognition_name: task任务名称,用于指定识别任务名称,返回该节点的结果。
return_text: 输出的描述,用于指定返回的描述
click_target: 点击坐标,格式为[x1, y1, x2, y2],仅在action_key为Click时使用。如果不提供click_target,则默认点击识别结果的中心位置。

"""
def run(
self,
context: Context,
argv: CustomAction.RunArg,
) -> CustomAction.RunResult:
# logger.info("进入returnOCR")
# 解析自定义参数,并判断是否为空
argv_dict: dict = json.loads(argv.custom_action_param)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: 对无效的 custom_action_param JSON 缺少错误处理,可能会导致未捕获的异常。

如果 argv.custom_action_param 缺失、为空或包含无效 JSON,json.loads 会抛出异常并中断该动作。建议捕获 json.JSONDecodeError,并根据对错误输入的期望行为,选择要么将其默认为一个空字典,要么返回一个失败的 RunResult 并记录清晰的日志信息。

Original comment in English

issue: Lack of error handling for invalid custom_action_param JSON may cause unhandled exceptions.

If argv.custom_action_param is missing, empty, or contains invalid JSON, json.loads will raise and terminate the action. Consider catching json.JSONDecodeError and either defaulting to an empty dict or returning a failed RunResult with a clear log message, depending on the desired behavior for bad input.

if not argv_dict:
return CustomAction.RunResult(success=True)
# 获取自定义参数
action_key = argv_dict.get("action_key", "")
recognition_name = argv_dict.get("recognition_name", "")
return_text = argv_dict.get("return_text", "")
click_target = argv_dict.get("click_target", [])

# 获取ocr识别结果数据
image = context.tasker.controller.post_screencap().wait().get()
reco_result = context.run_recognition(
recognition_name,
image
)
# 打印OCR识别结果
if reco_result and reco_result.hit:
best_result = reco_result.best_result
# 输出到ui界面
logger.info(f"{return_text}: {best_result.text}")
# 根据action_key执行不同的动作
if action_key == "Click":
# 点击传入参数中的坐标位置
if click_target:
box = click_target
center_x = box[0] + box[2] // 2
center_y = box[1] + box[3] // 2
Comment on lines +56 to +57

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): click_target 的中心点计算对于 [x1, y1, x2, y2] 格式的框来说很可能不正确。

在使用 [x1, y1, x2, y2] 的情况下,center_x = box[0] + box[2] // 2 实际计算的是 x1 + x2/2,而不是 x1x2 的中点。如果你想要的是矩形中心,更合适的写法是 (box[0] + box[2]) // 2(box[1] + box[3]) // 2(或者使用浮点除法以获得子像素精度);否则点击位置会偏向左上角。

Original comment in English

issue (bug_risk): Center point calculation for click_target is likely incorrect for [x1, y1, x2, y2] boxes.

With [x1, y1, x2, y2] boxes, center_x = box[0] + box[2] // 2 computes x1 + x2/2, not the midpoint between x1 and x2. If you want the rectangle center, it should be something like (box[0] + box[2]) // 2 and (box[1] + box[3]) // 2 (or float division for subpixel precision); otherwise the click will be biased toward the top-left.

logger.debug(f"点击位置: ({center_x}, {center_y})")
context.tasker.controller.post_click(center_x, center_y).wait()
# 点击最佳识别结果的中心位置
elif best_result:
box = best_result.box
center_x = box[0] + box[2] // 2
center_y = box[1] + box[3] // 2
logger.debug(f"点击位置: ({center_x}, {center_y})")
context.tasker.controller.post_click(center_x, center_y).wait()
else:
logger.warning("没有识别到结果,无法执行点击")
elif action_key == "":
logger.debug(f"仅返回OCR数据,不执行动作: {action_key}")
else:
logger.warning(f"OCR识别失败 - 任务名称: {recognition_name}")

return CustomAction.RunResult(success=True)
20 changes: 20 additions & 0 deletions Storage/customs/gitlihang/output-ocr-result-log/maahub_meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"id": "gitlihang/output-ocr-result-log",
"title": "在UI日志界面上显示OCR结果",
"description": "在UI界面上显示OCR识别结果,方便用户查看任务进度和结果。",
"author": "gitlihang",
"source": "Maa_MHXY_MG",
"sourceGithub": "https://github.com/gitlihang/Maa_MHXY_MG",
"tags": ["ocr-result", "log", "ui"],
"createdAt": "2026-06-04",
"updatedAt": "2026-06-04",
"version": "1.0.0",
"mfwVersion": "5.X",
"entry": "main.py",
"readme": "./README.md",
"status": "stable",
"type": "custom",
"language": "python",
"runtime": "python 3.12.10",
"dependencies": ["loguru","json"]
}
Empty file.
20 changes: 20 additions & 0 deletions Storage/customs/gitlihang/output-ocr-result-log/pipeline.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"OCR识别接口": {
"recognition": "OCR",
"expected": [],
"roi": [
320,
69,
116,
51
]
},"活动-三界奇缘-结束": {
"action": "Custom",
"custom_action": "returnOCR",
"custom_action_param": {
"recognition_name": "OCR识别接口",
"action_key": "",
"return_text": "三界奇缘结果正确率:"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .logger import logger
84 changes: 84 additions & 0 deletions Storage/customs/gitlihang/output-ocr-result-log/utils/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import os
import sys

try:
from loguru import logger as _logger

def setup_logger(log_dir="debug/custom", console_level="INFO"):
"""设置 loguru logger

Args:
log_dir: 日志文件目录
console_level: 控制台输出等级 (DEBUG, INFO, WARNING, ERROR)
"""
os.makedirs(log_dir, exist_ok=True)
_logger.remove()

# 定义日志级别的简短格式
def format_level(record):
level_map = {
"INFO": "info",
"ERROR": "err",
"WARNING": "warn",
"DEBUG": "debug",
"CRITICAL": "critical",
"SUCCESS": "success",
"TRACE": "trace",
}
record["extra"]["level_short"] = level_map.get(
record["level"].name, record["level"].name.lower()
)
return True

_logger.add(
sys.stderr,
format="<level>{extra[level_short]}</level>:<level>{message}</level>",
colorize=True,
level=console_level,
filter=format_level,
)
_logger.add(
f"{log_dir}/{{time:YYYY-MM-DD}}.log",
rotation="00:00", # midnight
retention="2 weeks",
compression="zip",
level="DEBUG",
format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} | {message}",
encoding="utf-8",
enqueue=True,
backtrace=True, # 包含完整的异常回溯信息
diagnose=True, # 包含变量值信息
)
return _logger

def change_console_level(level="DEBUG"):
"""动态修改控制台日志等级"""
setup_logger(console_level=level)
_logger.info(f"控制台日志等级已更改为: {level}")

logger = setup_logger()
except ImportError:
import logging

class ShortLevelFormatter(logging.Formatter):
"""自定义 Formatter,使用简短的日志级别名称"""

level_map = {
"INFO": "info",
"ERROR": "err",
"WARNING": "warn",
"DEBUG": "debug",
"CRITICAL": "critical",
}

def format(self, record):
record.level_short = self.level_map.get(
record.levelname, record.levelname.lower()
)
return super().format(record)

handler = logging.StreamHandler(sys.stderr)
handler.setFormatter(ShortLevelFormatter("%(level_short)s:%(message)s"))
logging.root.addHandler(handler)
logging.root.setLevel(logging.INFO)
logger = logging
Loading