nedo-vision-worker-core 0.3.9__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.9"
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,49 @@ 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."""
54
- 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]
65
+ with self._cv_lock:
66
+ with self.lock:
67
+ self._close_window_unsafe(window_name)
62
68
 
63
69
  def is_window_open(self, pipeline_id):
64
70
  """Check if a window is open for a given pipeline."""
@@ -176,6 +176,7 @@ class PipelineProcessor:
176
176
 
177
177
  target_render_fps = 25.0
178
178
  target_frame_time = 1.0 / target_render_fps
179
+ last_preview_check = 0
179
180
 
180
181
  try:
181
182
  while self.running:
@@ -186,22 +187,24 @@ class PipelineProcessor:
186
187
  if frame is None:
187
188
  if not self._handle_frame_failure(video_manager, worker_source_id):
188
189
  break
189
- # no frame this tick—just continue (the streamer will repeat last good frame)
190
190
  time.sleep(0.04)
191
191
  continue
192
192
 
193
- # successful frame
194
193
  self.consecutive_frame_failures = 0
195
- self.last_successful_frame_time = time.time()
194
+ self.last_successful_frame_time = loop_start
196
195
  self.frame_counter += 1
197
196
 
198
- should_draw = self.rtmp_streaming_active or self.debug_flag or self.video_debugger.is_window_open(pipeline_id)
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
199
203
 
200
204
  if should_draw:
201
- # draw annotations
202
205
  try:
203
- self.frame_drawer.draw_polygons(frame)
204
- frame_to_draw = frame.copy() if self.debug_flag else frame
206
+ frame_to_draw = frame.copy()
207
+ self.frame_drawer.draw_polygons(frame_to_draw)
205
208
  drawn_frame = self.frame_drawer.draw_frame(
206
209
  frame_to_draw,
207
210
  self.tracked_objects_render,
@@ -214,26 +217,19 @@ class PipelineProcessor:
214
217
  else:
215
218
  drawn_frame = frame
216
219
 
217
- # debug snapshot if requested
218
220
  if self.debug_flag:
219
221
  tracked_objects_render = self._process_frame(frame)
220
222
  try:
223
+ debug_frame = frame.copy()
221
224
  self.debug_repo.update_debug_entries_by_pipeline_id(
222
225
  self.pipeline_id,
223
- self.frame_drawer.draw_frame(frame, tracked_objects_render),
226
+ self.frame_drawer.draw_frame(debug_frame, tracked_objects_render),
224
227
  tracked_objects_render
225
228
  )
226
229
  except Exception as e:
227
230
  logging.warning(f"Debug save failed: {e}")
228
231
  self.debug_flag = False
229
232
 
230
- # Check if RTMP streaming should be active based on preview requests
231
- current_time = time.time()
232
- if current_time - self.last_preview_check_time >= self.preview_check_interval:
233
- self._check_and_update_rtmp_streaming()
234
- self.last_preview_check_time = current_time
235
-
236
- # Push frame to RTMP stream if preview is active
237
233
  if self.rtmp_streaming_active:
238
234
  if self.rtmp_streamer is None:
239
235
  try:
@@ -254,35 +250,18 @@ class PipelineProcessor:
254
250
  except Exception:
255
251
  pass
256
252
  self.rtmp_streamer = None
257
- else:
258
- # Stop RTMP streaming if preview is no longer active
259
- if self.rtmp_streamer is not None:
260
- try:
261
- logging.info(f"🛑 Stopping RTMP streamer for pipeline {pipeline_id} (preview expired)")
262
- self.rtmp_streamer.stop_stream()
263
- except Exception as e:
264
- logging.error(f"❌ Error stopping RTMP streamer: {e}")
265
- finally:
266
- self.rtmp_streamer = None
267
-
268
- # feed detection worker with latest-only behavior
269
- if self.detection_thread and self.detection_thread.is_alive():
253
+ elif self.rtmp_streamer is not None:
270
254
  try:
271
- self.frame_queue.put_nowait(frame)
272
- except queue.Full:
273
- try:
274
- _ = self.frame_queue.get_nowait()
275
- except queue.Empty:
276
- pass
277
- try:
278
- self.frame_queue.put_nowait(frame)
279
- except queue.Full:
280
- 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
281
261
 
282
- try:
283
- self.video_debugger.show_frame(pipeline_id, worker_source_id, drawn_frame)
284
- except Exception as e:
285
- 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)
286
265
 
287
266
  loop_elapsed = time.time() - loop_start
288
267
  sleep_time = max(0.001, target_frame_time - loop_elapsed)
@@ -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.9
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>
@@ -1,10 +1,10 @@
1
- nedo_vision_worker_core/__init__.py,sha256=QryRnDxJa6xTUFbrfTu0WKF5JjXqKErB4K1nEZVLgjQ,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=NQQ8O9gMR0CLmJ8gQ1N3F32qaQWTfbbgH-sb9pNC-Ng,3437
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=CyuMRsddb-86p8HF-pOgeNnCtH4o-je0apYo-qEpwJk,34656
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.9.dist-info/METADATA,sha256=VLdUQm-NwmGPkccYFQCDrEXrLnJW8x4WemX-39gTs5U,14426
102
- nedo_vision_worker_core-0.3.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
103
- nedo_vision_worker_core-0.3.9.dist-info/entry_points.txt,sha256=pIPafsvPnBw-fpBKBmc1NQCQ6PQY3ad8mZ6mn8_p5FI,70
104
- nedo_vision_worker_core-0.3.9.dist-info/top_level.txt,sha256=y8kusXjVYqtG8MSHYWTrk8bRrvjOrphKXYyzu943TTQ,24
105
- nedo_vision_worker_core-0.3.9.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,,