ascript 1.3.27__py3-none-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ascript/__init__.py +8 -0
- ascript/hooks/__init__.py +0 -0
- ascript/hooks/hook-ascript.py +2 -0
- ascript/windows/__init__.py +29 -0
- ascript/windows/daos/__init__.py +0 -0
- ascript/windows/daos/screen.py +143 -0
- ascript/windows/hardware/__init__.py +5 -0
- ascript/windows/hardware/keyboard.py +279 -0
- ascript/windows/hardware/logitech_driver.py +179 -0
- ascript/windows/hardware/mouse.py +308 -0
- ascript/windows/license/__init__.py +16 -0
- ascript/windows/license/achttp.cp310-win_amd64.pyd +0 -0
- ascript/windows/license/achttp.cp311-win_amd64.pyd +0 -0
- ascript/windows/license/achttp.cp312-win_amd64.pyd +0 -0
- ascript/windows/license/achttp.cp313-win_amd64.pyd +0 -0
- ascript/windows/license/achttp.cp314-win_amd64.pyd +0 -0
- ascript/windows/license/achttp.cp38-win_amd64.pyd +0 -0
- ascript/windows/license/achttp.cp39-win_amd64.pyd +0 -0
- ascript/windows/media/__init__.py +0 -0
- ascript/windows/resources/__init__.py +0 -0
- ascript/windows/resources/libs/logitech/x64/LogitechGkeyEnginesWrapper.dll +0 -0
- ascript/windows/resources/libs/logitech/x86/LogitechGkeyEnginesWrapper.dll +0 -0
- ascript/windows/resources/templates/__init__.py +0 -0
- ascript/windows/resources/templates/__init__py.win.template +51 -0
- ascript/windows/resources/templates/build.as.win.template +14 -0
- ascript/windows/resources/templates/build.py +71 -0
- ascript/windows/resources/templates/logo_windows.ico +0 -0
- ascript/windows/resources/templates/windows/main.py +50 -0
- ascript/windows/resources/templates/windows/pyproject.toml +44 -0
- ascript/windows/resources/templates/windows/res/img/logo.ico +0 -0
- ascript/windows/screen/__init__.py +13 -0
- ascript/windows/screen/as_cv.py +382 -0
- ascript/windows/screen/canny.py +128 -0
- ascript/windows/screen/code_scanner.py +72 -0
- ascript/windows/screen/color_algorithms.py +277 -0
- ascript/windows/screen/find_colors.py +251 -0
- ascript/windows/screen/find_images.py +217 -0
- ascript/windows/screen/ocr/__init__.py +4 -0
- ascript/windows/screen/ocr/ocr.py +223 -0
- ascript/windows/screen/ocr/rapidocr_hook.py +301 -0
- ascript/windows/screen/ocrx.py +169 -0
- ascript/windows/screen/screen_core.py +139 -0
- ascript/windows/store/__init__.py +21 -0
- ascript/windows/system/__init__.py +8 -0
- ascript/windows/system/device.py +355 -0
- ascript/windows/system/easy_print.py +115 -0
- ascript/windows/system/env.py +492 -0
- ascript/windows/system/exception/__init__.py +1 -0
- ascript/windows/system/key_value.py +145 -0
- ascript/windows/system/pip.py +175 -0
- ascript/windows/system/r.py +165 -0
- ascript/windows/system/runtime.py +62 -0
- ascript/windows/tools/__init__.py +27 -0
- ascript/windows/tools/__main__.py +21 -0
- ascript/windows/tools/action.json +33 -0
- ascript/windows/tools/action.py +168 -0
- ascript/windows/tools/colors_tool.py +391 -0
- ascript/windows/tools/config.py +26 -0
- ascript/windows/tools/dumptool.py +18 -0
- ascript/windows/tools/eel_api.py +495 -0
- ascript/windows/tools/gpplug/__init__.py +0 -0
- ascript/windows/tools/gpplug/plug.py +70 -0
- ascript/windows/tools/mouse_catch.py +168 -0
- ascript/windows/tools/plug_manager.py +38 -0
- ascript/windows/tools/tool_api.py +906 -0
- ascript/windows/tools/tool_window.py +56 -0
- ascript/windows/tools/uia_worker.py +365 -0
- ascript/windows/tools/web/app_store_window.html +481 -0
- ascript/windows/tools/web/colors.html +532 -0
- ascript/windows/tools/web/css/screen.css +1342 -0
- ascript/windows/tools/web/css/viewtree.css +357 -0
- ascript/windows/tools/web/flot_control.html +195 -0
- ascript/windows/tools/web/img/ico/favicon.png +0 -0
- ascript/windows/tools/web/img/ico/paintpalette.png +0 -0
- ascript/windows/tools/web/img/svg/find_colors.svg +1 -0
- ascript/windows/tools/web/img/svg/find_images.svg +1 -0
- ascript/windows/tools/web/img/svg/illustration-6.svg +89 -0
- ascript/windows/tools/web/img/svg/img_default.svg +1 -0
- ascript/windows/tools/web/img/svg/ocr.svg +1 -0
- ascript/windows/tools/web/js/screen/colors.js +466 -0
- ascript/windows/tools/web/js/screen/gp_plug.js +108 -0
- ascript/windows/tools/web/js/screen/image_mouse.js +464 -0
- ascript/windows/tools/web/js/screen/img_view.js +558 -0
- ascript/windows/tools/web/js/screen/plug.js +246 -0
- ascript/windows/tools/web/js/screen/screen.js +690 -0
- ascript/windows/tools/web/js/screen/screen_api.js +778 -0
- ascript/windows/tools/web/js/screen/screen_maker.js +10 -0
- ascript/windows/tools/web/js/utils/map.js +73 -0
- ascript/windows/tools/web/js/utils/upload.js +157 -0
- ascript/windows/tools/web/js/viewtree/niv_js.js +27 -0
- ascript/windows/tools/web/js/viewtree/vtree.js +1510 -0
- ascript/windows/tools/web/libs/bootstrap/css/bootstrap.min.css +6 -0
- ascript/windows/tools/web/libs/bootstrap/js/bootstrap.bundle.min.js +7 -0
- ascript/windows/tools/web/libs/bootstrap-icons/0-circle.svg +4 -0
- ascript/windows/tools/web/libs/bootstrap-icons/0-square.svg +4 -0
- ascript/windows/tools/web/libs/bootstrap-icons/font/bootstrap-icons.css +2078 -0
- ascript/windows/tools/web/libs/bootstrap-icons/font/bootstrap-icons.json +2052 -0
- ascript/windows/tools/web/libs/bootstrap-icons/font/bootstrap-icons.min.css +5 -0
- ascript/windows/tools/web/libs/bootstrap-icons/font/bootstrap-icons.scss +2090 -0
- ascript/windows/tools/web/libs/bootstrap-icons/font/fonts/bootstrap-icons.woff +0 -0
- ascript/windows/tools/web/libs/bootstrap-icons/font/fonts/bootstrap-icons.woff2 +0 -0
- ascript/windows/tools/web/libs/feather-icons/dist/feather.min.js +13 -0
- ascript/windows/tools/web/libs/feather-icons/dist/icons.json +1 -0
- ascript/windows/tools/web/libs/jquery/core.js +399 -0
- ascript/windows/tools/web/libs/jquery/jquery.js +10598 -0
- ascript/windows/tools/web/libs/jquery/jquery.min.js +4 -0
- ascript/windows/tools/web/libs/jquery/jquery.min.js.zip +0 -0
- ascript/windows/tools/web/libs/jquery/jquery.slim.js +8495 -0
- ascript/windows/tools/web/libs/jquery/jquery.slim.min.js +2 -0
- ascript/windows/tools/web/libs/jquery-menu/jquery.contextMenu.js +2134 -0
- ascript/windows/tools/web/libs/jquery-menu/jquery.contextMenu.min.css +16 -0
- ascript/windows/tools/web/libs/jquery-zTree/css/zTreeStyle.css +97 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/1_close.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/1_open.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/2.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/3.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/4.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/5.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/6.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/7.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/8.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/diy/9.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/line_conn.gif +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/loading.gif +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/tree-node-btn.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/zTreeStandard.gif +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/img/zTreeStandard.png +0 -0
- ascript/windows/tools/web/libs/jquery-zTree/js/jquery.ztree.all.min.js +3 -0
- ascript/windows/tools/web/libs/treeview/css/jquery.treeview.css +79 -0
- ascript/windows/tools/web/libs/treeview/img/file.png +0 -0
- ascript/windows/tools/web/libs/treeview/img/ico_loading.png +0 -0
- ascript/windows/tools/web/libs/treeview/img/icon4.png +0 -0
- ascript/windows/tools/web/libs/treeview/img/icon5.png +0 -0
- ascript/windows/tools/web/libs/treeview/img/icon6.png +0 -0
- ascript/windows/tools/web/libs/treeview/img/py.png +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-black-line.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-black.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-default-line.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-default.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-famfamfam-line.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-famfamfam.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-gray-line.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-gray.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-red-line.gif +0 -0
- ascript/windows/tools/web/libs/treeview/img/treeview-red.gif +0 -0
- ascript/windows/tools/web/libs/treeview/js/jquery.treeview.edit.js +37 -0
- ascript/windows/tools/web/libs/treeview/js/jquery.treeview.js +250 -0
- ascript/windows/tools/web/license_window.html +255 -0
- ascript/windows/tools/web/plugs/a.html +653 -0
- ascript/windows/tools/web/plugs/demo.html +52 -0
- ascript/windows/tools/web/plugs/find_colors.html +629 -0
- ascript/windows/tools/web/plugs/find_images.html +699 -0
- ascript/windows/tools/web/plugs/rapidocr.html +954 -0
- ascript/windows/tools/web/vtree.html +496 -0
- ascript/windows/tools/webview/assets/index-HymBPl-I.css +1 -0
- ascript/windows/tools/webview/assets/index-PqA_m8tL.js +17 -0
- ascript/windows/tools/webview/index.html +13 -0
- ascript/windows/ui/__init__.py +6 -0
- ascript/windows/ui/app_store_window.py +272 -0
- ascript/windows/ui/draw_window.py +231 -0
- ascript/windows/ui/flot_test.py +297 -0
- ascript/windows/ui/flot_window.py +488 -0
- ascript/windows/ui/license_window.py +111 -0
- ascript/windows/ui/web_window.py +466 -0
- ascript/windows/utils/__init__.py +0 -0
- ascript/windows/utils/node_utils.py +20 -0
- ascript/windows/utils/screen_utils.py +280 -0
- ascript/windows/window/Selector.py +1015 -0
- ascript/windows/window/Window.py +747 -0
- ascript/windows/window/__init__.py +5 -0
- ascript/windows/window/launcher.py +213 -0
- ascript/windows/window/uia_runtime.py +26 -0
- ascript-1.3.27.dist-info/METADATA +41 -0
- ascript-1.3.27.dist-info/RECORD +177 -0
- ascript-1.3.27.dist-info/WHEEL +5 -0
- ascript-1.3.27.dist-info/entry_points.txt +2 -0
- ascript-1.3.27.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
from typing import Iterable
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from .as_cv import AsCv, MatchResult
|
|
6
|
+
from .screen_core import Screen
|
|
7
|
+
from ..window import Window
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FindImages:
|
|
11
|
+
"""Android-compatible Windows image matching API.
|
|
12
|
+
|
|
13
|
+
Public methods accept paths, PIL images, NumPy arrays, or a list of
|
|
14
|
+
templates. Legacy Windows keyword names remain supported.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
_DEFAULT_CONFIDENCE = 0.9
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def _get_source_image(source):
|
|
21
|
+
if source is None:
|
|
22
|
+
return AsCv.to_numpy(Screen.capture())
|
|
23
|
+
if hasattr(source, "hwnd") and hasattr(source, "capture"):
|
|
24
|
+
return AsCv.to_numpy(source.capture())
|
|
25
|
+
if isinstance(source, int):
|
|
26
|
+
return AsCv.to_numpy(Window(hwnd=source).capture())
|
|
27
|
+
return AsCv.to_numpy(source)
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def _is_rect(value):
|
|
31
|
+
return isinstance(value, (list, tuple)) and len(value) == 4 and all(
|
|
32
|
+
isinstance(item, (int, float, np.integer, np.floating)) for item in value
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def _arguments(cls, rect, confidence, source_img, image, kwargs):
|
|
37
|
+
if "threshold" in kwargs:
|
|
38
|
+
confidence = kwargs.pop("threshold")
|
|
39
|
+
if source_img is None:
|
|
40
|
+
source_img = kwargs.pop("im_source", None)
|
|
41
|
+
if source_img is None:
|
|
42
|
+
source_img = image
|
|
43
|
+
# Old Windows calls used the second positional argument as im_source.
|
|
44
|
+
if rect is not None and not cls._is_rect(rect):
|
|
45
|
+
if source_img is None:
|
|
46
|
+
source_img = rect
|
|
47
|
+
rect = None
|
|
48
|
+
return rect, float(cls._DEFAULT_CONFIDENCE if confidence is None else confidence), source_img
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def _crop_source(source, rect):
|
|
52
|
+
if rect is None:
|
|
53
|
+
return source, 0, 0
|
|
54
|
+
height, width = source.shape[:2]
|
|
55
|
+
left = max(0, min(width, int(rect[0])))
|
|
56
|
+
top = max(0, min(height, int(rect[1])))
|
|
57
|
+
right = max(0, min(width, int(rect[2])))
|
|
58
|
+
bottom = max(0, min(height, int(rect[3])))
|
|
59
|
+
if right <= left or bottom <= top:
|
|
60
|
+
raise ValueError("找图范围为空或超出图片边界")
|
|
61
|
+
return source[top:bottom, left:right], left, top
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def _template_items(part_img) -> Iterable:
|
|
65
|
+
return part_img if isinstance(part_img, (list, tuple)) else [part_img]
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def _offset(result: MatchResult, offset_x: int, offset_y: int, template, index: int) -> MatchResult:
|
|
69
|
+
if offset_x or offset_y:
|
|
70
|
+
result.center = (result.center[0] + offset_x, result.center[1] + offset_y)
|
|
71
|
+
result.rectangle = [
|
|
72
|
+
(point[0] + offset_x, point[1] + offset_y) for point in result.rectangle
|
|
73
|
+
]
|
|
74
|
+
result.rect = [
|
|
75
|
+
result.rect[0] + offset_x,
|
|
76
|
+
result.rect[1] + offset_y,
|
|
77
|
+
result.rect[2] + offset_x,
|
|
78
|
+
result.rect[3] + offset_y,
|
|
79
|
+
]
|
|
80
|
+
if isinstance(template, str):
|
|
81
|
+
result.template = template
|
|
82
|
+
result.index = index
|
|
83
|
+
return result
|
|
84
|
+
|
|
85
|
+
@staticmethod
|
|
86
|
+
def _iou(first: MatchResult, second: MatchResult) -> float:
|
|
87
|
+
left = max(first.rect[0], second.rect[0])
|
|
88
|
+
top = max(first.rect[1], second.rect[1])
|
|
89
|
+
right = min(first.rect[2], second.rect[2])
|
|
90
|
+
bottom = min(first.rect[3], second.rect[3])
|
|
91
|
+
intersection = max(0, right - left) * max(0, bottom - top)
|
|
92
|
+
if not intersection:
|
|
93
|
+
return 0.0
|
|
94
|
+
first_area = max(1, first.rect[2] - first.rect[0]) * max(1, first.rect[3] - first.rect[1])
|
|
95
|
+
second_area = max(1, second.rect[2] - second.rect[0]) * max(1, second.rect[3] - second.rect[1])
|
|
96
|
+
return intersection / float(first_area + second_area - intersection)
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def _deduplicate(cls, results):
|
|
100
|
+
selected = []
|
|
101
|
+
for candidate in sorted(results, key=lambda item: item.confidence, reverse=True):
|
|
102
|
+
if any(cls._iou(candidate, existing) >= 0.75 for existing in selected):
|
|
103
|
+
continue
|
|
104
|
+
selected.append(candidate)
|
|
105
|
+
return selected
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
def _sort(results, ori):
|
|
109
|
+
key_map = {
|
|
110
|
+
1: lambda item: (item.center[0], item.center[1]),
|
|
111
|
+
2: lambda item: (item.center[1], item.center[0]),
|
|
112
|
+
3: lambda item: (item.center[1], -item.center[0]),
|
|
113
|
+
4: lambda item: (-item.center[0], item.center[1]),
|
|
114
|
+
5: lambda item: (-item.center[0], -item.center[1]),
|
|
115
|
+
6: lambda item: (-item.center[1], -item.center[0]),
|
|
116
|
+
7: lambda item: (-item.center[1], item.center[0]),
|
|
117
|
+
8: lambda item: (item.center[0], -item.center[1]),
|
|
118
|
+
}
|
|
119
|
+
return sorted(results, key=key_map.get(int(ori), key_map[2]))
|
|
120
|
+
|
|
121
|
+
@classmethod
|
|
122
|
+
def _find_all_impl(cls, part_img, rect, confidence, rgb, maxcnt, source_img,
|
|
123
|
+
ori, scale_range, mode):
|
|
124
|
+
source = cls._get_source_image(source_img)
|
|
125
|
+
if source is None:
|
|
126
|
+
raise RuntimeError("屏幕截图失败")
|
|
127
|
+
source, offset_x, offset_y = cls._crop_source(source, rect)
|
|
128
|
+
results = []
|
|
129
|
+
per_template_limit = int(maxcnt or 0)
|
|
130
|
+
for index, template in enumerate(cls._template_items(part_img)):
|
|
131
|
+
if mode == "mixed":
|
|
132
|
+
matches = AsCv.find(
|
|
133
|
+
source, template, threshold=confidence, maxcnt=per_template_limit,
|
|
134
|
+
use_scale=True, rgb=rgb, scale_range=scale_range,
|
|
135
|
+
)
|
|
136
|
+
elif scale_range:
|
|
137
|
+
matches = AsCv.find_all_multi_scale(
|
|
138
|
+
source, template, threshold=confidence, maxcnt=per_template_limit,
|
|
139
|
+
scale_range=tuple(scale_range), rgb=rgb,
|
|
140
|
+
)
|
|
141
|
+
else:
|
|
142
|
+
matches = AsCv.find_all_template(
|
|
143
|
+
source, template, threshold=confidence, maxcnt=per_template_limit, rgb=rgb,
|
|
144
|
+
)
|
|
145
|
+
results.extend(cls._offset(match, offset_x, offset_y, template, index) for match in matches)
|
|
146
|
+
return cls._sort(results, ori)
|
|
147
|
+
|
|
148
|
+
@classmethod
|
|
149
|
+
def find_template(cls, part_img, rect=None, confidence=0.9, rgb=False,
|
|
150
|
+
source_img=None, ori=2, scale_range=None, image=None,
|
|
151
|
+
fast=True, **kwargs):
|
|
152
|
+
rect, confidence, source_img = cls._arguments(rect, confidence, source_img, image, kwargs)
|
|
153
|
+
results = cls._find_all_impl(
|
|
154
|
+
part_img, rect, confidence, rgb, 1, source_img, ori, scale_range, "template"
|
|
155
|
+
)
|
|
156
|
+
return results[0] if results else None
|
|
157
|
+
|
|
158
|
+
@classmethod
|
|
159
|
+
def find_all_template(cls, part_img, rect=None, confidence=0.9, rgb=False,
|
|
160
|
+
maxcnt=0, source_img=None, ori=2, scale_range=None,
|
|
161
|
+
image=None, fast=True, **kwargs):
|
|
162
|
+
if "max_res" in kwargs:
|
|
163
|
+
maxcnt = kwargs.pop("max_res")
|
|
164
|
+
rect, confidence, source_img = cls._arguments(rect, confidence, source_img, image, kwargs)
|
|
165
|
+
return cls._find_all_impl(
|
|
166
|
+
part_img, rect, confidence, rgb, maxcnt, source_img, ori, scale_range, "template"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
@classmethod
|
|
170
|
+
def find_sift(cls, part_img, rect=None, confidence=0.9, rgb=False,
|
|
171
|
+
source_img=None, ori=2, scale_range=None, image=None,
|
|
172
|
+
fast=True, **kwargs):
|
|
173
|
+
"""Compatibility entry point; Android currently falls back to template matching."""
|
|
174
|
+
return cls.find_template(
|
|
175
|
+
part_img, rect, confidence, rgb, source_img, ori, scale_range, image, fast, **kwargs
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
@classmethod
|
|
179
|
+
def find_all_sift(cls, part_img, rect=None, confidence=0.9, rgb=False,
|
|
180
|
+
source_img=None, maxcnt=0, ori=2, scale_range=None,
|
|
181
|
+
image=None, fast=True, **kwargs):
|
|
182
|
+
return cls.find_all_template(
|
|
183
|
+
part_img, rect, confidence, rgb, maxcnt, source_img, ori,
|
|
184
|
+
scale_range, image, fast, **kwargs
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
@classmethod
|
|
188
|
+
def find(cls, part_img, rect=None, confidence=None, rgb=False,
|
|
189
|
+
source_img=None, ori=2, scale_range=None, image=None,
|
|
190
|
+
method="mixed", fast=True, **kwargs):
|
|
191
|
+
rect, confidence, source_img = cls._arguments(rect, confidence, source_img, image, kwargs)
|
|
192
|
+
mode = "mixed" if str(method).lower() in {"mixed", "auto"} else "template"
|
|
193
|
+
results = cls._find_all_impl(
|
|
194
|
+
part_img, rect, confidence, rgb, 1, source_img, ori, scale_range, mode
|
|
195
|
+
)
|
|
196
|
+
return results[0] if results else None
|
|
197
|
+
|
|
198
|
+
@classmethod
|
|
199
|
+
def find_all(cls, part_img, rect=None, confidence=None, rgb=False,
|
|
200
|
+
source_img=None, maxcnt=0, ori=2, scale_range=None,
|
|
201
|
+
image=None, method="mixed", fast=True, **kwargs):
|
|
202
|
+
if "max_res" in kwargs:
|
|
203
|
+
maxcnt = kwargs.pop("max_res")
|
|
204
|
+
use_scale = kwargs.pop("use_scale", None)
|
|
205
|
+
rect, confidence, source_img = cls._arguments(rect, confidence, source_img, image, kwargs)
|
|
206
|
+
mode = "mixed" if use_scale is not False or str(method).lower() in {"mixed", "auto"} else "template"
|
|
207
|
+
return cls._find_all_impl(
|
|
208
|
+
part_img, rect, confidence, rgb, maxcnt, source_img, ori, scale_range, mode
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
@classmethod
|
|
212
|
+
def find_akaze(cls, part_img, im_source=None, threshold=0.5, min_match=10, **kwargs):
|
|
213
|
+
source = cls._get_source_image(kwargs.pop("source_img", im_source))
|
|
214
|
+
results = []
|
|
215
|
+
for template in cls._template_items(part_img):
|
|
216
|
+
results.extend(AsCv.find_akaze(source, template, threshold=threshold, min_match=min_match))
|
|
217
|
+
return results
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import threading
|
|
3
|
+
import time
|
|
4
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from ..color_algorithms import _crop, _source_image
|
|
10
|
+
from .rapidocr_hook import NullOCRItem, OCRItem
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Ocr:
|
|
14
|
+
"""RapidOCR/ONNXRuntime OCR wrapper using PP-OCRv6 small models."""
|
|
15
|
+
|
|
16
|
+
MODE_RAPID_V6 = 3
|
|
17
|
+
MODE_RAPID_V5 = MODE_RAPID_V6
|
|
18
|
+
MODE_PADDLE_V5 = MODE_RAPID_V6
|
|
19
|
+
MODE_PADDLE_V2 = MODE_RAPID_V6
|
|
20
|
+
MODE_PADDLE_V3 = MODE_RAPID_V6
|
|
21
|
+
MODE_MLK = 1
|
|
22
|
+
MODE_TESS = 4
|
|
23
|
+
|
|
24
|
+
_engine = None
|
|
25
|
+
_lock = threading.RLock()
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def set_engine(cls, engine):
|
|
29
|
+
"""注入自定义 RapidOCR 兼容引擎,传 ``None`` 恢复默认引擎。"""
|
|
30
|
+
with cls._lock:
|
|
31
|
+
cls._engine = engine
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def _default_engine(cls):
|
|
35
|
+
with cls._lock:
|
|
36
|
+
if cls._engine is not None:
|
|
37
|
+
return cls._engine
|
|
38
|
+
try:
|
|
39
|
+
from rapidocr import EngineType, LangDet, LangRec, ModelType, OCRVersion, RapidOCR
|
|
40
|
+
except ImportError as exc:
|
|
41
|
+
raise RuntimeError("OCR 组件未安装,请安装 rapidocr 和 onnxruntime") from exc
|
|
42
|
+
cls._engine = RapidOCR(params={
|
|
43
|
+
"Det.engine_type": EngineType.ONNXRUNTIME,
|
|
44
|
+
"Det.lang_type": LangDet.CH,
|
|
45
|
+
"Det.model_type": ModelType.SMALL,
|
|
46
|
+
"Det.ocr_version": OCRVersion.PPOCRV6,
|
|
47
|
+
"Rec.engine_type": EngineType.ONNXRUNTIME,
|
|
48
|
+
"Rec.lang_type": LangRec.CH,
|
|
49
|
+
"Rec.model_type": ModelType.SMALL,
|
|
50
|
+
"Rec.ocr_version": OCRVersion.PPOCRV6,
|
|
51
|
+
"Cls.engine_type": EngineType.ONNXRUNTIME,
|
|
52
|
+
"Cls.lang_type": LangDet.CH,
|
|
53
|
+
"Cls.model_type": ModelType.MOBILE,
|
|
54
|
+
"Cls.ocr_version": OCRVersion.PPOCRV4,
|
|
55
|
+
})
|
|
56
|
+
return cls._engine
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def backend_info():
|
|
60
|
+
try:
|
|
61
|
+
rapidocr_version = version("rapidocr")
|
|
62
|
+
except PackageNotFoundError:
|
|
63
|
+
rapidocr_version = "not installed"
|
|
64
|
+
try:
|
|
65
|
+
onnxruntime_version = version("onnxruntime")
|
|
66
|
+
except PackageNotFoundError:
|
|
67
|
+
onnxruntime_version = "not installed"
|
|
68
|
+
return {
|
|
69
|
+
"engine": "RapidOCR",
|
|
70
|
+
"runtime": "ONNXRuntime",
|
|
71
|
+
"rapidocr_version": rapidocr_version,
|
|
72
|
+
"onnxruntime_version": onnxruntime_version,
|
|
73
|
+
"detection": "PP-OCRv6 small",
|
|
74
|
+
"recognition": "PP-OCRv6 small",
|
|
75
|
+
"classification": "PP-OCRv4 mobile",
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def _item(text, box, score, offset_x=0, offset_y=0):
|
|
80
|
+
points = np.asarray(box, dtype=float).reshape(-1, 2)
|
|
81
|
+
points[:, 0] += offset_x
|
|
82
|
+
points[:, 1] += offset_y
|
|
83
|
+
left, top = np.floor(points.min(axis=0)).astype(int)
|
|
84
|
+
right, bottom = np.ceil(points.max(axis=0)).astype(int)
|
|
85
|
+
left, top, right, bottom = map(int, (left, top, right, bottom))
|
|
86
|
+
return OCRItem(
|
|
87
|
+
txt=str(text), box=points.tolist(), score=float(score),
|
|
88
|
+
center=(int((left + right) // 2), int((top + bottom) // 2)),
|
|
89
|
+
rect=[left, top, right, bottom],
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def recognize(cls, rect=None, pattern: str = None, confidence: float = 0.1,
|
|
94
|
+
image=None, bitmap=None, file: str = None, binary: int = -1) -> List[OCRItem]:
|
|
95
|
+
"""识别图像文字。``pattern`` 是正则表达式;``rect`` 使用 left,top,right,bottom。"""
|
|
96
|
+
if bitmap is not None:
|
|
97
|
+
if image is not None:
|
|
98
|
+
raise ValueError("image 和 bitmap 不能同时传入")
|
|
99
|
+
image = bitmap
|
|
100
|
+
source_input = file if file else (image if image is not None else bitmap)
|
|
101
|
+
source, offset_x, offset_y = _crop(_source_image(source_input), rect)
|
|
102
|
+
if binary >= 0:
|
|
103
|
+
import cv2
|
|
104
|
+
gray = cv2.cvtColor(source, cv2.COLOR_BGR2GRAY)
|
|
105
|
+
_, source = cv2.threshold(gray, int(binary), 255, cv2.THRESH_BINARY)
|
|
106
|
+
with cls._lock:
|
|
107
|
+
output = cls._default_engine()(source)
|
|
108
|
+
if output is None:
|
|
109
|
+
return []
|
|
110
|
+
texts = getattr(output, "txts", None)
|
|
111
|
+
boxes = getattr(output, "boxes", None)
|
|
112
|
+
scores = getattr(output, "scores", None)
|
|
113
|
+
if texts is None or boxes is None or scores is None:
|
|
114
|
+
raise RuntimeError(
|
|
115
|
+
"RapidOCR 返回了不兼容的结果结构 "
|
|
116
|
+
f"({type(output).__module__}.{type(output).__name__}); "
|
|
117
|
+
"请确认安装 rapidocr==3.10.0 和 onnxruntime==1.23.2"
|
|
118
|
+
)
|
|
119
|
+
if not (len(texts) == len(boxes) == len(scores)):
|
|
120
|
+
raise RuntimeError(
|
|
121
|
+
"RapidOCR 返回数据长度不一致: "
|
|
122
|
+
f"texts={len(texts)}, boxes={len(boxes)}, scores={len(scores)}"
|
|
123
|
+
)
|
|
124
|
+
matcher = re.compile(pattern) if pattern else None
|
|
125
|
+
results = []
|
|
126
|
+
for text, box, score in zip(texts, boxes, scores):
|
|
127
|
+
if float(score) < float(confidence) or (matcher and not matcher.search(str(text))):
|
|
128
|
+
continue
|
|
129
|
+
results.append(cls._item(text, box, score, offset_x, offset_y))
|
|
130
|
+
results.sort(key=lambda item: (item.center[1], item.center[0]))
|
|
131
|
+
return results
|
|
132
|
+
|
|
133
|
+
@classmethod
|
|
134
|
+
def paddleocr(cls, rect=None, pattern: str = None, confidence: float = 0.1,
|
|
135
|
+
max_side_len: int = 1200, precision: int = 16, bitmap=None,
|
|
136
|
+
file: str = None, binary: int = -1, language: str = "chinese", image=None):
|
|
137
|
+
del max_side_len, precision, language
|
|
138
|
+
return cls.recognize(rect, pattern, confidence, image, bitmap, file, binary)
|
|
139
|
+
|
|
140
|
+
rapidocr = paddleocr
|
|
141
|
+
paddleocr_v2 = paddleocr
|
|
142
|
+
paddleocr_v3 = paddleocr
|
|
143
|
+
|
|
144
|
+
@classmethod
|
|
145
|
+
def find_all(cls, text: str = None, rect=None, mode: int = None, binary: int = -1,
|
|
146
|
+
confidence: float = 0.1, image=None, pattern: str = None,
|
|
147
|
+
bitmap=None, file: str = None):
|
|
148
|
+
"""Android-compatible alias; ``pattern`` and legacy ``text`` both accept regex."""
|
|
149
|
+
del mode # Android engine selection is platform-specific; Windows uses RapidOCR.
|
|
150
|
+
if text is not None and pattern is not None:
|
|
151
|
+
raise ValueError("text 和 pattern 只能指定一个")
|
|
152
|
+
return cls.recognize(
|
|
153
|
+
rect=rect, pattern=pattern if pattern is not None else text,
|
|
154
|
+
confidence=confidence, image=image, bitmap=bitmap, file=file, binary=binary,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
@classmethod
|
|
158
|
+
def find(cls, text: str = None, rect=None, mode: int = None, binary: int = -1,
|
|
159
|
+
confidence: float = 0.1, image=None, pattern: str = None,
|
|
160
|
+
bitmap=None, file: str = None):
|
|
161
|
+
results = cls.find_all(
|
|
162
|
+
text, rect, mode, binary, confidence, image,
|
|
163
|
+
pattern=pattern, bitmap=bitmap, file=file,
|
|
164
|
+
)
|
|
165
|
+
return results[0] if results else NullOCRItem()
|
|
166
|
+
|
|
167
|
+
@classmethod
|
|
168
|
+
def exists(cls, text: str = None, rect=None, mode: int = None, binary: int = -1,
|
|
169
|
+
confidence: float = 0.1, image=None, pattern: str = None,
|
|
170
|
+
bitmap=None, file: str = None) -> bool:
|
|
171
|
+
return bool(cls.find(
|
|
172
|
+
text, rect, mode, binary, confidence, image,
|
|
173
|
+
pattern=pattern, bitmap=bitmap, file=file,
|
|
174
|
+
))
|
|
175
|
+
|
|
176
|
+
@classmethod
|
|
177
|
+
def wait(cls, text: str = None, timeout: float = 10, rect=None, mode: int = None,
|
|
178
|
+
binary: int = -1, confidence: float = 0.1, image=None,
|
|
179
|
+
pattern: str = None, interval: float = 0.3):
|
|
180
|
+
"""Poll fresh screen captures until the regex matches or timeout expires."""
|
|
181
|
+
deadline = time.monotonic() + max(0.0, float(timeout))
|
|
182
|
+
current_image = image
|
|
183
|
+
while True:
|
|
184
|
+
item = cls.find(
|
|
185
|
+
text, rect=rect, mode=mode, binary=binary,
|
|
186
|
+
confidence=confidence, image=current_image, pattern=pattern,
|
|
187
|
+
)
|
|
188
|
+
current_image = None
|
|
189
|
+
if item:
|
|
190
|
+
return item
|
|
191
|
+
if time.monotonic() >= deadline:
|
|
192
|
+
return None
|
|
193
|
+
time.sleep(max(0.01, float(interval)))
|
|
194
|
+
|
|
195
|
+
@classmethod
|
|
196
|
+
def click(cls, text: str = None, rect=None, mode: int = None, binary: int = -1,
|
|
197
|
+
confidence: float = 0.1, timeout: float = 0, image=None,
|
|
198
|
+
pattern: str = None, window=None) -> bool:
|
|
199
|
+
"""Find and click text center; supply ``window`` for window-relative coordinates."""
|
|
200
|
+
item = cls.wait(
|
|
201
|
+
text, timeout=timeout, rect=rect, mode=mode, binary=binary,
|
|
202
|
+
confidence=confidence, image=image, pattern=pattern,
|
|
203
|
+
) if float(timeout) > 0 else cls.find(
|
|
204
|
+
text, rect=rect, mode=mode, binary=binary, confidence=confidence,
|
|
205
|
+
image=image, pattern=pattern,
|
|
206
|
+
)
|
|
207
|
+
if not item:
|
|
208
|
+
return False
|
|
209
|
+
item.click(window=window)
|
|
210
|
+
return True
|
|
211
|
+
|
|
212
|
+
@classmethod
|
|
213
|
+
def wait_click(cls, text: str = None, timeout: float = 10, rect=None,
|
|
214
|
+
mode: int = None, binary: int = -1, confidence: float = 0.1,
|
|
215
|
+
image=None, pattern: str = None, window=None) -> bool:
|
|
216
|
+
item = cls.wait(
|
|
217
|
+
text, timeout=timeout, rect=rect, mode=mode, binary=binary,
|
|
218
|
+
confidence=confidence, image=image, pattern=pattern,
|
|
219
|
+
)
|
|
220
|
+
if not item:
|
|
221
|
+
return False
|
|
222
|
+
item.click(window=window)
|
|
223
|
+
return True
|