gomyck-tools 1.5.5__py3-none-any.whl → 1.5.7__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.
ctools/ml/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ __author__ = 'haoyang'
4
+ __date__ = '2026/2/3 16:06'
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ __author__ = 'haoyang'
4
+ __date__ = '2026/2/3 16:06'
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ __author__ = 'haoyang'
4
+ __date__ = '2026/1/20 15:02'
5
+
6
+ import base64
7
+ from io import BytesIO
8
+
9
+ from PIL import Image
10
+
11
+
12
+ class ImageProcessor:
13
+
14
+ @staticmethod
15
+ def image_show(base64_str):
16
+ """base64字符串转PIL Image并显示"""
17
+ from io import BytesIO
18
+ import base64
19
+ img_data = base64.b64decode(base64_str)
20
+ img = Image.open(BytesIO(img_data))
21
+ img.show()
22
+
23
+ @staticmethod
24
+ def image_to_base64(img: Image.Image, format='PNG') -> str:
25
+ """PIL Image -> base64 字符串"""
26
+ buffer = BytesIO()
27
+ img.save(buffer, format=format)
28
+ return base64.b64encode(buffer.getvalue()).decode('utf-8')
29
+
30
+ @staticmethod
31
+ def crop_to_base64(img: Image.Image, coordinate) -> str:
32
+ """
33
+ img: PIL Image
34
+ coordinate: [xmin, ymin, xmax, ymax] 图像坐标
35
+ return: 裁剪区域的 base64
36
+ """
37
+ if not isinstance(coordinate, (list, tuple)) or len(coordinate) != 4:
38
+ raise ValueError("coordinate 必须是长度为 4 的 list 或 tuple")
39
+
40
+ xmin, ymin, xmax, ymax = [int(c) for c in coordinate]
41
+ cropped = img.crop((xmin, ymin, xmax, ymax))
42
+ return ImageProcessor.image_to_base64(cropped)
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ __author__ = 'haoyang'
4
+ __date__ = '2026/2/3 16:07'
5
+
6
+ import tempfile
7
+ import shutil
8
+ from pathlib import Path
9
+ from typing import Optional
10
+ from ctools.util.file_crypto import FileCrypto
11
+
12
+
13
+ class TempDecryptedDir:
14
+ """
15
+ 解密到临时目录的上下文工具
16
+ """
17
+
18
+ def __init__(self, encrypted_file: str, password: str):
19
+ self.encrypted_file = encrypted_file
20
+ self.password = password
21
+ self.temp_dir: Optional[Path] = None
22
+
23
+ def __enter__(self) -> str:
24
+ self.temp_dir = Path(
25
+ tempfile.mkdtemp(prefix="secure_model_")
26
+ )
27
+ FileCrypto.decrypt_to_directory(
28
+ encrypted_file=self.encrypted_file,
29
+ output_dir=str(self.temp_dir),
30
+ password=self.password
31
+ )
32
+ return str(self.temp_dir)
33
+
34
+ def __exit__(self, exc_type, exc, tb):
35
+ if self.temp_dir and self.temp_dir.exists():
36
+ shutil.rmtree(self.temp_dir, ignore_errors=True)
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ __author__ = 'haoyang'
4
+ __date__ = '2026/2/3 16:07'
5
+
6
+ import tarfile
7
+ import tempfile
8
+ import hashlib
9
+ from pathlib import Path
10
+ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
11
+ import secrets
12
+
13
+
14
+ class FileCrypto:
15
+ """
16
+ 文件 / 目录 加密解密工具
17
+ - AES-256-GCM
18
+ - 支持目录整体加密
19
+ """
20
+ NONCE_SIZE = 12
21
+ KEY_SIZE = 32
22
+ @staticmethod
23
+ def derive_key(password: str) -> bytes:
24
+ """
25
+ 从口令派生固定长度 key(简化版,工程可控)
26
+ """
27
+ return hashlib.sha256(password.encode("utf-8")).digest()
28
+
29
+ @staticmethod
30
+ def encrypt_directory(
31
+ src_dir: str,
32
+ output_file: str,
33
+ password: str
34
+ ) -> None:
35
+ """
36
+ 加密整个目录
37
+ """
38
+ src_dir = Path(src_dir).resolve()
39
+ output_file = Path(output_file).resolve()
40
+
41
+ with tempfile.NamedTemporaryFile(suffix=".tar", delete=False) as tmp_tar:
42
+ tar_path = Path(tmp_tar.name)
43
+
44
+ try:
45
+ # 1. 打包目录
46
+ with tarfile.open(tar_path, "w") as tar:
47
+ tar.add(src_dir, arcname=".")
48
+
49
+ # 2. 加密
50
+ key = FileCrypto.derive_key(password)
51
+ aesgcm = AESGCM(key)
52
+ nonce = secrets.token_bytes(FileCrypto.NONCE_SIZE)
53
+
54
+ data = tar_path.read_bytes()
55
+ encrypted = aesgcm.encrypt(nonce, data, None)
56
+
57
+ output_file.write_bytes(nonce + encrypted)
58
+
59
+ finally:
60
+ tar_path.unlink(missing_ok=True)
61
+
62
+ @staticmethod
63
+ def decrypt_to_directory(
64
+ encrypted_file: str,
65
+ output_dir: str,
66
+ password: str
67
+ ) -> None:
68
+ """
69
+ 解密文件并解包到指定目录
70
+ """
71
+ encrypted_file = Path(encrypted_file).resolve()
72
+ output_dir = Path(output_dir).resolve()
73
+ output_dir.mkdir(parents=True, exist_ok=True)
74
+
75
+ raw = encrypted_file.read_bytes()
76
+ nonce = raw[:FileCrypto.NONCE_SIZE]
77
+ ciphertext = raw[FileCrypto.NONCE_SIZE:]
78
+
79
+ key = FileCrypto.derive_key(password)
80
+ aesgcm = AESGCM(key)
81
+
82
+ data = aesgcm.decrypt(nonce, ciphertext, None)
83
+
84
+ with tempfile.NamedTemporaryFile(suffix=".tar", delete=False) as tmp_tar:
85
+ tmp_tar.write(data)
86
+ tar_path = Path(tmp_tar.name)
87
+
88
+ try:
89
+ with tarfile.open(tar_path, "r") as tar:
90
+ tar.extractall(output_dir)
91
+ finally:
92
+ tar_path.unlink(missing_ok=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gomyck-tools
3
- Version: 1.5.5
3
+ Version: 1.5.7
4
4
  Summary: A tools collection for python development by hao474798383
5
5
  Author-email: gomyck <hao474798383@163.com>
6
6
  License-Expression: Apache-2.0
@@ -32,21 +32,22 @@ Requires-Dist: redis==7.1.0
32
32
  Requires-Dist: uvloop>=0.22.1
33
33
  Requires-Dist: pyyaml>=6.0.3
34
34
  Requires-Dist: uiautomation>=2.0.20
35
+ Requires-Dist: req>=1.0.0
35
36
  Provides-Extra: kwc
36
37
  Requires-Dist: imageio>=2.37.0; extra == "kwc"
37
38
  Requires-Dist: wordcloud>=1.9.4; extra == "kwc"
38
39
  Requires-Dist: jieba==0.42.1; extra == "kwc"
39
- Requires-Dist: paddlepaddle==3.2.2; extra == "kwc"
40
+ Requires-Dist: paddlepaddle==3.3.0; extra == "kwc"
40
41
  Provides-Extra: cut
41
42
  Requires-Dist: jieba==0.42.1; extra == "cut"
42
- Requires-Dist: paddlepaddle==3.2.2; extra == "cut"
43
+ Requires-Dist: paddlepaddle==3.3.0; extra == "cut"
43
44
  Provides-Extra: ml
44
45
  Requires-Dist: scikit-learn>=1.7.1; extra == "ml"
45
46
  Requires-Dist: pandas>=2.3.2; extra == "ml"
46
47
  Requires-Dist: lxml>=6.0.1; extra == "ml"
47
48
  Requires-Dist: xlrd>=2.0.2; extra == "ml"
48
49
  Provides-Extra: xj-plate-server
49
- Requires-Dist: paddlepaddle==3.2.2; extra == "xj-plate-server"
50
+ Requires-Dist: paddlepaddle==3.3.0; extra == "xj-plate-server"
50
51
  Dynamic: license-file
51
52
 
52
53
  # Gomyck-Tools
@@ -1,7 +1,7 @@
1
1
  ctools/__init__.py,sha256=7EtnbO7mi1iYQeyU4CLusYrwO96_dC7eR7jboyWAqvg,1089
2
2
  ctools/application.py,sha256=QFLbwmjznz2HAwVyph9PhzMpJUU5R82eMaVcw5UDSMk,15899
3
3
  ctools/aspect.py,sha256=lXrpeu_F3w6v2Hu2yOwQIRGqfDN25_H-1YyW6fPt_mw,1667
4
- ctools/authcode_validator.py,sha256=g-AFBCEVvtLaoRTXn4qZgVfMv3TUFFYFUmc4fn8Hps8,12312
4
+ ctools/authcode_validator.py,sha256=YGQFxNcGNO5Att2fC1LuH0GfmVoRPC5eef82OEC0_fg,10722
5
5
  ctools/call.py,sha256=TFFC8PqvCu0PS0XelmV4QXdXezQiUsEacxg3RgKvdwE,1572
6
6
  ctools/cdate.py,sha256=OhKAaQfo2Rxd3Jx3g9AfPsaISRoLkstqZdaGT4ZZr_I,3096
7
7
  ctools/cdebug.py,sha256=_mihZRCEx_bi7Kv_QPjP4MPLNFrl-GR1Y_irTgOP7OU,4021
@@ -48,9 +48,10 @@ ctools/database/database.py,sha256=IQzPw2fOJh0Xn_OdSMeEOXBEV9YCRDoszg-dwJPXTTU,7
48
48
  ctools/geo/__init__.py,sha256=OkUaZv5ckkXJFNbRyFZqkX5m3GxTueEGEBU99_jJQNE,98
49
49
  ctools/geo/coord_trans.py,sha256=UWCU1wnrTDU1aLSVAwmiHOeH7Pu-Dp8lDLAngDG48NM,3761
50
50
  ctools/geo/douglas_rarefy.py,sha256=bJo6TwNxPa-7-8MOi8MULxeqnz4cvIJN-oXqBDWNAVM,4883
51
- ctools/ml/image_process.py,sha256=keiIbcCkCZq-dp3PBpI-8tHFXNqKJKXATcrojMEpAGs,3168
52
- ctools/ml/img_extractor.py,sha256=x_o3il6gWoKMBRgJ9m7HNt70nY3Etr5QcPzEKq76oiA,1829
53
- ctools/ml/ppi.py,sha256=q9y3Y_Lp5ZQTeIKIwvGOeZrzHYfefa7QDN73wcsVMDw,10919
51
+ ctools/ml/__init__.py,sha256=hVR5_6Nh7y5VL-3oijXYuquxm754pB1o4nbiBjWb9aM,97
52
+ ctools/ml/model_loader.py,sha256=U7DK3azEPZJ97g9swV9hFHQ887SD5F4V40uMvBySsKA,915
53
+ ctools/ml/interface/__init__.py,sha256=hVR5_6Nh7y5VL-3oijXYuquxm754pB1o4nbiBjWb9aM,97
54
+ ctools/ml/interface/image_processor.py,sha256=mzUuURjv9Ktqgu2ovL0nxcyt2nTDH3-aiVUSDz-g2eM,1172
54
55
  ctools/office/__init__.py,sha256=wum34b8YJg0qD7uKdDEbozSE8RIxWqTVa44CCIZyqPU,98
55
56
  ctools/office/cword.py,sha256=bIthKmf0oBqjcdkrU5hFDAPp66ZrjeMNzjIxOlMPeCc,837
56
57
  ctools/office/word_fill.py,sha256=ZoTxz0-agEy5atIRWqYmQZz82nVr2zz9JebSTMkulIo,18214
@@ -70,6 +71,7 @@ ctools/util/cklock.py,sha256=uS1g8H7jtQ5dINKe-k23osQ1sjvZSZrkXGwH6qKcgNo,3234
70
71
  ctools/util/compile_util.py,sha256=Nybh3vnkurIKnPnubdYzigjnzFu4GaTMKPvqFdibxmE,510
71
72
  ctools/util/config_util.py,sha256=FBacCqQ1kPtRl1VEz0IsAuxI0zdpqUiuBv11G8kiDLE,1230
72
73
  ctools/util/env_config.py,sha256=L98G9LPdpD7Yl5XbA_-KfkcA4mDunQoKiYtK5w7QR-s,1790
74
+ ctools/util/file_crypto.py,sha256=qgNBrqTcl61V0fVMOz3JID91FfbDcwd4uyp01vuE7sw,2258
73
75
  ctools/util/html_soup.py,sha256=rnr8M3gn3gQGo-wNaNFXDjdzp8AAkv9o4yqfIIfO-zw,1567
74
76
  ctools/util/http_util.py,sha256=cx0FRnPLFdJ0mF9UYphl40SZj68fqG30Q0udku9hZIE,769
75
77
  ctools/util/image_process.py,sha256=nqJOi2p8wLe8wRsfkH99MyEYSjE9i4fthxBJwrrZVB8,835
@@ -85,8 +87,8 @@ ctools/web/ctoken.py,sha256=qsxrQY7IWgpPe5HyiDDnf50vsRX27QXMWPzam7bGu_Y,2646
85
87
  ctools/web/download_util.py,sha256=v0JTXiED1bvoWFfwfd-LD5s7_aoRQ0lCkaGwSnSp7WI,1954
86
88
  ctools/web/params_util.py,sha256=eJDV3PSq-ZHb8UZf6xqs8kOhbyZzits1H9yPoUBIDXg,828
87
89
  ctools/web/upload_util.py,sha256=z1QQCi4SFx08jrAQH5-Y_ShiM4MghuD_5Qz6V9KK_4U,1076
88
- gomyck_tools-1.5.5.dist-info/licenses/LICENSE,sha256=X25ypfH9E6VTht2hcO8k7LCSdHUcoG_ALQt80jdYZfY,547
89
- gomyck_tools-1.5.5.dist-info/METADATA,sha256=5oF3ZH85g9MGhcJ0mSsVK01F0aUj5Gd272oeJ60W0f8,1957
90
- gomyck_tools-1.5.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
91
- gomyck_tools-1.5.5.dist-info/top_level.txt,sha256=-MiIH9FYRVKp1i5_SVRkaI-71WmF1sZSRrNWFU9ls3s,7
92
- gomyck_tools-1.5.5.dist-info/RECORD,,
90
+ gomyck_tools-1.5.7.dist-info/licenses/LICENSE,sha256=X25ypfH9E6VTht2hcO8k7LCSdHUcoG_ALQt80jdYZfY,547
91
+ gomyck_tools-1.5.7.dist-info/METADATA,sha256=ahdr3pJFSRaeugXRe8qzwMYVgns8YA6XhQfRLNJhB-o,1983
92
+ gomyck_tools-1.5.7.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
93
+ gomyck_tools-1.5.7.dist-info/top_level.txt,sha256=-MiIH9FYRVKp1i5_SVRkaI-71WmF1sZSRrNWFU9ls3s,7
94
+ gomyck_tools-1.5.7.dist-info/RECORD,,
@@ -1,121 +0,0 @@
1
- import random
2
-
3
- import numpy as np
4
- from PIL import Image, ImageDraw, ImageFont
5
-
6
-
7
- def preprocess(img, img_size):
8
- mean = [0.485, 0.456, 0.406]
9
- std = [0.229, 0.224, 0.225]
10
- img = resize(img, img_size)
11
- img = img[:, :, ::-1].astype('float32') # RGB->BGR
12
- img = normalize(img, mean, std)
13
- img = img.transpose((2, 0, 1)) # hwc -> chw
14
- #show_preprocess(img, mean, std)
15
- return img[np.newaxis, :]
16
-
17
-
18
- def resize(img, target_size):
19
- """
20
- img: numpy.ndarray (H,W,3) BGR or RGB
21
- return: numpy.ndarray (target_size, target_size, 3)
22
- """
23
- img = Image.fromarray(img)
24
- img = img.resize((target_size, target_size), Image.BILINEAR)
25
- return np.array(img)
26
-
27
-
28
- def normalize(img, mean, std):
29
- img = img / 255.0
30
- mean = np.array(mean)[np.newaxis, np.newaxis, :]
31
- std = np.array(std)[np.newaxis, np.newaxis, :]
32
- img -= mean
33
- img /= std
34
- return img
35
-
36
-
37
- def show_preprocess(chw_img, mean, std):
38
- """
39
- chw_img: (3, H, W), float32, normalized
40
- """
41
- img = chw_img.copy()
42
- # 1. CHW -> HWC
43
- img = img.transpose(1, 2, 0)
44
- # 2. de-normalize
45
- img = img * std + mean
46
- img = img * 255.0
47
- # 3. clamp + uint8
48
- img = np.clip(img, 0, 255).astype(np.uint8)
49
- Image.fromarray(img).show()
50
-
51
-
52
- def draw_bbox(img, result, threshold=0.5, save_name='res.jpg', scale_factor=None, im_size=320, class_names=None):
53
- draw = ImageDraw.Draw(img)
54
-
55
- if scale_factor is not None:
56
- h_scale, w_scale = scale_factor[0]
57
- else:
58
- h_scale = w_scale = 1.
59
-
60
- # 类别颜色随机但固定
61
- category_colors = {}
62
- if class_names is not None:
63
- for cls in class_names:
64
- category_colors[cls] = tuple(random.randint(0, 255) for _ in range(3))
65
-
66
- # 字体
67
- try:
68
- font = ImageFont.truetype("arial.ttf", 15)
69
- except:
70
- font = ImageFont.load_default()
71
-
72
- for res in result:
73
- cat_id, score, bbox = res[0], res[1], res[2:]
74
- if score < threshold:
75
- continue
76
-
77
- # 归一化 bbox -> 模型输入尺寸
78
- xmin = bbox[0] * im_size
79
- ymin = bbox[1] * im_size
80
- xmax = bbox[2] * im_size
81
- ymax = bbox[3] * im_size
82
-
83
- # 模型输入尺寸 -> 原图
84
- xmin = xmin / w_scale
85
- xmax = xmax / w_scale
86
- ymin = ymin / h_scale
87
- ymax = ymax / h_scale
88
-
89
- # 类别名和颜色
90
- if class_names is not None:
91
- class_name = class_names[int(cat_id)]
92
- color = category_colors[class_name]
93
- text = f"{class_name}:{score:.2f}"
94
-
95
- # 获取文字尺寸,兼容所有版本 Pillow
96
- try:
97
- text_width, text_height = font.getsize(text) # 旧版 / 大部分版本
98
- except AttributeError:
99
- # Pillow 9.2+ 推荐用 getbbox
100
- bbox_font = font.getbbox(text)
101
- text_width = bbox_font[2] - bbox_font[0]
102
- text_height = bbox_font[3] - bbox_font[1]
103
-
104
- text_origin = (xmin, max(0, ymin - text_height)) # 框上方显示
105
- draw.text(text_origin, text, fill=color, font=font)
106
- else:
107
- color = 'red'
108
-
109
- # 画矩形框
110
- draw.rectangle([xmin, ymin, xmax, ymax], outline=color, width=2)
111
-
112
- img.save(save_name)
113
-
114
-
115
- def image_show(base64_str):
116
- """base64字符串转PIL Image并显示"""
117
- from io import BytesIO
118
- import base64
119
- img_data = base64.b64decode(base64_str)
120
- img = Image.open(BytesIO(img_data))
121
- img.show()
@@ -1,59 +0,0 @@
1
- #!/usr/bin/env python
2
- # -*- coding: UTF-8 -*-
3
- __author__ = 'haoyang'
4
- __date__ = '2026/1/20 15:02'
5
-
6
- import base64
7
- from io import BytesIO
8
-
9
- class ClassRegionBase64ExtractorPIL:
10
- def __init__(self, class_names, target_classes=None, threshold=0.5):
11
- """
12
- class_names: 模型类别列表
13
- target_classes: 只截取的类别名列表,None 表示全部
14
- threshold: 置信度阈值
15
- """
16
- self.class_names = class_names
17
- self.target_classes = target_classes
18
- self.threshold = threshold
19
-
20
- @staticmethod
21
- def image_to_base64(img, format='PNG'):
22
- """
23
- PIL Image -> base64 字符串
24
- """
25
- buffer = BytesIO()
26
- img.save(buffer, format=format)
27
- return base64.b64encode(buffer.getvalue()).decode('utf-8')
28
-
29
- def extract(self, img, results, scale_factor=None, im_size=320):
30
- """
31
- img: PIL Image
32
- results: 模型输出 [[cat_id, score, xmin, ymin, xmax, ymax], ...]
33
- scale_factor: np.array([[h_scale, w_scale]]) 或 None
34
- im_size: 模型输入尺寸
35
- return: List[Dict] -> [{"class": class_name, "score": score, "base64": base64_str}, ...]
36
- """
37
- outputs = []
38
- for res in results:
39
- cat_id, score, bbox = res[0], res[1], res[2:]
40
- if score < self.threshold or cat_id > len(self.class_names) - 1:
41
- continue
42
- class_name = self.class_names[int(cat_id)]
43
- if self.target_classes is not None and class_name not in self.target_classes:
44
- continue
45
- xmin = bbox[0]
46
- ymin = bbox[1]
47
- xmax = bbox[2]
48
- ymax = bbox[3]
49
- # 裁剪
50
- pil_img_threadsafe = img.copy()
51
- cropped = pil_img_threadsafe.crop((xmin, ymin, xmax, ymax))
52
- # 转 base64
53
- b64_str = self.image_to_base64(cropped)
54
- outputs.append({
55
- "class": class_name,
56
- "score": float(score),
57
- "base64": b64_str
58
- })
59
- return outputs