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

Files changed (33) hide show
  1. nedo_vision_worker_core/__init__.py +47 -12
  2. nedo_vision_worker_core/callbacks/DetectionCallbackManager.py +306 -0
  3. nedo_vision_worker_core/callbacks/DetectionCallbackTypes.py +150 -0
  4. nedo_vision_worker_core/callbacks/__init__.py +27 -0
  5. nedo_vision_worker_core/cli.py +24 -34
  6. nedo_vision_worker_core/core_service.py +121 -55
  7. nedo_vision_worker_core/database/DatabaseManager.py +2 -2
  8. nedo_vision_worker_core/detection/BaseDetector.py +2 -1
  9. nedo_vision_worker_core/detection/DetectionManager.py +2 -2
  10. nedo_vision_worker_core/detection/RFDETRDetector.py +23 -5
  11. nedo_vision_worker_core/detection/YOLODetector.py +18 -5
  12. nedo_vision_worker_core/detection/detection_processing/DetectionProcessor.py +1 -1
  13. nedo_vision_worker_core/detection/detection_processing/HumanDetectionProcessor.py +57 -3
  14. nedo_vision_worker_core/detection/detection_processing/PPEDetectionProcessor.py +173 -10
  15. nedo_vision_worker_core/models/ai_model.py +23 -2
  16. nedo_vision_worker_core/pipeline/PipelineProcessor.py +299 -14
  17. nedo_vision_worker_core/pipeline/PipelineSyncThread.py +32 -0
  18. nedo_vision_worker_core/repositories/PPEDetectionRepository.py +18 -15
  19. nedo_vision_worker_core/repositories/RestrictedAreaRepository.py +17 -13
  20. nedo_vision_worker_core/services/SharedVideoStreamServer.py +276 -0
  21. nedo_vision_worker_core/services/VideoSharingDaemon.py +808 -0
  22. nedo_vision_worker_core/services/VideoSharingDaemonManager.py +257 -0
  23. nedo_vision_worker_core/streams/SharedVideoDeviceManager.py +383 -0
  24. nedo_vision_worker_core/streams/StreamSyncThread.py +16 -2
  25. nedo_vision_worker_core/streams/VideoStream.py +267 -246
  26. nedo_vision_worker_core/streams/VideoStreamManager.py +158 -6
  27. nedo_vision_worker_core/tracker/TrackerManager.py +25 -31
  28. nedo_vision_worker_core-0.3.1.dist-info/METADATA +444 -0
  29. {nedo_vision_worker_core-0.2.0.dist-info → nedo_vision_worker_core-0.3.1.dist-info}/RECORD +32 -25
  30. nedo_vision_worker_core-0.2.0.dist-info/METADATA +0 -347
  31. {nedo_vision_worker_core-0.2.0.dist-info → nedo_vision_worker_core-0.3.1.dist-info}/WHEEL +0 -0
  32. {nedo_vision_worker_core-0.2.0.dist-info → nedo_vision_worker_core-0.3.1.dist-info}/entry_points.txt +0 -0
  33. {nedo_vision_worker_core-0.2.0.dist-info → nedo_vision_worker_core-0.3.1.dist-info}/top_level.txt +0 -0
