nedo-vision-worker-core 0.3.9__tar.gz → 0.4.1__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.

Potentially problematic release.


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

Files changed (113) hide show
  1. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/PKG-INFO +1 -1
  2. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/__init__.py +2 -2
  3. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/ai/FrameDrawer.py +25 -16
  4. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/ai/VideoDebugger.py +29 -23
  5. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/pipeline/PipelineProcessor.py +37 -45
  6. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/streams/RTMPStreamer.py +64 -18
  7. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/util/DrawingUtils.py +4 -0
  8. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core.egg-info/PKG-INFO +1 -1
  9. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/MANIFEST.in +0 -0
  10. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/README.md +0 -0
  11. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/ai/ImageDebugger.py +0 -0
  12. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/ai/__init__.py +0 -0
  13. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/callbacks/DetectionCallbackManager.py +0 -0
  14. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/callbacks/DetectionCallbackTypes.py +0 -0
  15. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/callbacks/__init__.py +0 -0
  16. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/cli.py +0 -0
  17. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/config/ConfigurationManager.py +0 -0
  18. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/config/__init__.py +0 -0
  19. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/core_service.py +0 -0
  20. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/database/DatabaseManager.py +0 -0
  21. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/database/__init__.py +0 -0
  22. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/detection/BaseDetector.py +0 -0
  23. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/detection/RFDETRDetector.py +0 -0
  24. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/detection/YOLODetector.py +0 -0
  25. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/detection/__init__.py +0 -0
  26. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/detection/detection_processing/DetectionProcessor.py +0 -0
  27. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/detection/detection_processing/HumanDetectionProcessor.py +0 -0
  28. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/detection/detection_processing/PPEDetectionProcessor.py +0 -0
  29. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/detection/detection_processing/__init__.py +0 -0
  30. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/doctor.py +0 -0
  31. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/drawing_assets/blue/inner_corner.png +0 -0
  32. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/drawing_assets/blue/inner_frame.png +0 -0
  33. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/drawing_assets/blue/line.png +0 -0
  34. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/drawing_assets/blue/top_left.png +0 -0
  35. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/drawing_assets/blue/top_right.png +0 -0
  36. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/drawing_assets/red/inner_corner.png +0 -0
  37. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/drawing_assets/red/inner_frame.png +0 -0
  38. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/drawing_assets/red/line.png +0 -0
  39. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/drawing_assets/red/top_left.png +0 -0
  40. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/drawing_assets/red/top_right.png +0 -0
  41. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/icons/boots-green.png +0 -0
  42. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/icons/boots-red.png +0 -0
  43. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/icons/gloves-green.png +0 -0
  44. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/icons/gloves-red.png +0 -0
  45. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/icons/goggles-green.png +0 -0
  46. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/icons/goggles-red.png +0 -0
  47. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/icons/helmet-green.png +0 -0
  48. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/icons/helmet-red.png +0 -0
  49. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/icons/mask-red.png +0 -0
  50. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/icons/vest-green.png +0 -0
  51. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/icons/vest-red.png +0 -0
  52. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/models/__init__.py +0 -0
  53. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/models/ai_model.py +0 -0
  54. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/models/auth.py +0 -0
  55. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/models/config.py +0 -0
  56. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/models/dataset_source.py +0 -0
  57. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/models/logs.py +0 -0
  58. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/models/ppe_detection.py +0 -0
  59. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/models/ppe_detection_label.py +0 -0
  60. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/models/restricted_area_violation.py +0 -0
  61. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/models/user.py +0 -0
  62. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/models/worker_source.py +0 -0
  63. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/models/worker_source_pipeline.py +0 -0
  64. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/models/worker_source_pipeline_config.py +0 -0
  65. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/models/worker_source_pipeline_debug.py +0 -0
  66. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/models/worker_source_pipeline_detection.py +0 -0
  67. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/pipeline/ModelManager.py +0 -0
  68. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/pipeline/PipelineConfigManager.py +0 -0
  69. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/pipeline/PipelineManager.py +0 -0
  70. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/pipeline/PipelinePrepocessor.py +0 -0
  71. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/pipeline/PipelineSyncThread.py +0 -0
  72. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/pipeline/__init__.py +0 -0
  73. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/preprocessing/ImageResizer.py +0 -0
  74. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/preprocessing/ImageRoi.py +0 -0
  75. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/preprocessing/Preprocessor.py +0 -0
  76. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/preprocessing/__init__.py +0 -0
  77. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/repositories/AIModelRepository.py +0 -0
  78. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/repositories/BaseRepository.py +0 -0
  79. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/repositories/PPEDetectionRepository.py +0 -0
  80. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/repositories/RestrictedAreaRepository.py +0 -0
  81. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/repositories/WorkerSourcePipelineDebugRepository.py +0 -0
  82. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/repositories/WorkerSourcePipelineDetectionRepository.py +0 -0
  83. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/repositories/WorkerSourcePipelineRepository.py +0 -0
  84. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/repositories/WorkerSourceRepository.py +0 -0
  85. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/repositories/__init__.py +0 -0
  86. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/services/SharedVideoStreamServer.py +0 -0
  87. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/services/VideoSharingDaemon.py +0 -0
  88. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/services/VideoSharingDaemonManager.py +0 -0
  89. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/streams/SharedVideoDeviceManager.py +0 -0
  90. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/streams/StreamSyncThread.py +0 -0
  91. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/streams/VideoStream.py +0 -0
  92. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/streams/VideoStreamManager.py +0 -0
  93. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/streams/__init__.py +0 -0
  94. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/tracker/SFSORT.py +0 -0
  95. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/tracker/TrackerManager.py +0 -0
  96. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/tracker/__init__.py +0 -0
  97. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/util/BoundingBoxMetrics.py +0 -0
  98. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/util/ModelReadinessChecker.py +0 -0
  99. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/util/PersonAttributeMatcher.py +0 -0
  100. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/util/PersonRestrictedAreaMatcher.py +0 -0
  101. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/util/PipelinePreviewChecker.py +0 -0
  102. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/util/PlatformDetector.py +0 -0
  103. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/util/TablePrinter.py +0 -0
  104. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core/util/__init__.py +0 -0
  105. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core.egg-info/SOURCES.txt +0 -0
  106. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core.egg-info/dependency_links.txt +0 -0
  107. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core.egg-info/entry_points.txt +0 -0
  108. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core.egg-info/requires.txt +0 -0
  109. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/nedo_vision_worker_core.egg-info/top_level.txt +0 -0
  110. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/pyproject.toml +0 -0
  111. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/requirements.txt +0 -0
  112. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/setup.cfg +0 -0
  113. {nedo_vision_worker_core-0.3.9 → nedo_vision_worker_core-0.4.1}/setup.py +0 -0
