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,182 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Counting utilities for post-processing operations.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from typing import List, Dict, Any, Tuple, Optional, Set
|
|
7
|
+
from collections import defaultdict
|
|
8
|
+
|
|
9
|
+
from .geometry_utils import point_in_polygon, get_bbox_center, get_bbox_bottom25_center
|
|
10
|
+
from .filter_utils import calculate_bbox_fingerprint
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def count_objects_by_category(results: Any) -> Dict[str, int]:
|
|
14
|
+
"""
|
|
15
|
+
Count objects by category from detection results.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
results: Detection results (list or dict format)
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
Dict[str, int]: Category counts
|
|
22
|
+
"""
|
|
23
|
+
counts = defaultdict(int)
|
|
24
|
+
|
|
25
|
+
if isinstance(results, list):
|
|
26
|
+
# Detection format
|
|
27
|
+
for detection in results:
|
|
28
|
+
category = detection.get("category", "unknown")
|
|
29
|
+
counts[category] += 1
|
|
30
|
+
|
|
31
|
+
elif isinstance(results, dict):
|
|
32
|
+
# Frame-based format (tracking or activity recognition)
|
|
33
|
+
seen_tracks = set() # To avoid double counting same track across frames
|
|
34
|
+
|
|
35
|
+
for frame_id, detections in results.items():
|
|
36
|
+
if isinstance(detections, list):
|
|
37
|
+
for detection in detections:
|
|
38
|
+
category = detection.get("category", "unknown")
|
|
39
|
+
track_id = detection.get("track_id")
|
|
40
|
+
|
|
41
|
+
# If tracking data is available, count unique tracks only
|
|
42
|
+
if track_id is not None:
|
|
43
|
+
track_key = f"{category}_{track_id}"
|
|
44
|
+
if track_key not in seen_tracks:
|
|
45
|
+
seen_tracks.add(track_key)
|
|
46
|
+
counts[category] += 1
|
|
47
|
+
else:
|
|
48
|
+
# No tracking, count all detections
|
|
49
|
+
counts[category] += 1
|
|
50
|
+
|
|
51
|
+
return dict(counts)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def count_objects_in_zones(results: Any, zones: Dict[str, List[List[float]]], stream_info:Optional[Any]=None) -> Dict[str, Dict[str, int]]:
|
|
55
|
+
"""
|
|
56
|
+
Count objects in defined zones.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
results: Detection results
|
|
60
|
+
zones: Dictionary of zone_name -> polygon coordinates
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Dict[str, Dict[str, int]]: Zone counts by category
|
|
64
|
+
"""
|
|
65
|
+
zone_counts = {}
|
|
66
|
+
|
|
67
|
+
for zone_name, zone_polygon in zones.items():
|
|
68
|
+
zone_counts[zone_name] = defaultdict(int)
|
|
69
|
+
|
|
70
|
+
if isinstance(results, list):
|
|
71
|
+
# Detection format
|
|
72
|
+
for detection in results:
|
|
73
|
+
if _is_detection_in_zone(detection, zone_polygon, stream_info):
|
|
74
|
+
category = detection.get("category", "unknown")
|
|
75
|
+
zone_counts[zone_name][category] += 1
|
|
76
|
+
|
|
77
|
+
elif isinstance(results, dict):
|
|
78
|
+
# Frame-based format
|
|
79
|
+
seen_tracks = set()
|
|
80
|
+
|
|
81
|
+
for frame_id, detections in results.items():
|
|
82
|
+
if isinstance(detections, list):
|
|
83
|
+
for detection in detections:
|
|
84
|
+
if _is_detection_in_zone(detection, zone_polygon, stream_info):
|
|
85
|
+
category = detection.get("category", "unknown")
|
|
86
|
+
track_id = detection.get("track_id")
|
|
87
|
+
|
|
88
|
+
if track_id is not None:
|
|
89
|
+
track_key = f"{zone_name}_{category}_{track_id}"
|
|
90
|
+
if track_key not in seen_tracks:
|
|
91
|
+
seen_tracks.add(track_key)
|
|
92
|
+
zone_counts[zone_name][category] += 1
|
|
93
|
+
else:
|
|
94
|
+
zone_counts[zone_name][category] += 1
|
|
95
|
+
|
|
96
|
+
# Convert to regular dict
|
|
97
|
+
zone_counts[zone_name] = dict(zone_counts[zone_name])
|
|
98
|
+
|
|
99
|
+
return zone_counts
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def count_unique_tracks(results: Dict[str, List[Dict]]) -> Dict[str, int]:
|
|
103
|
+
"""
|
|
104
|
+
Count unique tracks by category from tracking results.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
results: Tracking results in frame format
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Dict[str, int]: Unique track counts by category
|
|
111
|
+
"""
|
|
112
|
+
unique_tracks = defaultdict(set)
|
|
113
|
+
|
|
114
|
+
for frame_id, detections in results.items():
|
|
115
|
+
if isinstance(detections, list):
|
|
116
|
+
for detection in detections:
|
|
117
|
+
track_id = detection.get("track_id")
|
|
118
|
+
category = detection.get("category", "unknown")
|
|
119
|
+
|
|
120
|
+
if track_id is not None:
|
|
121
|
+
unique_tracks[category].add(track_id)
|
|
122
|
+
|
|
123
|
+
return {category: len(tracks) for category, tracks in unique_tracks.items()}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def calculate_counting_summary(results: Any, zones: Optional[Dict[str, List[List[float]]]] = None) -> Dict[str, Any]:
|
|
127
|
+
"""
|
|
128
|
+
Calculate comprehensive counting summary.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
results: Detection/tracking results
|
|
132
|
+
zones: Optional zone definitions
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Dict[str, Any]: Comprehensive counting summary
|
|
136
|
+
"""
|
|
137
|
+
summary = {
|
|
138
|
+
"total_objects": 0,
|
|
139
|
+
"by_category": {},
|
|
140
|
+
"timestamp": time.time()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# Basic category counts
|
|
144
|
+
category_counts = count_objects_by_category(results)
|
|
145
|
+
summary["by_category"] = category_counts
|
|
146
|
+
summary["total_objects"] = sum(category_counts.values())
|
|
147
|
+
|
|
148
|
+
# Zone-based counts if zones provided
|
|
149
|
+
if zones:
|
|
150
|
+
zone_counts = count_objects_in_zones(results, zones)
|
|
151
|
+
summary["zone_analysis"] = zone_counts
|
|
152
|
+
|
|
153
|
+
# Calculate zone totals
|
|
154
|
+
zone_totals = {}
|
|
155
|
+
for zone_name, zone_data in zone_counts.items():
|
|
156
|
+
zone_totals[zone_name] = sum(zone_data.values())
|
|
157
|
+
summary["zone_totals"] = zone_totals
|
|
158
|
+
|
|
159
|
+
# Tracking-specific counts
|
|
160
|
+
if isinstance(results, dict):
|
|
161
|
+
unique_counts = count_unique_tracks(results)
|
|
162
|
+
if unique_counts:
|
|
163
|
+
summary["unique_tracks"] = unique_counts
|
|
164
|
+
summary["total_unique_tracks"] = sum(unique_counts.values())
|
|
165
|
+
|
|
166
|
+
return summary
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _is_detection_in_zone(detection: Dict[str, Any], zone_polygon: List[List[float]], stream_info:Optional[Any]=None) -> bool:
|
|
170
|
+
"""Check if a detection is within a zone polygon."""
|
|
171
|
+
bbox = detection.get("bounding_box", detection.get("bbox"))
|
|
172
|
+
if not bbox:
|
|
173
|
+
return False
|
|
174
|
+
if stream_info: #This code ensures that if zone is bigger than the stream resolution, then whole frame is considered as in the zone.
|
|
175
|
+
for p in zone_polygon:
|
|
176
|
+
if p[0] > stream_info.get("stream_resolution",{}).get("width",0) and stream_info.get("stream_resolution",{}).get("width",0) != 0:
|
|
177
|
+
return True
|
|
178
|
+
if p[1] > stream_info.get("stream_resolution",{}).get("height",0) and stream_info.get("stream_resolution",{}).get("height",0) != 0:
|
|
179
|
+
return True
|
|
180
|
+
#center = get_bbox_center(bbox)
|
|
181
|
+
bottom25_center = get_bbox_bottom25_center(bbox)
|
|
182
|
+
return point_in_polygon(bottom25_center, [(p[0], p[1]) for p in zone_polygon])
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Filter utilities for post-processing operations.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from typing import List, Dict, Any, Set
|
|
7
|
+
from collections import defaultdict
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def filter_by_confidence(results: Any, threshold: float = 0.5) -> Any:
|
|
11
|
+
"""
|
|
12
|
+
Filter results by confidence threshold.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
results: Detection or tracking results
|
|
16
|
+
threshold: Minimum confidence threshold
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
Filtered results in the same format
|
|
20
|
+
"""
|
|
21
|
+
if isinstance(results, list):
|
|
22
|
+
# Detection format
|
|
23
|
+
return [r for r in results if r.get("confidence", 0) >= threshold]
|
|
24
|
+
|
|
25
|
+
elif isinstance(results, dict):
|
|
26
|
+
# Check if it's a simple classification result
|
|
27
|
+
if "confidence" in results and "category" in results:
|
|
28
|
+
return results if results.get("confidence", 0) >= threshold else {}
|
|
29
|
+
|
|
30
|
+
# Frame-based format (tracking or activity recognition)
|
|
31
|
+
filtered_results = {}
|
|
32
|
+
for frame_id, detections in results.items():
|
|
33
|
+
if isinstance(detections, list):
|
|
34
|
+
filtered_detections = [
|
|
35
|
+
d for d in detections if d.get("confidence", 0) >= threshold
|
|
36
|
+
]
|
|
37
|
+
if filtered_detections:
|
|
38
|
+
filtered_results[frame_id] = filtered_detections
|
|
39
|
+
|
|
40
|
+
return filtered_results
|
|
41
|
+
|
|
42
|
+
return results
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def filter_by_categories(results: Any, allowed_categories: List[str]) -> Any:
|
|
46
|
+
"""
|
|
47
|
+
Filter results to only include specified categories.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
results: Detection or tracking results
|
|
51
|
+
allowed_categories: List of allowed category names
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Filtered results in the same format
|
|
55
|
+
"""
|
|
56
|
+
if isinstance(results, list):
|
|
57
|
+
# Detection format
|
|
58
|
+
return [r for r in results if r.get("category", "") in allowed_categories]
|
|
59
|
+
|
|
60
|
+
elif isinstance(results, dict):
|
|
61
|
+
# Check if it's a simple classification result
|
|
62
|
+
if "category" in results:
|
|
63
|
+
return results if results.get("category", "") in allowed_categories else {}
|
|
64
|
+
|
|
65
|
+
# Frame-based format
|
|
66
|
+
filtered_results = {}
|
|
67
|
+
for frame_id, detections in results.items():
|
|
68
|
+
if isinstance(detections, list):
|
|
69
|
+
filtered_detections = [
|
|
70
|
+
d for d in detections if d.get("category", "") in allowed_categories
|
|
71
|
+
]
|
|
72
|
+
if filtered_detections:
|
|
73
|
+
filtered_results[frame_id] = filtered_detections
|
|
74
|
+
|
|
75
|
+
return filtered_results
|
|
76
|
+
|
|
77
|
+
return results
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def calculate_bbox_fingerprint(bbox: Dict[str, Any], category: str = "") -> str:
|
|
81
|
+
"""
|
|
82
|
+
Calculate a fingerprint for a bounding box to detect duplicates.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
bbox: Bounding box dictionary
|
|
86
|
+
category: Object category
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
str: Unique fingerprint for the bbox
|
|
90
|
+
"""
|
|
91
|
+
# Extract coordinates
|
|
92
|
+
if "xmin" in bbox:
|
|
93
|
+
coords = (bbox["xmin"], bbox["ymin"], bbox["xmax"], bbox["ymax"])
|
|
94
|
+
elif "x1" in bbox:
|
|
95
|
+
coords = (bbox["x1"], bbox["y1"], bbox["x2"], bbox["y2"])
|
|
96
|
+
else:
|
|
97
|
+
values = list(bbox.values())
|
|
98
|
+
coords = tuple(values[:4]) if len(values) >= 4 else (0, 0, 0, 0)
|
|
99
|
+
|
|
100
|
+
# Round coordinates to reduce floating point precision issues
|
|
101
|
+
rounded_coords = tuple(round(c, 2) for c in coords)
|
|
102
|
+
|
|
103
|
+
return f"{category}_{rounded_coords}"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def clean_expired_tracks(track_timestamps: Dict[str, float],
|
|
107
|
+
track_last_seen: Dict[str, float],
|
|
108
|
+
current_timestamp: float,
|
|
109
|
+
expiry_time: float) -> None:
|
|
110
|
+
"""
|
|
111
|
+
Clean expired tracks from tracking dictionaries.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
track_timestamps: Dictionary of track_id -> first_seen_timestamp
|
|
115
|
+
track_last_seen: Dictionary of track_id -> last_seen_timestamp
|
|
116
|
+
current_timestamp: Current timestamp
|
|
117
|
+
expiry_time: Time after which tracks expire
|
|
118
|
+
"""
|
|
119
|
+
expired_tracks = []
|
|
120
|
+
|
|
121
|
+
for track_id, last_seen in track_last_seen.items():
|
|
122
|
+
if current_timestamp - last_seen > expiry_time:
|
|
123
|
+
expired_tracks.append(track_id)
|
|
124
|
+
|
|
125
|
+
for track_id in expired_tracks:
|
|
126
|
+
track_timestamps.pop(track_id, None)
|
|
127
|
+
track_last_seen.pop(track_id, None)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def remove_duplicate_detections(results: List[Dict[str, Any]],
|
|
131
|
+
similarity_threshold: float = 0.8) -> List[Dict[str, Any]]:
|
|
132
|
+
"""
|
|
133
|
+
Remove duplicate detections based on bbox similarity.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
results: List of detection dictionaries
|
|
137
|
+
similarity_threshold: IoU threshold for considering detections as duplicates
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
List of unique detections
|
|
141
|
+
"""
|
|
142
|
+
from .geometry_utils import calculate_iou
|
|
143
|
+
|
|
144
|
+
if not results:
|
|
145
|
+
return results
|
|
146
|
+
|
|
147
|
+
unique_detections = []
|
|
148
|
+
|
|
149
|
+
for detection in results:
|
|
150
|
+
is_duplicate = False
|
|
151
|
+
|
|
152
|
+
for existing in unique_detections:
|
|
153
|
+
# Check if same category
|
|
154
|
+
if detection.get("category") == existing.get("category"):
|
|
155
|
+
# Calculate IoU between bounding boxes
|
|
156
|
+
bbox1 = detection.get("bounding_box", detection.get("bbox", {}))
|
|
157
|
+
bbox2 = existing.get("bounding_box", existing.get("bbox", {}))
|
|
158
|
+
|
|
159
|
+
if bbox1 and bbox2:
|
|
160
|
+
iou = calculate_iou(bbox1, bbox2)
|
|
161
|
+
if iou >= similarity_threshold:
|
|
162
|
+
is_duplicate = True
|
|
163
|
+
# Keep the one with higher confidence
|
|
164
|
+
if detection.get("confidence", 0) > existing.get("confidence", 0):
|
|
165
|
+
unique_detections.remove(existing)
|
|
166
|
+
unique_detections.append(detection)
|
|
167
|
+
break
|
|
168
|
+
|
|
169
|
+
if not is_duplicate:
|
|
170
|
+
unique_detections.append(detection)
|
|
171
|
+
|
|
172
|
+
return unique_detections
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def apply_category_mapping(results: Any, index_to_category: Dict[str, str]) -> Any:
|
|
176
|
+
"""
|
|
177
|
+
Apply category index to name mapping.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
results: Detection or tracking results
|
|
181
|
+
index_to_category: Mapping from category index to category name
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Results with mapped category names
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
def map_detection(
|
|
188
|
+
detection: Dict[str, Any], index_to_category: Dict[str, str]
|
|
189
|
+
) -> Dict[str, Any]:
|
|
190
|
+
"""Map a single detection."""
|
|
191
|
+
detection = detection.copy()
|
|
192
|
+
category_id = str(detection.get("category", detection.get("category_id")))
|
|
193
|
+
index_to_category = {str(k): str(v) for k, v in index_to_category.items()}
|
|
194
|
+
if category_id in index_to_category:
|
|
195
|
+
detection["category"] = index_to_category[category_id]
|
|
196
|
+
detection["category_id"] = category_id
|
|
197
|
+
return detection
|
|
198
|
+
|
|
199
|
+
if isinstance(results, list):
|
|
200
|
+
# Detection format
|
|
201
|
+
return [map_detection(r, index_to_category) for r in results]
|
|
202
|
+
|
|
203
|
+
elif isinstance(results, dict):
|
|
204
|
+
# Check if it's a simple classification result
|
|
205
|
+
if "category" in results or "category_id" in results:
|
|
206
|
+
return map_detection(results, index_to_category)
|
|
207
|
+
|
|
208
|
+
# Frame-based format
|
|
209
|
+
mapped_results = {}
|
|
210
|
+
for frame_id, detections in results.items():
|
|
211
|
+
if isinstance(detections, list):
|
|
212
|
+
mapped_results[frame_id] = [
|
|
213
|
+
map_detection(d, index_to_category) for d in detections
|
|
214
|
+
]
|
|
215
|
+
else:
|
|
216
|
+
mapped_results[frame_id] = detections
|
|
217
|
+
|
|
218
|
+
return mapped_results
|
|
219
|
+
|
|
220
|
+
return results
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def filter_by_area(results: Any, min_area: float = 0, max_area: float = float('inf')) -> Any:
|
|
224
|
+
"""
|
|
225
|
+
Filter detections by bounding box area.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
results: Detection or tracking results
|
|
229
|
+
min_area: Minimum bounding box area
|
|
230
|
+
max_area: Maximum bounding box area
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
Filtered results
|
|
234
|
+
"""
|
|
235
|
+
from .geometry_utils import get_bbox_area
|
|
236
|
+
|
|
237
|
+
def is_valid_area(detection: Dict[str, Any]) -> bool:
|
|
238
|
+
"""Check if detection has valid area."""
|
|
239
|
+
bbox = detection.get("bounding_box", detection.get("bbox"))
|
|
240
|
+
if not bbox:
|
|
241
|
+
return True # Keep detections without bbox
|
|
242
|
+
|
|
243
|
+
area = get_bbox_area(bbox)
|
|
244
|
+
return min_area <= area <= max_area
|
|
245
|
+
|
|
246
|
+
if isinstance(results, list):
|
|
247
|
+
# Detection format
|
|
248
|
+
return [r for r in results if is_valid_area(r)]
|
|
249
|
+
|
|
250
|
+
elif isinstance(results, dict):
|
|
251
|
+
# Frame-based format
|
|
252
|
+
filtered_results = {}
|
|
253
|
+
for frame_id, detections in results.items():
|
|
254
|
+
if isinstance(detections, list):
|
|
255
|
+
filtered_detections = [d for d in detections if is_valid_area(d)]
|
|
256
|
+
if filtered_detections:
|
|
257
|
+
filtered_results[frame_id] = filtered_detections
|
|
258
|
+
|
|
259
|
+
return filtered_results
|
|
260
|
+
|
|
261
|
+
return results
|