@@ -5,19 +5,54 @@ A library for running AI vision processing and detection in the Nedo Vision plat
5
5
  """
6
6
 
7
7
  from .core_service import CoreService
8
+ from .callbacks import DetectionType, CallbackTrigger, DetectionData, IntervalMetadata
8
9
 
9
- __version__ = "0.2.0"
10
- __all__ = ["CoreService"]
10
+ __version__ = "0.3.1"
11
+ __all__ = [
12
+ "CoreService",
13
+ "DetectionType",
14
+ "CallbackTrigger",
15
+ "DetectionData",
16
+ "IntervalMetadata",
17
+ "DetectionAttribute",
18
+ "BoundingBox"
19
+ ]
11
20
 
12
- # Convenience functions for callback management
13
- def register_ppe_detection_callback(callback):
14
- """Register a callback for PPE detection events."""
15
- return CoreService.register_detection_callback('ppe_detection', callback)
21
+ # Convenience functions for common callback patterns
22
+ def register_immediate_ppe_callback(name: str, callback):
23
+ """Register an immediate PPE detection callback."""
24
+ return CoreService.register_callback(
25
+ name=name,
26
+ callback=callback,
27
+ trigger=CallbackTrigger.ON_NEW_DETECTION,
28
+ detection_types=[DetectionType.PPE_DETECTION]
29
+ )
16
30
 
17
- def register_area_violation_callback(callback):
18
- """Register a callback for restricted area violation events."""
19
- return CoreService.register_detection_callback('area_violation', callback)
31
+ def register_immediate_area_callback(name: str, callback):
32
+ """Register an immediate area violation callback."""
33
+ return CoreService.register_callback(
34
+ name=name,
35
+ callback=callback,
36
+ trigger=CallbackTrigger.ON_NEW_DETECTION,
37
+ detection_types=[DetectionType.AREA_VIOLATION]
38
+ )
20
39
 
21
- def register_general_detection_callback(callback):
22
- """Register a callback for all detection events."""
23
- return CoreService.register_detection_callback('general_detection', callback)
40
+ def register_interval_ppe_callback(name: str, callback, interval_seconds: int = 60):
41
+ """Register an interval-based PPE violation summary callback."""
42
+ return CoreService.register_callback(
43
+ name=name,
44
+ callback=callback,
45
+ trigger=CallbackTrigger.ON_VIOLATION_INTERVAL,
46
+ detection_types=[DetectionType.PPE_DETECTION],
47
+ interval_seconds=interval_seconds
48
+ )
49
+
50
+ def register_interval_area_callback(name: str, callback, interval_seconds: int = 60):
51
+ """Register an interval-based area violation summary callback."""
52
+ return CoreService.register_callback(
53
+ name=name,
54
+ callback=callback,
55
+ trigger=CallbackTrigger.ON_VIOLATION_INTERVAL,
56
+ detection_types=[DetectionType.AREA_VIOLATION],
57
+ interval_seconds=interval_seconds
58
+ )
@@ -0,0 +1,306 @@
1
+ """
2
+ Callback management system for detection events.
3
+ Supports immediate callbacks (triggered on each detection) and interval-based callbacks
4
+ (triggered periodically based on current violation state).
5
+ """
6
+
7
+ import logging
8
+ import threading
9
+ import time
10
+ from typing import Callable, Dict, List, Any, Optional
11
+ from collections import defaultdict, deque
12
+ from datetime import datetime, timedelta
13
+ from dataclasses import dataclass
14
+
15
+ from .DetectionCallbackTypes import DetectionType, CallbackTrigger, DetectionData
16
+
17
+
18
+ @dataclass
19
+ class CallbackConfig:
20
+ """Configuration for a registered callback."""
21
+ callback: Callable[[DetectionData], None]
22
+ trigger: CallbackTrigger
23
+ detection_types: List[DetectionType]
24
+ interval_seconds: Optional[int] = None
25
+
26
+ def __post_init__(self):
27
+ if self.trigger == CallbackTrigger.ON_VIOLATION_INTERVAL and self.interval_seconds is None:
28
+ raise ValueError("interval_seconds is required for ON_VIOLATION_INTERVAL callbacks")
29
+
30
+
31
+ class DetectionCallbackManager:
32
+ """Callback manager with support for immediate and current-state interval callbacks."""
33
+
34
+ def __init__(self):
35
+ self._callbacks: Dict[str, CallbackConfig] = {}
36
+ self._current_violations: Dict[str, DetectionData] = {} # Current active violations by key
37
+ self._interval_thread: Optional[threading.Thread] = None
38
+ self._stop_event = threading.Event()
39
+ self._lock = threading.RLock()
40
+ self._last_interval_trigger: Dict[int, datetime] = {} # Track last trigger time per interval
41
+
42
+ self._start_interval_thread()
43
+
44
+ def register_callback(self,
45
+ name: str,
46
+ callback: Callable[[DetectionData], None],
47
+ trigger: CallbackTrigger,
48
+ detection_types: List[DetectionType],
49
+ interval_seconds: Optional[int] = None) -> None:
50
+ """
51
+ Register a detection callback.
52
+
53
+ Args:
54
+ name: Unique name for the callback
55
+ callback: Function to call when detection occurs
56
+ trigger: When to trigger the callback (immediate or interval)
57
+ detection_types: Types of detections to listen for
58
+ interval_seconds: For interval callbacks, how often to call (in seconds)
59
+ """
60
+ with self._lock:
61
+ config = CallbackConfig(
62
+ callback=callback,
63
+ trigger=trigger,
64
+ detection_types=detection_types,
65
+ interval_seconds=interval_seconds
66
+ )
67
+
68
+ self._callbacks[name] = config
69
+ logging.info(f"📞 Registered callback '{name}' for {[dt.value for dt in detection_types]} "
70
+ f"with trigger {trigger.value}")
71
+
72
+ def unregister_callback(self, name: str) -> bool:
73
+ """
74
+ Unregister a callback.
75
+
76
+ Args:
77
+ name: Name of the callback to remove
78
+
79
+ Returns:
80
+ True if callback was found and removed, False otherwise
81
+ """
82
+ with self._lock:
83
+ if name in self._callbacks:
84
+ del self._callbacks[name]
85
+ logging.info(f"📞 Unregistered callback '{name}'")
86
+ return True
87
+ return False
88
+
89
+ def trigger_detection(self, detection_data: DetectionData) -> None:
90
+ """
91
+ Trigger callbacks for a new detection.
92
+ Callbacks consume the data as-is, with no modification of detection logic.
93
+
94
+ Args:
95
+ detection_data: The detection data to process
96
+ """
97
+ with self._lock:
98
+ key = f"{detection_data.pipeline_id}_{detection_data.person_id}_{detection_data.detection_type.value}"
99
+
100
+ # Store current violations (only if they have violations)
101
+ if detection_data.has_violations():
102
+ self._current_violations[key] = detection_data
103
+ else:
104
+ # Remove from current violations if no longer violating
105
+ self._current_violations.pop(key, None)
106
+
107
+ # Trigger immediate callbacks for all detections (including non-violations)
108
+ self._trigger_immediate_callbacks(detection_data)
109
+
110
+ def _trigger_immediate_callbacks(self, detection_data: DetectionData) -> None:
111
+ """Trigger callbacks that should fire immediately on new detections."""
112
+ for name, config in self._callbacks.items():
113
+ if (config.trigger == CallbackTrigger.ON_NEW_DETECTION and
114
+ detection_data.detection_type in config.detection_types):
115
+
116
+ try:
117
+ config.callback(detection_data)
118
+ except Exception as e:
119
+ logging.error(f"❌ Error in callback '{name}': {e}")
120
+
121
+ def _start_interval_thread(self) -> None:
122
+ """Start the background thread for interval-based callbacks."""
123
+ self._interval_thread = threading.Thread(
124
+ target=self._interval_processor,
125
+ name="DetectionCallbackInterval",
126
+ daemon=True
127
+ )
128
+ self._interval_thread.start()
129
+ logging.info("🔄 Started detection callback interval processor")
130
+
131
+ def _interval_processor(self) -> None:
132
+ """Background thread that processes interval-based callbacks."""
133
+ while not self._stop_event.is_set():
134
+ try:
135
+ interval_groups = defaultdict(list)
136
+ with self._lock:
137
+ for name, config in self._callbacks.items():
138
+ if config.trigger == CallbackTrigger.ON_VIOLATION_INTERVAL:
139
+ interval_groups[config.interval_seconds].append((name, config))
140
+
141
+ current_time = datetime.utcnow()
142
+
143
+ # Clean up stale violations
144
+ self._cleanup_stale_violations(current_time)
145
+
146
+ for interval_seconds, callbacks in interval_groups.items():
147
+ self._process_interval_group(current_time, interval_seconds, callbacks)
148
+
149
+ self._stop_event.wait(1.0)
150
+
151
+ except Exception as e:
152
+ logging.error(f"❌ Error in interval processor: {e}")
153
+ time.sleep(1.0)
154
+
155
+ def _process_interval_group(self, current_time: datetime, interval_seconds: int,
156
+ callbacks: List[tuple]) -> None:
157
+ """Process callbacks for a specific interval. Only triggers when violations are currently active."""
158
+ # Check if enough time has passed for this interval
159
+ last_trigger = self._last_interval_trigger.get(interval_seconds)
160
+ if last_trigger and (current_time - last_trigger).total_seconds() < interval_seconds:
161
+ return # Not time yet for this interval
162
+
163
+ with self._lock:
164
+ if not self._current_violations:
165
+ return
166
+
167
+ # Only process violations that are still current (updated recently)
168
+ recent_threshold = timedelta(seconds=3) # Must be updated within last 3 seconds
169
+ current_violations = []
170
+ for violation in self._current_violations.values():
171
+ if current_time - violation.timestamp <= recent_threshold:
172
+ current_violations.append(violation)
173
+
174
+ if not current_violations:
175
+ return
176
+
177
+ current_violation_summary = self._create_current_state_summary(
178
+ current_violations, current_time
179
+ )
180
+
181
+ if not current_violation_summary:
182
+ return
183
+
184
+ # Update last trigger time for this interval
185
+ self._last_interval_trigger[interval_seconds] = current_time
186
+
187
+ for summary in current_violation_summary:
188
+ for name, config in callbacks:
189
+ if summary.detection_type in config.detection_types:
190
+ try:
191
+ config.callback(summary)
192
+ except Exception as e:
193
+ logging.error(f"❌ Error in interval callback '{name}': {e}")
194
+
195
+ def _create_current_state_summary(self, current_violations: List[DetectionData], current_time: datetime) -> List[DetectionData]:
196
+ """Create current state detection data from active violations."""
197
+ if not current_violations:
198
+ return []
199
+
200
+ # Group by detection type and pipeline
201
+ groups = defaultdict(list)
202
+ for violation in current_violations:
203
+ key = f"{violation.detection_type.value}_{violation.pipeline_id}"
204
+ groups[key].append(violation)
205
+
206
+ summaries = []
207
+ for group_violations in groups.values():
208
+ if not group_violations:
209
+ continue
210
+
211
+ latest = max(group_violations, key=lambda v: v.timestamp)
212
+
213
+ current_violation_counts = defaultdict(int)
214
+ total_current_violations = 0
215
+
216
+ for violation in group_violations:
217
+ for attr in violation.get_violations():
218
+ current_violation_counts[attr.label] += 1
219
+ total_current_violations += 1
220
+
221
+ if total_current_violations == 0:
222
+ continue
223
+
224
+ metadata = {
225
+ 'current_violation_state': dict(current_violation_counts),
226
+ 'total_active_violations': total_current_violations,
227
+ 'unique_persons_in_violation': len(set(v.person_id for v in group_violations)),
228
+ 'state_timestamp': current_time.isoformat()
229
+ }
230
+
231
+ summary = DetectionData(
232
+ detection_type=latest.detection_type,
233
+ detection_id=f"current_state_{latest.detection_type.value}_{int(time.time())}",
234
+ person_id="multiple" if len(set(v.person_id for v in group_violations)) > 1 else latest.person_id,
235
+ pipeline_id=latest.pipeline_id,
236
+ worker_source_id=latest.worker_source_id,
237
+ confidence_score=sum(v.confidence_score for v in group_violations) / len(group_violations),
238
+ bbox=latest.bbox,
239
+ attributes=latest.get_violations(),
240
+ image_path=latest.image_path,
241
+ image_tile_path=latest.image_tile_path,
242
+ timestamp=current_time,
243
+ frame_id=latest.frame_id,
244
+ metadata=metadata
245
+ )
246
+
247
+ summaries.append(summary)
248
+
249
+ return summaries
250
+
251
+ def _cleanup_stale_violations(self, current_time: datetime) -> None:
252
+ """Remove violations that haven't been updated recently (stale detections)."""
253
+ stale_threshold = timedelta(seconds=5) # More aggressive cleanup
254
+ stale_keys = []
255
+
256
+ for key, violation_data in self._current_violations.items():
257
+ if current_time - violation_data.timestamp > stale_threshold:
258
+ stale_keys.append(key)
259
+ logging.debug(f"🧹 Removing stale violation: {key} (age: {current_time - violation_data.timestamp})")
260
+
261
+ for key in stale_keys:
262
+ self._current_violations.pop(key, None)
263
+
264
+ def get_callback_stats(self) -> Dict[str, Any]:
265
+ """Get statistics about registered callbacks and recent activity."""
266
+ with self._lock:
267
+ immediate_callbacks = sum(1 for c in self._callbacks.values()
268
+ if c.trigger == CallbackTrigger.ON_NEW_DETECTION)
269
+ interval_callbacks = sum(1 for c in self._callbacks.values()
270
+ if c.trigger == CallbackTrigger.ON_VIOLATION_INTERVAL)
271
+
272
+ return {
273
+ 'total_callbacks': len(self._callbacks),
274
+ 'immediate_callbacks': immediate_callbacks,
275
+ 'interval_callbacks': interval_callbacks,
276
+ 'current_active_violations': len(self._current_violations),
277
+ 'callback_names': list(self._callbacks.keys())
278
+ }
279
+
280
+ def list_callbacks(self) -> Dict[str, Dict[str, Any]]:
281
+ """List all registered callbacks with their configurations."""
282
+ with self._lock:
283
+ result = {}
284
+ for name, config in self._callbacks.items():
285
+ result[name] = {
286
+ 'trigger': config.trigger.value,
287
+ 'detection_types': [dt.value for dt in config.detection_types],
288
+ 'interval_seconds': config.interval_seconds,
289
+ 'callback_name': config.callback.__name__
290
+ }
291
+ return result
292
+
293
+ def stop(self) -> None:
294
+ """Stop the callback manager and cleanup resources."""
295
+ logging.info("🛑 Stopping detection callback manager...")
296
+ self._stop_event.set()
297
+
298
+ if self._interval_thread and self._interval_thread.is_alive():
299
+ self._interval_thread.join(timeout=5.0)
300
+
301
+ with self._lock:
302
+ self._callbacks.clear()
303
+ self._current_violations.clear()
304
+ self._last_interval_trigger.clear()
305
+
306
+ logging.info("✅ Detection callback manager stopped")
@@ -0,0 +1,150 @@
1
+ """
2
+ Detection callback types and unified data structures.
3
+ """
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Dict, Any, List, Optional
7
+ from typing_extensions import TypedDict
8
+ from datetime import datetime
9
+ from enum import Enum
10
+
11
+
12
+ class DetectionType(Enum):
13
+ """Types of detections."""
14
+ PPE_DETECTION = "ppe_detection"
15
+ AREA_VIOLATION = "area_violation"
16
+ GENERAL_DETECTION = "general_detection"
17
+
18
+
19
+ class CallbackTrigger(Enum):
20
+ """When callbacks should be triggered."""
21
+ ON_NEW_DETECTION = "on_new_detection"
22
+ ON_VIOLATION_INTERVAL = "on_violation_interval"
23
+
24
+
25
+ class IntervalMetadata(TypedDict):
26
+ """Metadata structure for interval-based callbacks with current state."""
27
+ current_violation_state: Dict[str, int] # violation_type -> current count
28
+ total_active_violations: int
29
+ unique_persons_in_violation: int
30
+ state_timestamp: str # ISO format datetime when state was captured
31
+
32
+
33
+ @dataclass
34
+ class BoundingBox:
35
+ """Unified bounding box representation."""
36
+ x1: float
37
+ y1: float
38
+ x2: float
39
+ y2: float
40
+
41
+ @classmethod
42
+ def from_list(cls, bbox: List[float]) -> 'BoundingBox':
43
+ """Create BoundingBox from list [x1, y1, x2, y2]."""
44
+ return cls(x1=bbox[0], y1=bbox[1], x2=bbox[2], y2=bbox[3])
45
+
46
+ def to_list(self) -> List[float]:
47
+ """Convert to list format [x1, y1, x2, y2]."""
48
+ return [self.x1, self.y1, self.x2, self.y2]
49
+
50
+
51
+ @dataclass
52
+ class DetectionAttribute:
53
+ """Represents a detection attribute (e.g., PPE violations)."""
54
+ label: str
55
+ confidence: float
56
+ count: int = 0
57
+ bbox: Optional[BoundingBox] = None
58
+ is_violation: bool = False
59
+
60
+ def to_dict(self) -> Dict[str, Any]:
61
+ """Convert to dictionary representation."""
62
+ result = {
63
+ 'label': self.label,
64
+ 'confidence': self.confidence,
65
+ 'count': self.count,
66
+ 'is_violation': self.is_violation
67
+ }
68
+ if self.bbox:
69
+ result['bbox'] = self.bbox.to_list()
70
+ return result
71
+
72
+
73
+ @dataclass
74
+ class DetectionData:
75
+ """Unified data structure for all detection types."""
76
+
77
+ # Core identification
78
+ detection_type: DetectionType
79
+ detection_id: str
80
+ person_id: str
81
+ pipeline_id: str
82
+ worker_source_id: str
83
+
84
+ # Detection details
85
+ confidence_score: float
86
+ bbox: BoundingBox
87
+ attributes: List[DetectionAttribute] = field(default_factory=list)
88
+
89
+ # Images
90
+ image_path: str = ""
91
+ image_tile_path: str = ""
92
+
93
+ # Timing
94
+ timestamp: datetime = field(default_factory=datetime.utcnow)
95
+ frame_id: int = 0
96
+
97
+ # Additional metadata
98
+ metadata: Dict[str, Any] = field(default_factory=dict)
99
+
100
+ def get_violations(self) -> List[DetectionAttribute]:
101
+ """Get only violation attributes."""
102
+ return [attr for attr in self.attributes if attr.is_violation]
103
+
104
+ def get_compliance(self) -> List[DetectionAttribute]:
105
+ """Get only compliance attributes."""
106
+ return [attr for attr in self.attributes if not attr.is_violation]
107
+
108
+ def has_violations(self) -> bool:
109
+ """Check if detection has any violations."""
110
+ return len(self.get_violations()) > 0
111
+
112
+ def get_interval_metadata(self) -> Optional[IntervalMetadata]:
113
+ """Get typed metadata for interval callbacks."""
114
+ if not self.metadata:
115
+ return None
116
+
117
+ # Check if this looks like interval metadata
118
+ required_keys = {'current_violation_state', 'total_active_violations',
119
+ 'unique_persons_in_violation', 'state_timestamp'}
120
+ if not required_keys.issubset(self.metadata.keys()):
121
+ return None
122
+
123
+ # Return typed metadata
124
+ return IntervalMetadata(
125
+ current_violation_state=self.metadata['current_violation_state'],
126
+ total_active_violations=self.metadata['total_active_violations'],
127
+ unique_persons_in_violation=self.metadata['unique_persons_in_violation'],
128
+ state_timestamp=self.metadata['state_timestamp']
129
+ )
130
+
131
+ def to_dict(self) -> Dict[str, Any]:
132
+ """Convert to dictionary representation for backward compatibility."""
133
+ return {
134
+ 'type': self.detection_type.value,
135
+ 'detection_id': self.detection_id,
136
+ 'person_id': self.person_id,
137
+ 'pipeline_id': self.pipeline_id,
138
+ 'worker_source_id': self.worker_source_id,
139
+ 'confidence_score': self.confidence_score,
140
+ 'bbox': self.bbox.to_list(),
141
+ 'attributes': [attr.to_dict() for attr in self.attributes],
142
+ 'violations': [attr.to_dict() for attr in self.get_violations()],
143
+ 'compliance': [attr.to_dict() for attr in self.get_compliance()],
144
+ 'image_path': self.image_path,
145
+ 'image_tile_path': self.image_tile_path,
146
+ 'timestamp': self.timestamp.isoformat(),
147
+ 'frame_id': self.frame_id,
148
+ 'has_violations': self.has_violations(),
149
+ 'metadata': self.metadata
150
+ }
@@ -0,0 +1,27 @@
1
+ """
2
+ Detection callback system for Nedo Vision Worker Core.
3
+ """
4
+
5
+ from .DetectionCallbackTypes import (
6
+ CallbackTrigger,
7
+ DetectionType,
8
+ DetectionAttribute,
9
+ BoundingBox,
10
+ DetectionData,
11
+ IntervalMetadata
12
+ )
13
+
14
+ from .DetectionCallbackManager import (
15
+ DetectionCallbackManager,
16
+ CallbackConfig
17
+ )
18
+
19
+ __all__ = [
20
+ 'DetectionCallbackManager',
21
+ 'CallbackTrigger',
22
+ 'DetectionType',
23
+ 'DetectionAttribute',
24
+ 'BoundingBox',
25
+ 'DetectionData',
26
+ 'IntervalMetadata'
27
+ ]
@@ -21,7 +21,7 @@ def main():
21
21
  formatter_class=argparse.RawDescriptionHelpFormatter,
