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.
Files changed (177) hide show
  1. ascript/__init__.py +8 -0
  2. ascript/hooks/__init__.py +0 -0
  3. ascript/hooks/hook-ascript.py +2 -0
  4. ascript/windows/__init__.py +29 -0
  5. ascript/windows/daos/__init__.py +0 -0
  6. ascript/windows/daos/screen.py +143 -0
  7. ascript/windows/hardware/__init__.py +5 -0
  8. ascript/windows/hardware/keyboard.py +279 -0
  9. ascript/windows/hardware/logitech_driver.py +179 -0
  10. ascript/windows/hardware/mouse.py +308 -0
  11. ascript/windows/license/__init__.py +16 -0
  12. ascript/windows/license/achttp.cp310-win_amd64.pyd +0 -0
  13. ascript/windows/license/achttp.cp311-win_amd64.pyd +0 -0
  14. ascript/windows/license/achttp.cp312-win_amd64.pyd +0 -0
  15. ascript/windows/license/achttp.cp313-win_amd64.pyd +0 -0
  16. ascript/windows/license/achttp.cp314-win_amd64.pyd +0 -0
  17. ascript/windows/license/achttp.cp38-win_amd64.pyd +0 -0
  18. ascript/windows/license/achttp.cp39-win_amd64.pyd +0 -0
  19. ascript/windows/media/__init__.py +0 -0
  20. ascript/windows/resources/__init__.py +0 -0
  21. ascript/windows/resources/libs/logitech/x64/LogitechGkeyEnginesWrapper.dll +0 -0
  22. ascript/windows/resources/libs/logitech/x86/LogitechGkeyEnginesWrapper.dll +0 -0
  23. ascript/windows/resources/templates/__init__.py +0 -0
  24. ascript/windows/resources/templates/__init__py.win.template +51 -0
  25. ascript/windows/resources/templates/build.as.win.template +14 -0
  26. ascript/windows/resources/templates/build.py +71 -0
  27. ascript/windows/resources/templates/logo_windows.ico +0 -0
  28. ascript/windows/resources/templates/windows/main.py +50 -0
  29. ascript/windows/resources/templates/windows/pyproject.toml +44 -0
  30. ascript/windows/resources/templates/windows/res/img/logo.ico +0 -0
  31. ascript/windows/screen/__init__.py +13 -0
  32. ascript/windows/screen/as_cv.py +382 -0
  33. ascript/windows/screen/canny.py +128 -0
  34. ascript/windows/screen/code_scanner.py +72 -0
  35. ascript/windows/screen/color_algorithms.py +277 -0
  36. ascript/windows/screen/find_colors.py +251 -0
  37. ascript/windows/screen/find_images.py +217 -0
  38. ascript/windows/screen/ocr/__init__.py +4 -0
  39. ascript/windows/screen/ocr/ocr.py +223 -0
  40. ascript/windows/screen/ocr/rapidocr_hook.py +301 -0
  41. ascript/windows/screen/ocrx.py +169 -0
  42. ascript/windows/screen/screen_core.py +139 -0
  43. ascript/windows/store/__init__.py +21 -0
  44. ascript/windows/system/__init__.py +8 -0
  45. ascript/windows/system/device.py +355 -0
  46. ascript/windows/system/easy_print.py +115 -0
  47. ascript/windows/system/env.py +492 -0
  48. ascript/windows/system/exception/__init__.py +1 -0
  49. ascript/windows/system/key_value.py +145 -0
  50. ascript/windows/system/pip.py +175 -0
  51. ascript/windows/system/r.py +165 -0
  52. ascript/windows/system/runtime.py +62 -0
  53. ascript/windows/tools/__init__.py +27 -0
  54. ascript/windows/tools/__main__.py +21 -0
  55. ascript/windows/tools/action.json +33 -0
  56. ascript/windows/tools/action.py +168 -0
  57. ascript/windows/tools/colors_tool.py +391 -0
  58. ascript/windows/tools/config.py +26 -0
  59. ascript/windows/tools/dumptool.py +18 -0
  60. ascript/windows/tools/eel_api.py +495 -0
  61. ascript/windows/tools/gpplug/__init__.py +0 -0
  62. ascript/windows/tools/gpplug/plug.py +70 -0
  63. ascript/windows/tools/mouse_catch.py +168 -0
  64. ascript/windows/tools/plug_manager.py +38 -0
  65. ascript/windows/tools/tool_api.py +906 -0
  66. ascript/windows/tools/tool_window.py +56 -0
  67. ascript/windows/tools/uia_worker.py +365 -0
  68. ascript/windows/tools/web/app_store_window.html +481 -0
  69. ascript/windows/tools/web/colors.html +532 -0
  70. ascript/windows/tools/web/css/screen.css +1342 -0
  71. ascript/windows/tools/web/css/viewtree.css +357 -0
  72. ascript/windows/tools/web/flot_control.html +195 -0
  73. ascript/windows/tools/web/img/ico/favicon.png +0 -0
  74. ascript/windows/tools/web/img/ico/paintpalette.png +0 -0
  75. ascript/windows/tools/web/img/svg/find_colors.svg +1 -0
  76. ascript/windows/tools/web/img/svg/find_images.svg +1 -0
  77. ascript/windows/tools/web/img/svg/illustration-6.svg +89 -0
  78. ascript/windows/tools/web/img/svg/img_default.svg +1 -0
  79. ascript/windows/tools/web/img/svg/ocr.svg +1 -0
  80. ascript/windows/tools/web/js/screen/colors.js +466 -0
  81. ascript/windows/tools/web/js/screen/gp_plug.js +108 -0
  82. ascript/windows/tools/web/js/screen/image_mouse.js +464 -0
  83. ascript/windows/tools/web/js/screen/img_view.js +558 -0
  84. ascript/windows/tools/web/js/screen/plug.js +246 -0
  85. ascript/windows/tools/web/js/screen/screen.js +690 -0
  86. ascript/windows/tools/web/js/screen/screen_api.js +778 -0
  87. ascript/windows/tools/web/js/screen/screen_maker.js +10 -0
  88. ascript/windows/tools/web/js/utils/map.js +73 -0
  89. ascript/windows/tools/web/js/utils/upload.js +157 -0
  90. ascript/windows/tools/web/js/viewtree/niv_js.js +27 -0
  91. ascript/windows/tools/web/js/viewtree/vtree.js +1510 -0
  92. ascript/windows/tools/web/libs/bootstrap/css/bootstrap.min.css +6 -0
  93. ascript/windows/tools/web/libs/bootstrap/js/bootstrap.bundle.min.js +7 -0
  94. ascript/windows/tools/web/libs/bootstrap-icons/0-circle.svg +4 -0
  95. ascript/windows/tools/web/libs/bootstrap-icons/0-square.svg +4 -0
  96. ascript/windows/tools/web/libs/bootstrap-icons/font/bootstrap-icons.css +2078 -0
  97. ascript/windows/tools/web/libs/bootstrap-icons/font/bootstrap-icons.json +2052 -0
  98. ascript/windows/tools/web/libs/bootstrap-icons/font/bootstrap-icons.min.css +5 -0
  99. ascript/windows/tools/web/libs/bootstrap-icons/font/bootstrap-icons.scss +2090 -0
  100. ascript/windows/tools/web/libs/bootstrap-icons/font/fonts/bootstrap-icons.woff +0 -0
  101. ascript/windows/tools/web/libs/bootstrap-icons/font/fonts/bootstrap-icons.woff2 +0 -0
  102. ascript/windows/tools/web/libs/feather-icons/dist/feather.min.js +13 -0
  103. ascript/windows/tools/web/libs/feather-icons/dist/icons.json +1 -0
  104. ascript/windows/tools/web/libs/jquery/core.js +399 -0
  105. ascript/windows/tools/web/libs/jquery/jquery.js +10598 -0
  106. ascript/windows/tools/web/libs/jquery/jquery.min.js +4 -0
  107. ascript/windows/tools/web/libs/jquery/jquery.min.js.zip +0 -0
  108. ascript/windows/tools/web/libs/jquery/jquery.slim.js +8495 -0
  109. ascript/windows/tools/web/libs/jquery/jquery.slim.min.js +2 -0
  110. ascript/windows/tools/web/libs/jquery-menu/jquery.contextMenu.js +2134 -0
  111. ascript/windows/tools/web/libs/jquery-menu/jquery.contextMenu.min.css +16 -0
  112. ascript/windows/tools/web/libs/jquery-zTree/css/zTreeStyle.css +97 -0
  113. ascript/windows/tools/web/libs/jquery-zTree/img/diy/1_close.png +0 -0
  114. ascript/windows/tools/web/libs/jquery-zTree/img/diy/1_open.png +0 -0
  115. ascript/windows/tools/web/libs/jquery-zTree/img/diy/2.png +0 -0
  116. ascript/windows/tools/web/libs/jquery-zTree/img/diy/3.png +0 -0
  117. ascript/windows/tools/web/libs/jquery-zTree/img/diy/4.png +0 -0
  118. ascript/windows/tools/web/libs/jquery-zTree/img/diy/5.png +0 -0
  119. ascript/windows/tools/web/libs/jquery-zTree/img/diy/6.png +0 -0
  120. ascript/windows/tools/web/libs/jquery-zTree/img/diy/7.png +0 -0
  121. ascript/windows/tools/web/libs/jquery-zTree/img/diy/8.png +0 -0
  122. ascript/windows/tools/web/libs/jquery-zTree/img/diy/9.png +0 -0
  123. ascript/windows/tools/web/libs/jquery-zTree/img/line_conn.gif +0 -0
  124. ascript/windows/tools/web/libs/jquery-zTree/img/loading.gif +0 -0
  125. ascript/windows/tools/web/libs/jquery-zTree/img/tree-node-btn.png +0 -0
  126. ascript/windows/tools/web/libs/jquery-zTree/img/zTreeStandard.gif +0 -0
  127. ascript/windows/tools/web/libs/jquery-zTree/img/zTreeStandard.png +0 -0
  128. ascript/windows/tools/web/libs/jquery-zTree/js/jquery.ztree.all.min.js +3 -0
  129. ascript/windows/tools/web/libs/treeview/css/jquery.treeview.css +79 -0
  130. ascript/windows/tools/web/libs/treeview/img/file.png +0 -0
  131. ascript/windows/tools/web/libs/treeview/img/ico_loading.png +0 -0
  132. ascript/windows/tools/web/libs/treeview/img/icon4.png +0 -0
  133. ascript/windows/tools/web/libs/treeview/img/icon5.png +0 -0
  134. ascript/windows/tools/web/libs/treeview/img/icon6.png +0 -0
  135. ascript/windows/tools/web/libs/treeview/img/py.png +0 -0
  136. ascript/windows/tools/web/libs/treeview/img/treeview-black-line.gif +0 -0
  137. ascript/windows/tools/web/libs/treeview/img/treeview-black.gif +0 -0
  138. ascript/windows/tools/web/libs/treeview/img/treeview-default-line.gif +0 -0
  139. ascript/windows/tools/web/libs/treeview/img/treeview-default.gif +0 -0
  140. ascript/windows/tools/web/libs/treeview/img/treeview-famfamfam-line.gif +0 -0
  141. ascript/windows/tools/web/libs/treeview/img/treeview-famfamfam.gif +0 -0
  142. ascript/windows/tools/web/libs/treeview/img/treeview-gray-line.gif +0 -0
  143. ascript/windows/tools/web/libs/treeview/img/treeview-gray.gif +0 -0
  144. ascript/windows/tools/web/libs/treeview/img/treeview-red-line.gif +0 -0
  145. ascript/windows/tools/web/libs/treeview/img/treeview-red.gif +0 -0
  146. ascript/windows/tools/web/libs/treeview/js/jquery.treeview.edit.js +37 -0
  147. ascript/windows/tools/web/libs/treeview/js/jquery.treeview.js +250 -0
  148. ascript/windows/tools/web/license_window.html +255 -0
  149. ascript/windows/tools/web/plugs/a.html +653 -0
  150. ascript/windows/tools/web/plugs/demo.html +52 -0
  151. ascript/windows/tools/web/plugs/find_colors.html +629 -0
  152. ascript/windows/tools/web/plugs/find_images.html +699 -0
  153. ascript/windows/tools/web/plugs/rapidocr.html +954 -0
  154. ascript/windows/tools/web/vtree.html +496 -0
  155. ascript/windows/tools/webview/assets/index-HymBPl-I.css +1 -0
  156. ascript/windows/tools/webview/assets/index-PqA_m8tL.js +17 -0
  157. ascript/windows/tools/webview/index.html +13 -0
  158. ascript/windows/ui/__init__.py +6 -0
  159. ascript/windows/ui/app_store_window.py +272 -0
  160. ascript/windows/ui/draw_window.py +231 -0
  161. ascript/windows/ui/flot_test.py +297 -0
  162. ascript/windows/ui/flot_window.py +488 -0
  163. ascript/windows/ui/license_window.py +111 -0
  164. ascript/windows/ui/web_window.py +466 -0
  165. ascript/windows/utils/__init__.py +0 -0
  166. ascript/windows/utils/node_utils.py +20 -0
  167. ascript/windows/utils/screen_utils.py +280 -0
  168. ascript/windows/window/Selector.py +1015 -0
  169. ascript/windows/window/Window.py +747 -0
  170. ascript/windows/window/__init__.py +5 -0
  171. ascript/windows/window/launcher.py +213 -0
  172. ascript/windows/window/uia_runtime.py +26 -0
  173. ascript-1.3.27.dist-info/METADATA +41 -0
  174. ascript-1.3.27.dist-info/RECORD +177 -0
  175. ascript-1.3.27.dist-info/WHEEL +5 -0
  176. ascript-1.3.27.dist-info/entry_points.txt +2 -0
  177. 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,4 @@
1
+ from .ocr import Ocr
2
+ from .rapidocr_hook import NullOCRItem, OCRItem
3
+
4
+ __all__ = ["Ocr", "OCRItem", "NullOCRItem"]
@@ -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