nedo-vision-worker-core 0.3.8__py3-none-any.whl → 0.4.0__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.

Potentially problematic release.


This version of nedo-vision-worker-core might be problematic. Click here for more details.

@@ -7,7 +7,7 @@ A library for running AI vision processing and detection in the Nedo Vision plat
7
7
  from .core_service import CoreService
8
8
  from .callbacks import DetectionType, CallbackTrigger, DetectionData, IntervalMetadata
9
9
 
10
- __version__ = "0.3.8"
10
+ __version__ = "0.4.0"
11
11
  __all__ = [
12
12
  "CoreService",
13
13
  "DetectionType",
@@ -3,12 +3,16 @@ import cv2
3
3
  import logging
4
4
  import os
5
5
  from pathlib import Path
6
+ import threading
6
7
 
7
8
  import numpy as np
8
9
  from ..util.DrawingUtils import DrawingUtils
9
10
 
10
11
  class FrameDrawer:
11
12
  """Handles frame processing by drawing objects, annotations, and managing icons."""
13
+
14
+ # Share the same lock as DrawingUtils for consistent locking
15
+ _cv_lock = DrawingUtils._cv_lock
12
16
 
13
17
  def __init__(self):
14
18
  self.icons = {}
@@ -67,7 +71,8 @@ class FrameDrawer:
67
71
  (int(x * width), int(y * height)) for (x, y) in normalized_points
68
72
  ]
69
73
  if len(points) >= 3:
70
- cv2.polylines(frame, [np.array(points, np.int32)], isClosed=True, color=color, thickness=2)
74
+ with self._cv_lock:
75
+ cv2.polylines(frame, [np.array(points, np.int32)], isClosed=True, color=color, thickness=2)
71
76
 
72
77
  def draw_frame(self, frame, tracked_objects, with_trails=False, trail_length=10):
73
78
  current_ids = set()
@@ -83,7 +88,9 @@ class FrameDrawer:
83
88
  attributes = obj.get("attributes", [])
84
89
  labels = [attr.get("label") for attr in attributes]
85
90
  (color, flag) = self._get_color_from_labels(labels)
86
- DrawingUtils.draw_bbox_info(frame, bbox, (color, flag), f"{track_id}", self.location_name, f"{obj.get('confidence', 0):.2f}")
91
+
92
+ with self._cv_lock:
93
+ DrawingUtils.draw_bbox_info(frame, bbox, (color, flag), f"{track_id}", self.location_name, f"{obj.get('confidence', 0):.2f}")
87
94
 
88
95
  # Trailing
89
96
  if with_trails:
@@ -109,26 +116,28 @@ class FrameDrawer:
109
116
  num_points = len(points)
110
117
 
111
118
  # Draw faded trail
112
- for i in range(1, num_points):
113
- cv2.line(frame, points[i-1], points[i], color, 1)
119
+ with self._cv_lock:
120
+ for i in range(1, num_points):
121
+ cv2.line(frame, points[i-1], points[i], color, 1)
114
122
 
115
123
  current_ids.add(track_id)
116
124
 
117
- DrawingUtils.draw_main_bbox(frame, bbox, (color, flag))
125
+ with self._cv_lock:
126
+ DrawingUtils.draw_main_bbox(frame, bbox, (color, flag))
118
127
 
119
- for attr in attributes:
120
- attr_bbox = attr.get("bbox", [])
121
- if len(attr_bbox) != 4:
122
- continue
128
+ for attr in attributes:
129
+ attr_bbox = attr.get("bbox", [])
130
+ if len(attr_bbox) != 4:
131
+ continue
123
132
 
124
- attr_label = attr.get("label", "")
125
- DrawingUtils.draw_inner_box(frame, attr_bbox, self._get_color(attr_label))
133
+ attr_label = attr.get("label", "")
134
+ DrawingUtils.draw_inner_box(frame, attr_bbox, self._get_color(attr_label))
126
135
 