22
22
  epilog="""
23
23
  Examples:
24
- # Start core service with default settings
24
+ # Start core service with default settings (includes auto video sharing daemon)
25
25
  nedo-core run
26
26
 
27
27
  # Start with custom drawing assets path
@@ -33,12 +33,20 @@ Examples:
33
33
  # Start with custom storage path and RTMP server
34
34
  nedo-core run --storage-path /path/to/storage --rtmp-server rtmp://server.com:1935/live
35
35
 
36
+ # Start without automatic video sharing daemon
37
+ nedo-core run --disable-video-sharing-daemon
38
+
36
39
  # Start with all custom parameters
37
40
  nedo-core run --drawing-assets /path/to/assets --log-level DEBUG --storage-path /data --rtmp-server rtmp://server.com:1935/live
38
41
 
39
42
  # Run system diagnostics
40
43
  nedo-core doctor
41
44
 
45
+ Video Sharing Daemon:
46
+ By default, the core service automatically starts video sharing daemons for devices
47
+ as needed. This enables multiple processes to access the same video device simultaneously
48
+ without "device busy" errors. Use --disable-video-sharing-daemon to turn this off.
49
+
42
50
  Detection Callbacks:
43
51
  The core service supports detection callbacks for extensible event handling.
44
52
  See example_callbacks.py for usage examples.
@@ -83,33 +91,15 @@ Detection Callbacks:
83
91
 
84
92
  run_parser.add_argument(
85
93
  "--rtmp-server",
86
- default="rtmp://localhost:1935/live",
87
- help="RTMP server URL for video streaming (default: rtmp://localhost:1935/live)"
94
+ default="rtmp://live.vision.sindika.co.id:1935/live",
95
+ help="RTMP server URL for video streaming (default: rtmp://live.vision.sindika.co.id:1935/live)"
88
96
  )
89
97
 
90
- # Add legacy arguments for backward compatibility (when no subcommand is used)
91
- parser.add_argument(
92
- "--drawing-assets",
93
- help="(Legacy) Path to drawing assets directory (optional, uses bundled assets by default)"
94
- )
95
-
96
- parser.add_argument(
97
- "--log-level",
98
- choices=["DEBUG", "INFO", "WARNING", "ERROR"],
99
- default="INFO",
100
- help="(Legacy) Logging level (default: INFO)"
101
- )
102
-
103
- parser.add_argument(
104
- "--storage-path",
105
- default="data",
106
- help="(Legacy) Storage path for databases and files (default: data)"
107
- )
108
-
109
- parser.add_argument(
110
- "--rtmp-server",
111
- default="rtmp://localhost:1935/live",
112
- help="(Legacy) RTMP server URL for video streaming (default: rtmp://localhost:1935/live)"
98
+ run_parser.add_argument(
99
+ "--disable_video_sharing_daemon",
100
+ action="store_true",
101
+ default=False,
102
+ help="Disable automatic video sharing daemon management (default: False)"
113
103
  )
114
104
 
115
105
  parser.add_argument(
@@ -125,11 +115,7 @@ Detection Callbacks:
125
115
  run_core_service(args)
126
116
  elif args.command == 'doctor':
127
117
  run_doctor()
128
- elif hasattr(args, 'drawing_assets') and args.drawing_assets is not None: # Legacy mode - if any arguments are provided without subcommand
129
- print("⚠️ Warning: Using legacy command format. Consider using 'nedo-core run --drawing-assets ...' instead.")
130
- run_core_service(args)
131
118
  else:
132
- # If no subcommand provided, show help
133
119
  parser.print_help()
134
120
  sys.exit(1)
135
121
 
@@ -148,9 +134,6 @@ def run_doctor():
148
134
  sys.exit(1)
149
135
 
150
136
 
151
- def run_core_service(args):
152
- """Run the core service with the provided arguments."""
153
-
154
137
  def run_core_service(args):
155
138
  """Run the core service with the provided arguments."""
156
139
  # Set up signal handlers for graceful shutdown
@@ -160,12 +143,18 @@ def run_core_service(args):
160
143
  logger = logging.getLogger(__name__)
161
144
 
162
145
  try:
146
+ # Determine video sharing daemon setting
147
+ enable_daemon = True # Default
148
+ if hasattr(args, 'disable_video_sharing_daemon') and args.disable_video_sharing_daemon:
149
+ enable_daemon = False
150
+
163
151
  # Create and start the core service
164
152
  service = CoreService(
165
153
  drawing_assets_path=args.drawing_assets,
166
154
  log_level=args.log_level,
167
155
  storage_path=args.storage_path,
168
- rtmp_server=args.rtmp_server
156
+ rtmp_server=args.rtmp_server,
157
+ enable_video_sharing_daemon=enable_daemon
169
158
  )
170
159
 
171
160
  logger.info("🚀 Starting Nedo Vision Core...")
@@ -176,6 +165,7 @@ def run_core_service(args):
176
165
  logger.info(f"📝 Log Level: {args.log_level}")
177
166
  logger.info(f"💾 Storage Path: {args.storage_path}")
178
167
  logger.info(f"📡 RTMP Server: {args.rtmp_server}")
168
+ logger.info(f"🔗 Video Sharing Daemon: {'Enabled' if enable_daemon else 'Disabled'}")
179
169
  logger.info("Press Ctrl+C to stop the service")
180
170
 
181
171
  # Start the service