pyxllib 0.0.43__py3-none-any.whl → 0.3.197__py3-none-any.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 (186) hide show
  1. pyxllib/__init__.py +9 -2
  2. pyxllib/algo/__init__.py +8 -0
  3. pyxllib/algo/disjoint.py +54 -0
  4. pyxllib/algo/geo.py +541 -0
  5. pyxllib/{util/mathlib.py → algo/intervals.py} +172 -36
  6. pyxllib/algo/matcher.py +389 -0
  7. pyxllib/algo/newbie.py +166 -0
  8. pyxllib/algo/pupil.py +629 -0
  9. pyxllib/algo/shapelylib.py +67 -0
  10. pyxllib/algo/specialist.py +241 -0
  11. pyxllib/algo/stat.py +494 -0
  12. pyxllib/algo/treelib.py +149 -0
  13. pyxllib/algo/unitlib.py +66 -0
  14. pyxllib/autogui/__init__.py +5 -0
  15. pyxllib/autogui/activewin.py +246 -0
  16. pyxllib/autogui/all.py +9 -0
  17. pyxllib/autogui/autogui.py +852 -0
  18. pyxllib/autogui/uiautolib.py +362 -0
  19. pyxllib/autogui/virtualkey.py +102 -0
  20. pyxllib/autogui/wechat.py +827 -0
  21. pyxllib/autogui/wechat_msg.py +421 -0
  22. pyxllib/autogui/wxautolib.py +84 -0
  23. pyxllib/cv/__init__.py +1 -11
  24. pyxllib/cv/expert.py +267 -0
  25. pyxllib/cv/{imlib.py → imfile.py} +18 -83
  26. pyxllib/cv/imhash.py +39 -0
  27. pyxllib/cv/pupil.py +9 -0
  28. pyxllib/cv/rgbfmt.py +1525 -0
  29. pyxllib/cv/slidercaptcha.py +137 -0
  30. pyxllib/cv/trackbartools.py +163 -49
  31. pyxllib/cv/xlcvlib.py +1040 -0
  32. pyxllib/cv/xlpillib.py +423 -0
  33. pyxllib/data/__init__.py +0 -0
  34. pyxllib/data/echarts.py +240 -0
  35. pyxllib/data/jsonlib.py +89 -0
  36. pyxllib/{util/oss2_.py → data/oss.py} +11 -9
  37. pyxllib/data/pglib.py +1127 -0
  38. pyxllib/data/sqlite.py +568 -0
  39. pyxllib/{util → data}/sqllib.py +13 -31
  40. pyxllib/ext/JLineViewer.py +505 -0
  41. pyxllib/ext/__init__.py +6 -0
  42. pyxllib/{util → ext}/demolib.py +119 -35
  43. pyxllib/ext/drissionlib.py +277 -0
  44. pyxllib/ext/kq5034lib.py +12 -0
  45. pyxllib/{util/main.py → ext/old.py} +122 -284
  46. pyxllib/ext/qt.py +449 -0
  47. pyxllib/ext/robustprocfile.py +497 -0
  48. pyxllib/ext/seleniumlib.py +76 -0
  49. pyxllib/{util/tklib.py → ext/tk.py} +10 -11
  50. pyxllib/ext/unixlib.py +827 -0
  51. pyxllib/ext/utools.py +351 -0
  52. pyxllib/{util/webhooklib.py → ext/webhook.py} +45 -17
  53. pyxllib/ext/win32lib.py +40 -0
  54. pyxllib/ext/wjxlib.py +88 -0
  55. pyxllib/ext/wpsapi.py +124 -0
  56. pyxllib/ext/xlwork.py +9 -0
  57. pyxllib/ext/yuquelib.py +1105 -0
  58. pyxllib/file/__init__.py +17 -0
  59. pyxllib/file/docxlib.py +761 -0
  60. pyxllib/{util → file}/gitlib.py +40 -27
  61. pyxllib/file/libreoffice.py +165 -0
  62. pyxllib/file/movielib.py +148 -0
  63. pyxllib/file/newbie.py +10 -0
  64. pyxllib/file/onenotelib.py +1469 -0
  65. pyxllib/file/packlib/__init__.py +330 -0
  66. pyxllib/{util → file/packlib}/zipfile.py +598 -195
  67. pyxllib/file/pdflib.py +426 -0
  68. pyxllib/file/pupil.py +185 -0
  69. pyxllib/file/specialist/__init__.py +685 -0
  70. pyxllib/{basic/_5_dirlib.py → file/specialist/dirlib.py} +364 -93
  71. pyxllib/file/specialist/download.py +193 -0
  72. pyxllib/file/specialist/filelib.py +2829 -0
  73. pyxllib/file/xlsxlib.py +3131 -0
  74. pyxllib/file/xlsyncfile.py +341 -0
  75. pyxllib/prog/__init__.py +5 -0
  76. pyxllib/prog/cachetools.py +64 -0
  77. pyxllib/prog/deprecatedlib.py +233 -0
  78. pyxllib/prog/filelock.py +42 -0
  79. pyxllib/prog/ipyexec.py +253 -0
  80. pyxllib/prog/multiprogs.py +940 -0
  81. pyxllib/prog/newbie.py +451 -0
  82. pyxllib/prog/pupil.py +1197 -0
  83. pyxllib/{sitepackages.py → prog/sitepackages.py} +5 -3
  84. pyxllib/prog/specialist/__init__.py +391 -0
  85. pyxllib/prog/specialist/bc.py +203 -0
  86. pyxllib/prog/specialist/browser.py +497 -0
  87. pyxllib/prog/specialist/common.py +347 -0
  88. pyxllib/prog/specialist/datetime.py +199 -0
  89. pyxllib/prog/specialist/tictoc.py +240 -0
  90. pyxllib/prog/specialist/xllog.py +180 -0
  91. pyxllib/prog/xlosenv.py +108 -0
  92. pyxllib/stdlib/__init__.py +17 -0
  93. pyxllib/{util → stdlib}/tablepyxl/__init__.py +1 -3
  94. pyxllib/{util → stdlib}/tablepyxl/style.py +1 -1
  95. pyxllib/{util → stdlib}/tablepyxl/tablepyxl.py +2 -4
  96. pyxllib/text/__init__.py +8 -0
  97. pyxllib/text/ahocorasick.py +39 -0
  98. pyxllib/text/airscript.js +744 -0
  99. pyxllib/text/charclasslib.py +121 -0
  100. pyxllib/text/jiebalib.py +267 -0
  101. pyxllib/text/jinjalib.py +32 -0
  102. pyxllib/text/jsa_ai_prompt.md +271 -0
  103. pyxllib/text/jscode.py +922 -0
  104. pyxllib/text/latex/__init__.py +158 -0
  105. pyxllib/text/levenshtein.py +303 -0
  106. pyxllib/text/nestenv.py +1215 -0
  107. pyxllib/text/newbie.py +300 -0
  108. pyxllib/text/pupil/__init__.py +8 -0
  109. pyxllib/text/pupil/common.py +1121 -0
  110. pyxllib/text/pupil/xlalign.py +326 -0
  111. pyxllib/text/pycode.py +47 -0
  112. pyxllib/text/specialist/__init__.py +8 -0
  113. pyxllib/text/specialist/common.py +112 -0
  114. pyxllib/text/specialist/ptag.py +186 -0
  115. pyxllib/text/spellchecker.py +172 -0
  116. pyxllib/text/templates/echart_base.html +11 -0
  117. pyxllib/text/templates/highlight_code.html +17 -0
  118. pyxllib/text/templates/latex_editor.html +103 -0
  119. pyxllib/text/vbacode.py +17 -0
  120. pyxllib/text/xmllib.py +747 -0
  121. pyxllib/xl.py +39 -0
  122. pyxllib/xlcv.py +17 -0
  123. pyxllib-0.3.197.dist-info/METADATA +48 -0
  124. pyxllib-0.3.197.dist-info/RECORD +126 -0
  125. {pyxllib-0.0.43.dist-info → pyxllib-0.3.197.dist-info}/WHEEL +4 -5
  126. pyxllib/basic/_1_strlib.py +0 -945
  127. pyxllib/basic/_2_timelib.py +0 -488
  128. pyxllib/basic/_3_pathlib.py +0 -916
  129. pyxllib/basic/_4_loglib.py +0 -419
  130. pyxllib/basic/__init__.py +0 -54
  131. pyxllib/basic/arrow_.py +0 -250
  132. pyxllib/basic/chardet_.py +0 -66
  133. pyxllib/basic/dirlib.py +0 -529
  134. pyxllib/basic/dprint.py +0 -202
  135. pyxllib/basic/extension.py +0 -12
  136. pyxllib/basic/judge.py +0 -31
  137. pyxllib/basic/log.py +0 -204
  138. pyxllib/basic/pathlib_.py +0 -705
  139. pyxllib/basic/pytictoc.py +0 -102
  140. pyxllib/basic/qiniu_.py +0 -61
  141. pyxllib/basic/strlib.py +0 -761
  142. pyxllib/basic/timer.py +0 -132
  143. pyxllib/cv/cv.py +0 -834
  144. pyxllib/cv/cvlib/_1_geo.py +0 -543
  145. pyxllib/cv/cvlib/_2_cvprcs.py +0 -309
  146. pyxllib/cv/cvlib/_2_imgproc.py +0 -594
  147. pyxllib/cv/cvlib/_3_pilprcs.py +0 -80
  148. pyxllib/cv/cvlib/_4_cvimg.py +0 -211
  149. pyxllib/cv/cvlib/__init__.py +0 -10
  150. pyxllib/cv/debugtools.py +0 -82
  151. pyxllib/cv/fitz_.py +0 -300
  152. pyxllib/cv/installer.py +0 -42
  153. pyxllib/debug/_0_installer.py +0 -38
  154. pyxllib/debug/_1_typelib.py +0 -277
  155. pyxllib/debug/_2_chrome.py +0 -198
  156. pyxllib/debug/_3_showdir.py +0 -161
  157. pyxllib/debug/_4_bcompare.py +0 -140
  158. pyxllib/debug/__init__.py +0 -49
  159. pyxllib/debug/bcompare.py +0 -132
  160. pyxllib/debug/chrome.py +0 -198
  161. pyxllib/debug/installer.py +0 -38
  162. pyxllib/debug/showdir.py +0 -158
  163. pyxllib/debug/typelib.py +0 -278
  164. pyxllib/image/__init__.py +0 -12
  165. pyxllib/torch/__init__.py +0 -20
  166. pyxllib/torch/modellib.py +0 -37
  167. pyxllib/torch/trainlib.py +0 -344
  168. pyxllib/util/__init__.py +0 -20
  169. pyxllib/util/aip_.py +0 -141
  170. pyxllib/util/casiadb.py +0 -59
  171. pyxllib/util/excellib.py +0 -495
  172. pyxllib/util/filelib.py +0 -612
  173. pyxllib/util/jsondata.py +0 -27
  174. pyxllib/util/jsondata2.py +0 -92
  175. pyxllib/util/labelmelib.py +0 -139
  176. pyxllib/util/onepy/__init__.py +0 -29
  177. pyxllib/util/onepy/onepy.py +0 -574
  178. pyxllib/util/onepy/onmanager.py +0 -170
  179. pyxllib/util/pyautogui_.py +0 -219
  180. pyxllib/util/textlib.py +0 -1305
  181. pyxllib/util/unorder.py +0 -22
  182. pyxllib/util/xmllib.py +0 -639
  183. pyxllib-0.0.43.dist-info/METADATA +0 -39
  184. pyxllib-0.0.43.dist-info/RECORD +0 -80
  185. pyxllib-0.0.43.dist-info/top_level.txt +0 -1
  186. {pyxllib-0.0.43.dist-info → pyxllib-0.3.197.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,852 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # @Author : 陈坤泽
4
+ # @Email : 877362867@qq.com
5
+ # @Date : 2020/06/06
6
+
7
+ import sys
8
+ from collections import defaultdict
9
+ import json
10
+ import os
11
+ import time
12
+ import random
13
+
14
+ import mss
15
+ import numpy as np
16
+ from pandas.api.types import is_list_like
17
+
18
+ if sys.platform == 'win32':
19
+ import pyautogui
20
+ import win32gui
21
+
22
+ try:
23
+ import pyscreeze # NOQA pyautogui安装的时候会自动安装依赖的pyscreeze
24
+ except ModuleNotFoundError:
25
+ pass
26
+
27
+ from pyxllib.prog.newbie import first_nonnone, round_int
28
+ from pyxllib.prog.pupil import xlwait, DictTool, check_install_package
29
+ from pyxllib.prog.specialist import TicToc
30
+ from pyxllib.algo.geo import ComputeIou, ltrb2xywh, xywh2ltrb, ltrb2polygon
31
+ from pyxllib.algo.shapelylib import ShapelyPolygon
32
+ from pyxllib.file.specialist import File, Dir, XlPath
33
+ from pyxllib.cv.expert import xlcv, xlpil
34
+ from pyxlpr.data.labelme import LabelmeDict
35
+
36
+
37
+ def __20210531():
38
+ """ 以前设想的一个跟labelme结合的自动化操作类 """
39
+
40
+
41
+ class AutoGuiLabelData:
42
+ """ AutoGuiLabelData """
43
+
44
+ def __init__(self, root):
45
+ """
46
+ data:dict
47
+ key: 相对路径/图片stem (这一节称为loc)
48
+ label名称:对应属性dict1 (跟loc拼在一起,称为loclabel)
49
+ label2:dict2
50
+ """
51
+ self.root = XlPath(root)
52
+ self.data = defaultdict(dict)
53
+ self.imfiles = {}
54
+
55
+ for file in self.root.rglob_files('*.json'):
56
+ lmdict = file.read_json()
57
+ imfile = file.with_name(lmdict['imagePath'])
58
+ img = xlcv.read(imfile)
59
+ loc = str(file.relpath(self.root)).replace('\\', '/')[:-5]
60
+ self.imfiles[loc] = imfile
61
+
62
+ for shape in lmdict['shapes']:
63
+ attrs = self.parse_shape(shape, img)
64
+ self.data[loc][attrs['text']] = attrs
65
+
66
+ @classmethod
67
+ def parse_shape(cls, shape, image=None):
68
+ """ 解析一个shape的数据为dict字典,会把一些主要的几何特征也加进label字典字段中 """
69
+ # 1 解析原label为字典
70
+ # 如果是来自普通的label,会把原文本自动转换为text字段。如果是来自xllabelme,则一般默认就是字典值,带text字段。
71
+ attrs = DictTool.json_loads(shape['label'], 'text')
72
+ attrs.update(DictTool.sub(shape, ['label']))
73
+
74
+ # 2 中心点 center
75
+ shape_type = shape['shape_type']
76
+ pts = shape['points']
77
+ if shape_type in ('rectangle', 'polygon', 'line'):
78
+ attrs['center'] = np.array(np.array(pts).mean(axis=0), dtype=int).tolist()
79
+ elif shape_type == 'circle':
80
+ attrs['center'] = pts[0]
81
+
82
+ # 3 外接矩形 rect
83
+ if shape_type in ('rectangle', 'polygon'):
84
+ attrs['ltrb'] = np.array(pts, dtype=int).reshape(-1).tolist()
85
+ elif shape_type == 'circle':
86
+ x, y = pts[0]
87
+ r = ((x - pts[1][0]) ** 2 + (y - pts[1][1]) ** 2) ** 0.5
88
+ attrs['ltrb'] = [round_int(v) for v in [x - r, y - r, x + r, y + r]]
89
+
90
+ # 4 图片数据 img, etag
91
+ if image is not None and attrs['ltrb']:
92
+ attrs['img'] = xlcv.get_sub(image, attrs['ltrb'])
93
+ # attrs['etag'] = get_etag(attrs['img'])
94
+ # TODO 这里目的其实就是让相同图片对比尽量相似,所以可以用dhash而不是etag
95
+
96
+ # 5 中心点像素值 pixel
97
+ p = attrs['center']
98
+ if image is not None and p:
99
+ attrs['pixel'] = tuple(image[p[1], p[0]].tolist()[::-1])
100
+
101
+ # if 'rect' in attrs:
102
+ # del attrs['rect'] # 旧版的格式数据,删除
103
+
104
+ return attrs
105
+
106
+ def update_loclabel_img(self, loclabel, img, *, if_exists='update'):
107
+ """ 添加一张图片数据
108
+
109
+ :param if_exists:
110
+ update,更新
111
+ skip,跳过,不更新
112
+ """
113
+ loc, label = os.path.split(loclabel)
114
+ h, w = img.shape[:2]
115
+
116
+ # 1 如果loc图片不存在,则新建一个jpg图片
117
+ update = True
118
+ if loc not in self.data or not self.data[loc]:
119
+ imfile = xlcv.write(img, File(loc, self.root, suffix='.jpg'))
120
+ self.imfiles[loc] = imfile
121
+ shape = LabelmeDict.gen_shape(label, [[0, 0], [w, h]])
122
+ self.data[loc][label] = self.parse_shape(shape)
123
+ # 2 不存在的标签,则在最后一行新建一张图
124
+ elif label not in self.data[loc]:
125
+ image = xlcv.read(self.imfiles[loc])
126
+ height, width = image.shape[:2]
127
+ assert width == w # 先按行拼接,以后有时间可以扩展更灵活的拼接操作
128
+ # 拼接,并重新存储为图片
129
+ image = np.concatenate([image, img])
130
+ xlcv.write(image, self.imfiles[loc])
131
+ shape = LabelmeDict.gen_shape(label, [[0, height], [width, height + h]])
132
+ self.data[loc][label] = self.parse_shape(shape)
133
+ # 3 已有的图,则进行替换
134
+ elif if_exists == 'update':
135
+ image = xlcv.read(self.imfiles[loc])
136
+ [x1, y1, x2, y2] = self.data[loc][label]['ltrb']
137
+ image[y1:y2, x1:x2] = img
138
+ xlcv.write(image, self.imfiles[loc])
139
+ else:
140
+ update = False
141
+
142
+ if update: # 需要实时保存到文件中
143
+ self.write(loc)
144
+
145
+ def write(self, loc):
146
+ f = File(loc, self.root, suffix='.json')
147
+ imfile = self.imfiles[loc]
148
+ lmdict = LabelmeDict.gen_data(imfile)
149
+ for label, ann in self.data[loc].items():
150
+ a = ann.copy()
151
+ DictTool.isub(a, ['img'])
152
+ shape = LabelmeDict.gen_shape(json.dumps(a, ensure_ascii=False),
153
+ a['points'], a['shape_type'],
154
+ group_id=a['group_id'], flags=a['flags'])
155
+ lmdict['shapes'].append(shape)
156
+ f.write(lmdict, indent=2)
157
+
158
+ def writes(self):
159
+ for loc in self.data.keys():
160
+ self.write(loc)
161
+
162
+ def __getitem__(self, loclabel):
163
+ """ 支持 self['主菜单/道友'] 的格式获取 self.data['主菜单']['道友'] 数据
164
+ """
165
+ # 注意功能效果:os.path.split('a/b/c') -> ('a/b', 'c')
166
+ loc, label = os.path.split(loclabel)
167
+ try:
168
+ return self.data[loc][label]
169
+ except KeyError:
170
+ return None
171
+
172
+ def __setitem__(self, loclabel, value):
173
+ loc, label = os.path.split(loclabel)
174
+ self.data[loc][label] = value
175
+
176
+
177
+ class NamedLocate(AutoGuiLabelData):
178
+ """ 对有命名的标注数据进行定位
179
+
180
+ 注意labelme在每张图片上写的label值最好不要有重复,否则按字典存储后可能会被覆盖
181
+
182
+ 特殊取值:
183
+ '@IMAGE_ID' 该状态图的标志区域,即游戏中在该位置出现此子图,则认为进入了对应的图像状态
184
+
185
+ 截图:screenshot,update_shot
186
+ 检查固定位像素:point2pixel,pixel_distance,check_pixel
187
+ 检查固定位图片:rect2img,img_distance,check_img
188
+ 检索图片:img2rects, img2rect,img2point,img2img
189
+ 操作:click,move_to
190
+ 高级:wait,check_click,wait_click
191
+ """
192
+
193
+ def __init__(self, root, *, region=None, grayscale=None, confidence=0.95, tolerance=20):
194
+ """
195
+
196
+ :param root: 标注数据所在文件夹
197
+ 有原图jpg、png和对应的json标注数据
198
+ :param region: 可以限定窗口位置 (xywh格式)
199
+ :param grayscale: 转成灰度图比较
200
+ :param confidence: 用于图片比较,相似度阈值
201
+ :param tolerance: 像素比较时能容忍的误差变化范围
202
+
203
+ TODO random_click: 增加该可选参数,返回point时,进行上下左右随机扰动
204
+ """
205
+ super().__init__(root)
206
+ self.grayscale = grayscale
207
+ self.confidence = confidence
208
+ self.tolerance = tolerance
209
+ self.region = region
210
+ self.last_shot = self.update_shot()
211
+
212
+ def point_local2global(self, p):
213
+ """ 一个self.region中相对坐标点p,转换到全局坐标位置 """
214
+ if self.region:
215
+ x, y = p
216
+ x += self.region[0]
217
+ y += self.region[1]
218
+ return [x, y]
219
+ else:
220
+ return p
221
+
222
+ def point_global2local(self, p):
223
+ """ 一个self.region中相对坐标点p,转换到全局坐标位置 """
224
+ if self.region:
225
+ x, y = p
226
+ x -= self.region[0]
227
+ y -= self.region[1]
228
+ return [x, y]
229
+ else:
230
+ return p
231
+
232
+ def screenshot(self, region=None):
233
+ """
234
+ :param region: ltrb
235
+ """
236
+ if isinstance(region, str):
237
+ region = ltrb2xywh(self[region]['ltrb'])
238
+ im = pyautogui.screenshot(region=region)
239
+ return xlpil.to_cv2_image(im)
240
+
241
+ def update_shot(self, region=None):
242
+ region = region or self.region
243
+ self.last_shot = self.screenshot(region)
244
+ return self.last_shot
245
+
246
+ def point2pixel(self, point, haystack=None):
247
+ """
248
+
249
+ :param point: 支持输入 (x, y) 坐标,或者 loclabel
250
+ 后面很多参数都是类似,除了标准数据类型,同时支持 loclabel 定位模式
251
+
252
+ 官方的pixel版本,在windows有时候会出bug
253
+ OSError: windll.user32.ReleaseDC failed : return 0
254
+ """
255
+ if isinstance(point, str):
256
+ point = self[point]['center']
257
+ if haystack is None:
258
+ haystack = self.update_shot()
259
+ return tuple(haystack[point[1], point[0]].tolist()[::-1])
260
+
261
+ def rect2img(self, ltrb, haystack=None):
262
+ """
263
+ :param ltrb: 可以输入坐标定位,也可以输入 loclabel 定位
264
+ """
265
+ if isinstance(ltrb, str):
266
+ ltrb = self[ltrb]['ltrb']
267
+ l, t, r, b = ltrb
268
+ if haystack is None:
269
+ haystack = self.update_shot()
270
+ return haystack[t:b, l:r]
271
+
272
+ @classmethod
273
+ def pixel_distance(cls, pixel1, pixel2):
274
+ """ 返回最大的像素值差,如果相同返回0 """
275
+ return max([abs(x - y) for x, y in zip(pixel1, pixel2)])
276
+
277
+ @classmethod
278
+ def img_distance(cls, img1, img2, *, tolerance=10):
279
+ """ 返回距离:不相同像素占的百分比,相同返回0
280
+
281
+ :param tolerance: 每个像素允许10的差距
282
+ """
283
+ cmp = np.array(abs(np.array(img1, dtype=int) - img2) > tolerance)
284
+ return cmp.sum() / cmp.size
285
+
286
+ def check_pixel(self, loclabel, haystack=None, *, tolerance=None):
287
+ """ 判断对应位置上的像素值是否相同
288
+ """
289
+ tolerance = first_nonnone([tolerance, self.tolerance, 20])
290
+ p1 = self[loclabel]['pixel']
291
+ p2 = self.point2pixel(loclabel, haystack)
292
+ return self.pixel_distance(p1, p2) < tolerance
293
+
294
+ def check_img(self, loclabel, needle=None, *, grayscale=None, confidence=None):
295
+ grayscale = first_nonnone([grayscale, self.grayscale, False])
296
+ confidence = first_nonnone([confidence, self.confidence, 0.95])
297
+ if needle is None:
298
+ needle = self[loclabel]['img']
299
+ elif isinstance(needle, str):
300
+ needle = self[needle]['img']
301
+ haystack = self.screenshot(loclabel)
302
+
303
+ if confidence >= 1:
304
+ return np.array_equal(needle, haystack)
305
+ else:
306
+ boxes = pyautogui.locateAll(needle, self.screenshot(loclabel),
307
+ grayscale=grayscale, confidence=confidence)
308
+ boxes = [xywh2ltrb(box) for box in list(boxes)]
309
+ return len(boxes)
310
+
311
+ def img2rects(self, img, haystack=None, *, grayscale=None, confidence=None):
312
+ """ 根据预存的img数据,匹配出多个内容对应的所在的rect位置 """
313
+ # 1 配置参数
314
+ if isinstance(img, str):
315
+ img = self[img]['img']
316
+ grayscale = first_nonnone([grayscale, self.grayscale, False])
317
+ confidence = first_nonnone([confidence, self.confidence, 0.95])
318
+ # 2 查找子图
319
+ if haystack is None:
320
+ self.update_shot()
321
+ haystack = self.last_shot
322
+ boxes = pyautogui.locateAll(img, haystack,
323
+ grayscale=grayscale,
324
+ confidence=confidence)
325
+ # 3 过滤掉重叠超过一半面积的框
326
+ rects = ComputeIou.nms_ltrb([xywh2ltrb(box) for box in list(boxes)])
327
+ return rects
328
+
329
+ def img2rect(self, img, haystack=None, *, grayscale=None, confidence=None):
330
+ """ img2rects的简化,只返回一个匹配框
331
+ """
332
+ rects = self.img2rects(img, haystack, grayscale=grayscale, confidence=confidence)
333
+ return rects[0] if rects else None
334
+
335
+ def img2point(self, img, haystack=None, *, grayscale=None, confidence=None):
336
+ """ 将 img2rect 的结果统一转成点 """
337
+ res = self.img2rect(img, haystack, grayscale=grayscale, confidence=confidence)
338
+ if res:
339
+ return np.array(np.array(res).reshape(2, 2).mean(axis=0), dtype=int).tolist()
340
+
341
+ def img2img(self, img, haystack=None, *, grayscale=None, confidence=None):
342
+ """ 找到rect后,返回匹配的目标img图片内容 """
343
+ ltrb = self.img2rect(img, haystack, grayscale=grayscale, confidence=confidence)
344
+ if ltrb:
345
+ l, t, r, b = ltrb
346
+ if haystack is not None:
347
+ haystack = self.last_shot
348
+ return haystack[t:b, l:r]
349
+
350
+ def click(self, point, *, random_bias=0, back=False, wait_change=False, wait_seconds=0):
351
+ """
352
+ :param back: 点击后鼠标移回原坐标位置
353
+ :param wait_change: 点击后,等待画面产生变化,才结束函数
354
+ point为 loclabel 才能用这个功能
355
+ 注意这个只是比较loclabel的局部区域图片变化,不是全图的变化
356
+ :param int random_bias: 允许在一定范围内随机点击点
357
+ """
358
+ # 1 判断类型
359
+ if isinstance(point, str):
360
+ loclabel = point
361
+ point = self[loclabel]['center']
362
+ else:
363
+ loclabel = ''
364
+
365
+ # 随机偏移点
366
+ if random_bias:
367
+ x, y = point
368
+ x += random.randint(-random_bias, +random_bias)
369
+ y += random.randint(-random_bias, +random_bias)
370
+ point = (x, y)
371
+
372
+ # 2 鼠标移动
373
+ pos0 = pyautogui.position()
374
+ pyautogui.click(*self.point_local2global(point))
375
+ if back:
376
+ pyautogui.moveTo(*pos0) # 恢复鼠标原位置
377
+
378
+ # 3 等待画面变化
379
+ if loclabel and wait_change:
380
+ func = lambda: self.img_distance(self[loclabel]['img'],
381
+ self.rect2img(loclabel)) > self.confidence
382
+ xlwait(func)
383
+ if wait_seconds:
384
+ time.sleep(wait_seconds)
385
+
386
+ return point
387
+
388
+ def move_to(self, point):
389
+ if isinstance(point, str):
390
+ point = self[point]['center']
391
+ pyautogui.moveTo(*self.point_local2global(point))
392
+
393
+ def drag_to(self, loclabel, direction=0, duration=1, *, to_tail=False, **kwargs):
394
+ """ 在loclabel区域内进行拖拽操作
395
+
396
+ :param direction: 方向,0123分别表示向"上/右/下/左"拖拽。
397
+ :param to_tail:
398
+ False,默认只拖拽一次
399
+ True,一直拖拽到末尾,即内容不再变化为止
400
+ """
401
+
402
+ def core():
403
+ x = self[loclabel]
404
+ l, t, r, b = x['ltrb']
405
+ x2, y2 = x['center']
406
+
407
+ self.move_to(loclabel)
408
+ if direction == 0:
409
+ y2 = (y2 + t) // 2
410
+ elif direction == 1:
411
+ x2 = (x2 + r) // 2
412
+ elif direction == 2:
413
+ y2 = (y2 + b) // 2
414
+ elif direction == 3:
415
+ x2 = (x2 + l) // 2
416
+ else:
417
+ raise ValueError
418
+
419
+ x2, y2 = self.point_local2global([x2, y2])
420
+ pyautogui.dragTo(x2, y2, duration=duration, **kwargs)
421
+
422
+ if to_tail:
423
+ last_im = self.rect2img(loclabel)
424
+ while True:
425
+ core()
426
+ time.sleep(1)
427
+ new_im = self.rect2img(loclabel)
428
+ if self.img_distance(last_im, new_im) > 0.95: # 95%的像素相似
429
+ break
430
+ last_im = new_im
431
+ else:
432
+ core()
433
+
434
+ def wait(self, loclabel, *, fixpos=False, limit=None, interval=1):
435
+ """ 封装的比较高层的功能 """
436
+ # dprint(loclabel, fixpos)
437
+ if fixpos:
438
+ xlwait(lambda: self.check_img(loclabel))
439
+ point = self[loclabel]['center']
440
+ else:
441
+ point = xlwait(lambda: self.img2point(loclabel), limit=limit, interval=interval)
442
+ return point
443
+
444
+ def wait_leave(self, loclabel, *, fixpos=False, limit=None, interval=1):
445
+ """ 封装的比较高层的功能 """
446
+ if fixpos:
447
+ xlwait(lambda: self.check_img(loclabel))
448
+ point = self[loclabel]['center']
449
+ else:
450
+ point = xlwait(lambda: not self.img2point(loclabel), limit=limit, interval=interval)
451
+ return point
452
+
453
+ def check_click(self, loclabel, *, back=False, wait_change=False):
454
+ point = self.img2point(loclabel)
455
+ if point:
456
+ self.click(point, back=back, wait_change=wait_change)
457
+
458
+ def wait_click(self, loclabel, *, fixpos=False, back=False, wait_change=False):
459
+ """ 封装的比较高层的功能 """
460
+ point = self.wait(loclabel, fixpos=fixpos)
461
+ self.click(point, back=back, wait_change=wait_change)
462
+
463
+ def point2loclabels(self, point, loclabels):
464
+ """ 判断loc这张图里有哪些标签覆盖到了point这个点
465
+
466
+ :param loclabels: 可以输入一张图的定位
467
+ 也可以输入多个loclabel清单,会返回所有满足的loclabel名称
468
+ """
469
+ from shapely.geometry import Point
470
+
471
+ # 1 loclabels
472
+ if not is_list_like(loclabels):
473
+ loclabels = [loclabels]
474
+ point = Point(*point)
475
+
476
+ # 2 result
477
+ res = []
478
+ for loclabel in loclabels:
479
+ if loclabel in self.data:
480
+ for k, v in self.data.items():
481
+ if 'ltrb' in v:
482
+ if point.within(ShapelyPolygon.gen(v['ltrb'])):
483
+ res.append(f'{loclabel}/{k}')
484
+ else:
485
+ v = self[loclabel]
486
+ if 'ltrb' in v:
487
+ if point.within(ShapelyPolygon.gen(v['ltrb'])):
488
+ res.append(f'{loclabel}')
489
+
490
+ return res
491
+
492
+ def point2loclabel(self, point, loclabels):
493
+ """ 只返回第一个匹配结果
494
+ """
495
+ from shapely.geometry import Point
496
+
497
+ # 1 loclabels
498
+ if not is_list_like(loclabels):
499
+ loclabels = [loclabels]
500
+ point = Point(*point)
501
+
502
+ # 2 result
503
+ for loclabel in loclabels:
504
+ if loclabel in self.data:
505
+ for k, v in self.data.items():
506
+ if 'ltrb' in v:
507
+ if point.within(ShapelyPolygon.gen(v['ltrb'])):
508
+ return f'{loclabel}/{k}'
509
+ else:
510
+ v = self[loclabel]
511
+ if 'ltrb' in v:
512
+ if point.within(ShapelyPolygon.gen(v['ltrb'])):
513
+ return f'{loclabel}'
514
+
515
+
516
+ class PosTran:
517
+ """ 坐标位置变换
518
+
519
+ 应用场景: 原来在A窗口下的点p和区域r,
520
+ 在窗口位置、大小改成B后,p和r的坐标
521
+ """
522
+
523
+ def __init__(self, w1, w2):
524
+ """
525
+ :param w1: 原窗口位置 (x, y, w, h)
526
+ :param w2: 新窗口位置
527
+ """
528
+ self.w1 = w1
529
+ self.w2 = w2
530
+
531
+ @classmethod
532
+ def point2point(cls, w1, p1, w2):
533
+ """ 窗口w1切到w2,原点p1变换到坐标p2
534
+ """
535
+ x1, y1, w1, h1 = w1
536
+ x2, y2, w2, h2 = w2
537
+ x = x2 + (p1[0] - x1) * w2 / w1
538
+ y = y2 + (p1[1] - y1) * h2 / h1
539
+ return round(x), round(y)
540
+
541
+ def w1point(self, w2point):
542
+ return self.point2point(self.w2, w2point, self.w1)
543
+
544
+ def w2point(self, w1point):
545
+ """旧窗口中的点坐标转回新窗口点坐标"""
546
+ return self.point2point(self.w1, w1point, self.w2)
547
+
548
+ @classmethod
549
+ def dx2dx(cls, w1, dx1, w2):
550
+ """ 宽度偏移量的变化
551
+ """
552
+ return round(dx1 * w2[2] / w1[2])
553
+
554
+ def w1dx(self, dx2):
555
+ return self.dx2dx(self.w2, dx2, self.w1)
556
+
557
+ def w2dx(self, dx1):
558
+ return self.dx2dx(self.w1, dx1, self.w2)
559
+
560
+ @classmethod
561
+ def dy2dy(cls, w1, dy1, w2):
562
+ return round(dy1 * w2[3] / w1[3])
563
+
564
+ def w1dy(self, dy2):
565
+ return self.dy2dy(self.w2, dy2, self.w1)
566
+
567
+ def w2dy(self, dy1):
568
+ return self.dy2dy(self.w1, dy1, self.w2)
569
+
570
+ @classmethod
571
+ def region2region(cls, w1, r1, w2):
572
+ """ 窗口w1切到w2,原区域r1变换到坐标r2
573
+ """
574
+ x, y = cls.point2point(w1, r1[:2], w2)
575
+ w = round(r1[2] * w2[2] / w1[2])
576
+ h = round(r1[3] * w2[3] / w1[3])
577
+ return x, y, w, h
578
+
579
+ def w1region(self, w2region):
580
+ return self.region2region(self.w2, w2region, self.w1)
581
+
582
+ def w2region(self, w1region):
583
+ return self.region2region(self.w1, w1region, self.w2)
584
+
585
+
586
+ def lookup_mouse_position():
587
+ """ 查看鼠标位置的工具
588
+ """
589
+ from keyboard import read_key
590
+
591
+ left_top_point = None
592
+ while True:
593
+ k = read_key()
594
+ if k == 'ctrl':
595
+ # 定位当前鼠标坐标位置
596
+ # 也可以用来定位区域时,先确定区域的左上角点
597
+ left_top_point = pyautogui.position()
598
+ print('坐标:', *left_top_point)
599
+ elif k == 'alt':
600
+ # 定位区域的右下角点,并输出区域
601
+ p = pyautogui.position()
602
+ print('区域(x y w h):', left_top_point.x, left_top_point.y, p.x - left_top_point.x, p.y - left_top_point.y)
603
+ elif k == 'esc':
604
+ break
605
+ # keyboard的监控太快了,需要暂停一下
606
+ time.sleep(0.4)
607
+
608
+
609
+ def lookup_mouse_position2(w1, w2, reverse=False):
610
+ """ 涉及到窗口位置、大小调整时的坐标计算
611
+ 当前在窗口w1的坐标,切换到w2时的坐标
612
+ :param reverse: 当前窗口实际是变换后的w2,输出的时候需要按照 w1 -> w2 的格式展示
613
+ 代码实现的时候,其实只要把输出的内容对调即可
614
+
615
+ 该函数是lookup_mouse_postion的进阶版,两个函数还是先不做合并,便于理解维护
616
+ """
617
+ import keyboard
618
+ postran = PosTran(w1, w2)
619
+
620
+ left_top_point = None
621
+ while True:
622
+ k = keyboard.read_key()
623
+ if k == 'ctrl':
624
+ # 定位当前鼠标坐标位置
625
+ # 也可以用来定位区域时,先确定区域的左上角点
626
+ left_top_point = pyautogui.position()
627
+ p1, p2 = left_top_point, postran.w2point(left_top_point)
628
+ if reverse: p1, p2 = p2, p1
629
+ print('坐标:', *p1, '-->', *p2)
630
+ elif k == 'alt':
631
+ # 定位区域的右下角点,并输出区域
632
+ p = pyautogui.position()
633
+ r1 = [left_top_point.x, left_top_point.y, p.x - left_top_point.x, p.y - left_top_point.y]
634
+ r2 = postran.w2region(r1)
635
+ if reverse: r1, r2 = r2, r1
636
+ print('区域(x y w h):', *r1, '-->', *r2)
637
+ elif k == 'esc':
638
+ break
639
+ time.sleep(0.4)
640
+
641
+
642
+ def type_text(text):
643
+ """ 打印出文本内容
644
+
645
+ 相比pyautogui.write,这里支持中文等unicode格式
646
+
647
+ 这种需求一般也可以用剪切板实现,但是剪切板不够独立轻量,可能会有意想不到的副作用
648
+ """
649
+ check_install_package('pynput')
650
+ from pynput.keyboard import Controller
651
+
652
+ keyboard = Controller()
653
+ keyboard.type(text)
654
+
655
+
656
+ def clipboard_decorator(rtype='text', *, copy=True, paste=False, typing=False):
657
+ """ 装饰器,能方便地将函数的返回值转移到剪切板文本、富文本,甚至typing打印出来。
658
+
659
+ Args:
660
+ rtype: 函数返回值类型
661
+ paste: 是否执行剪切板的粘贴功能
662
+ typing: 是否键入文本内容
663
+
664
+ Returns:
665
+
666
+ """
667
+ from bs4 import BeautifulSoup
668
+ import pyautogui
669
+ import klembord
670
+
671
+ def decorator(func):
672
+ def wrapper(*args, **kwargs):
673
+ # 1 运行函数获得结果
674
+ s = func(*args, **kwargs)
675
+
676
+ # 2 复制到剪切板
677
+ if copy:
678
+ if rtype == 'text' and isinstance(s, str):
679
+ klembord.set_text(s)
680
+ elif rtype == 'html' and isinstance(s, str):
681
+ s0 = BeautifulSoup(s, 'lxml').text
682
+ klembord.set_with_rich_text(s0, s)
683
+ elif rtype == 'dict' and isinstance(s, dict):
684
+ klembord.set(s)
685
+
686
+ # 3 输出
687
+ if paste:
688
+ pyautogui.hotkey('ctrl', 'v') # 目前来看就这个方法最靠谱
689
+ if typing:
690
+ type_text(s)
691
+
692
+ return s
693
+
694
+ return wrapper
695
+
696
+ return decorator
697
+
698
+
699
+ def get_clipboard_content(rich=False, *, head=False):
700
+ """ klembord的get_with_rich_text获取富文本有点小问题,所以自己重写了一个版本
701
+ 好在有底层接口,所以也不难改
702
+
703
+ :param rich: 是否返回html格式的富文本,默认True
704
+ :param head: 富文本时,是否要返回<html><body>的标记
705
+ """
706
+ import re
707
+ import html
708
+
709
+ import klembord
710
+ from klembord import W_HTML, W_UNICODE
711
+
712
+ if not rich:
713
+ return klembord.get_text()
714
+
715
+ content = klembord.get((W_HTML, W_UNICODE))
716
+ html_content = content[W_HTML]
717
+ if html_content:
718
+ html_content = html_content.decode('utf8')
719
+ html_content = re.search(r'<body>\s*(?:<!--StartFragment-->)?(.+?)(?:<!--EndFragment-->)?\s*</body>',
720
+ html_content).group(1)
721
+ else:
722
+ # 没有html的时候,可以封装成html格式返回
723
+ text = content[W_UNICODE].decode('utf16')
724
+ html_content = html.escape(text)
725
+
726
+ if head:
727
+ html_content = '<html><body>' + html_content + '</body></html>'
728
+
729
+ return html_content
730
+
731
+
732
+ def set_clipboard_content(content, *, rich=False):
733
+ """ 对klembord剪切板的功能做略微的简化 """
734
+ from bs4 import BeautifulSoup
735
+ import klembord
736
+
737
+ if rich:
738
+ klembord.set_with_rich_text(BeautifulSoup(content, 'lxml').text, content)
739
+ else:
740
+ klembord.set_text(content)
741
+
742
+
743
+ def grab_pixel_color(radius=0):
744
+ """
745
+ :param radius: 以中心像素为例,计算矩阵范围内的颜色分布情况(未实装)
746
+ """
747
+ from pyxllib.cv.rgbfmt import RgbFormatter
748
+ last_pos = None
749
+ print('精确全体/精确中文')
750
+
751
+ while True:
752
+ pos = pyautogui.position()
753
+ if pos == last_pos:
754
+ continue
755
+ else:
756
+ last_pos = pos
757
+
758
+ color = RgbFormatter(*pyautogui.pixel(*pos))
759
+ desc = color.relative_color_desc(color_range=2, precise_mode=True)
760
+ print('\r' + f'{color.to_hex()} {desc}', end='', flush=True)
761
+ time.sleep(0.1)
762
+
763
+
764
+ def grab_monitor(order=0, to_cv2=True):
765
+ """ 截屏
766
+
767
+ :param int order:
768
+ 默认0,截取全部屏幕
769
+ 1,截取第1个屏幕
770
+ 2,截取第2个屏幕
771
+ ...
772
+ :param to_cv2:
773
+ True, 转成cv2格式的图片
774
+ False, 使用pil格式的图片
775
+ """
776
+ from PIL import Image
777
+ from pyxllib.xlcv import PilImg
778
+
779
+ with mss.mss() as sct:
780
+ assert order < len(sct.monitors), '屏幕下标越界'
781
+ monitor = sct.monitors[order]
782
+ sct_img = sct.grab(monitor)
783
+ img = Image.frombytes("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX")
784
+ if to_cv2:
785
+ img = PilImg.read(img).to_cv2_image() # 返回是np矩阵
786
+ return img
787
+
788
+
789
+ def list_windows(mode=1):
790
+ """ 列出所有窗口
791
+
792
+ :param mode:
793
+ 0, 列出所有窗口
794
+ 1, 只列出有名称,有宽、高的窗口
795
+ str, 指定名称的窗口
796
+ 注意,当结果有多个的时候,一般后面那个才是紧贴边缘的窗口位置
797
+
798
+ :return: [[窗口名, xywh], [窗口2, xywh]...]
799
+ """
800
+ import win32gui
801
+
802
+ ls = []
803
+
804
+ def callback(hwnd, extra):
805
+ l, t, r, b = win32gui.GetWindowRect(hwnd)
806
+ w, h = r - l, b - t
807
+ name = win32gui.GetWindowText(hwnd)
808
+ if mode == 0:
809
+ ls.append([name, [l, t, w, h]])
810
+ elif mode == 1 and name and w and h:
811
+ ls.append([name, [l, t, w, h]])
812
+ elif isinstance(mode, str) and name == mode:
813
+ ls.append([name, [l, t, w, h]])
814
+
815
+ win32gui.EnumWindows(callback, None)
816
+ return ls
817
+
818
+
819
+ class UiTracePath:
820
+ """ 在window窗口上用鼠标勾画路径 """
821
+
822
+ @classmethod
823
+ def from_polygon(cls, points, duration_per_circle=3, num_circles=1):
824
+ """
825
+ 让鼠标顺时针围绕任意多边形移动。
826
+
827
+ :param points: 多边形顶点的坐标列表,按顺时针或逆时针顺序排列
828
+ :param duration_per_circle: 每圈耗时
829
+ :param num_circles: 总圈数
830
+ """
831
+ num_points = len(points)
832
+ segment_duration = duration_per_circle / num_points # 每条边的耗时
833
+
834
+ for _ in range(num_circles):
835
+ # 顺时针移动鼠标
836
+ for i in range(num_points):
837
+ start = points[i]
838
+ pyautogui.moveTo(start[0], start[1])
839
+ end = points[(i + 1) % num_points]
840
+ # 使用 moveTo 的 duration 参数控制每条边的移动时间
841
+ pyautogui.moveTo(end[0], end[1], duration=segment_duration)
842
+
843
+ @classmethod
844
+ def from_ltrb(cls, ltrb, **kwargs):
845
+ points = ltrb2polygon(ltrb)
846
+ return cls.from_polygon(points, **kwargs)
847
+
848
+
849
+ if __name__ == '__main__':
850
+ with TicToc(__name__):
851
+ agld = AutoGuiLabelData(r'D:\slns\py4101\py4101\touhou\label')
852
+ agld.writes()