@@ -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.1
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>
@@ -7,10 +7,10 @@ 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.1"
11
11
  __all__ = [
12
12
  "CoreService",
13
- "DetectionType",
13
+ "DetectionType",
14
14
  "CallbackTrigger",
15
15
  "DetectionData",
16
16
  "IntervalMetadata",
@@ -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,19 @@ 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
+ # Only feed frames to detection queue if detection processor is active
263
+ if self.detection_processor is not None:
264
+ if not self.frame_queue.full():
265
+ self.frame_queue.put_nowait(frame)
286
266
 
287
267
  loop_elapsed = time.time() - loop_start
288
268
  sleep_time = max(0.001, target_frame_time - loop_elapsed)
@@ -328,7 +308,13 @@ class PipelineProcessor:
328
308
 
329
309
  while self.running:
330
310
  try:
331
- frame = self.frame_queue.get(block=True, timeout=1)
311
+ # Calculate how long to wait for next detection
312
+ current_time = time.time()
313
+ time_since_last_detection = current_time - last_detection_time
314
+ time_until_next_detection = max(0.1, self.detection_interval - time_since_last_detection)
315
+
316
+ # Wait for frame with timeout aligned to detection interval
317
+ frame = self.frame_queue.get(block=True, timeout=time_until_next_detection)
332
318
 
333
319
  # Check for poison pill (None = stop signal)
334
320
  if frame is None:
@@ -353,9 +339,14 @@ class PipelineProcessor:
353
339
  except queue.Empty:
354
340
  pass
355
341
 
356
- # Respect detection interval
342
+ # Respect detection interval - skip if too soon
357
343
  if (current_time - last_detection_time) < self.detection_interval:
344
+ # Sleep the remaining time instead of busy-waiting
345
+ remaining_time = self.detection_interval - (current_time - last_detection_time)
346
+ if remaining_time > 0.01:
347
+ time.sleep(remaining_time)
358
348
  continue
349
+
359
350
  last_detection_time = current_time
360
351
 
361
352
  if self.detection_processor is None or frame is None or frame.size == 0:
@@ -384,6 +375,7 @@ class PipelineProcessor:
384
375
  )
385
376
 
386
377
  except queue.Empty:
378
+ # Timeout occurred - this is normal, just continue to next iteration
387
379
  pass
388
380
  except Exception as e:
389
381
  logging.error(f"❌ Error in detection thread for pipeline {pipeline_id}: {e}", exc_info=True)
@@ -307,6 +307,7 @@ class RTMPStreamer:
307
307
  if not self.is_active():
308
308
  raise BrokenPipeError("FFmpeg process is not active")
309
309
  self._ffmpeg_process.stdin.write(frame.tobytes())
310
+ self._ffmpeg_process.stdin.flush() # Ensure data is sent immediately
310
311
  except (BrokenPipeError, OSError) as e:
311
312
 
312
313
  # Check if this failure was from a HW encoder
@@ -356,23 +357,20 @@ class RTMPStreamer:
356
357
  '-framerate', str(self.fps), '-i', '-',
357
358
  ]
358
359
 
360
+ # Add encoder and encoder-specific parameters
359
361
  cmd.extend(encoder_args)
360
362
 
