nedo-vision-worker-core 0.2.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.

Files changed (95) hide show
  1. nedo_vision_worker_core/__init__.py +23 -0
  2. nedo_vision_worker_core/ai/FrameDrawer.py +144 -0
  3. nedo_vision_worker_core/ai/ImageDebugger.py +126 -0
  4. nedo_vision_worker_core/ai/VideoDebugger.py +69 -0
  5. nedo_vision_worker_core/ai/__init__.py +1 -0
  6. nedo_vision_worker_core/cli.py +197 -0
  7. nedo_vision_worker_core/config/ConfigurationManager.py +173 -0
  8. nedo_vision_worker_core/config/__init__.py +1 -0
  9. nedo_vision_worker_core/core_service.py +237 -0
  10. nedo_vision_worker_core/database/DatabaseManager.py +236 -0
  11. nedo_vision_worker_core/database/__init__.py +1 -0
  12. nedo_vision_worker_core/detection/BaseDetector.py +22 -0
  13. nedo_vision_worker_core/detection/DetectionManager.py +83 -0
  14. nedo_vision_worker_core/detection/RFDETRDetector.py +62 -0
  15. nedo_vision_worker_core/detection/YOLODetector.py +57 -0
  16. nedo_vision_worker_core/detection/__init__.py +1 -0
  17. nedo_vision_worker_core/detection/detection_processing/DetectionProcessor.py +29 -0
  18. nedo_vision_worker_core/detection/detection_processing/HumanDetectionProcessor.py +47 -0
  19. nedo_vision_worker_core/detection/detection_processing/PPEDetectionProcessor.py +44 -0
  20. nedo_vision_worker_core/detection/detection_processing/__init__.py +1 -0
  21. nedo_vision_worker_core/doctor.py +342 -0
  22. nedo_vision_worker_core/drawing_assets/blue/inner_corner.png +0 -0
  23. nedo_vision_worker_core/drawing_assets/blue/inner_frame.png +0 -0
  24. nedo_vision_worker_core/drawing_assets/blue/line.png +0 -0
  25. nedo_vision_worker_core/drawing_assets/blue/top_left.png +0 -0
  26. nedo_vision_worker_core/drawing_assets/blue/top_right.png +0 -0
  27. nedo_vision_worker_core/drawing_assets/red/inner_corner.png +0 -0
  28. nedo_vision_worker_core/drawing_assets/red/inner_frame.png +0 -0
  29. nedo_vision_worker_core/drawing_assets/red/line.png +0 -0
  30. nedo_vision_worker_core/drawing_assets/red/top_left.png +0 -0
  31. nedo_vision_worker_core/drawing_assets/red/top_right.png +0 -0
  32. nedo_vision_worker_core/icons/boots-green.png +0 -0
  33. nedo_vision_worker_core/icons/boots-red.png +0 -0
  34. nedo_vision_worker_core/icons/gloves-green.png +0 -0
  35. nedo_vision_worker_core/icons/gloves-red.png +0 -0
  36. nedo_vision_worker_core/icons/goggles-green.png +0 -0
  37. nedo_vision_worker_core/icons/goggles-red.png +0 -0
  38. nedo_vision_worker_core/icons/helmet-green.png +0 -0
  39. nedo_vision_worker_core/icons/helmet-red.png +0 -0
  40. nedo_vision_worker_core/icons/mask-red.png +0 -0
  41. nedo_vision_worker_core/icons/vest-green.png +0 -0
  42. nedo_vision_worker_core/icons/vest-red.png +0 -0
  43. nedo_vision_worker_core/models/__init__.py +20 -0
  44. nedo_vision_worker_core/models/ai_model.py +41 -0
  45. nedo_vision_worker_core/models/auth.py +14 -0
  46. nedo_vision_worker_core/models/config.py +9 -0
  47. nedo_vision_worker_core/models/dataset_source.py +30 -0
  48. nedo_vision_worker_core/models/logs.py +9 -0
  49. nedo_vision_worker_core/models/ppe_detection.py +39 -0
  50. nedo_vision_worker_core/models/ppe_detection_label.py +20 -0
  51. nedo_vision_worker_core/models/restricted_area_violation.py +20 -0
  52. nedo_vision_worker_core/models/user.py +10 -0
  53. nedo_vision_worker_core/models/worker_source.py +19 -0
  54. nedo_vision_worker_core/models/worker_source_pipeline.py +21 -0
  55. nedo_vision_worker_core/models/worker_source_pipeline_config.py +24 -0
  56. nedo_vision_worker_core/models/worker_source_pipeline_debug.py +15 -0
  57. nedo_vision_worker_core/models/worker_source_pipeline_detection.py +14 -0
  58. nedo_vision_worker_core/pipeline/PipelineConfigManager.py +32 -0
  59. nedo_vision_worker_core/pipeline/PipelineManager.py +133 -0
  60. nedo_vision_worker_core/pipeline/PipelinePrepocessor.py +40 -0
  61. nedo_vision_worker_core/pipeline/PipelineProcessor.py +338 -0
  62. nedo_vision_worker_core/pipeline/PipelineSyncThread.py +202 -0
  63. nedo_vision_worker_core/pipeline/__init__.py +1 -0
  64. nedo_vision_worker_core/preprocessing/ImageResizer.py +42 -0
  65. nedo_vision_worker_core/preprocessing/ImageRoi.py +61 -0
  66. nedo_vision_worker_core/preprocessing/Preprocessor.py +16 -0
  67. nedo_vision_worker_core/preprocessing/__init__.py +1 -0
  68. nedo_vision_worker_core/repositories/AIModelRepository.py +31 -0
  69. nedo_vision_worker_core/repositories/PPEDetectionRepository.py +146 -0
  70. nedo_vision_worker_core/repositories/RestrictedAreaRepository.py +90 -0
  71. nedo_vision_worker_core/repositories/WorkerSourcePipelineDebugRepository.py +81 -0
  72. nedo_vision_worker_core/repositories/WorkerSourcePipelineDetectionRepository.py +71 -0
  73. nedo_vision_worker_core/repositories/WorkerSourcePipelineRepository.py +79 -0
  74. nedo_vision_worker_core/repositories/WorkerSourceRepository.py +19 -0
  75. nedo_vision_worker_core/repositories/__init__.py +1 -0
  76. nedo_vision_worker_core/streams/RTMPStreamer.py +146 -0
  77. nedo_vision_worker_core/streams/StreamSyncThread.py +66 -0
  78. nedo_vision_worker_core/streams/VideoStream.py +324 -0
  79. nedo_vision_worker_core/streams/VideoStreamManager.py +121 -0
  80. nedo_vision_worker_core/streams/__init__.py +1 -0
  81. nedo_vision_worker_core/tracker/SFSORT.py +325 -0
  82. nedo_vision_worker_core/tracker/TrackerManager.py +163 -0
  83. nedo_vision_worker_core/tracker/__init__.py +1 -0
  84. nedo_vision_worker_core/util/BoundingBoxMetrics.py +53 -0
  85. nedo_vision_worker_core/util/DrawingUtils.py +354 -0
  86. nedo_vision_worker_core/util/ModelReadinessChecker.py +188 -0
  87. nedo_vision_worker_core/util/PersonAttributeMatcher.py +70 -0
  88. nedo_vision_worker_core/util/PersonRestrictedAreaMatcher.py +45 -0
  89. nedo_vision_worker_core/util/TablePrinter.py +28 -0
  90. nedo_vision_worker_core/util/__init__.py +1 -0
  91. nedo_vision_worker_core-0.2.0.dist-info/METADATA +347 -0
  92. nedo_vision_worker_core-0.2.0.dist-info/RECORD +95 -0
  93. nedo_vision_worker_core-0.2.0.dist-info/WHEEL +5 -0
  94. nedo_vision_worker_core-0.2.0.dist-info/entry_points.txt +2 -0
  95. nedo_vision_worker_core-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,83 @@