127
- icon_x = x1
128
- for (label, icon) in self.icons.items():
129
- if label in labels:
130
- DrawingUtils.draw_alpha_overlay(frame, icon, icon_x, y1 - 25)
131
- icon_x += 25
136
+ icon_x = x1
137
+ for (label, icon) in self.icons.items():
138
+ if label in labels:
139
+ DrawingUtils.draw_alpha_overlay(frame, icon, icon_x, y1 - 25)
140
+ icon_x += 25
132
141
 
133
142
  # Cleanup trails for objects that disappeared
134
143
  if with_trails and hasattr(self, "trails"):
@@ -22,43 +22,58 @@ class VideoDebugger:
22
22
  window_name = f"Pipeline {pipeline_id} - {worker_source_id}"
23
23
 
24
24
  try:
25
- with self.lock:
26
- if window_name not in self.fps_tracker:
27
- self.fps_tracker[window_name] = {"start_time": time.time(), "frame_count": 0}
28
-
29
- self.fps_tracker[window_name]["frame_count"] += 1
30
- elapsed_time = time.time() - self.fps_tracker[window_name]["start_time"]
31
- fps = self.fps_tracker[window_name]["frame_count"] / max(elapsed_time, 1e-5)
32
-
33
- cv2.putText(frame, f"FPS: {fps:.2f}", (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 1)
25
+ # Serialize ALL OpenCV operations to prevent segfaults
26
+ with self._cv_lock:
27
+ with self.lock:
28
+ if window_name not in self.fps_tracker:
29
+ self.fps_tracker[window_name] = {"start_time": time.time(), "frame_count": 0}
34
30
 
35
- if window_name not in self.windows:
36
- self.windows[window_name] = True
31
+ self.fps_tracker[window_name]["frame_count"] += 1
32
+ elapsed_time = time.time() - self.fps_tracker[window_name]["start_time"]
33
+ fps = self.fps_tracker[window_name]["frame_count"] / max(elapsed_time, 1e-5)
37
34
 
38
- # Serialize cv2 calls to prevent segfaults
39
- with self._cv_lock:
35
+ if window_name not in self.windows:
36
+ self.windows[window_name] = True
37
+
38
+ # Make a copy to avoid modifying the original frame from multiple threads
39
+ display_frame = frame.copy()
40
+
40
41
  try:
41
- cv2.imshow(window_name, frame)
42
+ cv2.putText(display_frame, f"FPS: {fps:.2f}", (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 1)
43
+ cv2.imshow(window_name, display_frame)
42
44
  key = cv2.waitKey(1) & 0xFF
43
45
 
44
46
  if key == ord('q'):
45
- self.close_window(window_name)
47
+ self._close_window_unsafe(window_name)
46
48
  except Exception as e:
47
49
  logging.error(f"Error displaying frame for {window_name}: {e}")
48
50
 
49
51
  except Exception as e:
50
52
  logging.error(f"Error in show_frame for {window_name}: {e}")
51
53
 
54
+ def _close_window_unsafe(self, window_name):
55
+ """Close window without acquiring locks (called when already locked)."""
56
+ if window_name in self.windows:
57
+ try:
58
+ cv2.destroyWindow(window_name)
59
+ except Exception as e:
60
+ logging.error(f"Error closing window {window_name}: {e}")
61
+ del self.windows[window_name]
62
+
52
63
  def close_window(self, window_name):
53
64
  """Close specific window."""
65
+ with self._cv_lock:
66
+ with self.lock:
67
+ self._close_window_unsafe(window_name)
68
+
69
+ def is_window_open(self, pipeline_id):
70
+ """Check if a window is open for a given pipeline."""
54
71
  with self.lock:
55
- if window_name in self.windows:
56
- with self._cv_lock:
57
- try:
58
- cv2.destroyWindow(window_name)
59
- except Exception as e:
60
- logging.error(f"Error closing window {window_name}: {e}")
61
- del self.windows[window_name]
72
+ # Check if any window exists for this pipeline
73
+ for window_name in self.windows.keys():
74
+ if f"Pipeline {pipeline_id}" in window_name:
75
+ return True
76
+ return False
62
77
 
63
78
  def close_all(self):
64
79
  """Close all windows."""
@@ -45,7 +45,7 @@ class PipelineProcessor:
45
45
  self.rtmp_streamer = None
46
46
  self.rtmp_streaming_active = False
47
47
  self.last_preview_check_time = 0
48
- self.preview_check_interval = 5.0 # Check every 5 seconds
48
+ self.preview_check_interval = 10.0 # Check every 10 seconds (reduced from 5s to save CPU)
49
49
  self.pipeline_repo = WorkerSourcePipelineRepository()
50
50
 
51
51
  self.detection_processor_codes = [
@@ -174,55 +174,62 @@ class PipelineProcessor:
174
174
  )
175
175
  self.detection_thread.start()
176
176
 
177
+ target_render_fps = 25.0
178
+ target_frame_time = 1.0 / target_render_fps
179
+ last_preview_check = 0
180
+
177
181
  try:
178
182
  while self.running:
183
+ loop_start = time.time()
184
+
179
185
  frame = video_manager.get_frame(worker_source_id)
180
186
 
181
187
  if frame is None:
182
188
  if not self._handle_frame_failure(video_manager, worker_source_id):
183
189
  break
184
- # no frame this tick—just continue (the streamer will repeat last good frame)
190
+ time.sleep(0.04)
185
191
  continue
186
192
 
187
- # successful frame
188
193
  self.consecutive_frame_failures = 0
189
- self.last_successful_frame_time = time.time()
194
+ self.last_successful_frame_time = loop_start
190
195
  self.frame_counter += 1
191
196
 
192
- # draw annotations
193
- try:
194
- self.frame_drawer.draw_polygons(frame)
195
- frame_to_draw = frame.copy() if self.debug_flag else frame
196
- drawn_frame = self.frame_drawer.draw_frame(
197
- frame_to_draw,
198
- self.tracked_objects_render,
199
- with_trails=True,
200
- trail_length=int(max(1, 2 / self.detection_interval))
201
- )
202
- except Exception as e:
203
- logging.error(f"❌ Draw failed, using raw frame: {e}")
197
+ # Check preview status less frequently
198
+ if loop_start - last_preview_check >= self.preview_check_interval:
199
+ self._check_and_update_rtmp_streaming()
200
+ last_preview_check = loop_start
201
+
202
+ should_draw = self.rtmp_streaming_active or self.debug_flag
203
+
204
+ if should_draw:
205
+ try:
206
+ frame_to_draw = frame.copy()
207
+ self.frame_drawer.draw_polygons(frame_to_draw)
208
+ drawn_frame = self.frame_drawer.draw_frame(
209
+ frame_to_draw,
210
+ self.tracked_objects_render,
211
+ with_trails=True,
212
+ trail_length=int(max(1, 2 / self.detection_interval))
213
+ )
214
+ except Exception as e:
215
+ logging.error(f"❌ Draw failed, using raw frame: {e}")
216
+ drawn_frame = frame
217
+ else:
204
218
  drawn_frame = frame
205
219
 
206
- # debug snapshot if requested
207
220
  if self.debug_flag:
208
221
  tracked_objects_render = self._process_frame(frame)
209
222
  try:
223
+ debug_frame = frame.copy()
210
224
  self.debug_repo.update_debug_entries_by_pipeline_id(
211
225
  self.pipeline_id,
212
- self.frame_drawer.draw_frame(frame, tracked_objects_render),
226
+ self.frame_drawer.draw_frame(debug_frame, tracked_objects_render),
213
227
  tracked_objects_render
214
228
  )
215
229
  except Exception as e:
216
230
  logging.warning(f"Debug save failed: {e}")
217
231
  self.debug_flag = False
218
232
 
219
- # Check if RTMP streaming should be active based on preview requests
220
- current_time = time.time()
221
- if current_time - self.last_preview_check_time >= self.preview_check_interval:
222
- self._check_and_update_rtmp_streaming()
223
- self.last_preview_check_time = current_time
224
-
225
- # Push frame to RTMP stream if preview is active
226
233
  if self.rtmp_streaming_active:
227
234
  if self.rtmp_streamer is None:
228
235
  try:
@@ -243,39 +250,21 @@ class PipelineProcessor:
243
250
  except Exception:
244
251
  pass
245
252
  self.rtmp_streamer = None
246
- else:
247
- # Stop RTMP streaming if preview is no longer active
248
- if self.rtmp_streamer is not None:
249
- try:
250
- logging.info(f"🛑 Stopping RTMP streamer for pipeline {pipeline_id} (preview expired)")
251
- self.rtmp_streamer.stop_stream()
252
- except Exception as e:
253
- logging.error(f"❌ Error stopping RTMP streamer: {e}")
254
- finally:
255
- self.rtmp_streamer = None
256
-
257
- # feed detection worker with latest-only behavior
258
- if self.detection_thread and self.detection_thread.is_alive():
253
+ elif self.rtmp_streamer is not None:
259
254
  try:
260
- self.frame_queue.put_nowait(frame)
261
- except queue.Full:
262
- try:
263
- _ = self.frame_queue.get_nowait()
264
- except queue.Empty:
265
- pass
266
- try:
267
- self.frame_queue.put_nowait(frame)
268
- except queue.Full:
269
- pass
255
+ logging.info(f"🛑 Stopping RTMP streamer for pipeline {pipeline_id} (preview expired)")
256
+ self.rtmp_streamer.stop_stream()
257
+ except Exception as e:
258
+ logging.error(f"❌ Error stopping RTMP streamer: {e}")
259
+ finally:
260
+ self.rtmp_streamer = None
270
261
 
271
- # visualize
272
- try:
273
- self.video_debugger.show_frame(pipeline_id, worker_source_id, drawn_frame)
274
- except Exception as e:
275
- logging.error(f"⚠️ Failed to render frame for pipeline {pipeline_id}: {e}")
262
+ # Simplified queue feeding - avoid expensive try-except
263
+ if not self.frame_queue.full():
264
+ self.frame_queue.put_nowait(frame)
276
265
 
277
- # Dynamic sleep based on detection interval to reduce CPU
278
- sleep_time = min(0.01, self.detection_interval / 10)
266
+ loop_elapsed = time.time() - loop_start
267
+ sleep_time = max(0.001, target_frame_time - loop_elapsed)
279
268
  time.sleep(sleep_time)
280
269
 
281
270
  except Exception as e:
@@ -2,8 +2,12 @@ import math
2
2
  import cv2
3
3
  import numpy as np
4
4
  import os
5
+ import threading
5
6
 
6
7
  class DrawingUtils:
8
+ # Global lock for all OpenCV drawing operations to prevent segfaults
9
+ _cv_lock = threading.Lock()
10
+
7
11
  _color_map = {
8
12
  True: "blue",
9
13
  False: "red",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nedo-vision-worker-core
3
- Version: 0.3.8
3
+ Version: 0.4.0
4
4
  Summary: Nedo Vision Worker Core Library for AI Vision Processing
5
5
  Author-email: Willy Achmat Fauzi <willy.achmat@gmail.com>
6
6
  Maintainer-email: Willy Achmat Fauzi <willy.achmat@gmail.com>
@@ -30,7 +30,7 @@ Classifier: Environment :: No Input/Output (Daemon)
30
30
  Requires-Python: >=3.8
31
31
  Description-Content-Type: text/markdown
32
32
  Requires-Dist: alembic>=1.8.0
33
- Requires-Dist: numpy>=1.21.0
33
+ Requires-Dist: numpy<2.0.0,>=1.21.0
34
34
  Requires-Dist: pillow>=8.0.0
35
35
  Requires-Dist: psutil>=5.9.0
36
36
  Requires-Dist: scipy>=1.9.0
@@ -40,8 +40,6 @@ Requires-Dist: torch>=1.9.0
40
40
  Requires-Dist: torchvision>=0.10.0
41
41
  Requires-Dist: ultralytics>=8.0.0
42
42
  Requires-Dist: rfdetr<2.0.0,>=1.2.0
43
- Provides-Extra: opencv
44
- Requires-Dist: opencv-python>=4.6.0; extra == "opencv"
45
43
  Provides-Extra: dev
46
44
  Requires-Dist: pytest>=7.0.0; extra == "dev"
47
45
  Requires-Dist: black>=22.0.0; extra == "dev"
@@ -49,6 +47,8 @@ Requires-Dist: isort>=5.10.0; extra == "dev"
49
47
  Requires-Dist: mypy>=0.950; extra == "dev"
50
48
  Requires-Dist: flake8>=4.0.0; extra == "dev"
51
49
  Requires-Dist: pre-commit>=2.17.0; extra == "dev"
50
+ Provides-Extra: opencv
51
+ Requires-Dist: opencv-python<5.0.0,>=4.6.0; extra == "opencv"
52
52
 
53
53
  # Nedo Vision Worker Core
54
54
 
@@ -1,10 +1,10 @@
1
- nedo_vision_worker_core/__init__.py,sha256=HAUgt6tA__5tRxc5EtEDSooC_CQI0Uyjp0RFJtSK754,1924
1
+ nedo_vision_worker_core/__init__.py,sha256=_NVuaujglbPX63LBohmNR6a1n7ctmMl97EsX9LBIVHA,1924
2
2
  nedo_vision_worker_core/cli.py,sha256=8YuKWsIgICUYXE_QtwyU3WzGhVjTWiAo5uzpFOmjNc8,5766
3
3
  nedo_vision_worker_core/core_service.py,sha256=q8-GuGW_l5l6wTWQDqc7BDdhM7zKC-mMLZ5wIHu9xV0,11628
4
4
  nedo_vision_worker_core/doctor.py,sha256=K_-hVV2-mdEefZ4Cfu5hMCiOxBiI1aXY8VtkkpK80Lc,10651
5
- nedo_vision_worker_core/ai/FrameDrawer.py,sha256=lj83WFaE70BQfkEc6AHcMBXaiEm8l3s_zJZG9C0NkAs,5286
5
+ nedo_vision_worker_core/ai/FrameDrawer.py,sha256=47z3qnj75tDxKHasZozBCzBhW3yJU-57GGVMoVD6b78,5623
6
6
  nedo_vision_worker_core/ai/ImageDebugger.py,sha256=5FwgNGZrxO2eT7hxdxp7N2gQ0oyyYDZChJ3PJapKu-w,4612
7
- nedo_vision_worker_core/ai/VideoDebugger.py,sha256=4BqOB_vszS4Prux1VFnZ3nGwQ3dyQOljtp5ud0XZBCU,3074
7
+ nedo_vision_worker_core/ai/VideoDebugger.py,sha256=2qQrCrUTUxr3TCA5NfbxIGvIhdhphPoi5FgqmK99b1c,3818
8
8
  nedo_vision_worker_core/ai/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
9
9
  nedo_vision_worker_core/callbacks/DetectionCallbackManager.py,sha256=Ogoco3JD_o5IMccruau1ly69bDWnsckJyVtzCw259JQ,13379
10
10
  nedo_vision_worker_core/callbacks/DetectionCallbackTypes.py,sha256=U7Qb0dCMtOHuZi_HNjapKjPqsCNM9ucHQosjHk9vPJ8,5057
@@ -61,7 +61,7 @@ nedo_vision_worker_core/pipeline/ModelManager.py,sha256=2DoQiIdF-PAqU7nT_u6bj-DY
61
61
  nedo_vision_worker_core/pipeline/PipelineConfigManager.py,sha256=X55i9GyXcW9ylO6cj2UMAZFSxxPViacL4H4DZl60CAY,1157
62
62
  nedo_vision_worker_core/pipeline/PipelineManager.py,sha256=3I9UBJu_rRfTEctwj8i4hO4MHjpBtYpfh-rIi64qgEw,7638
63
63
  nedo_vision_worker_core/pipeline/PipelinePrepocessor.py,sha256=cCiVSHHqsKCtKYURdYoEjHJX2GnT6zd8kQ6ZukjQ3V0,1271
64
- nedo_vision_worker_core/pipeline/PipelineProcessor.py,sha256=9gQ6AVOD_cC-c9VLqJ8WwaDb8HZNkAOT35Op0nHDROs,34194
64
+ nedo_vision_worker_core/pipeline/PipelineProcessor.py,sha256=FYpZw2vRRuweJ798gRhygOfF7cCJbKRApwZ52kxSEEM,33478
65
65
  nedo_vision_worker_core/pipeline/PipelineSyncThread.py,sha256=HkW6wj0eDr6M1K3Y25IlB2V6tpIZsKA34AM49AXvcQk,8707
66
66
  nedo_vision_worker_core/pipeline/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
67
67
  nedo_vision_worker_core/preprocessing/ImageResizer.py,sha256=RvOazxe6dJQuiy0ZH4lIGbdFfiu0FLUVCHoMvxkDNT4,1324
@@ -90,7 +90,7 @@ nedo_vision_worker_core/tracker/SFSORT.py,sha256=0kggw0l4yPZ55AKHdqVX6mu9ehHmJed
90
90
  nedo_vision_worker_core/tracker/TrackerManager.py,sha256=xtDMI657W2s7HM2lMGtwU0x5Hq74BZpLHd-5xk-278I,6152
91
91
  nedo_vision_worker_core/tracker/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
92
92
  nedo_vision_worker_core/util/BoundingBoxMetrics.py,sha256=S2h87VRSmgD2t5lPBYW0EKSIAvWjjy4fo37zQwqc21g,1735
93
- nedo_vision_worker_core/util/DrawingUtils.py,sha256=sLptmzVaJakP_ZgbZsLL03RMH_9Nwkw0f8_gbIAf03A,13621
93
+ nedo_vision_worker_core/util/DrawingUtils.py,sha256=YXL3TUB4A7PEwk3k4OuRTLEY9f0bjZncVzfTSYxVVU0,13748
94
94
  nedo_vision_worker_core/util/ModelReadinessChecker.py,sha256=ywHvt_d7UlY3DyFEJrO4Iyl0zx3SaLKb-Qab5l5Q8n4,6548
95
95
  nedo_vision_worker_core/util/PersonAttributeMatcher.py,sha256=PhYTPYSF62Nfuc7dage03K6icw_bBBdpvXvnlzCbS30,2773
96
96
  nedo_vision_worker_core/util/PersonRestrictedAreaMatcher.py,sha256=iuzCU32BQKaZ3dIy0QHNg2yoWJA-XhTRwwYqCvFdDgg,1711
@@ -98,8 +98,8 @@ nedo_vision_worker_core/util/PipelinePreviewChecker.py,sha256=XxlSMlrDlRrzfV8_Y-
98
98
  nedo_vision_worker_core/util/PlatformDetector.py,sha256=GGL8UfeMQITR22EMYIRWnuOEnSqo7Dr5mb0PaFrl8AM,3006
99
99
  nedo_vision_worker_core/util/TablePrinter.py,sha256=wzLGgb1GFMeIbAP6HmKcZD33j4D-IlyqlyeR7C5yD7w,1137
100
100
  nedo_vision_worker_core/util/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
101
- nedo_vision_worker_core-0.3.8.dist-info/METADATA,sha256=uClzBEF9GttUKm4thh8BpzA7VEhVBeZ8M6Yf1bGFl10,14412
102
- nedo_vision_worker_core-0.3.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
103
- nedo_vision_worker_core-0.3.8.dist-info/entry_points.txt,sha256=pIPafsvPnBw-fpBKBmc1NQCQ6PQY3ad8mZ6mn8_p5FI,70
104
- nedo_vision_worker_core-0.3.8.dist-info/top_level.txt,sha256=y8kusXjVYqtG8MSHYWTrk8bRrvjOrphKXYyzu943TTQ,24
105
- nedo_vision_worker_core-0.3.8.dist-info/RECORD,,
101
+ nedo_vision_worker_core-0.4.0.dist-info/METADATA,sha256=xzaULdQDUdB5jKhmotw2EL4Ua4LJlQ0kExewX2JfUn8,14426
102
+ nedo_vision_worker_core-0.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
103
+ nedo_vision_worker_core-0.4.0.dist-info/entry_points.txt,sha256=pIPafsvPnBw-fpBKBmc1NQCQ6PQY3ad8mZ6mn8_p5FI,70
104
+ nedo_vision_worker_core-0.4.0.dist-info/top_level.txt,sha256=y8kusXjVYqtG8MSHYWTrk8bRrvjOrphKXYyzu943TTQ,24
105
+ nedo_vision_worker_core-0.4.0.dist-info/RECORD,,