363
+ # Common video parameters
361
364
  cmd.extend([
362
- '-profile:v', 'main', '-pix_fmt', 'yuv420p',
365
+ '-pix_fmt', 'yuv420p',
363
366
  '-b:v', f"{self.bitrate}k", '-maxrate', f"{self.bitrate}k", '-bufsize', f"{self.bitrate*2}k",
364
367
  '-g', str(self.fps * 2), '-keyint_min', str(self.fps),
365
- '-force_key_frames', 'expr:gte(t,n_forced*1)', '-an',
366
- '-flvflags', 'no_duration_filesize', '-f', 'flv', self.rtmp_url,
368
+ '-force_key_frames', 'expr:gte(t,n_forced*1)',
369
+ '-an', # No audio
370
+ '-flvflags', 'no_duration_filesize',
371
+ '-f', 'flv', self.rtmp_url,
367
372
  ])
368
373
 
369
- if encoder_name == "libx264":
370
- cmd.extend([
371
- "-preset", "ultrafast",
372
- "-tune", "zerolatency",
373
- "-x264-params", "open_gop=0:aud=1:repeat-headers=1:nal-hrd=cbr",
374
- ])
375
-
376
374
  return cmd, encoder_name
377
375
 
378
376
  def _select_ffmpeg_encoder(self, force_cpu: bool = False) -> Tuple[List[str], str]:
@@ -381,26 +379,74 @@ class RTMPStreamer:
381
379
  Will force CPU if force_cpu is True.
382
380
  """
383
381
  if force_cpu:
384
- return ["-c:v", "libx264"], "libx264"
382
+ return [
383
+ "-c:v", "libx264",
384
+ "-preset", "ultrafast",
385
+ "-tune", "zerolatency",
386
+ "-profile:v", "main",
387
+ ], "libx264"
385
388
 
386
389
  force_encoder = os.environ.get("RTMP_ENCODER", "").lower()
387
390
 
388
391
  if force_encoder == "cpu" or force_encoder == "libx264":
389
- return ["-c:v", "libx264"], "libx264"
392
+ return [
393
+ "-c:v", "libx264",
394
+ "-preset", "ultrafast",
395
+ "-tune", "zerolatency",
396
+ "-profile:v", "main",
397
+ ], "libx264"
390
398
  elif force_encoder == "nvenc":
391
- return ["-c:v", "h264_nvenc", "-preset", "llhp"], "h264_nvenc"
399
+ return [
400
+ "-c:v", "h264_nvenc",
401
+ "-preset", "p1", # p1 = fastest, p7 = slowest
402
+ "-tune", "ull", # ultra-low latency
403
+ "-rc:v", "cbr", # constant bitrate for streaming
404
+ "-rc-lookahead", "0", # disable lookahead for lower latency
405
+ "-delay", "0", # zero delay
406
+ "-zerolatency", "1", # enable zero latency mode
407
+ "-profile:v", "main",
408
+ "-gpu", "0", # Use first GPU
409
+ ], "h264_nvenc"
392
410
 
393
411
  if self._platform.is_jetson():
394
- # Jetson-specific encoder
395
- return ["-c:v", "h264_omx"], "h264_omx"
412
+ # Jetson-specific encoder with optimizations
413
+ return [
414
+ "-c:v", "h264_nvenc",
415
+ "-preset", "p1",
416
+ "-tune", "ull",
417
+ "-rc:v", "cbr",
418
+ "-rc-lookahead", "0",
419
+ "-delay", "0",
420
+ "-zerolatency", "1",
421
+ "-profile:v", "main",
422
+ ], "h264_nvenc"
396
423
 
397
424
  if sys.platform == "darwin":
398
- return ["-c:v", "h264_videotoolbox"], "h264_videotoolbox"
425
+ return [
426
+ "-c:v", "h264_videotoolbox",
427
+ "-profile:v", "main",
428
+ "-realtime", "1",
429
+ ], "h264_videotoolbox"
399
430
 
400
431
  has_nvidia = (os.environ.get("NVIDIA_VISIBLE_DEVICES") is not None or
401
432
  os.path.exists("/proc/driver/nvidia/version"))
402
433
 
403
434
  if has_nvidia:
404
- return ["-c:v", "h264_nvenc", "-preset", "llhp"], "h264_nvenc"
435
+ return [
436
+ "-c:v", "h264_nvenc",
437
+ "-preset", "p1", # p1 = fastest preset
438
+ "-tune", "ull", # ultra-low latency
439
+ "-rc:v", "cbr", # constant bitrate
440
+ "-rc-lookahead", "0", # disable lookahead
441
+ "-delay", "0", # zero delay
442
+ "-zerolatency", "1", # zero latency mode
443
+ "-profile:v", "main",
444
+ "-gpu", "0", # Use first GPU
445
+ ], "h264_nvenc"
405
446
 
406
- return ["-c:v", "libx264"], "libx264"
447
+ return [
448
+ "-c:v", "libx264",
449
+ "-preset", "ultrafast",
450
+ "-tune", "zerolatency",
451
+ "-profile:v", "main",
452
+ ], "libx264"
@@ -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.1
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>