1
+ import logging
2
+ try:
3
+ import torch
4
+ TORCH_AVAILABLE = True
5
+ except ImportError:
6
+ TORCH_AVAILABLE = False
7
+ from .BaseDetector import BaseDetector
8
+ from .RFDETRDetector import RFDETRDetector
9
+ from .YOLODetector import YOLODetector
10
+
11
+
12
+ class DetectionManager:
13
+ def __init__(self, model=None):
14
+ self.detector: BaseDetector | None = None
15
+ self.model_metadata = None
16
+
17
+ if model:
18
+ self.load_model(model)
19
+
20
+ def load_model(self, model):
21
+ """
22
+ Loads a new model at runtime and replaces current detector if successful.
23
+ Checks download status before attempting to load the model.
24
+ """
25
+ if not model:
26
+ if self.detector:
27
+ logging.info("🧹 Model unloaded")
28
+ self.detector = None
29
+ self.model_metadata = None
30
+ return
31
+
32
+ # Check download status before loading
33
+ if not model.is_ready_for_use():
34
+ if model.is_downloading():
35
+ logging.warning(f"⏳ Model {model.name} is still downloading. Skipping load.")
36
+ self.detector = None
37
+ self.model_metadata = None
38
+ return
39
+ elif model.has_download_failed():
40
+ logging.error(f"❌ Model {model.name} download failed: {model.download_error}")
41
+ self.detector = None
42
+ self.model_metadata = None
43
+ return
44
+ else:
45
+ logging.warning(f"⚠️ Model {model.name} is not ready for use (status: {model.download_status})")
46
+ self.detector = None
47
+ self.model_metadata = None
48
+ return
49
+
50
+ detector_type = model.type.lower()
51
+
52
+ try:
53
+ if detector_type == "yolo":
54
+ detector = YOLODetector(model)
55
+ elif detector_type == "rf_detr":
56
+ detector = RFDETRDetector(model)
57
+ else:
58
+ raise ValueError(f"Unsupported model type: {detector_type}")
59
+
60
+ if detector.model is not None:
61
+ self.detector = detector
62
+ self.model_metadata = model
63
+ # Log device info
64
+ if TORCH_AVAILABLE:
65
+ device = "GPU" if torch.cuda.is_available() else "CPU"
66
+ else:
67
+ device = "CPU (torch not installed)"
68
+ logging.info(f"🚀 Model {model.name} loaded on {device}")
69
+ logging.info(f"📥 Model {model.name} with {detector_type} detector loaded")
70
+ else:
71
+ logging.error(f"❌ Error loading model: {model.name} with {detector_type} detector")
72
+ self.detector = None
73
+ self.model_metadata = None
74
+
75
+ except Exception as e:
76
+ logging.error(f"❌ Error loading model: {model.name} with {detector_type} detector: {e}")
77
+ self.detector = None
78
+ self.model_metadata = None
79
+
80
+ def detect_objects(self, frame, confidence_threshold=0.7):
81
+ if not self.detector:
82
+ return []
83
+ return self.detector.detect_objects(frame, confidence_threshold)
@@ -0,0 +1,62 @@
1
+ import cv2
2
+ import logging
3
+ try:
4
+ from rfdetr import RFDETRBase
5
+ RFDETR_AVAILABLE = True
6
+ except ImportError:
7
+ RFDETR_AVAILABLE = False
8
+ RFDETRBase = None
9
+
10
+ from ..database.DatabaseManager import DatabaseManager
11
+ from .BaseDetector import BaseDetector
12
+
13
+ logging.getLogger("ultralytics").setLevel(logging.WARNING)
14
+
15
+ class RFDETRDetector(BaseDetector):
16
+ def __init__(self, model):
17
+ if not RFDETR_AVAILABLE:
18
+ raise ImportError(
19
+ "RF-DETR is required but not installed. Install it manually with:\n"
20
+ "pip install rfdetr @ git+https://github.com/roboflow/rf-detr.git@1e63dbad402eea10f110e86013361d6b02ee0c09\n"
21
+ "See the documentation for more details."
22
+ )
23
+ self.model = None
24
+ self.metadata = None
25
+
26
+ if model:
27
+ self.load_model(model)
28
+
29
+ def load_model(self, model):
30
+ self.metadata = model
31
+ path = DatabaseManager.STORAGE_PATHS["models"] / model.file
32
+
33
+ if not path.is_file() or path.stat().st_size == 0:
34
+ logging.error(f"❌ Model file not found or empty: {path}")
35
+ self.model = None
36
+ return False
37
+
38
+ try:
39
+ self.model = RFDETRBase(pretrain_weights=path.as_posix())
40
+ self.model.optimize_for_inference()
41
+ return True
42
+ except Exception as e:
43
+ logging.error(f"❌ Error loading RFDETR model {model.name}: {e}")
44
+ self.model = None
45
+ return False
46
+
47
+ def detect_objects(self, frame, confidence_threshold=0.7):
48
+ if self.model is None:
49
+ return []
50
+
51
+ frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
52
+ results = self.model.predict(frame_rgb, confidence_threshold)
53
+
54
+ detections = []
55
+ for class_id, conf, xyxy in zip(results.class_id, results.confidence, results.xyxy):
56
+ detections.append({
57
+ "label": self.model.class_names[class_id],
58
+ "confidence": conf,
59
+ "bbox": xyxy
60
+ })
61
+
62
+ return detections
@@ -0,0 +1,57 @@
1
+ import cv2
2
+ import logging
3
+ from ultralytics import YOLO
4
+ from ..database.DatabaseManager import DatabaseManager
5
+ from .BaseDetector import BaseDetector
6
+
7
+ logging.getLogger("ultralytics").setLevel(logging.WARNING)
8
+
9
+ class YOLODetector(BaseDetector):
10
+ def __init__(self, model):
11
+ self.model = None
12
+ self.metadata = None
13
+
14
+ if model:
15
+ self.load_model(model)
16
+
17
+ def load_model(self, model):
18
+ self.metadata = model
19
+ path = DatabaseManager.STORAGE_PATHS["models"] / model.file
20
+
21
+ # Check if file exists and has content
22
+ if not path.is_file() or path.stat().st_size == 0:
23
+ logging.error(f"❌ Model file not found or empty: {path}")
24
+ self.model = None
25
+ return False
26
+
27
+ try:
28
+ self.model = YOLO(path)
29
+ return True
30
+ except Exception as e:
31
+ logging.error(f"❌ Error loading YOLO model {model.name}: {e}")
32
+ self.model = None
33
+ return False
34
+
35
+ def detect_objects(self, frame, target_classes=None, confidence_threshold=0.7):
36
+ if self.model is None:
37
+ return []
38
+
39
+ frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
40
+ results = self.model(frame_rgb)
41
+
42
+ detections = []
43
+ for box in results[0].boxes:
44
+ class_id = int(box.cls)
45
+ label = self.model.names[class_id]
46
+ confidence = float(box.conf)
47
+
48
+ if confidence < confidence_threshold:
49
+ continue
50
+
51
+ detections.append({
52
+ "label": label,
53
+ "confidence": confidence,
54
+ "bbox": box.xyxy.tolist()[0]
55
+ })
56
+
57
+ return detections
@@ -0,0 +1,29 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import List, Dict, Any, Tuple
3
+
4
+ import numpy as np
5
+
6
+ from ...ai.FrameDrawer import FrameDrawer
7
+ from ...pipeline.PipelineConfigManager import PipelineConfigManager
8
+
9
+ class DetectionProcessor(ABC):
10
+ code = ""
11
+ icons = {}
12
+ labels = []
13
+ exclusive_labels = []
14
+ violation_labels = []
15
+ compliance_labels = []
16
+
17
+ @abstractmethod
18
+ def process(self, detections: List[Dict[str, Any]], dimension: Tuple[int, int]) -> List[Dict[str, Any]]:
19
+ """Process raw detections and return processed results."""
20
+ pass
21
+
22
+ @abstractmethod
23
+ def update(self, config_manager: PipelineConfigManager):
24
+ pass
25
+
26
+ @abstractmethod
27
+ def save_to_db(self, pipeline_id: str, worker_source_id: str, frame_counter: int, tracked_objects: List[Dict[str, Any]], frame: np.ndarray, frame_drawer: FrameDrawer):
28
+ """Save processed detections to the database."""
29
+ pass
@@ -0,0 +1,47 @@
1
+ from typing import Any, Dict, List, Tuple
2
+
3
+ import numpy as np
4
+ from shapely.geometry import Polygon
5
+ from .DetectionProcessor import DetectionProcessor
6
+ from ...pipeline.PipelineConfigManager import PipelineConfigManager
7
+ from ...repositories.RestrictedAreaRepository import RestrictedAreaRepository
8
+ from ...util.PersonRestrictedAreaMatcher import PersonRestrictedAreaMatcher
9
+
10
+
11
+ class HumanDetectionProcessor(DetectionProcessor):
12
+ code = "human"
13
+ labels = ["in_restricted_area"]
14
+ violation_labels = ["in_restricted_area"]
15
+
16
+ def __init__(self):
17
+ self.repository = RestrictedAreaRepository()
18
+ self.restricted_areas = []
19
+
20
+ def update(self, config_manager: PipelineConfigManager):
21
+ config = config_manager.get_feature_config(self.code, [])
22
+ area_list = config.get("restrictedArea", [])
23
+ self.restricted_areas = [
24
+ [(p["x"], p["y"]) for p in area] for area in area_list
25
+ ]
26
+
27
+ def process(self, detections: List[Dict[str, Any]], dimension: Tuple[int, int]) -> List[Dict[str, Any]]:
28
+ persons = [d for d in detections if d["label"] == "person"]
29
+
30
+ height, width = dimension
31
+ area_polygons = []
32
+
33
+ for area in self.restricted_areas:
34
+ points = [(int(x * width), int(y * height)) for x, y in area]
35
+ area_polygons.append(Polygon(points))
36
+
37
+ matched_results = PersonRestrictedAreaMatcher.match_persons_with_restricted_areas(
38
+ persons, area_polygons
39
+ )
40
+
41
+ return matched_results
42
+
43
+ def save_to_db(self, pipeline_id: str, worker_source_id: str, frame_counter: int, tracked_objects: List[Dict[str, Any]], frame: np.ndarray, frame_drawer):
44
+ """Save the processed detections to the database if the feature is enabled."""
45
+ self.repository.save_area_violation(
46
+ pipeline_id, worker_source_id, frame_counter, tracked_objects, frame, frame_drawer
47
+ )
@@ -0,0 +1,44 @@
1
+ from typing import Any, Dict, List, Tuple
2
+
3
+ import numpy as np
4
+ from ...ai.FrameDrawer import FrameDrawer
5
+ from .DetectionProcessor import DetectionProcessor
6
+ from ...pipeline.PipelineConfigManager import PipelineConfigManager
7
+ from ...repositories.PPEDetectionRepository import PPEDetectionRepository
8
+ from ...util.PersonAttributeMatcher import PersonAttributeMatcher
9
+
10
+ class PPEDetectionProcessor(DetectionProcessor):
11
+ code = "ppe"
12
+ icons = {
13
+ "helmet": "icons/helmet-green.png",
14
+ "no_helmet": "icons/helmet-red.png",
15
+ "vest": "icons/vest-green.png",
16
+ "no_vest": "icons/vest-red.png"
17
+ }
18
+ labels = ["helmet", "no_helmet", "vest", "no_vest", "gloves", "no_gloves", "goggles", "no_goggles", "boots", "no_boots"]
19
+ violation_labels = ["no_helmet", "no_vest", "no_gloves", "no_goggles", "no_boots"]
20
+ compliance_labels = ["helmet", "vest", "gloves", "goggles", "boots"]
21
+ exclusive_labels = [("helmet", "no_helmet"), ("vest", "no_vest"), ("gloves", "no_gloves"), ("goggles", "no_goggles"), ("boots", "no_boots")]
22
+
23
+ def __init__(self):
24
+ self.ppe_storage = PPEDetectionRepository()
25
+ self.types = []
26
+
27
+ def update(self, config_manager: PipelineConfigManager):
28
+ config = config_manager.get_feature_config(self.code, [])
29
+ self.types = config.get("ppeType", [])
30
+
31
+ def process(self, detections: List[Dict[str, Any]], dimension: Tuple[int, int]) -> List[Dict[str, Any]]:
32
+ persons = [d for d in detections if d["label"] == "person"]
33
+ ppe_attributes = [d for d in detections if any(x in d["label"] for x in self.types)]
34
+
35
+ matched_results = PersonAttributeMatcher.match_persons_with_attributes(
36
+ persons, ppe_attributes, coverage_threshold=0.5
37
+ )
38
+
39
+ return matched_results
40
+
41
+ def save_to_db(self, pipeline_id: str, worker_source_id: str, frame_counter: int, tracked_objects: List[Dict[str, Any]], frame: np.ndarray, frame_drawer: FrameDrawer):
42
+ self.ppe_storage.save_ppe_detection(
43
+ pipeline_id, worker_source_id, frame_counter, tracked_objects, frame, frame_drawer
44
+ )
@@ -0,0 +1,342 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Nedo Vision Worker Core Doctor
4
+
5
+ This module provides diagnostic capabilities to check system requirements
6
+ and dependencies for the Nedo Vision Worker Core.
7
+ """
8
+
9
+ import subprocess
10
+ import sys
11
+ import platform
12
+ import shutil
13
+ import os
14
+ from pathlib import Path
15
+
16
+
17
+ def check_python_version():
18
+ """Check if Python version meets requirements."""
19
+ print("🐍 Checking Python version...")
20
+
21
+ version = sys.version_info
22
+ min_version = (3, 8)
23
+
24
+ if version >= min_version:
25
+ print(f" ✅ Python {version.major}.{version.minor}.{version.micro} (meets requirement >= {min_version[0]}.{min_version[1]})")
26
+ return True
27
+ else:
28
+ print(f" ❌ Python {version.major}.{version.minor}.{version.micro} (requires >= {min_version[0]}.{min_version[1]})")
29
+ return False
30
+
31
+
32
+ def check_pytorch():
33
+ """Check if PyTorch is properly installed."""
34
+ print("🔥 Checking PyTorch...")
35
+
36
+ try:
37
+ import torch
38
+ import torchvision
39
+
40
+ torch_version = torch.__version__
41
+ torchvision_version = torchvision.__version__
42
+
43
+ print(f" ✅ PyTorch {torch_version} installed")
44
+ print(f" ✅ TorchVision {torchvision_version} installed")
45
+
46
+ # Check CUDA availability
47
+ if torch.cuda.is_available():
48
+ device_count = torch.cuda.device_count()
49
+ current_device = torch.cuda.current_device()
50
+ device_name = torch.cuda.get_device_name(current_device)
51
+
52
+ print(f" 🚀 CUDA available with {device_count} device(s)")
53
+ print(f" 🎮 Current device: {device_name}")
54
+
55
+ # Check CUDA version
56
+ cuda_version = torch.version.cuda
57
+ if cuda_version:
58
+ print(f" 🔧 CUDA version: {cuda_version}")
59
+ else:
60
+ print(" ⚠️ CUDA not available (using CPU only)")
61
+
62
+ # Test basic tensor operations
63
+ try:
64
+ x = torch.randn(5, 3)
65
+ y = torch.randn(3, 4)
66
+ z = torch.mm(x, y)
67
+ print(" ✅ PyTorch basic operations working")
68
+ return True
69
+ except Exception as e:
70
+ print(f" ❌ PyTorch basic operations failed: {e}")
71
+ return False
72
+
73
+ except ImportError as e:
74
+ print(f" ❌ PyTorch not installed: {e}")
75
+ return False
76
+ except Exception as e:
77
+ print(f" ❌ PyTorch test failed: {e}")
78
+ return False
79
+
80
+
81
+ def check_ultralytics():
82
+ """Check if Ultralytics (YOLO) is properly installed."""
83
+ print("🎯 Checking Ultralytics YOLO...")
84
+
85
+ try:
86
+ from ultralytics import YOLO
87
+ import ultralytics
88
+
89
+ version = ultralytics.__version__
90
+ print(f" ✅ Ultralytics {version} installed")
91
+
92
+ # Test YOLO model loading (without downloading)
93
+ try:
94
+ # Check if we can import required modules
95
+ from ultralytics.models import YOLO as YOLOModel
96
+ from ultralytics.utils import checks
97
+ print(" ✅ Ultralytics modules importing correctly")
98
+ return True
99
+ except Exception as e:
100
+ print(f" ⚠️ Ultralytics modules test failed: {e}")
101
+ return False
102
+
103
+ except ImportError:
104
+ print(" ❌ Ultralytics not installed")
105
+ return False
106
+ except Exception as e:
107
+ print(f" ❌ Ultralytics test failed: {e}")
108
+ return False
109
+
110
+
111
+ def check_opencv():
112
+ """Check if OpenCV is properly installed."""
113
+ print("👁️ Checking OpenCV...")
114
+
115
+ try:
116
+ import cv2
117
+ version = cv2.__version__
118
+ build_info = cv2.getBuildInformation()
119
+
120
+ print(f" ✅ OpenCV {version} installed")
121
+
122
+ # Check OpenCV build configuration
123
+ if "CUDA" in build_info:
124
+ print(" 🚀 OpenCV built with CUDA support")
125
+ if "OpenMP" in build_info:
126
+ print(" ⚡ OpenCV built with OpenMP support")
127
+
128
+ # Check for platform-specific optimizations
129
+ machine = platform.machine()
130
+ if machine in ["aarch64", "armv7l", "arm64"]:
131
+ if "NEON" in build_info:
132
+ print(" 🎯 OpenCV built with ARM NEON optimizations")
133
+ else:
134
+ print(" ⚠️ OpenCV may not have ARM optimizations")
135
+
136
+ # Test basic functionality
137
+ import numpy as np
138
+ test_img = np.zeros((100, 100, 3), dtype=np.uint8)
139
+
140
+ # Test encoding/decoding
141
+ _, encoded = cv2.imencode('.jpg', test_img)
142
+ decoded = cv2.imdecode(encoded, cv2.IMREAD_COLOR)
143
+
144
+ if decoded is not None:
145
+ print(" ✅ OpenCV basic functionality working")
146
+ return True
147
+ else:
148
+ print(" ❌ OpenCV encoding/decoding test failed")
149
+ return False
150
+
151
+ except ImportError:
152
+ print(" ❌ OpenCV not installed")
153
+ return False
154
+ except Exception as e:
155
+ print(f" ❌ OpenCV test failed: {e}")
156
+ return False
157
+
158
+
159
+ def check_ffmpeg():
160
+ """Check if FFmpeg is installed and accessible."""
161
+ print("🎬 Checking FFmpeg...")
162
+
163
+ try:
164
+ # Check if ffmpeg is in PATH
165
+ ffmpeg_path = shutil.which("ffmpeg")
166
+ if not ffmpeg_path:
167
+ print(" ❌ FFmpeg not found in PATH")
168
+ return False
169
+
170
+ # Check ffmpeg version
171
+ result = subprocess.run(
172
+ ["ffmpeg", "-version"],
173
+ capture_output=True,
174
+ text=True,
175
+ timeout=10
176
+ )
177
+
178
+ if result.returncode == 0:
179
+ # Extract version from output
180
+ version_line = result.stdout.split('\n')[0]
181
+ print(f" ✅ {version_line}")
182
+ print(f" 📍 Location: {ffmpeg_path}")
183
+ return True
184
+ else:
185
+ print(" ❌ FFmpeg found but failed to get version")
186
+ return False
187
+
188
+ except subprocess.TimeoutExpired:
189
+ print(" ❌ FFmpeg check timed out")
190
+ return False
191
+ except Exception as e:
192
+ print(f" ❌ Error checking FFmpeg: {e}")
193
+ return False
194
+
195
+
196
+ def check_storage_access():
197
+ """Check if storage directories are accessible."""
198
+ print("💾 Checking storage access...")
199
+
200
+ try:
201
+ storage_path = Path("data")
202
+
203
+ # Check if we can create the directory
204
+ storage_path.mkdir(exist_ok=True)
205
+ print(f" ✅ Storage directory accessible: {storage_path.absolute()}")
206
+
207
+ # Test write access
208
+ test_file = storage_path / "test_access.txt"
209
+ try:
210
+ with open(test_file, 'w') as f:
211
+ f.write("test")
212
+ test_file.unlink() # Clean up
213
+ print(" ✅ Storage write access working")
214
+ return True
215
+ except Exception as e:
216
+ print(f" ❌ Storage write access failed: {e}")
217
+ return False
218
+
219
+ except Exception as e:
220
+ print(f" ❌ Storage access check failed: {e}")
221
+ return False
222
+
223
+
224
+ def check_system_resources():
225
+ """Check system resources (memory, disk space)."""
226
+ print("🖥️ Checking system resources...")
227
+
228
+ try:
229
+ import psutil
230
+
231
+ # Check memory
232
+ memory = psutil.virtual_memory()
233
+ memory_gb = memory.total / (1024**3)
234
+ print(f" 💾 Total RAM: {memory_gb:.1f} GB")
235
+
236
+ if memory_gb >= 4:
237
+ print(" ✅ Sufficient RAM available")
238
+ else:
239
+ print(" ⚠️ Low RAM (recommended: 4+ GB)")
240
+
241
+ # Check disk space
242
+ disk = psutil.disk_usage('.')
243
+ disk_free_gb = disk.free / (1024**3)
244
+ print(f" 💿 Free disk space: {disk_free_gb:.1f} GB")
245
+
246
+ if disk_free_gb >= 2:
247
+ print(" ✅ Sufficient disk space available")
248
+ else:
249
+ print(" ⚠️ Low disk space (recommended: 2+ GB)")
250
+
251
+ # Check CPU
252
+ cpu_count = psutil.cpu_count()
253
+ cpu_freq = psutil.cpu_freq()
254
+ print(f" 🔢 CPU cores: {cpu_count}")
255
+ if cpu_freq:
256
+ print(f" ⚡ CPU frequency: {cpu_freq.current:.0f} MHz")
257
+
258
+ return True
259
+
260
+ except ImportError:
261
+ print(" ❌ psutil not installed for system resource checks")
262
+ return False
263
+ except Exception as e:
264
+ print(f" ❌ System resource check failed: {e}")
265
+ return False
266
+
267
+
268
+ def check_model_files():
269
+ """Check if model files are available."""
270
+ print("🤖 Checking model files...")
271
+
272
+ try:
273
+ # Check for YOLO model file
274
+ model_file = Path("yolov11n.pt")
275
+ if model_file.exists():
276
+ size_mb = model_file.stat().st_size / (1024**2)
277
+ print(f" ✅ YOLO model found: {model_file} ({size_mb:.1f} MB)")
278
+ return True
279
+ else:
280
+ print(" ⚠️ YOLO model file not found (will be downloaded on first use)")
281
+ return True # Not a critical failure
282
+
283
+ except Exception as e:
284
+ print(f" ❌ Model file check failed: {e}")
285
+ return False
286
+
287
+
288
+ def run_diagnostics():
289
+ """Run all diagnostic checks."""
290
+ print("🏥 Nedo Vision Worker Core - System Diagnostics")
291
+ print("=" * 60)
292
+
293
+ checks = [
294
+ ("Python Version", check_python_version),
295
+ ("PyTorch", check_pytorch),
296
+ ("Ultralytics YOLO", check_ultralytics),
297
+ ("OpenCV", check_opencv),
298
+ ("FFmpeg", check_ffmpeg),
299
+ ("Storage Access", check_storage_access),
300
+ ("System Resources", check_system_resources),
301
+ ("Model Files", check_model_files),
302
+ ]
303
+
304
+ results = []
305
+
306
+ for name, check_func in checks:
307
+ print()
308
+ try:
309
+ result = check_func()
310
+ results.append((name, result))
311
+ except Exception as e:
312
+ print(f" ❌ Unexpected error in {name}: {e}")
313
+ results.append((name, False))
314
+
315
+ # Summary
316
+ print()
317
+ print("=" * 60)
318
+ print("📋 DIAGNOSTIC SUMMARY")
319
+ print("=" * 60)
320
+
321
+ passed = 0
322
+ total = len(results)
323
+
324
+ for name, result in results:
325
+ status = "✅ PASS" if result else "❌ FAIL"
326
+ print(f"{status:8} {name}")
327
+ if result:
328
+ passed += 1
329
+
330
+ print()
331
+ print(f"Results: {passed}/{total} checks passed")
332
+
333
+ if passed == total:
334
+ print("🎉 All checks passed! The system is ready for Nedo Vision Worker Core.")
335
+ return True
336
+ else:
337
+ print("⚠️ Some checks failed. Please address the issues above.")
338
+ return False
339
+
340
+
341
+ if __name__ == "__main__":
342
+ run_diagnostics()