func2stream 0.0.1.dev240519063723__tar.gz → 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.
@@ -0,0 +1,27 @@
1
+ # func2stream MANIFEST.in
2
+ #
3
+ # PyPI 发布时的文件包含/排除规则
4
+
5
+ # 包含
6
+ include LICENSE
7
+ include README.md
8
+ include pyproject.toml
9
+
10
+ # 排除开发文档
11
+ prune docs/internal
12
+
13
+ # 排除测试和示例
14
+ prune samples
15
+ prune tests
16
+
17
+ # 排除开发配置
18
+ prune .github
19
+ prune .windsurf
20
+ prune .cursor
21
+ exclude .windsurfrules
22
+ exclude .gitattributes
23
+
24
+ # 排除构建产物
25
+ prune *.egg-info
26
+ global-exclude __pycache__
27
+ global-exclude *.py[cod]
@@ -0,0 +1,405 @@
1
+ Metadata-Version: 2.4
2
+ Name: func2stream
3
+ Version: 1.0
4
+ Summary: Effortlessly transform functions into asynchronous elements for building high-performance pipelines
5
+ Home-page: https://github.com/BICHENG/func2stream
6
+ Author: BI CHENG
7
+ License: MPL-2.0
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.6
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: loguru
17
+ Requires-Dist: numpy<1.22,>=1.19.5; python_version < "3.8"
18
+ Requires-Dist: numpy>=1.21.0; python_version >= "3.8"
19
+ Provides-Extra: test
20
+ Requires-Dist: pytest>=6.2; extra == "test"
21
+ Provides-Extra: video
22
+ Requires-Dist: opencv-python>=4; extra == "video"
23
+ Dynamic: author
24
+ Dynamic: classifier
25
+ Dynamic: description
26
+ Dynamic: description-content-type
27
+ Dynamic: home-page
28
+ Dynamic: license
29
+ Dynamic: license-file
30
+ Dynamic: provides-extra
31
+ Dynamic: requires-dist
32
+ Dynamic: requires-python
33
+ Dynamic: summary
34
+
35
+ # func2stream v1.0
36
+
37
+ [中文](README.md) | [English](README_en.md)
38
+
39
+ > [!TIP]
40
+ > 把函数串成异步流水线,工作量和写注释一样,但性能天差地别
41
+
42
+ ---
43
+
44
+ **你的原生代码:**
45
+ ```python
46
+ def detect(tensor):
47
+ return model(tensor)
48
+ ```
49
+
50
+ **只需加个返回注解:**
51
+ ```python
52
+ def detect(tensor) -> "boxes":
53
+ return model(tensor)
54
+ ```
55
+
56
+ **然后放进 Pipeline,自动异步:**
57
+ ```python
58
+ Pipeline([... preprocess, detect, display]).start()
59
+ ```
60
+
61
+ 这不是魔法,只是我认为没人会注解字符串,所以在这个前提下,完成了很多工作。🤷‍♂️
62
+
63
+ ## 为什么设计了 func2stream
64
+
65
+ 目的:最小侵入串行代码,让阶段N的处理与阶段N+1的处理在时间上重叠。
66
+
67
+ 对于实时推理,或希望处理序列数据(如视频)的场景,你有一堆处理函数,顺序调用时,吞吐量很低。
68
+
69
+ 但在开发时,如果过早考虑并行,会到处管理变量和状态,增加代码复杂度。
70
+
71
+ ```python
72
+ # 例如一个实时美颜程序
73
+ while True:
74
+ frame = camera.read()
75
+ tensor = some_preprocess(frame)
76
+ face_boxes = detect(tensor)
77
+ landmarks,crops = get_face_crop(tensor, face_boxes)
78
+ beautified = beautify_model(crops)
79
+ display(frame, landmarks, beautified)
80
+ ```
81
+
82
+ **需求**:让它们并行运行、不增加代码复杂度、专注于业务逻辑。
83
+
84
+ **传统做法**:写线程、队列,代码变乱,无法专注业务逻辑,再加上全局变量到处飞,改一处动全身。
85
+
86
+ **速度可能起来了,但掉头发的速度也起来了。(bushi**
87
+
88
+ **现在**:
89
+
90
+ ```python
91
+ Pipeline([
92
+ DataSource(camera.read),
93
+ some_preprocess,
94
+ detect,
95
+ get_face_crop,
96
+ beautify_model,
97
+ display,
98
+ ]).start()
99
+ ```
100
+
101
+ 每个函数自动在独立线程运行,数据自动传递,无需在全局暴露 queue、thread 等基础设施。
102
+
103
+ > 完整可运行示例:[samples/beauty_filter_mock.py](samples/beauty_filter_mock.py)
104
+
105
+ ---
106
+
107
+ ## 安装
108
+
109
+ ```bash
110
+ pip install git+https://github.com/bicheng/func2stream.git
111
+ ```
112
+
113
+ ---
114
+
115
+ ## 如何使用?
116
+
117
+ 在现有代码的基础上,只需要针对可参与异步流水线的函数做修改,加 `-> "数据名"` 即可。
118
+
119
+ ```python
120
+ from func2stream import Pipeline, DataSource
121
+
122
+ # ─── 工具函数(不进流水线,不加 -> "数据名")─────────────────
123
+
124
+ def normalize(img):
125
+ return img.astype(np.float32) / 255.0
126
+
127
+ def get_eye_positions(x, y, w, h):
128
+ return [(x + w//3, y + h//3), (x + 2*w//3, y + h//3)]
129
+
130
+ def apply_brightness(crop, factor=0.9):
131
+ return crop * factor + (1 - factor)
132
+
133
+
134
+ # ─── 流水线函数(加 -> "数据名")──────────────────────────────
135
+
136
+ def some_preprocess(frame) -> "tensor":
137
+ return normalize(frame) # 调用工具函数
138
+
139
+ def detect(tensor) -> "face_boxes":
140
+ return model.detect(tensor)
141
+
142
+ def get_face_crop(tensor, face_boxes) -> ("landmarks", "crops"): # 多输出
143
+ landmarks, crops = [], []
144
+ for (x, y, w, h) in face_boxes:
145
+ landmarks.append(get_eye_positions(x, y, w, h)) # 调用工具函数
146
+ crops.append(tensor[y:y+h, x:x+w])
147
+ return landmarks, crops
148
+
149
+ def beautify_model(crops) -> "beautified":
150
+ return [apply_brightness(crop) for crop in crops] # 调用工具函数
151
+
152
+ def display(frame, landmarks, beautified) -> "displayed": # 多输入
153
+ cv2.imshow("win", frame)
154
+ return True
155
+
156
+
157
+ # ─── 组装 ──────────────────────────────────────────────────────
158
+
159
+ Pipeline([
160
+ DataSource(camera.read), # → frame
161
+ some_preprocess, # → tensor
162
+ detect, # → face_boxes
163
+ get_face_crop, # → landmarks, crops
164
+ beautify_model, # → beautified
165
+ display, # → displayed
166
+ ]).start()
167
+ ```
168
+
169
+ **数据流**:`frame → tensor → face_boxes → (landmarks, crops) → beautified → displayed`
170
+
171
+ ---
172
+
173
+ ## 用 `@init_ctx` 持有状态,避免全局变量
174
+
175
+ 你可能会遇到这样的开发问题:
176
+ - 全局变量满天飞
177
+ - 流水线内需要持有状态(模型、计数器、配置等)
178
+ - 写一个类属于过度设计
179
+
180
+ 用 `@init_ctx` 把它们打包在一起。
181
+
182
+ > 完整可运行示例:[samples/tracker_mock.py](samples/tracker_mock.py)
183
+
184
+ ### 例子:多目标追踪器
185
+
186
+ ```python
187
+ from func2stream import Pipeline, DataSource, init_ctx
188
+
189
+ @init_ctx
190
+ def create_tracker(model_path, threshold=0.5):
191
+ # ─── 状态:模型、计数器、追踪历史 ───────────────────────────
192
+ model = load_model(model_path)
193
+ frame_count = 0
194
+ track_history = {}
195
+
196
+ # ─── 流水线函数 ────────────────────────────────────────────
197
+ def detect(frame) -> "boxes":
198
+ return [b for b in model(frame) if b.conf > threshold]
199
+
200
+ def track(frame, boxes) -> "tracks":
201
+ nonlocal frame_count
202
+ frame_count += 1
203
+ # ... 追踪逻辑,更新 track_history ...
204
+ return tracks
205
+
206
+ def draw(frame, tracks) -> "frame":
207
+ for t in tracks:
208
+ cv2.rectangle(frame, t.bbox, (0, 255, 0), 2)
209
+ return frame
210
+
211
+ # ─── 工具函数(不进流水线)─────────────────────────────────
212
+ def get_frame_count() -> int:
213
+ return frame_count
214
+
215
+ def get_track_history() -> dict:
216
+ return track_history
217
+
218
+ return locals()
219
+
220
+
221
+ # 同一个工厂,创建两个互相隔离的追踪器
222
+ tracker_front = create_tracker("yolo.pt", threshold=0.7)
223
+ tracker_rear = create_tracker("yolo.pt", threshold=0.5)
224
+
225
+ p1 = Pipeline([
226
+ DataSource(front_camera.read),
227
+ tracker_front.detect,
228
+ tracker_front.track,
229
+ tracker_front.draw,
230
+ display,
231
+ ])
232
+
233
+ p2 = Pipeline([
234
+ DataSource(rear_camera.read),
235
+ tracker_rear.detect,
236
+ tracker_rear.track,
237
+ tracker_rear.draw,
238
+ display,
239
+ ])
240
+
241
+ p1.start()
242
+ p2.start()
243
+
244
+ # 查看状态
245
+ while min(tracker_front.get_frame_count(), tracker_rear.get_frame_count()) < 100:
246
+ print(f"front: {tracker_front.get_frame_count()}")
247
+ print(f"rear: {tracker_rear.get_frame_count()}")
248
+ time.sleep(1)
249
+
250
+ ```
251
+
252
+ ---
253
+
254
+ ## 用 `gpu_model()` 处理 GPU 资源
255
+
256
+ `gpu_model()` 可以避免 GPU 模型跨线程执行时的性能问题。
257
+
258
+ ```python
259
+ from func2stream import Pipeline, DataSource, init_ctx, gpu_model
260
+
261
+ @init_ctx
262
+ def create_detector(threshold=0.5):
263
+ # 主线程变量
264
+ frame_count = 0
265
+
266
+ # GPU 模型 - 延迟到工作线程创建
267
+ model = gpu_model(lambda: TRTModel(device='cuda'))
268
+
269
+ def detect(frame) -> "boxes":
270
+ nonlocal frame_count
271
+ frame_count += 1
272
+ return [b for b in model(frame) if b.conf > threshold]
273
+
274
+ def get_count():
275
+ return frame_count
276
+
277
+ return locals()
278
+
279
+ ctx = create_detector(threshold=0.7)
280
+ Pipeline([DataSource(camera.read), ctx.detect, display]).start()
281
+ ```
282
+
283
+ `gpu_model()` 接受一个无参 lambda,在工作线程首次访问时执行。调用方式与原模型一致。
284
+
285
+ > 示例:[samples/gpu_model_trt.py](samples/gpu_model_trt.py)
286
+
287
+ ---
288
+
289
+ ## 常见误区
290
+
291
+ ### 误区 1:流水线函数不再能嵌套调用
292
+ 解释:如果你有多个流水线函数,不要在某一个流水线函数里直接调用另一个流水线函数。
293
+
294
+ PS: 后期会想办法支持嵌套
295
+
296
+
297
+ ```python
298
+ def to_gray(frame) -> "gray":
299
+ return cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
300
+
301
+ # 💥 流水线函数会被自动包装,行为不再是直接调用
302
+ def step1(frame) -> "edges":
303
+ gray = to_gray(frame) # 不能在这里调用 to_gray
304
+ return cv2.Canny(gray, 50, 150)
305
+
306
+ # -------------------------------------------------------------
307
+ # ✅ 方案一:如果overlap的性能收益比较小,合并处理步骤到一个函数
308
+ def step1(frame) -> "edges":
309
+ gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
310
+ return cv2.Canny(gray, 50, 150)
311
+
312
+ Pipeline([
313
+ DataSource(...),
314
+ step1,
315
+ ])
316
+
317
+ # -------------------------------------------------------------
318
+ # ✅ 方案二:拆成独立步骤,让 Pipeline 自动串联
319
+ def canny(gray) -> "edges":
320
+ return cv2.Canny(gray, 50, 150)
321
+
322
+ Pipeline([
323
+ DataSource(...),
324
+ to_gray, # 先执行,产出 gray
325
+ canny, # 后执行,自动读取 gray
326
+ ...
327
+ ])
328
+
329
+ ```
330
+
331
+ ### 误区 2:流水线中使用的函数忘了写 `-> "数据名"`
332
+
333
+ PS: 这是唯一工作量,好好写吧
334
+
335
+ ```python
336
+ def process(frame):
337
+ return frame * 2
338
+
339
+ Pipeline([
340
+ DataSource(...),
341
+ process, # 💥 Pipeline 会警告,且函数运行时收到的并非 frame
342
+ ])
343
+
344
+ # ✅ 放进 Pipeline 的函数,必须声明输出
345
+ def process(frame) -> "result":
346
+ return frame * 2
347
+ ```
348
+
349
+ ### 误区 3:类型注解
350
+
351
+ ```python
352
+ # 👀 -> int 是类型注解,Pipeline 不会识别它
353
+ def process(frame) -> int:
354
+ return frame * 2
355
+
356
+ # ✅ 用引号包裹的字符串才会被识别
357
+ def process(frame) -> "result":
358
+ return frame * 2
359
+ ```
360
+
361
+
362
+ ---
363
+
364
+ ## v1.0 Breaking Changes
365
+
366
+ 相比之下,早期版本更像是 geek 的玩具,现已升级到 v1.0,以下 API 已移除,**使用就像注释一样简单**:
367
+
368
+ | 移除项 | 替代方案 |
369
+ |--------|----------|
370
+ | `@from_ctx(get=[], ret=[])` | 使用 `-> "key"` 返回注解 |
371
+ | `build_ctx()` | `DataSource` 自动构建 ctx |
372
+ | `MapReduce` | 实验性功能,已弃用(见 Roadmap) |
373
+ | `nodrop()` | 无替代,该功能无使用场景 |
374
+
375
+ **v1.0 的核心改进**:
376
+ - **零装饰器数据流**:只需 `-> "key"` 注解,Pipeline 自动识别并包装
377
+ - **隐式 Context**:参数名即读取键,无需手动声明
378
+ - **状态隔离**:`@init_ctx` 封装模型、计数器等,避免全局变量
379
+
380
+ ---
381
+
382
+ ## Roadmap
383
+
384
+ ### 并行分支与同步(规划中)
385
+
386
+ ```python
387
+ Pipeline([
388
+ preprocess,
389
+ ( # ← 元组 = 并行分支
390
+ [branch_a1, branch_a2], # 分支1:串行子流水线
391
+ [branch_b1], # 分支2
392
+ branch_c, # 分支3:单函数
393
+ ), # ← 元组结束 = 隐式同步点
394
+ merge_results, # 收到 (a2_out, b1_out, c_out) 打包
395
+ postprocess,
396
+ ]).start()
397
+ ```
398
+
399
+ **设计原则**:同步点语法隐式,元组结束即同步,无需显式 Barrier。
400
+
401
+ ---
402
+
403
+ ## 许可证
404
+
405
+ MPL-2.0