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
pyxllib/cv/xlpillib.py ADDED
@@ -0,0 +1,423 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # @Author : 陈坤泽
4
+ # @Email : 877362867@qq.com
5
+ # @Date : 2021/08/25 15:57
6
+
7
+ """
8
+ 图方便的时候,PilImg和CvImg可以进行图片类型转换,然后互相引用彼此已有的一个实现版本
9
+ 为了性能的时候,则尽量减少各种绕弯,使用最源生的代码来实现
10
+ """
11
+
12
+ import base64
13
+ import io
14
+ import os
15
+ import random
16
+
17
+ import cv2
18
+ import numpy as np
19
+ import PIL.ExifTags
20
+ import PIL.Image
21
+ import PIL.ImageOps
22
+ import requests
23
+
24
+ try:
25
+ import accimage
26
+ except ImportError:
27
+ accimage = None
28
+
29
+ from pyxllib.prog.pupil import inject_members
30
+ from pyxllib.file.specialist import XlPath, get_font_file
31
+ from pyxllib.cv.xlcvlib import xlcv
32
+
33
+
34
+ class PilImg(PIL.Image.Image):
35
+
36
+ def __1_read(self):
37
+ pass
38
+
39
+ @classmethod
40
+ def read(cls, file, flags=None, *, apply_exif_orientation=False, **kwargs) -> 'PilImg':
41
+ if PilImg.is_pil_image(file):
42
+ im = file
43
+ elif xlcv.is_cv2_image(file):
44
+ im = xlcv.to_pil_image(file)
45
+ elif XlPath.safe_init(file):
46
+ im = PIL.Image.open(str(file), **kwargs)
47
+ else:
48
+ raise TypeError(f'类型错误或文件不存在:{type(file)} {file}')
49
+ if apply_exif_orientation:
50
+ im = PilImg.apply_exif_orientation(im)
51
+ return PilImg.cvt_channel(im, flags)
52
+
53
+ @classmethod
54
+ def read_from_buffer(cls, buffer, flags=None, *, b64decode=False):
55
+ """ 先用opencv实现,以后可以再研究PIL.Image.frombuffer是否有更快处理策略 """
56
+ if b64decode:
57
+ buffer = base64.b64decode(buffer)
58
+ im = PIL.Image.open(io.BytesIO(buffer))
59
+ return PilImg.cvt_channel(im, flags)
60
+
61
+ @classmethod
62
+ def read_from_url(cls, url, flags=None, *, b64decode=False):
63
+ content = requests.get(url).content
64
+ return PilImg.read_from_buffer(content, flags, b64decode=b64decode)
65
+
66
+ def __2_attrs(self):
67
+ pass
68
+
69
+ def imsize(self):
70
+ return self.size[::-1]
71
+
72
+ def n_channels(self):
73
+ """ 通道数 """
74
+ return len(self.getbands())
75
+
76
+ def __3_write(self):
77
+ pass
78
+
79
+ def to_cv2_image(self):
80
+ """ pil图片转np图片 """
81
+ y = np.array(self)
82
+ y = cv2.cvtColor(y, cv2.COLOR_BGR2RGB) if y.size else None
83
+ return y
84
+
85
+ def is_pil_image(self):
86
+ if accimage is not None:
87
+ return isinstance(self, (PIL.Image.Image, accimage.Image))
88
+ else:
89
+ return isinstance(self, PIL.Image.Image)
90
+
91
+ def write(self, path, *, if_exists=None, **kwargs):
92
+ p = XlPath(path)
93
+ if p.exist_preprcs(if_exists):
94
+ os.makedirs(p.parent, exist_ok=True)
95
+ suffix = p.suffix[1:]
96
+ if suffix.lower() == 'jpg':
97
+ suffix = 'jpeg'
98
+ if self.mode in ('RGBA', 'P') and suffix == 'jpeg':
99
+ im = self.convert('RGB')
100
+ else:
101
+ im = self
102
+ im.save(str(p), suffix, **kwargs)
103
+
104
+ def cvt_channel(self, flags=None):
105
+ im = self
106
+ if flags is None or flags == -1: return im
107
+ n_c = im.n_channels
108
+ if flags == 0 and n_c > 1:
109
+ im = im.convert('L')
110
+ elif flags == 1 and n_c != 3:
111
+ im = im.convert('RGB')
112
+ return im
113
+
114
+ def to_buffer(self, ext='.jpg', *, b64encode=False):
115
+ # 主要是偷懒,不想重写一遍,就直接去调用cv版本的实现了
116
+ return xlcv.to_buffer(self.to_cv2_image(), ext, b64encode=b64encode)
117
+
118
+ def display(self):
119
+ """ 在jupyter中展示 """
120
+ try:
121
+ from IPython.display import display
122
+ display(self)
123
+ except ModuleNotFoundError:
124
+ pass
125
+
126
+ def __4_plot(self):
127
+ pass
128
+
129
+ def plot_border(self, border=1, fill='black'):
130
+ """ 给图片加上边框
131
+
132
+ Args:
133
+ self:
134
+ border: 边框的厚度
135
+ fill: 边框颜色
136
+
137
+ Returns: 一张新图
138
+ """
139
+ from PIL import ImageOps
140
+ im2 = ImageOps.expand(self, border=border, fill=fill)
141
+ return im2
142
+
143
+ def plot_text(self, text, xy=None, font_size=10, font_type='simfang.ttf', **kwargs):
144
+ """
145
+ :param xy: 写入文本的起始坐标,没写入则自动写在垂直居中位置
146
+ """
147
+ from PIL import ImageFont, ImageDraw
148
+ font_file = get_font_file(font_type)
149
+ font = ImageFont.truetype(font=str(font_file), size=font_size, encoding="utf-8")
150
+ draw = ImageDraw.Draw(self)
151
+ if xy is None:
152
+ w, h = font_getsize(font, text)
153
+ xy = ((self.size[0] - w) / 2, (self.size[1] - h) / 2)
154
+ draw.text(xy, text, font=font, **kwargs)
155
+ return self
156
+
157
+ def __5_resize(self):
158
+ pass
159
+
160
+ def reduce_area(self, area):
161
+ """ 根据面积上限缩小图片
162
+
163
+ 即图片面积超过area时,按照等比例缩小到面积为area的图片
164
+ """
165
+ im = self
166
+ h, w = PilImg.imsize(im)
167
+ s = h * w
168
+ if s > area:
169
+ r = (area / s) ** 0.5
170
+ size = int(r * h), int(r * w)
171
+ im = PilImg.resize2(im, size)
172
+ return im
173
+
174
+ def resize2(self, size, **kwargs):
175
+ """
176
+ :param size: 默认是 (w, h), 这里我倒过来 (h, w)
177
+ 但计算机领域,确实经常都是用 (w, h) 的格式,毕竟横轴是x,纵轴才是y
178
+ :param kwargs:
179
+ resample=3,插值算法;有PIL.Image.NEAREST, ~BOX, ~BILINEAR, ~HAMMING, ~BICUBIC, ~LANCZOS等
180
+ 默认是 PIL.Image.BICUBIC;如果mode是"1"或"P"模式,则总是 PIL.Image.NEAREST
181
+
182
+ >>> im = read(np.zeros([100, 200], dtype='uint8'), 0)
183
+ >>> self.size
184
+ (100, 200)
185
+ >>> im2 = im.reduce_area(50*50, **kwargs)
186
+ >>> im2.size
187
+ (35, 70)
188
+ """
189
+ # 注意pil图像尺寸接口都是[w,h],跟标准的[h,w]相反
190
+ return self.resize(size[::-1], **kwargs)
191
+
192
+ def evaluate_image_file_size(self, suffix='.jpeg'):
193
+ """ 评估图像存成文件后的大小
194
+
195
+ :param suffix: 使用的图片类型
196
+ :return int: 存储后的文件大小,单位为字节
197
+ """
198
+ im = self
199
+
200
+ # save接口不支持jpg参数
201
+ if suffix[0] == '.':
202
+ suffix = suffix[1:]
203
+ if suffix.lower() == 'jpg':
204
+ suffix = 'jpeg'
205
+
206
+ file = io.BytesIO()
207
+ if im.mode in ('RGBA', 'P') and suffix == 'jpeg':
208
+ im = im.convert('RGB')
209
+ im.save(file, suffix)
210
+ return len(file.getvalue())
211
+
212
+ def reduce_filesize(self, filesize=None, suffix='.jpeg'):
213
+ """ 按照保存后的文件大小来压缩im
214
+
215
+ :param filesize: 单位Bytes
216
+ int, 可以用 300*1024 来表示 300KB
217
+ None, 可以不输入,默认读取后按原尺寸返回,这样看似没变化,其实图片一读一写,是会对手机拍照的很多大图进行压缩的
218
+
219
+ >> reduce_filesize(im, 300*1024, 'jpg')
220
+ """
221
+ im = self
222
+ # 循环处理
223
+ while filesize:
224
+ r = xlpil.evaluate_image_file_size(im, suffix) / filesize
225
+ if r <= 1:
226
+ break
227
+
228
+ # 假设图片面积和文件大小成正比,如果r=4,表示长宽要各减小至1/(r**0.5)才能到目标文件大小
229
+ rate = min(1 / (r ** 0.5), 0.95) # 并且限制每轮至少要缩小至95%,避免可能会迭代太多轮
230
+ im = im.resize((int(im.size[0] * rate), int(im.size[1] * rate)))
231
+ return im
232
+
233
+ def trim(self, *, border=0, color=None):
234
+ """ 默认裁剪掉白色边缘,可以配合 get_backgroup_color 裁剪掉背景色
235
+
236
+ :param border: 上下左右保留多少边缘
237
+ 输入一个整数,表示统一留边
238
+ 也可以输入[int, int, int, int],分别表示left top right bottom留白
239
+ :param color: 要裁剪的颜色,这是精确值,没有误差,如果需要模糊,可以提前预处理成精确值
240
+ 这个默认值设成None,本来是想说支持灰度图,此时输入一个255的值就好
241
+ 但是pil的灰度图机制好像有些不太一样,总之目前默认值还是自动会设成 (255, 255, 255)
242
+ :param percent-background: TODO 控制裁剪百分比上限
243
+ """
244
+ from PIL import Image, ImageChops
245
+ from pyxllib.algo.geo import xywh2ltrb, ltrb2xywh, ltrb_border
246
+
247
+ if color is None:
248
+ color = (255, 255, 255)
249
+ else:
250
+ color = tuple(color)
251
+
252
+ # 如果图片通道数跟预设的color不同,要断言
253
+ assert self.n_channels() == len(color), f'图片通道数{self.n_channels}跟预设的color{color}不同'
254
+
255
+ im = self
256
+ bg = Image.new(im.mode, im.size, color)
257
+ diff = ImageChops.difference(im, bg)
258
+ bbox = diff.getbbox() # 如果im跟bg一样,也就是裁"消失"了,此时bbox值为None
259
+ if bbox:
260
+ if border:
261
+ ltrb = xywh2ltrb(bbox)
262
+ ltrb = ltrb_border(ltrb, border, im.size)
263
+ bbox = ltrb2xywh(ltrb)
264
+ im = im.crop(bbox)
265
+ return im
266
+
267
+ def __6_warp(self):
268
+ pass
269
+
270
+ def __x_other(self):
271
+ pass
272
+
273
+ def random_direction(self):
274
+ """ 假设原图片是未旋转的状态0
275
+
276
+ 顺时针转90度是label=1,顺时针转180度是label2 ...
277
+ """
278
+ im = self
279
+ label = np.random.randint(4)
280
+ if label == 1:
281
+ # PIL的旋转角度,是指逆时针角度;但是我这里编号是顺时针
282
+ im = im.transpose(PIL.Image.ROTATE_270)
283
+ elif label == 2:
284
+ im = im.transpose(PIL.Image.ROTATE_180)
285
+ elif label == 3:
286
+ im = im.transpose(PIL.Image.ROTATE_90)
287
+ return im, label
288
+
289
+ def flip_direction(self, direction):
290
+ """
291
+ :param direction: 顺时针旋转几个90度
292
+ 标记现在图片是哪个方向:0是正常,1是向右翻转,2是向下翻转,3是向左翻转
293
+ """
294
+ im = self
295
+ direction = direction % 4
296
+ if direction:
297
+ im = im.transpose({1: PIL.Image.ROTATE_270,
298
+ 2: PIL.Image.ROTATE_180,
299
+ 3: PIL.Image.ROTATE_90}[direction])
300
+ return im
301
+
302
+ def apply_exif_orientation(self):
303
+ """ 摆正图片角度
304
+
305
+ Image.open读取图片时,是手机严格正放时拍到的图片效果,
306
+ 但手机拍照时是会记录旋转位置的,即可以判断是物理空间中,实际朝上、朝下的方向,
307
+ 从而识别出正拍(代号1),顺时针旋转90度拍摄(代号8),顺时针180度拍摄(代号3),顺时针270度拍摄(代号6)。
308
+ windows上的图片查阅软件能识别方向代号后正确摆放;
309
+ 为了让python处理图片的时候能增加这个属性的考虑,这个函数能修正识别角度返回新的图片。
310
+
311
+ 我自己写过个版本,后来发现 labelme.utils.image 写过功能更强的,抄了过来~~
312
+ """
313
+ im = self
314
+ try:
315
+ exif = im._getexif()
316
+ except AttributeError:
317
+ exif = None
318
+
319
+ if exif is None:
320
+ return im
321
+
322
+ exif = {
323
+ PIL.ExifTags.TAGS[k]: v
324
+ for k, v in exif.items()
325
+ if k in PIL.ExifTags.TAGS
326
+ }
327
+
328
+ orientation = exif.get("Orientation", None)
329
+
330
+ if orientation == 1:
331
+ # do nothing
332
+ return im
333
+ elif orientation == 2:
334
+ # left-to-right mirror
335
+ return PIL.ImageOps.mirror(im)
336
+ elif orientation == 3:
337
+ # rotate 180
338
+ return im.transpose(PIL.Image.ROTATE_180)
339
+ elif orientation == 4:
340
+ # top-to-bottom mirror
341
+ return PIL.ImageOps.flip(im)
342
+ elif orientation == 5:
343
+ # top-to-left mirror
344
+ return PIL.ImageOps.mirror(im.transpose(PIL.Image.ROTATE_270))
345
+ elif orientation == 6:
346
+ # rotate 270
347
+ return im.transpose(PIL.Image.ROTATE_270)
348
+ elif orientation == 7:
349
+ # top-to-right mirror
350
+ return PIL.ImageOps.mirror(im.transpose(PIL.Image.ROTATE_90))
351
+ elif orientation == 8:
352
+ # rotate 90
353
+ return im.transpose(PIL.Image.ROTATE_90)
354
+ else:
355
+ return im
356
+
357
+ def get_exif(self):
358
+ """ 旧函数名:查看图片的Exif信息 """
359
+ exif_data = self._getexif()
360
+ if exif_data:
361
+ exif = {PIL.ExifTags.TAGS[k]: v for k, v in exif_data.items() if k in PIL.ExifTags.TAGS}
362
+ else:
363
+ exif = None
364
+ return exif
365
+
366
+ def rgba2rgb(self):
367
+ if self.mode in ('RGBA', 'P'):
368
+ # 判断图片mode模式,如果是RGBA或P等可能有透明底,则和一个白底图片合成去除透明底
369
+ background = PIL.Image.new('RGBA', self.size, (255, 255, 255))
370
+ # composite是合成的意思。将右图的alpha替换为左图内容
371
+ self = PIL.Image.alpha_composite(background, self.convert('RGBA')).convert('RGB')
372
+ return self
373
+
374
+ def keep_subtitles(self, judge_func=None, trim_color=(255, 255, 255)):
375
+ im = self.to_cv2_image()
376
+ im = im.keep_subtitles(judge_func=judge_func, trim_color=trim_color)
377
+ return im.to_pil_image()
378
+
379
+
380
+ # pil相比cv,由于无法类似CvImg这样新建一个和np.ndarray等效的类,所以还是比较支持嵌入到Image中直接操作
381
+ inject_members(PilImg, PIL.Image.Image)
382
+
383
+ xlpil = PilImg # 与xlcv对称
384
+
385
+
386
+ def font_getsize(font, text):
387
+ """ 官方自带的font.getsize遇到换行符的text,计算不准确
388
+ """
389
+ texts = text.split('\n')
390
+ sizes = [font.getsize(t) for t in texts]
391
+ w = max([w for w, h in sizes])
392
+ h = sum([h for w, h in sizes])
393
+ return w, h
394
+
395
+
396
+ def create_text_image(text, size=None, *, xy=None, font_size=14, bg_color=None, text_color=None, **kwargs):
397
+ """ 生成文字图片
398
+
399
+ :param size: 注意我这里顺序是 (height, width)
400
+ 默认None,根据写入的文字动态生成图片大小
401
+ :param bg_color: 背景图颜色,如 (0, 0, 0)
402
+ 默认None,随机颜色
403
+ :param text_color: 文本颜色,如 (255, 255, 255)
404
+ 默认None,随机颜色
405
+ """
406
+ if size is None:
407
+ from PIL import ImageFont, ImageDraw
408
+ font_file = get_font_file(kwargs.get('font_type', 'simfang.ttf'))
409
+ font = ImageFont.truetype(font=str(font_file), size=font_size, encoding="utf-8")
410
+ w, h = font_getsize(font, text)
411
+ size = (h + 10, w + 10)
412
+ xy = (5, 0) # 自动生成尺寸的情况下,文本从左上角开始写
413
+
414
+ if bg_color is None:
415
+ bg_color = tuple([random.randint(0, 255) for i in range(3)])
416
+ if text_color is None:
417
+ text_color = tuple([random.randint(0, 255) for i in range(3)])
418
+
419
+ h, w = size
420
+ im: PilImg = PIL.Image.new('RGB', (w, h), tuple(bg_color))
421
+ im2 = im.plot_text(text, xy=xy, font_size=font_size, fill=tuple(text_color), **kwargs)
422
+
423
+ return im2
File without changes
@@ -0,0 +1,240 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # @Author : 陈坤泽
4
+ # @Email : 877362867@qq.com
5
+ # @Date : 2022/02/09 11:14
6
+
7
+
8
+ """
9
+ Apache ECharts: https://echarts.apache.org/zh/index.html
10
+ python版:pyechats的封装
11
+ """
12
+
13
+ from pyxllib.prog.pupil import check_install_package
14
+
15
+ check_install_package('pyecharts')
16
+
17
+ # import types
18
+
19
+ import pyecharts
20
+ from pyecharts import options as opts
21
+ from pyecharts.commons.utils import JsCode
22
+ from pyecharts.globals import ChartType
23
+ from pyecharts import types
24
+ from pyecharts.charts import Bar, Line, Radar
25
+ from pyecharts.charts.chart import Chart
26
+
27
+ from pyxllib.prog.pupil import inject_members
28
+ from pyxllib.prog.specialist import TicToc
29
+ from pyxllib.file.specialist import XlPath
30
+
31
+
32
+ class XlChart(Chart):
33
+ def set_title(self, title):
34
+ self.set_global_opts(title_opts=pyecharts.options.TitleOpts(title=title))
35
+
36
+ def add_series(self, name, data, *, type=None, color=None, labels=None,
37
+ **kwargs):
38
+ """ 垃圾pyecharts,毁我青春~~
39
+
40
+ 很多图x都不是等间距的,pyecharts处理不等间距x好像有很大的不兼容问题
41
+ 需要手动添加x、y坐标位置
42
+
43
+ :param list|tuple labels: 直接提供现成的文本标签
44
+
45
+ """
46
+ if type is None:
47
+ type = self.__class__.__name__.lower()
48
+
49
+ if labels:
50
+ s = [[x[0][0], x[1]] for x in zip(data, labels)]
51
+ fmt = JsCode(f"function(x){{var m = new Map({s}); return m.get(x.value[0]);}}")
52
+ if 'label' not in kwargs:
53
+ kwargs['label'] = opts.LabelOpts(is_show=True, formatter=fmt)
54
+ elif isinstance(kwargs['label'], opts.LabelOpts):
55
+ kwargs['label'].opts['formatter'] = fmt
56
+ else:
57
+ kwargs['label']['show'] = True
58
+ kwargs['label']['formatter'] = fmt
59
+
60
+ self._append_color(color)
61
+ # self._append_legend(name, is_selected=True)
62
+ self._append_legend(name)
63
+
64
+ self.options.get('series').append(
65
+ {
66
+ 'type': type,
67
+ 'name': name,
68
+ 'data': data,
69
+ **kwargs,
70
+ }
71
+ )
72
+ return self
73
+
74
+
75
+ inject_members(XlChart, Chart)
76
+
77
+
78
+ class XlLine(Line):
79
+ @classmethod
80
+ def from_dict(cls, yaxis, xaxis=None, *, title=None,
81
+ xaxis_name=None, yaxis_name=None, **kwargs):
82
+ """ 查看一个数据的折线图
83
+
84
+ :param list|dict yaxis: 列表数据,或者字典name: values表示的多组数据
85
+ """
86
+ c = cls()
87
+
88
+ if isinstance(yaxis, (list, tuple)):
89
+ yaxis = {'value': yaxis}
90
+ if xaxis is None:
91
+ xaxis = list(range(max([len(v) for v in yaxis.values()])))
92
+ c.add_xaxis(xaxis)
93
+ for k, v in yaxis.items():
94
+ c.add_yaxis(k, v)
95
+
96
+ configs = {
97
+ 'tooltip_opts': opts.TooltipOpts(trigger="axis"),
98
+ **kwargs,
99
+ }
100
+ c.set_global_opts(tooltip_opts=opts.TooltipOpts(trigger="axis"))
101
+ if title:
102
+ configs['title_opts'] = opts.TitleOpts(title=title)
103
+ if xaxis_name:
104
+ configs['xaxis_opts'] = opts.AxisOpts(name=xaxis_name, axislabel_opts=opts.LabelOpts(rotate=45, interval=0))
105
+ if yaxis_name:
106
+ configs['yaxis_opts'] = opts.AxisOpts(name=yaxis_name)
107
+ c.set_global_opts(**configs)
108
+ return c
109
+
110
+
111
+ class XlBar(Bar):
112
+
113
+ @classmethod
114
+ def from_dict(cls, yaxis, xaxis=None, *, title=None,
115
+ xaxis_name=None, yaxis_name=None, **kwargs):
116
+ """ 查看一个数据的条形图
117
+
118
+ :param list|dict yaxis: 列表数据,或者字典name: values表示的多组数据
119
+ """
120
+ c = cls()
121
+
122
+ if isinstance(yaxis, (list, tuple)):
123
+ yaxis = {'value': yaxis}
124
+ if xaxis is None:
125
+ xaxis = list(range(max([len(v) for v in yaxis.values()])))
126
+ c.add_xaxis(xaxis)
127
+ for k, v in yaxis.items():
128
+ c.add_yaxis(k, v)
129
+
130
+ configs = {
131
+ 'tooltip_opts': opts.TooltipOpts(trigger="axis"),
132
+ **kwargs,
133
+ }
134
+ c.set_global_opts(tooltip_opts=opts.TooltipOpts(trigger="axis"))
135
+ if title:
136
+ configs['title_opts'] = opts.TitleOpts(title=title)
137
+ if xaxis_name:
138
+ configs['xaxis_opts'] = opts.AxisOpts(name=xaxis_name)
139
+ if yaxis_name:
140
+ configs['yaxis_opts'] = opts.AxisOpts(name=yaxis_name)
141
+ c.set_global_opts(**configs)
142
+ return c
143
+
144
+ @classmethod
145
+ def from_list(cls, yaxis, xaxis=None, *, title=None):
146
+ """
147
+ >> browser(pyecharts.charts.Bar.from_list(numbers))
148
+ """
149
+ return cls.from_dict({'value': list(yaxis)}, xaxis=xaxis, title=title)
150
+
151
+ @classmethod
152
+ def from_data_split_into_groups(cls, data, groups, *, title=None):
153
+ """根据给定的组数自动拆分数据并生成条形图
154
+ :param list data: 数据清单
155
+ :param int groups: 要拆分成的组数
156
+ """
157
+ # 找到最大值和最小值
158
+ min_val, max_val = min(data), max(data)
159
+
160
+ # 计算间隔
161
+ interval = (max_val - min_val) / groups
162
+
163
+ # 分组和标签
164
+ group_counts = [0] * groups
165
+ labels = []
166
+ # todo 如果数据量特别大,这里应该排序后,再用特殊方法计算分组
167
+ for value in data:
168
+ index = min(int((value - min_val) / interval), groups - 1)
169
+ group_counts[index] += 1
170
+
171
+ for i in range(groups):
172
+ labels.append(f"{min_val + interval * i:.2f}-{min_val + interval * (i + 1):.2f}")
173
+ # t = cls.from_dict({'value': group_counts}, xaxis=labels, title=title)
174
+
175
+ return cls.from_dict({'value': group_counts}, xaxis=labels, title=title)
176
+
177
+
178
+ class XlRadar(Radar):
179
+ """ 雷达图 """
180
+
181
+ def __init__(self, *args, **kwargs):
182
+ super().__init__(*args, **kwargs)
183
+ self.color_idx = 0
184
+
185
+ def add(
186
+ self,
187
+ series_name: str,
188
+ data: types.Sequence[types.Union[opts.RadarItem, dict]],
189
+ *,
190
+ label_opts=None,
191
+ color: types.Optional[str] = None,
192
+ linestyle_opts=None,
193
+ **kwargs
194
+ ):
195
+ """ 标准库(2.0.5版)的雷达图颜色渲染有问题,这里要增加一个修正过程 """
196
+ if label_opts is None:
197
+ label_opts = opts.LabelOpts(is_show=False)
198
+
199
+ if linestyle_opts is None:
200
+ linestyle_opts = opts.LineStyleOpts(color=self.colors[self.color_idx % len(self.colors)])
201
+ self.color_idx += 1
202
+ elif linestyle_opts.get('color') is None:
203
+ linestyle_opts.update(color=self.colors[self.color_idx % len(self.colors)])
204
+ self.color_idx += 1
205
+
206
+ if color is None:
207
+ color = linestyle_opts.get('color')
208
+
209
+ return super(XlRadar, self).add(series_name, data,
210
+ label_opts=label_opts,
211
+ color=color,
212
+ linestyle_opts=linestyle_opts,
213
+ **kwargs)
214
+
215
+
216
+ inject_members(XlBar, Bar)
217
+
218
+
219
+ def render_echart_html(title='Awesome-pyecharts', body=''):
220
+ from pyxllib.text.xmllib import get_jinja_template
221
+ return get_jinja_template('echart_base.html').render(title=title, body=body)
222
+
223
+
224
+ # 绘制帕累托累计图
225
+ def draw_pareto_chart(data, accuracy=0.1, *, title='帕累托累积权重', value_unit_type='K'):
226
+ from pyxllib.algo.stat import pareto_accumulate
227
+ pts, labels = pareto_accumulate(data, accuracy=accuracy, value_unit_type=value_unit_type)
228
+ x = Line()
229
+ x.add_series(title, pts, labels=labels, label={'position': 'right'})
230
+ x.set_global_opts(
231
+ # x轴末尾要故意撑大一些,不然有部分内容会显示不全
232
+ xaxis_opts=opts.AxisOpts(name='条目数', max_=int(float(f'{pts[-1][0] * 1.2:.2g}'))),
233
+ yaxis_opts=opts.AxisOpts(name='累积和')
234
+ )
235
+ return x
236
+
237
+
238
+ if __name__ == '__main__':
239
+ with TicToc(__name__):
240
+ pass