matrice-analytics 0.1.2__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 matrice-analytics might be problematic. Click here for more details.
- matrice_analytics/__init__.py +28 -0
- matrice_analytics/boundary_drawing_internal/README.md +305 -0
- matrice_analytics/boundary_drawing_internal/__init__.py +45 -0
- matrice_analytics/boundary_drawing_internal/boundary_drawing_internal.py +1207 -0
- matrice_analytics/boundary_drawing_internal/boundary_drawing_tool.py +429 -0
- matrice_analytics/boundary_drawing_internal/boundary_tool_template.html +1036 -0
- matrice_analytics/boundary_drawing_internal/data/.gitignore +12 -0
- matrice_analytics/boundary_drawing_internal/example_usage.py +206 -0
- matrice_analytics/boundary_drawing_internal/usage/README.md +110 -0
- matrice_analytics/boundary_drawing_internal/usage/boundary_drawer_launcher.py +102 -0
- matrice_analytics/boundary_drawing_internal/usage/simple_boundary_launcher.py +107 -0
- matrice_analytics/post_processing/README.md +455 -0
- matrice_analytics/post_processing/__init__.py +732 -0
- matrice_analytics/post_processing/advanced_tracker/README.md +650 -0
- matrice_analytics/post_processing/advanced_tracker/__init__.py +17 -0
- matrice_analytics/post_processing/advanced_tracker/base.py +99 -0
- matrice_analytics/post_processing/advanced_tracker/config.py +77 -0
- matrice_analytics/post_processing/advanced_tracker/kalman_filter.py +370 -0
- matrice_analytics/post_processing/advanced_tracker/matching.py +195 -0
- matrice_analytics/post_processing/advanced_tracker/strack.py +230 -0
- matrice_analytics/post_processing/advanced_tracker/tracker.py +367 -0
- matrice_analytics/post_processing/config.py +142 -0
- matrice_analytics/post_processing/core/__init__.py +63 -0
- matrice_analytics/post_processing/core/base.py +704 -0
- matrice_analytics/post_processing/core/config.py +3188 -0
- matrice_analytics/post_processing/core/config_utils.py +925 -0
- matrice_analytics/post_processing/face_reg/__init__.py +43 -0
- matrice_analytics/post_processing/face_reg/compare_similarity.py +556 -0
- matrice_analytics/post_processing/face_reg/embedding_manager.py +681 -0
- matrice_analytics/post_processing/face_reg/face_recognition.py +1870 -0
- matrice_analytics/post_processing/face_reg/face_recognition_client.py +339 -0
- matrice_analytics/post_processing/face_reg/people_activity_logging.py +283 -0
- matrice_analytics/post_processing/ocr/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/easyocr_extractor.py +248 -0
- matrice_analytics/post_processing/ocr/postprocessing.py +271 -0
- matrice_analytics/post_processing/ocr/preprocessing.py +52 -0
- matrice_analytics/post_processing/post_processor.py +1153 -0
- matrice_analytics/post_processing/test_cases/__init__.py +1 -0
- matrice_analytics/post_processing/test_cases/run_tests.py +143 -0
- matrice_analytics/post_processing/test_cases/test_advanced_customer_service.py +841 -0
- matrice_analytics/post_processing/test_cases/test_basic_counting_tracking.py +523 -0
- matrice_analytics/post_processing/test_cases/test_comprehensive.py +531 -0
- matrice_analytics/post_processing/test_cases/test_config.py +852 -0
- matrice_analytics/post_processing/test_cases/test_customer_service.py +585 -0
- matrice_analytics/post_processing/test_cases/test_data_generators.py +583 -0
- matrice_analytics/post_processing/test_cases/test_people_counting.py +510 -0
- matrice_analytics/post_processing/test_cases/test_processor.py +524 -0
- matrice_analytics/post_processing/test_cases/test_utilities.py +356 -0
- matrice_analytics/post_processing/test_cases/test_utils.py +743 -0
- matrice_analytics/post_processing/usecases/Histopathological_Cancer_Detection_img.py +604 -0
- matrice_analytics/post_processing/usecases/__init__.py +267 -0
- matrice_analytics/post_processing/usecases/abandoned_object_detection.py +797 -0
- matrice_analytics/post_processing/usecases/advanced_customer_service.py +1601 -0
- matrice_analytics/post_processing/usecases/age_detection.py +842 -0
- matrice_analytics/post_processing/usecases/age_gender_detection.py +1043 -0
- matrice_analytics/post_processing/usecases/anti_spoofing_detection.py +656 -0
- matrice_analytics/post_processing/usecases/assembly_line_detection.py +841 -0
- matrice_analytics/post_processing/usecases/banana_defect_detection.py +624 -0
- matrice_analytics/post_processing/usecases/basic_counting_tracking.py +667 -0
- matrice_analytics/post_processing/usecases/blood_cancer_detection_img.py +881 -0
- matrice_analytics/post_processing/usecases/car_damage_detection.py +834 -0
- matrice_analytics/post_processing/usecases/car_part_segmentation.py +946 -0
- matrice_analytics/post_processing/usecases/car_service.py +1601 -0
- matrice_analytics/post_processing/usecases/cardiomegaly_classification.py +864 -0
- matrice_analytics/post_processing/usecases/cell_microscopy_segmentation.py +897 -0
- matrice_analytics/post_processing/usecases/chicken_pose_detection.py +648 -0
- matrice_analytics/post_processing/usecases/child_monitoring.py +814 -0
- matrice_analytics/post_processing/usecases/color/clip.py +232 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/merges.txt +48895 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/preprocessor_config.json +28 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/special_tokens_map.json +30 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer.json +245079 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer_config.json +32 -0
- matrice_analytics/post_processing/usecases/color/clip_processor/vocab.json +1 -0
- matrice_analytics/post_processing/usecases/color/color_map_utils.py +70 -0
- matrice_analytics/post_processing/usecases/color/color_mapper.py +468 -0
- matrice_analytics/post_processing/usecases/color_detection.py +1835 -0
- matrice_analytics/post_processing/usecases/color_map_utils.py +70 -0
- matrice_analytics/post_processing/usecases/concrete_crack_detection.py +827 -0
- matrice_analytics/post_processing/usecases/crop_weed_detection.py +781 -0
- matrice_analytics/post_processing/usecases/customer_service.py +1008 -0
- matrice_analytics/post_processing/usecases/defect_detection_products.py +936 -0
- matrice_analytics/post_processing/usecases/distracted_driver_detection.py +822 -0
- matrice_analytics/post_processing/usecases/drone_traffic_monitoring.py +930 -0
- matrice_analytics/post_processing/usecases/drowsy_driver_detection.py +829 -0
- matrice_analytics/post_processing/usecases/dwell_detection.py +829 -0
- matrice_analytics/post_processing/usecases/emergency_vehicle_detection.py +827 -0
- matrice_analytics/post_processing/usecases/face_emotion.py +813 -0
- matrice_analytics/post_processing/usecases/face_recognition.py +827 -0
- matrice_analytics/post_processing/usecases/fashion_detection.py +835 -0
- matrice_analytics/post_processing/usecases/field_mapping.py +902 -0
- matrice_analytics/post_processing/usecases/fire_detection.py +1112 -0
- matrice_analytics/post_processing/usecases/flare_analysis.py +891 -0
- matrice_analytics/post_processing/usecases/flower_segmentation.py +1006 -0
- matrice_analytics/post_processing/usecases/gas_leak_detection.py +837 -0
- matrice_analytics/post_processing/usecases/gender_detection.py +832 -0
- matrice_analytics/post_processing/usecases/human_activity_recognition.py +871 -0
- matrice_analytics/post_processing/usecases/intrusion_detection.py +1672 -0
- matrice_analytics/post_processing/usecases/leaf.py +821 -0
- matrice_analytics/post_processing/usecases/leaf_disease.py +840 -0
- matrice_analytics/post_processing/usecases/leak_detection.py +837 -0
- matrice_analytics/post_processing/usecases/license_plate_detection.py +914 -0
- matrice_analytics/post_processing/usecases/license_plate_monitoring.py +1194 -0
- matrice_analytics/post_processing/usecases/litter_monitoring.py +717 -0
- matrice_analytics/post_processing/usecases/mask_detection.py +869 -0
- matrice_analytics/post_processing/usecases/natural_disaster.py +907 -0
- matrice_analytics/post_processing/usecases/parking.py +787 -0
- matrice_analytics/post_processing/usecases/parking_space_detection.py +822 -0
- matrice_analytics/post_processing/usecases/pcb_defect_detection.py +888 -0
- matrice_analytics/post_processing/usecases/pedestrian_detection.py +808 -0
- matrice_analytics/post_processing/usecases/people_counting.py +1728 -0
- matrice_analytics/post_processing/usecases/people_tracking.py +1842 -0
- matrice_analytics/post_processing/usecases/pipeline_detection.py +605 -0
- matrice_analytics/post_processing/usecases/plaque_segmentation_img.py +874 -0
- matrice_analytics/post_processing/usecases/pothole_segmentation.py +915 -0
- matrice_analytics/post_processing/usecases/ppe_compliance.py +645 -0
- matrice_analytics/post_processing/usecases/price_tag_detection.py +822 -0
- matrice_analytics/post_processing/usecases/proximity_detection.py +1901 -0
- matrice_analytics/post_processing/usecases/road_lane_detection.py +623 -0
- matrice_analytics/post_processing/usecases/road_traffic_density.py +832 -0
- matrice_analytics/post_processing/usecases/road_view_segmentation.py +915 -0
- matrice_analytics/post_processing/usecases/shelf_inventory_detection.py +583 -0
- matrice_analytics/post_processing/usecases/shoplifting_detection.py +822 -0
- matrice_analytics/post_processing/usecases/shopping_cart_analysis.py +899 -0
- matrice_analytics/post_processing/usecases/skin_cancer_classification_img.py +864 -0
- matrice_analytics/post_processing/usecases/smoker_detection.py +833 -0
- matrice_analytics/post_processing/usecases/solar_panel.py +810 -0
- matrice_analytics/post_processing/usecases/suspicious_activity_detection.py +1030 -0
- matrice_analytics/post_processing/usecases/template_usecase.py +380 -0
- matrice_analytics/post_processing/usecases/theft_detection.py +648 -0
- matrice_analytics/post_processing/usecases/traffic_sign_monitoring.py +724 -0
- matrice_analytics/post_processing/usecases/underground_pipeline_defect_detection.py +775 -0
- matrice_analytics/post_processing/usecases/underwater_pollution_detection.py +842 -0
- matrice_analytics/post_processing/usecases/vehicle_monitoring.py +950 -0
- matrice_analytics/post_processing/usecases/warehouse_object_segmentation.py +899 -0
- matrice_analytics/post_processing/usecases/waterbody_segmentation.py +923 -0
- matrice_analytics/post_processing/usecases/weapon_detection.py +771 -0
- matrice_analytics/post_processing/usecases/weld_defect_detection.py +615 -0
- matrice_analytics/post_processing/usecases/wildlife_monitoring.py +898 -0
- matrice_analytics/post_processing/usecases/windmill_maintenance.py +834 -0
- matrice_analytics/post_processing/usecases/wound_segmentation.py +856 -0
- matrice_analytics/post_processing/utils/__init__.py +150 -0
- matrice_analytics/post_processing/utils/advanced_counting_utils.py +400 -0
- matrice_analytics/post_processing/utils/advanced_helper_utils.py +317 -0
- matrice_analytics/post_processing/utils/advanced_tracking_utils.py +461 -0
- matrice_analytics/post_processing/utils/alerting_utils.py +213 -0
- matrice_analytics/post_processing/utils/category_mapping_utils.py +94 -0
- matrice_analytics/post_processing/utils/color_utils.py +592 -0
- matrice_analytics/post_processing/utils/counting_utils.py +182 -0
- matrice_analytics/post_processing/utils/filter_utils.py +261 -0
- matrice_analytics/post_processing/utils/format_utils.py +293 -0
- matrice_analytics/post_processing/utils/geometry_utils.py +300 -0
- matrice_analytics/post_processing/utils/smoothing_utils.py +358 -0
- matrice_analytics/post_processing/utils/tracking_utils.py +234 -0
- matrice_analytics/py.typed +0 -0
- matrice_analytics-0.1.2.dist-info/METADATA +481 -0
- matrice_analytics-0.1.2.dist-info/RECORD +160 -0
- matrice_analytics-0.1.2.dist-info/WHEEL +5 -0
- matrice_analytics-0.1.2.dist-info/licenses/LICENSE.txt +21 -0
- matrice_analytics-0.1.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
|
2
|
+
from dataclasses import asdict, dataclass, field
|
|
3
|
+
import time
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
|
|
6
|
+
from ..core.base import BaseProcessor, ProcessingContext, ProcessingResult, ConfigProtocol, ResultFormat
|
|
7
|
+
from ..utils import (
|
|
8
|
+
filter_by_confidence,
|
|
9
|
+
filter_by_categories,
|
|
10
|
+
apply_category_mapping,
|
|
11
|
+
count_objects_by_category,
|
|
12
|
+
count_objects_in_zones,
|
|
13
|
+
calculate_counting_summary,
|
|
14
|
+
match_results_structure,
|
|
15
|
+
bbox_smoothing,
|
|
16
|
+
BBoxSmoothingConfig,
|
|
17
|
+
BBoxSmoothingTracker
|
|
18
|
+
)
|
|
19
|
+
from ..core.config import BaseConfig, AlertConfig, ZoneConfig
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class ChickenPoseDetectionConfig(BaseConfig):
|
|
23
|
+
"""Configuration for Chicken Pose Detection use case."""
|
|
24
|
+
enable_smoothing: bool = True
|
|
25
|
+
smoothing_algorithm: str = "observability"
|
|
26
|
+
smoothing_window_size: int = 20
|
|
27
|
+
smoothing_cooldown_frames: int = 5
|
|
28
|
+
smoothing_confidence_range_factor: float = 0.5
|
|
29
|
+
confidence_threshold: float = 0.6
|
|
30
|
+
pose_categories: List[str] = field(
|
|
31
|
+
default_factory=lambda: ['moving', 'eat-drink', 'rest']
|
|
32
|
+
)
|
|
33
|
+
target_pose_categories: List[str] = field(
|
|
34
|
+
default_factory=lambda: ['moving', 'eat-drink', 'rest']
|
|
35
|
+
)
|
|
36
|
+
alert_config: Optional[AlertConfig] = None
|
|
37
|
+
index_to_category: Optional[Dict[int, str]] = field(
|
|
38
|
+
default_factory=lambda: {
|
|
39
|
+
0: "moving",
|
|
40
|
+
1: "eat-drink",
|
|
41
|
+
2: "rest"
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
class ChickenPoseDetectionUseCase(BaseProcessor):
|
|
46
|
+
def _get_track_ids_info(self, detections: list) -> Dict[str, Any]:
|
|
47
|
+
frame_track_ids = set()
|
|
48
|
+
for det in detections:
|
|
49
|
+
tid = det.get('track_id')
|
|
50
|
+
if tid is not None:
|
|
51
|
+
frame_track_ids.add(tid)
|
|
52
|
+
total_track_ids = set()
|
|
53
|
+
for s in getattr(self, '_pose_total_track_ids', {}).values():
|
|
54
|
+
total_track_ids.update(s)
|
|
55
|
+
return {
|
|
56
|
+
"total_count": len(total_track_ids),
|
|
57
|
+
"current_frame_count": len(frame_track_ids),
|
|
58
|
+
"total_unique_track_ids": len(total_track_ids),
|
|
59
|
+
"current_frame_track_ids": list(frame_track_ids),
|
|
60
|
+
"last_update_time": time.time(),
|
|
61
|
+
"total_frames_processed": getattr(self, '_total_frame_counter', 0)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def _iou(bbox1, bbox2):
|
|
66
|
+
x1 = max(bbox1["xmin"], bbox2["xmin"])
|
|
67
|
+
y1 = max(bbox1["ymin"], bbox2["ymin"])
|
|
68
|
+
x2 = min(bbox1["xmax"], bbox2["xmax"])
|
|
69
|
+
y2 = min(bbox1["ymax"], bbox2["ymax"])
|
|
70
|
+
inter_w = max(0, x2 - x1)
|
|
71
|
+
inter_h = max(0, y2 - y1)
|
|
72
|
+
inter_area = inter_w * inter_h
|
|
73
|
+
area1 = (bbox1["xmax"] - bbox1["xmin"]) * (bbox1["ymax"] - bbox1["ymin"])
|
|
74
|
+
area2 = (bbox2["xmax"] - bbox2["xmin"]) * (bbox2["ymax"] - bbox2["ymin"])
|
|
75
|
+
union = area1 + area2 - inter_area
|
|
76
|
+
if union == 0:
|
|
77
|
+
return 0.0
|
|
78
|
+
return inter_area / union
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def _deduplicate_chickens(detections, iou_thresh=0.7):
|
|
82
|
+
filtered = []
|
|
83
|
+
used = [False] * len(detections)
|
|
84
|
+
for i, det in enumerate(detections):
|
|
85
|
+
if used[i]:
|
|
86
|
+
continue
|
|
87
|
+
group = [i]
|
|
88
|
+
for j in range(i+1, len(detections)):
|
|
89
|
+
if used[j]:
|
|
90
|
+
continue
|
|
91
|
+
if det.get("category") == detections[j].get("category"):
|
|
92
|
+
bbox1 = det.get("bounding_box")
|
|
93
|
+
bbox2 = detections[j].get("bounding_box")
|
|
94
|
+
if bbox1 and bbox2:
|
|
95
|
+
iou = ChickenPoseDetectionUseCase._iou(bbox1, bbox2)
|
|
96
|
+
if iou > iou_thresh:
|
|
97
|
+
used[j] = True
|
|
98
|
+
group.append(j)
|
|
99
|
+
best_idx = max(group, key=lambda idx: detections[idx].get("confidence", 0))
|
|
100
|
+
filtered.append(detections[best_idx])
|
|
101
|
+
used[best_idx] = True
|
|
102
|
+
return filtered
|
|
103
|
+
|
|
104
|
+
def _update_pose_tracking_state(self, detections: list):
|
|
105
|
+
if not hasattr(self, "_pose_total_track_ids"):
|
|
106
|
+
self._pose_total_track_ids = {cat: set() for cat in self.pose_categories}
|
|
107
|
+
self._pose_current_frame_track_ids = {cat: set() for cat in self.pose_categories}
|
|
108
|
+
|
|
109
|
+
for det in detections:
|
|
110
|
+
cat = det.get("category")
|
|
111
|
+
raw_track_id = det.get("track_id")
|
|
112
|
+
if cat not in self.pose_categories or raw_track_id is None:
|
|
113
|
+
continue
|
|
114
|
+
bbox = det.get("bounding_box", det.get("bbox"))
|
|
115
|
+
canonical_id = self._merge_or_register_track(raw_track_id, bbox)
|
|
116
|
+
det["track_id"] = canonical_id
|
|
117
|
+
self._pose_total_track_ids.setdefault(cat, set()).add(canonical_id)
|
|
118
|
+
self._pose_current_frame_track_ids[cat].add(canonical_id)
|
|
119
|
+
|
|
120
|
+
def get_total_pose_counts(self):
|
|
121
|
+
return {cat: len(ids) for cat, ids in getattr(self, '_pose_total_track_ids', {}).items()}
|
|
122
|
+
|
|
123
|
+
def _format_timestamp_for_video(self, timestamp: float) -> str:
|
|
124
|
+
hours = int(timestamp // 3600)
|
|
125
|
+
minutes = int((timestamp % 3600) // 60)
|
|
126
|
+
seconds = timestamp % 60
|
|
127
|
+
return f"{hours:02d}:{minutes:02d}:{seconds:06.2f}"
|
|
128
|
+
|
|
129
|
+
def _format_timestamp_for_stream(self, timestamp: float) -> str:
|
|
130
|
+
dt = datetime.fromtimestamp(timestamp, tz=timezone.utc)
|
|
131
|
+
return dt.strftime('%Y:%m:%d %H:%M:%S')
|
|
132
|
+
|
|
133
|
+
def _get_current_timestamp_str(self, stream_info: Optional[Dict[str, Any]]) -> str:
|
|
134
|
+
if not stream_info:
|
|
135
|
+
return "00:00:00.00"
|
|
136
|
+
if stream_info.get("input_settings", {}).get("stream_type", "video_file") == "video_file":
|
|
137
|
+
stream_time_str = stream_info.get("video_timestamp", "")
|
|
138
|
+
return stream_time_str[:8]
|
|
139
|
+
else:
|
|
140
|
+
stream_time_str = stream_info.get("stream_time", "")
|
|
141
|
+
if stream_time_str:
|
|
142
|
+
try:
|
|
143
|
+
timestamp_str = stream_time_str.replace(" UTC", "")
|
|
144
|
+
dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H:%M:%S.%f")
|
|
145
|
+
timestamp = dt.replace(tzinfo=timezone.utc).timestamp()
|
|
146
|
+
return self._format_timestamp_for_stream(timestamp)
|
|
147
|
+
except:
|
|
148
|
+
return self._format_timestamp_for_stream(time.time())
|
|
149
|
+
else:
|
|
150
|
+
return self._format_timestamp_for_stream(time.time())
|
|
151
|
+
|
|
152
|
+
def _get_start_timestamp_str(self, stream_info: Optional[Dict[str, Any]]) -> str:
|
|
153
|
+
if not stream_info:
|
|
154
|
+
return "00:00:00"
|
|
155
|
+
is_video_chunk = stream_info.get("input_settings", {}).get("is_video_chunk", False)
|
|
156
|
+
if is_video_chunk or stream_info.get("input_settings", {}).get("stream_type", "video_file") == "video_file":
|
|
157
|
+
return "00:00:00"
|
|
158
|
+
else:
|
|
159
|
+
if self._tracking_start_time is None:
|
|
160
|
+
stream_time_str = stream_info.get("stream_time", "")
|
|
161
|
+
if stream_time_str:
|
|
162
|
+
try:
|
|
163
|
+
timestamp_str = stream_time_str.replace(" UTC", "")
|
|
164
|
+
dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H:%M:%S.%f")
|
|
165
|
+
self._tracking_start_time = dt.replace(tzinfo=timezone.utc).timestamp()
|
|
166
|
+
except:
|
|
167
|
+
self._tracking_start_time = time.time()
|
|
168
|
+
else:
|
|
169
|
+
self._tracking_start_time = time.time()
|
|
170
|
+
dt = datetime.fromtimestamp(self._tracking_start_time, tz=timezone.utc)
|
|
171
|
+
dt = dt.replace(minute=0, second=0, microsecond=0)
|
|
172
|
+
return dt.strftime('%Y:%m:%d %H:%M:%S')
|
|
173
|
+
|
|
174
|
+
def __init__(self):
|
|
175
|
+
super().__init__("chicken_pose_detection")
|
|
176
|
+
self.category = "agriculture"
|
|
177
|
+
self.pose_categories = ['moving', 'eat-drink', 'rest']
|
|
178
|
+
self.smoothing_tracker = None
|
|
179
|
+
self.tracker = None
|
|
180
|
+
self._total_frame_counter = 0
|
|
181
|
+
self._global_frame_offset = 0
|
|
182
|
+
self._tracking_start_time = None
|
|
183
|
+
self._track_aliases: Dict[Any, Any] = {}
|
|
184
|
+
self._canonical_tracks: Dict[Any, Dict[str, Any]] = {}
|
|
185
|
+
self._track_merge_iou_threshold: float = 0.05
|
|
186
|
+
self._track_merge_time_window: float = 7.0
|
|
187
|
+
|
|
188
|
+
def process(self, data: Any, config: ConfigProtocol, context: Optional[ProcessingContext] = None, stream_info: Optional[Dict[str, Any]] = None) -> ProcessingResult:
|
|
189
|
+
start_time = time.time()
|
|
190
|
+
if not isinstance(config, ChickenPoseDetectionConfig):
|
|
191
|
+
return self.create_error_result("Invalid config type", usecase=self.name, category=self.category, context=context)
|
|
192
|
+
if context is None:
|
|
193
|
+
context = ProcessingContext()
|
|
194
|
+
|
|
195
|
+
input_format = match_results_structure(data)
|
|
196
|
+
context.input_format = input_format
|
|
197
|
+
context.confidence_threshold = config.confidence_threshold
|
|
198
|
+
|
|
199
|
+
if config.confidence_threshold is not None:
|
|
200
|
+
processed_data = filter_by_confidence(data, config.confidence_threshold)
|
|
201
|
+
self.logger.debug(f"Applied confidence filtering with threshold {config.confidence_threshold}")
|
|
202
|
+
else:
|
|
203
|
+
processed_data = data
|
|
204
|
+
self.logger.debug("Did not apply confidence filtering with threshold since nothing was provided")
|
|
205
|
+
|
|
206
|
+
if config.index_to_category:
|
|
207
|
+
processed_data = apply_category_mapping(processed_data, config.index_to_category)
|
|
208
|
+
self.logger.debug("Applied category mapping")
|
|
209
|
+
|
|
210
|
+
if config.target_pose_categories:
|
|
211
|
+
processed_data = [d for d in processed_data if d.get('category') in self.pose_categories]
|
|
212
|
+
self.logger.debug("Applied pose category filtering")
|
|
213
|
+
|
|
214
|
+
if config.enable_smoothing:
|
|
215
|
+
if self.smoothing_tracker is None:
|
|
216
|
+
smoothing_config = BBoxSmoothingConfig(
|
|
217
|
+
smoothing_algorithm=config.smoothing_algorithm,
|
|
218
|
+
window_size=config.smoothing_window_size,
|
|
219
|
+
cooldown_frames=config.smoothing_cooldown_frames,
|
|
220
|
+
confidence_threshold=config.confidence_threshold,
|
|
221
|
+
confidence_range_factor=config.smoothing_confidence_range_factor,
|
|
222
|
+
enable_smoothing=True
|
|
223
|
+
)
|
|
224
|
+
self.smoothing_tracker = BBoxSmoothingTracker(smoothing_config)
|
|
225
|
+
smoothed_chickens = bbox_smoothing(processed_data, self.smoothing_tracker.config, self.smoothing_tracker)
|
|
226
|
+
processed_data = smoothed_chickens
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
from ..advanced_tracker import AdvancedTracker
|
|
230
|
+
from ..advanced_tracker.config import TrackerConfig
|
|
231
|
+
if self.tracker is None:
|
|
232
|
+
tracker_config = TrackerConfig()
|
|
233
|
+
self.tracker = AdvancedTracker(tracker_config)
|
|
234
|
+
self.logger.info("Initialized AdvancedTracker for Chicken Pose Detection and tracking")
|
|
235
|
+
processed_data = self.tracker.update(processed_data)
|
|
236
|
+
except Exception as e:
|
|
237
|
+
self.logger.warning(f"AdvancedTracker failed: {e}")
|
|
238
|
+
|
|
239
|
+
processed_data = self._deduplicate_chickens(processed_data, iou_thresh=0.95)
|
|
240
|
+
self._update_pose_tracking_state(processed_data)
|
|
241
|
+
self._total_frame_counter += 1
|
|
242
|
+
|
|
243
|
+
frame_number = None
|
|
244
|
+
if stream_info:
|
|
245
|
+
input_settings = stream_info.get("input_settings", {})
|
|
246
|
+
start_frame = input_settings.get("start_frame")
|
|
247
|
+
end_frame = input_settings.get("end_frame")
|
|
248
|
+
if start_frame is not None and end_frame is not None and start_frame == end_frame:
|
|
249
|
+
frame_number = start_frame
|
|
250
|
+
|
|
251
|
+
general_counting_summary = calculate_counting_summary(data)
|
|
252
|
+
counting_summary = self._count_categories(processed_data, config)
|
|
253
|
+
total_pose_counts = self.get_total_pose_counts()
|
|
254
|
+
counting_summary['total_pose_counts'] = total_pose_counts
|
|
255
|
+
insights = self._generate_insights(counting_summary, config)
|
|
256
|
+
alerts = self._check_alerts(counting_summary, config)
|
|
257
|
+
predictions = self._extract_predictions(processed_data)
|
|
258
|
+
summary = self._generate_summary(counting_summary, alerts)
|
|
259
|
+
|
|
260
|
+
events_list = self._generate_events(counting_summary, alerts, config, frame_number, stream_info)
|
|
261
|
+
tracking_stats_list = self._generate_tracking_stats(counting_summary, insights, summary, config, frame_number, stream_info)
|
|
262
|
+
|
|
263
|
+
events = events_list[0] if events_list else {}
|
|
264
|
+
tracking_stats = tracking_stats_list[0] if tracking_stats_list else {}
|
|
265
|
+
|
|
266
|
+
context.mark_completed()
|
|
267
|
+
result = self.create_result(
|
|
268
|
+
data={
|
|
269
|
+
"counting_summary": counting_summary,
|
|
270
|
+
"general_counting_summary": general_counting_summary,
|
|
271
|
+
"alerts": alerts,
|
|
272
|
+
"total_chickens": counting_summary.get("total_count", 0),
|
|
273
|
+
"events": events,
|
|
274
|
+
"tracking_stats": tracking_stats,
|
|
275
|
+
},
|
|
276
|
+
usecase=self.name,
|
|
277
|
+
category=self.category,
|
|
278
|
+
context=context
|
|
279
|
+
)
|
|
280
|
+
result.summary = summary
|
|
281
|
+
result.insights = insights
|
|
282
|
+
result.predictions = predictions
|
|
283
|
+
return result
|
|
284
|
+
|
|
285
|
+
def reset_tracker(self) -> None:
|
|
286
|
+
if self.tracker is not None:
|
|
287
|
+
self.tracker.reset()
|
|
288
|
+
self.logger.info("AdvancedTracker reset for new tracking session")
|
|
289
|
+
|
|
290
|
+
def reset_pose_tracking(self) -> None:
|
|
291
|
+
self._pose_total_track_ids = {cat: set() for cat in self.pose_categories}
|
|
292
|
+
self._total_frame_counter = 0
|
|
293
|
+
self._global_frame_offset = 0
|
|
294
|
+
self._tracking_start_time = None
|
|
295
|
+
self._track_aliases.clear()
|
|
296
|
+
self._canonical_tracks.clear()
|
|
297
|
+
self.logger.info("Chicken Pose Detection tracking state reset")
|
|
298
|
+
|
|
299
|
+
def reset_all_tracking(self) -> None:
|
|
300
|
+
self.reset_tracker()
|
|
301
|
+
self.reset_pose_tracking()
|
|
302
|
+
self.logger.info("All Chickens tracking state reset")
|
|
303
|
+
|
|
304
|
+
def _generate_events(self, counting_summary: Dict, alerts: List, config: ChickenPoseDetectionConfig, frame_number: Optional[int] = None, stream_info: Optional[Dict[str, Any]] = None) -> List[Dict]:
|
|
305
|
+
frame_key = str(frame_number) if frame_number is not None else "current_frame"
|
|
306
|
+
events = [{frame_key: []}]
|
|
307
|
+
frame_events = events[0][frame_key]
|
|
308
|
+
total_chickens = counting_summary.get("total_count", 0)
|
|
309
|
+
|
|
310
|
+
if total_chickens > 0:
|
|
311
|
+
level = "info"
|
|
312
|
+
intensity = 5.0
|
|
313
|
+
if config.alert_config and config.alert_config.count_thresholds:
|
|
314
|
+
threshold = config.alert_config.count_thresholds.get("all", 15)
|
|
315
|
+
intensity = min(10.0, (total_chickens / threshold) * 10)
|
|
316
|
+
if intensity >= 7:
|
|
317
|
+
level = "critical"
|
|
318
|
+
elif intensity >= 5:
|
|
319
|
+
level = "warning"
|
|
320
|
+
else:
|
|
321
|
+
level = "info"
|
|
322
|
+
else:
|
|
323
|
+
if total_chickens > 25:
|
|
324
|
+
level = "critical"
|
|
325
|
+
intensity = 9.0
|
|
326
|
+
elif total_chickens > 15:
|
|
327
|
+
level = "warning"
|
|
328
|
+
intensity = 7.0
|
|
329
|
+
else:
|
|
330
|
+
level = "info"
|
|
331
|
+
intensity = min(10.0, total_chickens / 3.0)
|
|
332
|
+
|
|
333
|
+
human_text_lines = ["EVENTS DETECTED:"]
|
|
334
|
+
human_text_lines.append(f" - {total_chickens} Chicken(s) detected [INFO]")
|
|
335
|
+
human_text = "\n".join(human_text_lines)
|
|
336
|
+
|
|
337
|
+
event = {
|
|
338
|
+
"type": "chicken_pose_detection",
|
|
339
|
+
"stream_time": datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S UTC"),
|
|
340
|
+
"level": level,
|
|
341
|
+
"intensity": round(intensity, 1),
|
|
342
|
+
"config": {
|
|
343
|
+
"min_value": 0,
|
|
344
|
+
"max_value": 10,
|
|
345
|
+
"level_settings": {"info": 2, "warning": 5, "critical": 7}
|
|
346
|
+
},
|
|
347
|
+
"application_name": "Chicken Pose Detection System",
|
|
348
|
+
"application_version": "1.0",
|
|
349
|
+
"location_info": None,
|
|
350
|
+
"human_text": human_text
|
|
351
|
+
}
|
|
352
|
+
frame_events.append(event)
|
|
353
|
+
|
|
354
|
+
for alert in alerts:
|
|
355
|
+
total_chickens = counting_summary.get("total_count", 0)
|
|
356
|
+
intensity_message = "ALERT: Low chicken density in the scene"
|
|
357
|
+
if config.alert_config and config.alert_config.count_thresholds:
|
|
358
|
+
threshold = config.alert_config.count_thresholds.get("all", 15)
|
|
359
|
+
percentage = (total_chickens / threshold) * 100 if threshold > 0 else 0
|
|
360
|
+
if percentage < 20:
|
|
361
|
+
intensity_message = "ALERT: Low chicken density in the scene"
|
|
362
|
+
elif percentage <= 50:
|
|
363
|
+
intensity_message = "ALERT: Moderate chicken density in the scene"
|
|
364
|
+
elif percentage <= 70:
|
|
365
|
+
intensity_message = "ALERT: High chicken density in the scene"
|
|
366
|
+
else:
|
|
367
|
+
intensity_message = "ALERT: Severe chicken density in the scene"
|
|
368
|
+
else:
|
|
369
|
+
if total_chickens > 15:
|
|
370
|
+
intensity_message = "ALERT: High chicken density in the scene"
|
|
371
|
+
elif total_chickens == 1:
|
|
372
|
+
intensity_message = "ALERT: Low chicken density in the scene"
|
|
373
|
+
else:
|
|
374
|
+
intensity_message = "ALERT: Moderate chicken density in the scene"
|
|
375
|
+
|
|
376
|
+
alert_event = {
|
|
377
|
+
"type": alert.get("type", "density_alert"),
|
|
378
|
+
"stream_time": datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S UTC"),
|
|
379
|
+
"level": alert.get("severity", "warning"),
|
|
380
|
+
"intensity": 8.0,
|
|
381
|
+
"config": {
|
|
382
|
+
"min_value": 0,
|
|
383
|
+
"max_value": 10,
|
|
384
|
+
"level_settings": {"info": 2, "warning": 5, "critical": 7}
|
|
385
|
+
},
|
|
386
|
+
"application_name": "Chicken Density Alert System",
|
|
387
|
+
"application_version": "1.0",
|
|
388
|
+
"location_info": alert.get("zone"),
|
|
389
|
+
"human_text": f"{datetime.now(timezone.utc).strftime('%Y-%m-%d-%H:%M:%S UTC')} : {intensity_message}"
|
|
390
|
+
}
|
|
391
|
+
frame_events.append(alert_event)
|
|
392
|
+
|
|
393
|
+
return events
|
|
394
|
+
|
|
395
|
+
def _generate_tracking_stats(
|
|
396
|
+
self,
|
|
397
|
+
counting_summary: Dict,
|
|
398
|
+
insights: List[str],
|
|
399
|
+
summary: str,
|
|
400
|
+
config: ChickenPoseDetectionConfig,
|
|
401
|
+
frame_number: Optional[int] = None,
|
|
402
|
+
stream_info: Optional[Dict[str, Any]] = None
|
|
403
|
+
) -> List[Dict]:
|
|
404
|
+
frame_key = str(frame_number) if frame_number is not None else "current_frame"
|
|
405
|
+
tracking_stats = [{frame_key: []}]
|
|
406
|
+
frame_tracking_stats = tracking_stats[0][frame_key]
|
|
407
|
+
|
|
408
|
+
total_chickens = counting_summary.get("total_count", 0)
|
|
409
|
+
total_pose_counts = counting_summary.get("total_pose_counts", {})
|
|
410
|
+
cumulative_total = sum(total_pose_counts.values()) if total_pose_counts else 0
|
|
411
|
+
per_category_count = counting_summary.get("per_category_count", {})
|
|
412
|
+
|
|
413
|
+
track_ids_info = self._get_track_ids_info(counting_summary.get("detections", []))
|
|
414
|
+
|
|
415
|
+
current_timestamp = self._get_current_timestamp_str(stream_info)
|
|
416
|
+
start_timestamp = self._get_start_timestamp_str(stream_info)
|
|
417
|
+
|
|
418
|
+
human_text_lines = []
|
|
419
|
+
human_text_lines.append(f"CURRENT FRAME @ {current_timestamp}:")
|
|
420
|
+
if total_chickens > 0:
|
|
421
|
+
category_counts = [f"{count} {cat}" for cat, count in per_category_count.items() if count > 0]
|
|
422
|
+
if len(category_counts) == 1:
|
|
423
|
+
chickens_text = category_counts[0] + " detected"
|
|
424
|
+
elif len(category_counts) == 2:
|
|
425
|
+
chickens_text = f"{category_counts[0]} and {category_counts[1]} detected"
|
|
426
|
+
else:
|
|
427
|
+
chickens_text = f"{', '.join(category_counts[:-1])}, and {category_counts[-1]} detected"
|
|
428
|
+
human_text_lines.append(f"\t- {chickens_text}")
|
|
429
|
+
else:
|
|
430
|
+
human_text_lines.append(f"\t- No chickens detected")
|
|
431
|
+
|
|
432
|
+
human_text_lines.append("")
|
|
433
|
+
human_text_lines.append(f"TOTAL SINCE {start_timestamp}:")
|
|
434
|
+
human_text_lines.append(f"\t- Total Chickens Detected: {cumulative_total}")
|
|
435
|
+
if total_pose_counts:
|
|
436
|
+
for cat, count in total_pose_counts.items():
|
|
437
|
+
if count > 0:
|
|
438
|
+
human_text_lines.append(f"\t- {cat}: {count}")
|
|
439
|
+
|
|
440
|
+
human_text = "\n".join(human_text_lines)
|
|
441
|
+
|
|
442
|
+
tracking_stat = {
|
|
443
|
+
"type": "chicken_pose_tracking",
|
|
444
|
+
"category": "chicken",
|
|
445
|
+
"count": total_chickens,
|
|
446
|
+
"insights": insights,
|
|
447
|
+
"summary": summary,
|
|
448
|
+
"timestamp": datetime.now(timezone.utc).strftime('%Y-%m-%d-%H:%M:%S UTC'),
|
|
449
|
+
"human_text": human_text,
|
|
450
|
+
"track_ids_info": track_ids_info,
|
|
451
|
+
"global_frame_offset": getattr(self, '_global_frame_offset', 0),
|
|
452
|
+
"local_frame_id": frame_key
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
frame_tracking_stats.append(tracking_stat)
|
|
456
|
+
return tracking_stats
|
|
457
|
+
|
|
458
|
+
def _count_categories(self, detections: list, config: ChickenPoseDetectionConfig) -> dict:
|
|
459
|
+
counts = {}
|
|
460
|
+
for det in detections:
|
|
461
|
+
cat = det.get('category', 'unknown')
|
|
462
|
+
counts[cat] = counts.get(cat, 0) + 1
|
|
463
|
+
return {
|
|
464
|
+
"total_count": sum(counts.values()),
|
|
465
|
+
"per_category_count": counts,
|
|
466
|
+
"detections": [
|
|
467
|
+
{
|
|
468
|
+
"bounding_box": det.get("bounding_box"),
|
|
469
|
+
"category": det.get("category"),
|
|
470
|
+
"confidence": det.get("confidence"),
|
|
471
|
+
"track_id": det.get("track_id"),
|
|
472
|
+
"frame_id": det.get("frame_id")
|
|
473
|
+
}
|
|
474
|
+
for det in detections
|
|
475
|
+
]
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
CATEGORY_DISPLAY = {
|
|
479
|
+
"Moving": "moving",
|
|
480
|
+
"Eat-Drink": "eat-drink",
|
|
481
|
+
"Rest": "rest"
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
def _generate_insights(self, summary: dict, config: ChickenPoseDetectionConfig) -> List[str]:
|
|
485
|
+
insights = []
|
|
486
|
+
per_cat = summary.get("per_category_count", {})
|
|
487
|
+
total_chickens = summary.get("total_count", 0)
|
|
488
|
+
|
|
489
|
+
if total_chickens == 0:
|
|
490
|
+
insights.append("No chickens detected in the scene")
|
|
491
|
+
return insights
|
|
492
|
+
insights.append(f"EVENT: Detected {total_chickens} chickens in the scene")
|
|
493
|
+
intensity_threshold = None
|
|
494
|
+
if config.alert_config and config.alert_config.count_thresholds and "all" in config.alert_config.count_thresholds:
|
|
495
|
+
intensity_threshold = config.alert_config.count_thresholds["all"]
|
|
496
|
+
|
|
497
|
+
if intensity_threshold is not None:
|
|
498
|
+
percentage = (total_chickens / intensity_threshold) * 100
|
|
499
|
+
if percentage < 20:
|
|
500
|
+
insights.append(f"INTENSITY: Low chicken density in the scene ({percentage:.1f}% of capacity)")
|
|
501
|
+
elif percentage <= 50:
|
|
502
|
+
insights.append(f"INTENSITY: Moderate chicken density in the scene ({percentage:.1f}% of capacity)")
|
|
503
|
+
elif percentage <= 70:
|
|
504
|
+
insights.append(f"INTENSITY: High chicken density in the scene ({percentage:.1f}% of capacity)")
|
|
505
|
+
else:
|
|
506
|
+
insights.append(f"INTENSITY: Severe chicken density in the scene ({percentage:.1f}% of capacity)")
|
|
507
|
+
|
|
508
|
+
for cat, count in per_cat.items():
|
|
509
|
+
display = self.CATEGORY_DISPLAY.get(cat, cat)
|
|
510
|
+
insights.append(f"{display}: {count}")
|
|
511
|
+
return insights
|
|
512
|
+
|
|
513
|
+
def _check_alerts(self, summary: dict, config: ChickenPoseDetectionConfig) -> List[Dict]:
|
|
514
|
+
alerts = []
|
|
515
|
+
if not config.alert_config:
|
|
516
|
+
return alerts
|
|
517
|
+
total = summary.get("total_count", 0)
|
|
518
|
+
if config.alert_config.count_thresholds:
|
|
519
|
+
for category, threshold in config.alert_config.count_thresholds.items():
|
|
520
|
+
if category == "all" and total >= threshold:
|
|
521
|
+
alerts.append({
|
|
522
|
+
"type": "count_threshold",
|
|
523
|
+
"severity": "warning",
|
|
524
|
+
"message": f"Total chicken count ({total}) exceeds threshold ({threshold})",
|
|
525
|
+
"category": category,
|
|
526
|
+
"current_count": total,
|
|
527
|
+
"threshold": threshold
|
|
528
|
+
})
|
|
529
|
+
elif category in summary.get("per_category_count", {}):
|
|
530
|
+
count = summary.get("per_category_count", {})[category]
|
|
531
|
+
if count >= threshold:
|
|
532
|
+
alerts.append({
|
|
533
|
+
"type": "count_threshold",
|
|
534
|
+
"severity": "warning",
|
|
535
|
+
"message": f"{category} count ({count}) exceeds threshold ({threshold})",
|
|
536
|
+
"category": category,
|
|
537
|
+
"current_count": count,
|
|
538
|
+
"threshold": threshold
|
|
539
|
+
})
|
|
540
|
+
return alerts
|
|
541
|
+
|
|
542
|
+
def _extract_predictions(self, detections: list) -> List[Dict[str, Any]]:
|
|
543
|
+
return [
|
|
544
|
+
{
|
|
545
|
+
"category": det.get("category", "unknown"),
|
|
546
|
+
"confidence": det.get("confidence", 0.0),
|
|
547
|
+
"bounding_box": det.get("bounding_box", {})
|
|
548
|
+
}
|
|
549
|
+
for det in detections
|
|
550
|
+
]
|
|
551
|
+
|
|
552
|
+
def _generate_summary(self, summary: dict, alerts: List) -> str:
|
|
553
|
+
total = summary.get("total_count", 0)
|
|
554
|
+
per_cat = summary.get("per_category_count", {})
|
|
555
|
+
cumulative = summary.get("total_pose_counts", {})
|
|
556
|
+
cumulative_total = sum(cumulative.values()) if cumulative else 0
|
|
557
|
+
lines = []
|
|
558
|
+
if total > 0:
|
|
559
|
+
lines.append(f"{total} Chicken(s) detected")
|
|
560
|
+
if per_cat:
|
|
561
|
+
lines.append("Chickens:")
|
|
562
|
+
for cat, count in per_cat.items():
|
|
563
|
+
lines.append(f"\t{cat}: {count}")
|
|
564
|
+
else:
|
|
565
|
+
lines.append("No chickens detected")
|
|
566
|
+
lines.append(f"Total chickens detected: {cumulative_total}")
|
|
567
|
+
if alerts:
|
|
568
|
+
lines.append(f"{len(alerts)} alert(s)")
|
|
569
|
+
return "\n".join(lines)
|
|
570
|
+
|
|
571
|
+
def _compute_iou(self, box1: Any, box2: Any) -> float:
|
|
572
|
+
def _bbox_to_list(bbox):
|
|
573
|
+
if bbox is None:
|
|
574
|
+
return []
|
|
575
|
+
if isinstance(bbox, list):
|
|
576
|
+
return bbox[:4] if len(bbox) >= 4 else []
|
|
577
|
+
if isinstance(bbox, dict):
|
|
578
|
+
if "xmin" in bbox:
|
|
579
|
+
return [bbox["xmin"], bbox["ymin"], bbox["xmax"], bbox["ymax"]]
|
|
580
|
+
if "x1" in bbox:
|
|
581
|
+
return [bbox["x1"], bbox["y1"], bbox["x2"], bbox["y2"]]
|
|
582
|
+
values = [v for v in bbox.values() if isinstance(v, (int, float))]
|
|
583
|
+
return values[:4] if len(values) >= 4 else []
|
|
584
|
+
return []
|
|
585
|
+
|
|
586
|
+
l1 = _bbox_to_list(box1)
|
|
587
|
+
l2 = _bbox_to_list(box2)
|
|
588
|
+
if len(l1) < 4 or len(l2) < 4:
|
|
589
|
+
return 0.0
|
|
590
|
+
x1_min, y1_min, x1_max, y1_max = l1
|
|
591
|
+
x2_min, y2_min, x2_max, y2_max = l2
|
|
592
|
+
x1_min, x1_max = min(x1_min, x1_max), max(x1_min, x1_max)
|
|
593
|
+
y1_min, y1_max = min(y1_min, y1_max), max(y1_min, y1_max)
|
|
594
|
+
x2_min, x2_max = min(x2_min, x2_max), max(x2_min, x2_max)
|
|
595
|
+
y2_min, y2_max = min(y2_min, y2_max), max(y2_min, y2_max)
|
|
596
|
+
inter_x_min = max(x1_min, x2_min)
|
|
597
|
+
inter_y_min = max(y1_min, y2_min)
|
|
598
|
+
inter_x_max = min(x1_max, x2_max)
|
|
599
|
+
inter_y_max = min(y1_max, y2_max)
|
|
600
|
+
inter_w = max(0.0, inter_x_max - inter_x_min)
|
|
601
|
+
inter_h = max(0.0, inter_y_max - inter_y_min)
|
|
602
|
+
inter_area = inter_w * inter_h
|
|
603
|
+
area1 = (x1_max - x1_min) * (y1_max - y1_min)
|
|
604
|
+
area2 = (x2_max - x2_min) * (y2_max - y2_min)
|
|
605
|
+
union_area = area1 + area2 - inter_area
|
|
606
|
+
return (inter_area / union_area) if union_area > 0 else 0.0
|
|
607
|
+
|
|
608
|
+
def _merge_or_register_track(self, raw_id: Any, bbox: Any) -> Any:
|
|
609
|
+
if raw_id is None or bbox is None:
|
|
610
|
+
return raw_id
|
|
611
|
+
now = time.time()
|
|
612
|
+
if raw_id in self._track_aliases:
|
|
613
|
+
canonical_id = self._track_aliases[raw_id]
|
|
614
|
+
track_info = self._canonical_tracks.get(canonical_id)
|
|
615
|
+
if track_info is not None:
|
|
616
|
+
track_info["last_bbox"] = bbox
|
|
617
|
+
track_info["last_update"] = now
|
|
618
|
+
track_info["raw_ids"].add(raw_id)
|
|
619
|
+
return canonical_id
|
|
620
|
+
for canonical_id, info in self._canonical_tracks.items():
|
|
621
|
+
if now - info["last_update"] > self._track_merge_time_window:
|
|
622
|
+
continue
|
|
623
|
+
iou = self._compute_iou(bbox, info["last_bbox"])
|
|
624
|
+
if iou >= self._track_merge_iou_threshold:
|
|
625
|
+
self._track_aliases[raw_id] = canonical_id
|
|
626
|
+
info["last_bbox"] = bbox
|
|
627
|
+
info["last_update"] = now
|
|
628
|
+
info["raw_ids"].add(raw_id)
|
|
629
|
+
return canonical_id
|
|
630
|
+
canonical_id = raw_id
|
|
631
|
+
self._track_aliases[raw_id] = canonical_id
|
|
632
|
+
self._canonical_tracks[canonical_id] = {
|
|
633
|
+
"last_bbox": bbox,
|
|
634
|
+
"last_update": now,
|
|
635
|
+
"raw_ids": {raw_id},
|
|
636
|
+
}
|
|
637
|
+
return canonical_id
|
|
638
|
+
|
|
639
|
+
def _format_timestamp(self, timestamp: float) -> str:
|
|
640
|
+
return datetime.fromtimestamp(timestamp, timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')
|
|
641
|
+
|
|
642
|
+
def _get_tracking_start_time(self) -> str:
|
|
643
|
+
if self._tracking_start_time is None:
|
|
644
|
+
return "N/A"
|
|
645
|
+
return self._format_timestamp(self._tracking_start_time)
|
|
646
|
+
|
|
647
|
+
def _set_tracking_start_time(self) -> None:
|
|
648
|
+
self._tracking_start_time = time.time()
|