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.
- nedo_vision_worker_core/__init__.py +23 -0
- nedo_vision_worker_core/ai/FrameDrawer.py +144 -0
- nedo_vision_worker_core/ai/ImageDebugger.py +126 -0
- nedo_vision_worker_core/ai/VideoDebugger.py +69 -0
- nedo_vision_worker_core/ai/__init__.py +1 -0
- nedo_vision_worker_core/cli.py +197 -0
- nedo_vision_worker_core/config/ConfigurationManager.py +173 -0
- nedo_vision_worker_core/config/__init__.py +1 -0
- nedo_vision_worker_core/core_service.py +237 -0
- nedo_vision_worker_core/database/DatabaseManager.py +236 -0
- nedo_vision_worker_core/database/__init__.py +1 -0
- nedo_vision_worker_core/detection/BaseDetector.py +22 -0
- nedo_vision_worker_core/detection/DetectionManager.py +83 -0
- nedo_vision_worker_core/detection/RFDETRDetector.py +62 -0
- nedo_vision_worker_core/detection/YOLODetector.py +57 -0
- nedo_vision_worker_core/detection/__init__.py +1 -0
- nedo_vision_worker_core/detection/detection_processing/DetectionProcessor.py +29 -0
- nedo_vision_worker_core/detection/detection_processing/HumanDetectionProcessor.py +47 -0
- nedo_vision_worker_core/detection/detection_processing/PPEDetectionProcessor.py +44 -0
- nedo_vision_worker_core/detection/detection_processing/__init__.py +1 -0
- nedo_vision_worker_core/doctor.py +342 -0
- nedo_vision_worker_core/drawing_assets/blue/inner_corner.png +0 -0
- nedo_vision_worker_core/drawing_assets/blue/inner_frame.png +0 -0
- nedo_vision_worker_core/drawing_assets/blue/line.png +0 -0
- nedo_vision_worker_core/drawing_assets/blue/top_left.png +0 -0
- nedo_vision_worker_core/drawing_assets/blue/top_right.png +0 -0
- nedo_vision_worker_core/drawing_assets/red/inner_corner.png +0 -0
- nedo_vision_worker_core/drawing_assets/red/inner_frame.png +0 -0
- nedo_vision_worker_core/drawing_assets/red/line.png +0 -0
- nedo_vision_worker_core/drawing_assets/red/top_left.png +0 -0
- nedo_vision_worker_core/drawing_assets/red/top_right.png +0 -0
- nedo_vision_worker_core/icons/boots-green.png +0 -0
- nedo_vision_worker_core/icons/boots-red.png +0 -0
- nedo_vision_worker_core/icons/gloves-green.png +0 -0
- nedo_vision_worker_core/icons/gloves-red.png +0 -0
- nedo_vision_worker_core/icons/goggles-green.png +0 -0
- nedo_vision_worker_core/icons/goggles-red.png +0 -0
- nedo_vision_worker_core/icons/helmet-green.png +0 -0
- nedo_vision_worker_core/icons/helmet-red.png +0 -0
- nedo_vision_worker_core/icons/mask-red.png +0 -0
- nedo_vision_worker_core/icons/vest-green.png +0 -0
- nedo_vision_worker_core/icons/vest-red.png +0 -0
- nedo_vision_worker_core/models/__init__.py +20 -0
- nedo_vision_worker_core/models/ai_model.py +41 -0
- nedo_vision_worker_core/models/auth.py +14 -0
- nedo_vision_worker_core/models/config.py +9 -0
- nedo_vision_worker_core/models/dataset_source.py +30 -0
- nedo_vision_worker_core/models/logs.py +9 -0
- nedo_vision_worker_core/models/ppe_detection.py +39 -0
- nedo_vision_worker_core/models/ppe_detection_label.py +20 -0
- nedo_vision_worker_core/models/restricted_area_violation.py +20 -0
- nedo_vision_worker_core/models/user.py +10 -0
- nedo_vision_worker_core/models/worker_source.py +19 -0
- nedo_vision_worker_core/models/worker_source_pipeline.py +21 -0
- nedo_vision_worker_core/models/worker_source_pipeline_config.py +24 -0
- nedo_vision_worker_core/models/worker_source_pipeline_debug.py +15 -0
- nedo_vision_worker_core/models/worker_source_pipeline_detection.py +14 -0
- nedo_vision_worker_core/pipeline/PipelineConfigManager.py +32 -0
- nedo_vision_worker_core/pipeline/PipelineManager.py +133 -0
- nedo_vision_worker_core/pipeline/PipelinePrepocessor.py +40 -0
- nedo_vision_worker_core/pipeline/PipelineProcessor.py +338 -0
- nedo_vision_worker_core/pipeline/PipelineSyncThread.py +202 -0
- nedo_vision_worker_core/pipeline/__init__.py +1 -0
- nedo_vision_worker_core/preprocessing/ImageResizer.py +42 -0
- nedo_vision_worker_core/preprocessing/ImageRoi.py +61 -0
- nedo_vision_worker_core/preprocessing/Preprocessor.py +16 -0
- nedo_vision_worker_core/preprocessing/__init__.py +1 -0
- nedo_vision_worker_core/repositories/AIModelRepository.py +31 -0
- nedo_vision_worker_core/repositories/PPEDetectionRepository.py +146 -0
- nedo_vision_worker_core/repositories/RestrictedAreaRepository.py +90 -0
- nedo_vision_worker_core/repositories/WorkerSourcePipelineDebugRepository.py +81 -0
- nedo_vision_worker_core/repositories/WorkerSourcePipelineDetectionRepository.py +71 -0
- nedo_vision_worker_core/repositories/WorkerSourcePipelineRepository.py +79 -0
- nedo_vision_worker_core/repositories/WorkerSourceRepository.py +19 -0
- nedo_vision_worker_core/repositories/__init__.py +1 -0
- nedo_vision_worker_core/streams/RTMPStreamer.py +146 -0
- nedo_vision_worker_core/streams/StreamSyncThread.py +66 -0
- nedo_vision_worker_core/streams/VideoStream.py +324 -0
- nedo_vision_worker_core/streams/VideoStreamManager.py +121 -0
- nedo_vision_worker_core/streams/__init__.py +1 -0
- nedo_vision_worker_core/tracker/SFSORT.py +325 -0
- nedo_vision_worker_core/tracker/TrackerManager.py +163 -0
- nedo_vision_worker_core/tracker/__init__.py +1 -0
- nedo_vision_worker_core/util/BoundingBoxMetrics.py +53 -0
- nedo_vision_worker_core/util/DrawingUtils.py +354 -0
- nedo_vision_worker_core/util/ModelReadinessChecker.py +188 -0
- nedo_vision_worker_core/util/PersonAttributeMatcher.py +70 -0
- nedo_vision_worker_core/util/PersonRestrictedAreaMatcher.py +45 -0
- nedo_vision_worker_core/util/TablePrinter.py +28 -0
- nedo_vision_worker_core/util/__init__.py +1 -0
- nedo_vision_worker_core-0.2.0.dist-info/METADATA +347 -0
- nedo_vision_worker_core-0.2.0.dist-info/RECORD +95 -0
- nedo_vision_worker_core-0.2.0.dist-info/WHEEL +5 -0
- nedo_vision_worker_core-0.2.0.dist-info/entry_points.txt +2 -0
- 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 @@
|
|
|
1
|
+
|
|
@@ -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 @@
|
|
|
1
|
+
|
|
@@ -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()
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|