zpylibs 0.1.0__tar.gz

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.
zpylibs-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,31 @@
1
+ Metadata-Version: 2.4
2
+ Name: zpylibs
3
+ Version: 0.1.0
4
+ Summary: 个人常用的python库工具
5
+ Author-email: "lionel.zc" <lionel.zhuc@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/z541880714/zpylibs
8
+ Project-URL: Bug Tracker, https://github.com/z541880714/zpylibs/issues
9
+ Keywords: utility,tools,pylib
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: requests>=2.28.0
17
+ Requires-Dist: numpy>=1.20.0
18
+ Requires-Dist: Pillow
19
+ Requires-Dist: opencv-python
20
+ Requires-Dist: exifread
21
+ Requires-Dist: matplotlib
22
+ Requires-Dist: pandas
23
+ Requires-Dist: sqlalchemy
24
+ Requires-Dist: flask
25
+ Requires-Dist: python-pptx
26
+
27
+ # zpylibs
28
+
29
+ ## 2024-11-28
30
+
31
+ 1. 加入了 image 的 截取圆形图片的库
@@ -0,0 +1,5 @@
1
+ # zpylibs
2
+
3
+ ## 2024-11-28
4
+
5
+ 1. 加入了 image 的 截取圆形图片的库
@@ -0,0 +1,52 @@
1
+ [build-system]
2
+ # 指定构建项目所需的工具,这里使用最主流的 setuptools
3
+ requires = ["setuptools>=61.0", "wheel"]
4
+ build-backend = "setuptools.build_meta"
5
+
6
+ [project]
7
+ # 1. 基础信息配置
8
+ name = "zpylibs" # 必填!您的库在 PyPI 上的名字(必须全网唯一,建议去官网搜一下是否被占)
9
+ version = "0.1.0" # 必填!版本号,后续更新只需往上递增(如 0.1.1, 0.2.0)
10
+ description = "个人常用的python库工具"
11
+ readme = "README.md" # 指定项目长篇介绍文档,上传后会作为 PyPI 主页展示
12
+ requires-python = ">=3.10" # 支持的最低 Python 版本
13
+ license = { text = "MIT" } # 开源协议(如 MIT, Apache-2.0, GPL 等)
14
+
15
+ # 2. 作者信息
16
+ authors = [
17
+ { name = "lionel.zc", email = "lionel.zhuc@gmail.com" }
18
+ ]
19
+
20
+ # 3. 关键词与分类(方便别人在 PyPI 官网上检索到你的库)
21
+ keywords = ["utility", "tools", "pylib"]
22
+ classifiers = [
23
+ "Programming Language :: Python :: 3",
24
+ "License :: OSI Approved :: MIT License",
25
+ "Operating System :: OS Independent",
26
+ "Development Status :: 3 - Alpha", # 或者是 "5 - Production/Stable"
27
+ ]
28
+
29
+ # 4. 第三方依赖(关键配置:如果你的库 import 了别的外网库,写在这里!)
30
+ # 当别人 pip install 你的库时,pip 会自动下载并安装这里列出的所有依赖
31
+ dependencies = [
32
+ "requests>=2.28.0",
33
+ "numpy>=1.20.0",
34
+ "Pillow",
35
+ "opencv-python",
36
+ "exifread",
37
+ "matplotlib",
38
+ "pandas",
39
+ "sqlalchemy",
40
+ "flask",
41
+ "python-pptx"
42
+ ]
43
+
44
+ [project.urls]
45
+ # 可选:配置在 PyPI 页面右侧显示的链接导航
46
+ "Homepage" = "https://github.com/z541880714/zpylibs"
47
+ "Bug Tracker" = "https://github.com/z541880714/zpylibs/issues"
48
+
49
+ [tool.setuptools]
50
+ # 告诉打包工具去哪里找你的源码。
51
+ # 推荐使用标准的 src 布局(项目根目录下建一个 src 文件夹,里面放你的核心代码文件夹)
52
+ package-dir = { "" = "src" }
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,58 @@
1
+ import math
2
+
3
+
4
+ def rgb2hsv(r, g, b):
5
+ '''
6
+ RGB转HSV
7
+ r,g,b在(0-255)
8
+ '''
9
+ r, g, b = r / 255.0, g / 255.0, b / 255.0
10
+ mx, mn = max(r, g, b), min(r, g, b)
11
+ m = mx - mn
12
+ if mx == mn:
13
+ h = 0
14
+ elif mx == r:
15
+ if g >= b:
16
+ h = ((g - b) / m) * 60
17
+ else:
18
+ h = ((g - b) / m) * 60 + 360
19
+ elif mx == g:
20
+ h = ((b - r) / m) * 60 + 120
21
+ elif mx == b:
22
+ h = ((r - g) / m) * 60 + 240
23
+ if mx == 0:
24
+ s = 0
25
+ else:
26
+ s = m / mx
27
+ v = mx
28
+ return h, s, v
29
+
30
+
31
+ def hsv2rgb(h, s, v):
32
+ '''
33
+ HSV转RGB
34
+ 注意, h在(0-360), s,v在(0-1)
35
+ '''
36
+ h, s, v = map(float, (h, s, v))
37
+ h60 = h / 60.0
38
+ h60f = math.floor(h60)
39
+ hi = int(h60f) % 6
40
+ f = h60 - h60f
41
+ p = v * (1 - s)
42
+ q = v * (1 - f * s)
43
+ t = v * (1 - (1 - f) * s)
44
+ r, g, b = 0, 0, 0
45
+ if hi == 0:
46
+ r, g, b = v, t, p
47
+ elif hi == 1:
48
+ r, g, b = q, v, p
49
+ elif hi == 2:
50
+ r, g, b = p, v, t
51
+ elif hi == 3:
52
+ r, g, b = p, q, v
53
+ elif hi == 4:
54
+ r, g, b = t, p, v
55
+ elif hi == 5:
56
+ r, g, b = v, p, q
57
+ r, g, b = int(r * 255), int(g * 255), int(b * 255)
58
+ return r, g, b
@@ -0,0 +1,55 @@
1
+ import os
2
+ import cv2
3
+ import numpy as np
4
+
5
+ SIZE = 1024
6
+
7
+
8
+ def make_circle(image):
9
+ size = (SIZE, SIZE)
10
+ mask = np.zeros((SIZE, SIZE), dtype=np.uint8)
11
+ cv2.circle(mask, (SIZE // 2, SIZE // 2), SIZE // 2, 255, -1)
12
+ output = cv2.resize(image, size)
13
+ output[:, :, 3] = mask
14
+ output = cv2.GaussianBlur(output, (5, 5), 0) # Apply Gaussian blur to reduce aliasing
15
+ return output
16
+
17
+
18
+ def resize_and_crop(image, size):
19
+ h, w = image.shape[:2]
20
+ aspect_ratio = w / h
21
+
22
+ new_h = int(min(SIZE, SIZE / aspect_ratio))
23
+ new_w = int(aspect_ratio * new_h)
24
+ resized_image = cv2.resize(image, (new_w, new_h))
25
+ new_size = min(new_h, new_w)
26
+ top = (new_h - new_size) // 2
27
+ left = (new_w - new_size) // 2
28
+ result = resized_image[top:top + new_size, left:left + new_size]
29
+ return result
30
+
31
+
32
+ def process_image(file_path, output_directory):
33
+ try:
34
+ img = cv2.imread(file_path)
35
+ if img is None:
36
+ raise ValueError(f"Image not found or unable to read: {file_path}")
37
+ img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
38
+ img = resize_and_crop(img, SIZE)
39
+ img = make_circle(img)
40
+ output_path = os.path.join(output_directory, f'circle_{os.path.basename(file_path)}.png')
41
+ cv2.imwrite(output_path, img)
42
+ except Exception as e:
43
+ print(f"Error processing {file_path}: {e}")
44
+
45
+
46
+ def process_images(input_directory, output_directory):
47
+ if not os.path.exists(output_directory):
48
+ os.makedirs(output_directory)
49
+ for entry in os.scandir(input_directory):
50
+ if entry.is_file() and entry.name.lower().endswith(('.png', '.jpg', '.jpeg')):
51
+ process_image(entry.path, output_directory)
52
+
53
+
54
+ if __name__ == '__main__':
55
+ process_images('res', 'out')
@@ -0,0 +1,38 @@
1
+ import os
2
+ from PIL import Image, ImageOps, ImageDraw
3
+
4
+ SIZE = 1024
5
+
6
+
7
+ def make_circle(image):
8
+ size = (SIZE, SIZE)
9
+ mask = Image.new('L', size, 0)
10
+ draw = ImageDraw.Draw(mask)
11
+ draw.ellipse((0, 0) + size, fill=255)
12
+ output = ImageOps.fit(image, size, centering=(0.5, 0.5), method=Image.LANCZOS)
13
+ output.putalpha(mask)
14
+ return output
15
+
16
+
17
+ def process_image(file_path, output_directory):
18
+ try:
19
+ with Image.open(file_path) as img:
20
+ img.thumbnail((SIZE, SIZE), Image.LANCZOS)
21
+ img = img.convert('RGBA')
22
+ img = make_circle(img)
23
+ output_path = os.path.join(output_directory, f'circle_{os.path.basename(file_path)}')
24
+ img.save(output_path, 'PNG', quality=95)
25
+ except Exception as e:
26
+ print(f"Error processing {file_path}: {e}")
27
+
28
+
29
+ def process_images(input_directory, output_directory):
30
+ if not os.path.exists(output_directory):
31
+ os.makedirs(output_directory)
32
+ for entry in os.scandir(input_directory):
33
+ if entry.is_file() and entry.name.lower().endswith(('.png', '.jpg', '.jpeg')):
34
+ process_image(entry.path, output_directory)
35
+
36
+
37
+ if __name__ == '__main__':
38
+ process_images('res', 'out')
@@ -0,0 +1,86 @@
1
+ # encoding=utf-8
2
+ import sys
3
+ import os
4
+ import exifread
5
+ import re
6
+ from datetime import datetime
7
+ import requests
8
+
9
+
10
+ def get_city_from_gps(_longitude, _latitude):
11
+ url = "https://restapi.amap.com/v3/geocode/regeo?key=6169f44183f51488a26aa0302e868b9c&location={},{}".format(
12
+ _longitude, _latitude)
13
+ print('url:', url)
14
+ headers = {
15
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chro'
16
+ 'me/53.0.2785.104 Safari/537.36 Core/1.53.2372.400 QQBrowser/9.5.10548.400'
17
+ }
18
+ r = requests.get(url, headers=headers)
19
+ print("response :", r.text)
20
+
21
+
22
+ # 高德 key 名称:pyHttp; key:6169f44183f51488a26aa0302e868b9c
23
+ # url: https://restapi.amap.com/v3/geocode/regeo?key=6169f44183f51488a26aa0302e868b9c&location=116.310003,39.991957
24
+ def get_exif_datetime(img_path):
25
+ global longitude
26
+ pass
27
+ with open(img_path, 'rb') as f:
28
+ tags = exifread.process_file(f)
29
+ key_list = ['Image DateTime', 'EXIF DateTimeOriginal', 'EXIF DateTimeDigitized']
30
+ ret_datetime = None
31
+ for key in key_list:
32
+ if not tags.__contains__(key):
33
+ continue
34
+ datetime_str = str(tags[key])
35
+ # print(datetime_str)
36
+ pattern = r'^(\d+):(\d+):(\d+) (\d+):(\d+):(\d+)$'
37
+ ret = re.match(pattern, datetime_str.strip(), re.I)
38
+ if ret is None:
39
+ continue
40
+ # print("regix ret:", ret.groups())
41
+ ret_groups = ret.groups()
42
+ ret_datetime = datetime(int(ret_groups[0]), int(ret_groups[1]), int(ret_groups[2]),
43
+ int(ret_groups[3]), int(ret_groups[4]), int(ret_groups[5]))
44
+ break
45
+
46
+ def analyze_coordination(_key):
47
+ content = str(tags[_key])
48
+ _ret = re.match(r'^\[(\d+), (\d+),.*]$', content.strip(), re.I)
49
+ return float(_ret.group(1)) + float(_ret.group(2)) / 60
50
+
51
+ longitude, latitude = 0, 0
52
+ if tags.__contains__('GPS GPSLongitude'):
53
+ longitude = analyze_coordination('GPS GPSLongitude')
54
+
55
+ if tags.__contains__('GPS GPSLatitude'):
56
+ latitude = analyze_coordination('GPS GPSLatitude')
57
+ coord_info = None if longitude * latitude == 0 else (longitude, latitude)
58
+ get_city_from_gps(longitude, latitude)
59
+ return ret_datetime, coord_info
60
+
61
+
62
+ def main(img_path):
63
+ if os.path.isdir(img_path):
64
+ err_list = []
65
+ no_jpeg_count = 0
66
+ for root, dirs, files in os.walk(img_path):
67
+ for f_path in files:
68
+ _path = os.path.join(root, f_path)
69
+ if not _path.endswith('.jpeg'):
70
+ no_jpeg_count += 1
71
+ continue
72
+ ret_datetime, ret_coord = get_exif_datetime(_path)
73
+ if ret_datetime is None:
74
+ err_list.append(_path)
75
+ print("\n".join(err_list))
76
+ print("No. of JPEG Images: {}".format(no_jpeg_count))
77
+ elif os.path.isfile(img_path):
78
+ _datetime, coord = get_exif_datetime(img_path)
79
+ print("datetime:", _datetime, " gps:", coord)
80
+ else:
81
+ print("image path not exist...")
82
+
83
+
84
+ if __name__ == '__main__':
85
+ if len(sys.argv) == 2:
86
+ main(sys.argv[1])
File without changes
@@ -0,0 +1,101 @@
1
+ import unittest
2
+ from pathlib import Path
3
+
4
+ import numpy as np
5
+
6
+ import onnx
7
+ from onnx import numpy_helper, shape_inference
8
+
9
+ _cur_dir = Path(__file__).parent
10
+ onnx_path_std = _cur_dir / "models/model.onnx"
11
+ out_put = _cur_dir / "output" / "models.onnx"
12
+ out_put.parent.mkdir(parents=True, exist_ok=True)
13
+
14
+
15
+ class U(unittest.TestCase):
16
+ def test_main(self):
17
+ """
18
+ python -m unittest tests.test_onnx_static_2_dynamic.U.test_main
19
+ """
20
+ out_put.parent.mkdir(parents=True, exist_ok=True)
21
+ update_onnx_to_dynamic(onnx_path_std, out_put)
22
+
23
+
24
+ def update_onnx_to_dynamic(input_onnx, output_onnx):
25
+ model = onnx.load(input_onnx)
26
+ graph = model.graph
27
+
28
+ # 1. 彻底清除旧的形状缓存
29
+ while len(graph.value_info) > 0:
30
+ graph.value_info.pop()
31
+
32
+ # 2. 设置入口和出口
33
+ for ipt in graph.input:
34
+ if ipt.name == "images":
35
+ ipt.type.tensor_type.shape.dim[0].dim_param = "batch"
36
+ for opt in graph.output:
37
+ if opt.name == "output0":
38
+ opt.type.tensor_type.shape.dim[0].dim_param = "batch"
39
+
40
+ # === 【精准靶向定位】 ===
41
+ reshape_shape_names = set()
42
+ for node in graph.node:
43
+ if node.op_type == "Reshape" and len(node.input) >= 2:
44
+ reshape_shape_names.add(node.input[1])
45
+
46
+ # 3. 靶向修复 1:Initializer
47
+ for init in graph.initializer:
48
+ if init.name in reshape_shape_names:
49
+ arr = numpy_helper.to_array(init).copy()
50
+ if arr.ndim == 1 and arr[0] == 1:
51
+ print(f"🎯 修正 Reshape Initializer: {init.name} -> 使用 0 (动态透传)")
52
+ arr[0] = 0 # <--- 【关键修改】使用 0 代替 -1
53
+ init.CopyFrom(numpy_helper.from_array(arr, name=init.name))
54
+
55
+ # 4. 靶向修复 2:Constant 节点
56
+ for node in graph.node:
57
+ if node.op_type == "Constant" and node.output[0] in reshape_shape_names:
58
+ for attr in node.attribute:
59
+ if attr.name == "value":
60
+ arr = numpy_helper.to_array(attr.t).copy()
61
+ if arr.ndim == 1 and arr[0] == 1:
62
+ print(
63
+ f"🎯 修正 Reshape Constant: {node.name} -> 使用 0 (动态透传)"
64
+ )
65
+ arr[0] = 0 # <--- 【关键修改】使用 0 代替 -1
66
+ attr.t.CopyFrom(numpy_helper.from_array(arr, name=attr.t.name))
67
+
68
+ # 5. 执行全图形状推导
69
+ try:
70
+ model = shape_inference.infer_shapes(model)
71
+ except Exception as e:
72
+ print(f"⚠️ 推导警告: {e}")
73
+
74
+ # 6. 【全图符号归一化】擦除 unk_x
75
+ model = unify_batch_dimension(model, "batch")
76
+
77
+ onnx.save(model, str(output_onnx))
78
+
79
+
80
+ def unify_batch_dimension(model, target_name="batch"):
81
+ graph = model.graph
82
+
83
+ def fix_tensor_dim(tensor_type):
84
+ if tensor_type.HasField("shape"):
85
+ dim = tensor_type.shape.dim
86
+ if len(dim) > 0:
87
+ if dim[0].HasField("dim_param") or dim[0].dim_value == 1:
88
+ dim[0].dim_param = target_name
89
+
90
+ for info in graph.value_info:
91
+ fix_tensor_dim(info.type.tensor_type)
92
+ for opt in graph.output:
93
+ fix_tensor_dim(opt.type.tensor_type)
94
+ for ipt in graph.input:
95
+ fix_tensor_dim(ipt.type.tensor_type)
96
+
97
+ return model
98
+
99
+
100
+ if __name__ == "__main__":
101
+ unittest.main()
@@ -0,0 +1,44 @@
1
+ from pathlib import Path
2
+
3
+ from sqlalchemy import Column, Integer, String, create_engine, MetaData
4
+ from sqlalchemy.orm import declarative_base, Session
5
+ import unittest
6
+ from .z_sqlalchemy import init_db, Base
7
+
8
+ cur_dir = Path(__file__).parent
9
+
10
+
11
+ class U(unittest.TestCase):
12
+ def test_init(self):
13
+ out_dir = cur_dir / 'output'
14
+ out_dir.mkdir(parents=True, exist_ok=True)
15
+ engine = init_db(out_dir / "test.db")
16
+
17
+ with Session(engine) as session:
18
+ # --- 增:直接操作对象 ---
19
+ new_user = User(name="路人甲")
20
+ new_user2 = User(name="路人乙")
21
+ session.add_all([new_user, new_user2])
22
+ session.commit()
23
+
24
+ # --- 查:使用接口函数,不需要写 SQL ---
25
+ user = session.query(User).filter_by(name="路人甲").first()
26
+ print(f"查到的用户 ID: {user.id}")
27
+
28
+ # --- 改:直接改对象属性 ---
29
+ user.name = "实名用户"
30
+ session.commit()
31
+
32
+ # --- 删 ---
33
+ # session.delete(user)
34
+ # session.commit()
35
+
36
+
37
+ class User(Base):
38
+ __tablename__ = 'users'
39
+ id = Column(Integer, primary_key=True)
40
+ name = Column(String)
41
+
42
+ # 创建表结构
43
+
44
+ # 3. 创建 Session
@@ -0,0 +1,18 @@
1
+ from pathlib import Path
2
+ from sqlalchemy import create_engine, MetaData
3
+ from sqlalchemy.orm import declarative_base, Session
4
+
5
+ _metadata = MetaData()
6
+ Base = declarative_base(metadata=_metadata)
7
+
8
+
9
+ def init_db(db_path: Path | str):
10
+ """
11
+ 初始化 数据处理, 后续创建表的时候, 也需要 显示的将 metadata 在表中声明, 将表注册到 此metadata 中...
12
+ 防止使用全局的 metadata 容易报错...
13
+ 在所有的表 使用metadata 注册后, , 最后在使用的代码里, 使用 metadata.create_all(engine) 就可以完成数据库表的
14
+ 初始化了.
15
+ """
16
+ engine = create_engine(f'sqlite:///{str(Path(db_path).resolve())}', echo=True)
17
+ _metadata.create_all(engine)
18
+ return engine
@@ -0,0 +1,13 @@
1
+ from pandas import DataFrame
2
+ import numpy as np
3
+
4
+
5
+ def test_generate_cvs_data():
6
+ print()
7
+ data = DataFrame(np.zeros((2, 5)), columns=['a', 'b', 'c', 'd', 'e'])
8
+ data.to_csv('tmp/tmp.csv')
9
+ list = []
10
+ for i in range(5):
11
+ list.append((None, 1, None, 2, None))
12
+ data = DataFrame(list, columns=['a', 'b', 'c', 'd', 'e'])
13
+ data.to_csv('tmp/tmp2.csv')
@@ -0,0 +1 @@
1
+ import matplotlib.pyplot as plt
@@ -0,0 +1,31 @@
1
+ import io
2
+
3
+ from flask import Flask, request, send_file, render_template
4
+ from pptx import Presentation, util
5
+ import os
6
+ import time
7
+ from PIL import Image
8
+ from ppt import generate_pptx
9
+
10
+ app = Flask(__name__)
11
+ g_cur_dir = os.path.dirname(__file__)
12
+
13
+
14
+ @app.route('/upload', methods=['POST'])
15
+ def upload():
16
+ rows = int(request.form['rows'])
17
+ columns = int(request.form['columns'])
18
+ images = request.files.getlist('images')
19
+ ret_bytes = generate_pptx(rows, columns, images)
20
+ return send_file(ret_bytes, as_attachment=True, download_name=f'{int(time.time())}.pptx')
21
+
22
+
23
+ @app.route('/')
24
+ def index():
25
+ return render_template('index.html')
26
+
27
+
28
+ if __name__ == '__main__':
29
+ if not os.path.exists('temp'):
30
+ os.makedirs('temp')
31
+ app.run(host='0.0.0.0', port=10004, debug=True)
@@ -0,0 +1,65 @@
1
+ # encoding: utf-8
2
+ import io
3
+ from pptx import Presentation, util
4
+ from PIL import Image
5
+
6
+
7
+ def generate_pptx(rows, columns, images) -> io.BytesIO:
8
+ prs = Presentation()
9
+ w = util.Cm(6)
10
+ h = util.Cm(6)
11
+ left = util.Cm(0.1)
12
+ top = util.Cm(0.1)
13
+ right = util.Cm(0.1)
14
+ bottom = util.Cm(0.1)
15
+ padding_top = util.Cm(2)
16
+ padding_bottom = util.Cm(2)
17
+
18
+ prs.slide_width = (w + left + right) * columns
19
+ prs.slide_height = (h + top + bottom) * rows + padding_top + padding_bottom
20
+ blank_slide_layout = prs.slide_layouts[6]
21
+
22
+ index = 0
23
+ while index < len(images):
24
+ slide = prs.slides.add_slide(blank_slide_layout)
25
+ for i in range(rows * columns):
26
+ if index >= len(images):
27
+ break
28
+ x = i % columns
29
+ y = i // columns
30
+
31
+ with io.BytesIO() as converted_image:
32
+ image_bytes = images[index].stream.read()
33
+ image = Image.open(io.BytesIO(image_bytes))
34
+ print('image format:', image.format)
35
+ # 检查图片格式
36
+ if image.format not in ['BMP', 'GIF', 'JPEG', 'PNG', 'TIFF', 'WMF']:
37
+ # 转换为 PNG 格式
38
+ image.save(converted_image, format='JPEG')
39
+ else:
40
+ converted_image.write(image_bytes)
41
+ converted_image.seek(0)
42
+ # Calculate the aspect ratio
43
+ aspect_ratio = image.width / image.height
44
+ if aspect_ratio > 1:
45
+ # Landscape orientation
46
+ new_width = w
47
+ new_height = w / aspect_ratio
48
+ x_offset = 0
49
+ y_offset = (h - new_height) / 2
50
+ else:
51
+ # Portrait orientation
52
+ new_height = h
53
+ new_width = h * aspect_ratio
54
+ x_offset = (w - new_width) / 2
55
+ y_offset = 0
56
+
57
+ slide.shapes.add_picture(converted_image,
58
+ (left + w + right) * x + left + x_offset,
59
+ padding_top + (top + h + bottom) * y + top + y_offset,
60
+ new_width, new_height)
61
+ index += 1
62
+ ret_bytes = io.BytesIO()
63
+ prs.save(ret_bytes)
64
+ ret_bytes.seek(0)
65
+ return ret_bytes
@@ -0,0 +1,33 @@
1
+ import threading
2
+
3
+
4
+ class Counter:
5
+ def __init__(self):
6
+ self.value = 0
7
+ self.condition = threading.Condition()
8
+
9
+ def increment(self):
10
+ with self.condition:
11
+ self.value += 1
12
+ print(f"Value is now {self.value}")
13
+ # 模拟耗时操作
14
+ import time
15
+ time.sleep(1)
16
+ self.condition.notify_all() # 通知所有等待的线程
17
+
18
+
19
+ # 使用
20
+ counter = Counter()
21
+
22
+
23
+ def worker():
24
+ with counter.condition:
25
+ counter.condition.wait() # 等待条件
26
+ counter.increment()
27
+
28
+
29
+ threads = [threading.Thread(target=worker) for _ in range(2)]
30
+ for thread in threads:
31
+ thread.start()
32
+ for thread in threads:
33
+ thread.join()
@@ -0,0 +1,43 @@
1
+ import threading
2
+ import time
3
+
4
+ """
5
+ 模拟 事件的同步.
6
+ """
7
+
8
+ class Counter:
9
+ def __init__(self):
10
+ self.value = 0
11
+ self.event = threading.Event()
12
+
13
+ def increment(self):
14
+ self.value += 1
15
+ print(f"Value is now {self.value}")
16
+ # 模拟耗时操作
17
+ time.sleep(1)
18
+
19
+
20
+ # 使用
21
+ counter = Counter()
22
+
23
+
24
+ def worker():
25
+ counter.event.wait() # 等待事件
26
+ counter.increment()
27
+ counter.event.clear() # 清除事件,使后续线程等待
28
+
29
+
30
+ def worker2():
31
+ time.sleep(2)
32
+ print(" work 2 ......")
33
+ counter.event.set()
34
+
35
+
36
+ thread1 = threading.Thread(target=worker)
37
+ thread2 = threading.Thread(target=worker2)
38
+
39
+ thread1.start()
40
+ thread2.start()
41
+
42
+ thread1.join()
43
+ thread2.join()
@@ -0,0 +1,31 @@
1
+ Metadata-Version: 2.4
2
+ Name: zpylibs
3
+ Version: 0.1.0
4
+ Summary: 个人常用的python库工具
5
+ Author-email: "lionel.zc" <lionel.zhuc@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/z541880714/zpylibs
8
+ Project-URL: Bug Tracker, https://github.com/z541880714/zpylibs/issues
9
+ Keywords: utility,tools,pylib
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: requests>=2.28.0
17
+ Requires-Dist: numpy>=1.20.0
18
+ Requires-Dist: Pillow
19
+ Requires-Dist: opencv-python
20
+ Requires-Dist: exifread
21
+ Requires-Dist: matplotlib
22
+ Requires-Dist: pandas
23
+ Requires-Dist: sqlalchemy
24
+ Requires-Dist: flask
25
+ Requires-Dist: python-pptx
26
+
27
+ # zpylibs
28
+
29
+ ## 2024-11-28
30
+
31
+ 1. 加入了 image 的 截取圆形图片的库
@@ -0,0 +1,21 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/zpylibs.egg-info/PKG-INFO
4
+ src/zpylibs.egg-info/SOURCES.txt
5
+ src/zpylibs.egg-info/dependency_links.txt
6
+ src/zpylibs.egg-info/requires.txt
7
+ src/zpylibs.egg-info/top_level.txt
8
+ src/zpylibs/color/hsv2rgb.py
9
+ src/zpylibs/image/jpegInfo.py
10
+ src/zpylibs/image/pil_img_util.py
11
+ src/zpylibs/image/img_crop/cv2_crop.py
12
+ src/zpylibs/image/img_crop/pil_crop.py
13
+ src/zpylibs/onnx/test_onnx_static_2_dynamic.py
14
+ src/zpylibs/zdb/test_alchemy.py
15
+ src/zpylibs/zdb/z_sqlalchemy.py
16
+ src/zpylibs/zio/csv_util.py
17
+ src/zpylibs/zplt/zplt.py
18
+ src/zpylibs/zpptx/flask_server.py
19
+ src/zpylibs/zpptx/ppt.py
20
+ src/zpylibs/zthread/test/condition.py
21
+ src/zpylibs/zthread/test/event.py
@@ -0,0 +1,10 @@
1
+ requests>=2.28.0
2
+ numpy>=1.20.0
3
+ Pillow
4
+ opencv-python
5
+ exifread
6
+ matplotlib
7
+ pandas
8
+ sqlalchemy
9
+ flask
10
+ python-pptx
@@ -0,0 +1 @@
1
+ zpylibs