matrice-analytics 0.1.60__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.
- 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 +146 -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 +3291 -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 +950 -0
- matrice_analytics/post_processing/face_reg/face_recognition.py +2234 -0
- matrice_analytics/post_processing/face_reg/face_recognition_client.py +606 -0
- matrice_analytics/post_processing/face_reg/people_activity_logging.py +321 -0
- matrice_analytics/post_processing/ocr/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/easyocr_extractor.py +250 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/__init__.py +9 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/__init__.py +4 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/cli.py +33 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/dataset_stats.py +139 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/export.py +398 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/train.py +447 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/utils.py +129 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/valid.py +93 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/validate_dataset.py +240 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_augmentation.py +176 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_predictions.py +96 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/__init__.py +3 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/process.py +246 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/types.py +60 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/utils.py +87 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/__init__.py +3 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/config.py +82 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/hub.py +141 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/plate_recognizer.py +323 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/py.typed +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/augmentation.py +101 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/dataset.py +97 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/config.py +114 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/layers.py +553 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/loss.py +55 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/metric.py +86 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_builders.py +95 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_schema.py +395 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/__init__.py +0 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/backend_utils.py +38 -0
- matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/utils.py +214 -0
- matrice_analytics/post_processing/ocr/postprocessing.py +270 -0
- matrice_analytics/post_processing/ocr/preprocessing.py +52 -0
- matrice_analytics/post_processing/post_processor.py +1175 -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_usecases.py +165 -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 +1085 -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 +660 -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 +1936 -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 +585 -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 +1146 -0
- matrice_analytics/post_processing/usecases/flare_analysis.py +836 -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 +1188 -0
- matrice_analytics/post_processing/usecases/license_plate_monitoring.py +1781 -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 +706 -0
- matrice_analytics/post_processing/usecases/people_counting_bckp.py +1683 -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 +1029 -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.60.dist-info/METADATA +481 -0
- matrice_analytics-0.1.60.dist-info/RECORD +196 -0
- matrice_analytics-0.1.60.dist-info/WHEEL +5 -0
- matrice_analytics-0.1.60.dist-info/licenses/LICENSE.txt +21 -0
- matrice_analytics-0.1.60.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BBox smoothing utilities for post-processing operations.
|
|
3
|
+
|
|
4
|
+
This module provides generalized smoothing algorithms for bounding box detections
|
|
5
|
+
to reduce noise and false positives in detection results.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import List, Dict, Any, Optional, Union
|
|
9
|
+
from collections import deque
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
import logging
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class BBoxSmoothingConfig:
|
|
18
|
+
"""Configuration for bbox smoothing algorithms."""
|
|
19
|
+
|
|
20
|
+
smoothing_algorithm: str = "observability" # "window" or "observability"
|
|
21
|
+
window_size: int = 20
|
|
22
|
+
cooldown_frames: int = 5
|
|
23
|
+
confidence_threshold: float = 0.5
|
|
24
|
+
confidence_range_factor: float = 0.5 # For observability algorithm
|
|
25
|
+
track_by_centroid: bool = True
|
|
26
|
+
centroid_quantization: int = 10
|
|
27
|
+
enable_smoothing: bool = True # Master flag to enable/disable smoothing
|
|
28
|
+
|
|
29
|
+
def __post_init__(self):
|
|
30
|
+
"""Validate configuration parameters."""
|
|
31
|
+
if self.smoothing_algorithm not in ["window", "observability"]:
|
|
32
|
+
logging.error(f"Invalid smoothing_algorithm: {self.smoothing_algorithm}. Must be 'window' or 'observability'")
|
|
33
|
+
self.smoothing_algorithm = "observability"
|
|
34
|
+
|
|
35
|
+
# Convert window_size to int if it's a string
|
|
36
|
+
if isinstance(self.window_size, str):
|
|
37
|
+
try:
|
|
38
|
+
self.window_size = int(self.window_size)
|
|
39
|
+
except ValueError:
|
|
40
|
+
logging.error(f"window_size must be a valid integer, got {self.window_size}")
|
|
41
|
+
self.window_size = 20
|
|
42
|
+
|
|
43
|
+
if self.window_size <= 0:
|
|
44
|
+
logging.error(f"window_size must be positive, got {self.window_size}")
|
|
45
|
+
self.window_size = 20
|
|
46
|
+
# Convert cooldown_frames to int if it's a string
|
|
47
|
+
if isinstance(self.cooldown_frames, str):
|
|
48
|
+
try:
|
|
49
|
+
self.cooldown_frames = int(self.cooldown_frames)
|
|
50
|
+
except ValueError:
|
|
51
|
+
logging.error(f"cooldown_frames must be a valid integer, got {self.cooldown_frames}")
|
|
52
|
+
self.cooldown_frames = 5
|
|
53
|
+
|
|
54
|
+
if self.cooldown_frames < 0:
|
|
55
|
+
logging.error(f"cooldown_frames must be non-negative, got {self.cooldown_frames}")
|
|
56
|
+
self.cooldown_frames = 5
|
|
57
|
+
# Convert confidence_threshold to float if it's a string
|
|
58
|
+
if isinstance(self.confidence_threshold, str):
|
|
59
|
+
try:
|
|
60
|
+
self.confidence_threshold = float(self.confidence_threshold)
|
|
61
|
+
except ValueError:
|
|
62
|
+
logging.error(f"confidence_threshold must be a valid number, got {self.confidence_threshold}")
|
|
63
|
+
self.confidence_threshold = 0.5
|
|
64
|
+
if not 0.0 <= self.confidence_threshold <= 1.0:
|
|
65
|
+
logging.error(f"confidence_threshold must be between 0.0 and 1.0, got {self.confidence_threshold}")
|
|
66
|
+
self.confidence_threshold = 0.5
|
|
67
|
+
|
|
68
|
+
# Convert confidence_range_factor to float if it's a string
|
|
69
|
+
if isinstance(self.confidence_range_factor, str):
|
|
70
|
+
try:
|
|
71
|
+
self.confidence_range_factor = float(self.confidence_range_factor)
|
|
72
|
+
except ValueError:
|
|
73
|
+
logging.error(f"confidence_range_factor must be a valid number, got {self.confidence_range_factor}")
|
|
74
|
+
self.confidence_range_factor = 0.5
|
|
75
|
+
|
|
76
|
+
if not 0.0 <= self.confidence_range_factor <= 1.0:
|
|
77
|
+
logging.error(f"confidence_range_factor must be between 0.0 and 1.0, got {self.confidence_range_factor}")
|
|
78
|
+
self.confidence_range_factor = 0.5
|
|
79
|
+
|
|
80
|
+
# Convert centroid_quantization to int if it's a string
|
|
81
|
+
if isinstance(self.centroid_quantization, str):
|
|
82
|
+
try:
|
|
83
|
+
self.centroid_quantization = int(self.centroid_quantization)
|
|
84
|
+
except ValueError:
|
|
85
|
+
logging.error(f"centroid_quantization must be a valid integer, got {self.centroid_quantization}")
|
|
86
|
+
self.centroid_quantization = 10
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class BBoxSmoothingTracker:
|
|
90
|
+
"""Tracks individual objects for smoothing across frames."""
|
|
91
|
+
|
|
92
|
+
def __init__(self, config: BBoxSmoothingConfig):
|
|
93
|
+
self.config = config
|
|
94
|
+
self.object_windows = {} # {object_id: deque}
|
|
95
|
+
self.object_cooldowns = {} # {object_id: cooldown_counter}
|
|
96
|
+
self.logger = logging.getLogger(f"{__name__}.BBoxSmoothingTracker")
|
|
97
|
+
|
|
98
|
+
def reset(self):
|
|
99
|
+
"""Reset tracker state."""
|
|
100
|
+
self.object_windows.clear()
|
|
101
|
+
self.object_cooldowns.clear()
|
|
102
|
+
self.logger.debug("BBox smoothing tracker reset")
|
|
103
|
+
|
|
104
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
105
|
+
"""Get tracker statistics."""
|
|
106
|
+
return {
|
|
107
|
+
"active_objects": len(self.object_windows),
|
|
108
|
+
"total_cooldowns": len(self.object_cooldowns),
|
|
109
|
+
"window_size": self.config.window_size,
|
|
110
|
+
"cooldown_frames": self.config.cooldown_frames
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _get_object_id(detection: Dict, config: BBoxSmoothingConfig) -> str:
|
|
115
|
+
"""
|
|
116
|
+
Generate unique object ID from detection using robust hashing.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
detection: Detection dictionary
|
|
120
|
+
config: Smoothing configuration
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
str: Unique object identifier
|
|
124
|
+
"""
|
|
125
|
+
# Extract bbox coordinates (handle different formats)
|
|
126
|
+
bbox = detection.get('bounding_box', detection.get('bbox', {}))
|
|
127
|
+
|
|
128
|
+
# Normalize bbox to consistent format
|
|
129
|
+
if isinstance(bbox, dict):
|
|
130
|
+
if 'x' in bbox and 'y' in bbox and 'width' in bbox and 'height' in bbox:
|
|
131
|
+
x, y, w, h = bbox['x'], bbox['y'], bbox['width'], bbox['height']
|
|
132
|
+
elif 'xmin' in bbox and 'ymin' in bbox and 'xmax' in bbox and 'ymax' in bbox:
|
|
133
|
+
x, y, w, h = bbox['xmin'], bbox['ymin'], bbox['xmax'] - bbox['xmin'], bbox['ymax'] - bbox['ymin']
|
|
134
|
+
elif 'x1' in bbox and 'y1' in bbox and 'x2' in bbox and 'y2' in bbox:
|
|
135
|
+
x, y, w, h = bbox['x1'], bbox['y1'], bbox['x2'] - bbox['x1'], bbox['y2'] - bbox['y1']
|
|
136
|
+
else:
|
|
137
|
+
# Fallback: try to extract any numeric values
|
|
138
|
+
values = [v for v in bbox.values() if isinstance(v, (int, float))]
|
|
139
|
+
if len(values) >= 4:
|
|
140
|
+
x, y, w, h = values[0], values[1], values[2], values[3]
|
|
141
|
+
else:
|
|
142
|
+
x, y, w, h = 0, 0, 0, 0
|
|
143
|
+
else:
|
|
144
|
+
# Handle list/tuple format
|
|
145
|
+
if isinstance(bbox, (list, tuple)) and len(bbox) >= 4:
|
|
146
|
+
x, y, w, h = bbox[0], bbox[1], bbox[2], bbox[3]
|
|
147
|
+
else:
|
|
148
|
+
x, y, w, h = 0, 0, 0, 0
|
|
149
|
+
|
|
150
|
+
# Quantize coordinates to reduce jitter (similar to centroid approach but more robust)
|
|
151
|
+
quantized_x = int(x // config.centroid_quantization) if hasattr(config, 'centroid_quantization') else int(x)
|
|
152
|
+
quantized_y = int(y // config.centroid_quantization) if hasattr(config, 'centroid_quantization') else int(y)
|
|
153
|
+
quantized_w = int(w // config.centroid_quantization) if hasattr(config, 'centroid_quantization') else int(w)
|
|
154
|
+
quantized_h = int(h // config.centroid_quantization) if hasattr(config, 'centroid_quantization') else int(h)
|
|
155
|
+
|
|
156
|
+
# Get other attributes (handle missing values)
|
|
157
|
+
confidence = detection.get('confidence', 0.0)
|
|
158
|
+
category = detection.get('category', 'unknown')
|
|
159
|
+
|
|
160
|
+
# Create hash string from quantized bbox and attributes (no track_id dependency)
|
|
161
|
+
hash_string = f"{quantized_x}_{quantized_y}_{quantized_w}_{quantized_h}_{confidence}_{category}"
|
|
162
|
+
|
|
163
|
+
# Generate hash and ensure it's positive
|
|
164
|
+
detection_hash = abs(hash(hash_string))
|
|
165
|
+
return f"detection_{detection_hash}"
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _apply_window_smoothing(detections: List[Dict],
|
|
169
|
+
config: BBoxSmoothingConfig,
|
|
170
|
+
tracker: BBoxSmoothingTracker) -> List[Dict]:
|
|
171
|
+
"""
|
|
172
|
+
Apply window smoothing without cooldown (frame-accurate).
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
detections: List of detection dictionaries
|
|
176
|
+
config: Smoothing configuration
|
|
177
|
+
tracker: Tracker instance for state management
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
List[Dict]: Smoothed detections (only from current frame)
|
|
181
|
+
"""
|
|
182
|
+
output = []
|
|
183
|
+
current_object_ids = set()
|
|
184
|
+
|
|
185
|
+
# Process current detections
|
|
186
|
+
for det in detections:
|
|
187
|
+
object_id = _get_object_id(det, config)
|
|
188
|
+
current_object_ids.add(object_id)
|
|
189
|
+
|
|
190
|
+
# Initialize window if new object
|
|
191
|
+
if object_id not in tracker.object_windows:
|
|
192
|
+
tracker.object_windows[object_id] = deque(maxlen=config.window_size)
|
|
193
|
+
|
|
194
|
+
# Add to window
|
|
195
|
+
tracker.object_windows[object_id].append(det)
|
|
196
|
+
|
|
197
|
+
# Only output detections from current frame
|
|
198
|
+
for object_id in current_object_ids:
|
|
199
|
+
if object_id in tracker.object_windows:
|
|
200
|
+
window = tracker.object_windows[object_id]
|
|
201
|
+
if window:
|
|
202
|
+
# Calculate average confidence for smoothing
|
|
203
|
+
confidences = [d.get('confidence', 0.0) for d in window]
|
|
204
|
+
avg_confidence = sum(confidences) / len(confidences) if confidences else 0.0
|
|
205
|
+
|
|
206
|
+
# Output if above threshold
|
|
207
|
+
if avg_confidence >= config.confidence_threshold:
|
|
208
|
+
output.append(window[-1]) # Most recent detection
|
|
209
|
+
|
|
210
|
+
# Clean up unused windows (optional memory management)
|
|
211
|
+
for object_id in list(tracker.object_windows.keys()):
|
|
212
|
+
if object_id not in current_object_ids:
|
|
213
|
+
del tracker.object_windows[object_id]
|
|
214
|
+
|
|
215
|
+
return output
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _apply_observability_smoothing(detections: List[Dict],
|
|
219
|
+
config: BBoxSmoothingConfig,
|
|
220
|
+
tracker: BBoxSmoothingTracker) -> List[Dict]:
|
|
221
|
+
"""
|
|
222
|
+
Apply observability/confidence tradeoff smoothing without cooldown (frame-accurate).
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
detections: List of detection dictionaries
|
|
226
|
+
config: Smoothing configuration
|
|
227
|
+
tracker: Tracker instance for state management
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
List[Dict]: Smoothed detections (only from current frame)
|
|
231
|
+
"""
|
|
232
|
+
output = []
|
|
233
|
+
current_object_ids = set()
|
|
234
|
+
|
|
235
|
+
# Process current detections
|
|
236
|
+
for det in detections:
|
|
237
|
+
object_id = _get_object_id(det, config)
|
|
238
|
+
current_object_ids.add(object_id)
|
|
239
|
+
|
|
240
|
+
# Initialize window if new object
|
|
241
|
+
if object_id not in tracker.object_windows:
|
|
242
|
+
tracker.object_windows[object_id] = deque(maxlen=config.window_size)
|
|
243
|
+
|
|
244
|
+
tracker.object_windows[object_id].append(det)
|
|
245
|
+
|
|
246
|
+
# Only process detections from current frame
|
|
247
|
+
for object_id in current_object_ids:
|
|
248
|
+
if object_id in tracker.object_windows:
|
|
249
|
+
window = tracker.object_windows[object_id]
|
|
250
|
+
if window:
|
|
251
|
+
# Calculate observability score
|
|
252
|
+
observability_score = len(window) / config.window_size
|
|
253
|
+
|
|
254
|
+
# Get current confidence
|
|
255
|
+
current_confidence = window[-1].get('confidence', 0.0)
|
|
256
|
+
|
|
257
|
+
# Define confidence range
|
|
258
|
+
conf_range = config.confidence_threshold * config.confidence_range_factor
|
|
259
|
+
|
|
260
|
+
# Decision logic
|
|
261
|
+
if current_confidence >= config.confidence_threshold:
|
|
262
|
+
# High confidence: always keep
|
|
263
|
+
output.append(window[-1])
|
|
264
|
+
elif current_confidence >= (config.confidence_threshold - conf_range):
|
|
265
|
+
# Borderline: apply tradeoff
|
|
266
|
+
confidence_factor = (config.confidence_threshold - current_confidence) / conf_range
|
|
267
|
+
if confidence_factor <= observability_score:
|
|
268
|
+
output.append(window[-1])
|
|
269
|
+
# else: too low confidence, discard
|
|
270
|
+
|
|
271
|
+
# Clean up unused windows (optional memory management)
|
|
272
|
+
for object_id in list(tracker.object_windows.keys()):
|
|
273
|
+
if object_id not in current_object_ids:
|
|
274
|
+
del tracker.object_windows[object_id]
|
|
275
|
+
|
|
276
|
+
return output
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def bbox_smoothing(detections: Union[List[Dict], Dict[str, List[Dict]]],
|
|
280
|
+
config: BBoxSmoothingConfig,
|
|
281
|
+
tracker: Optional[BBoxSmoothingTracker] = None) -> Union[List[Dict], Dict[str, List[Dict]]]:
|
|
282
|
+
"""
|
|
283
|
+
Apply smoothing algorithm to bbox detections.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
detections: Either:
|
|
287
|
+
- List of detection dictionaries (detection format)
|
|
288
|
+
- Dict with frame keys containing lists of detections (tracking format)
|
|
289
|
+
config: Smoothing configuration
|
|
290
|
+
tracker: Optional tracker instance for persistent state across frames
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
Same format as input: List[Dict] or Dict[str, List[Dict]]
|
|
294
|
+
"""
|
|
295
|
+
# Early return if smoothing is disabled
|
|
296
|
+
if not config.enable_smoothing:
|
|
297
|
+
return detections
|
|
298
|
+
|
|
299
|
+
# Early return if no detections
|
|
300
|
+
if not detections:
|
|
301
|
+
return detections
|
|
302
|
+
|
|
303
|
+
# Create tracker if not provided
|
|
304
|
+
if tracker is None:
|
|
305
|
+
tracker = BBoxSmoothingTracker(config)
|
|
306
|
+
|
|
307
|
+
# Handle tracking format (dict with frame keys)
|
|
308
|
+
if isinstance(detections, dict):
|
|
309
|
+
smoothed_tracking_results = {}
|
|
310
|
+
|
|
311
|
+
for frame_id, frame_detections in detections.items():
|
|
312
|
+
if isinstance(frame_detections, list):
|
|
313
|
+
# Apply smoothing to this frame's detections
|
|
314
|
+
if config.smoothing_algorithm == "observability":
|
|
315
|
+
smoothed_frame = _apply_observability_smoothing(frame_detections, config, tracker)
|
|
316
|
+
else: # "window"
|
|
317
|
+
smoothed_frame = _apply_window_smoothing(frame_detections, config, tracker)
|
|
318
|
+
|
|
319
|
+
smoothed_tracking_results[frame_id] = smoothed_frame
|
|
320
|
+
|
|
321
|
+
return smoothed_tracking_results
|
|
322
|
+
|
|
323
|
+
# Handle detection format (list of detections)
|
|
324
|
+
elif isinstance(detections, list):
|
|
325
|
+
# Apply selected smoothing algorithm
|
|
326
|
+
if config.smoothing_algorithm == "observability":
|
|
327
|
+
return _apply_observability_smoothing(detections, config, tracker)
|
|
328
|
+
else: # "window"
|
|
329
|
+
return _apply_window_smoothing(detections, config, tracker)
|
|
330
|
+
|
|
331
|
+
# Fallback for unknown format
|
|
332
|
+
return detections
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def create_bbox_smoothing_tracker(config: BBoxSmoothingConfig) -> BBoxSmoothingTracker:
|
|
336
|
+
"""
|
|
337
|
+
Create a new bbox smoothing tracker instance.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
config: Smoothing configuration
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
BBoxSmoothingTracker: New tracker instance
|
|
344
|
+
"""
|
|
345
|
+
return BBoxSmoothingTracker(config)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def create_default_smoothing_config(**overrides) -> BBoxSmoothingConfig:
|
|
349
|
+
"""
|
|
350
|
+
Create default smoothing configuration with optional overrides.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
**overrides: Configuration overrides
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
BBoxSmoothingConfig: Configuration instance
|
|
357
|
+
"""
|
|
358
|
+
return BBoxSmoothingConfig(**overrides)
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tracking utilities for post-processing operations.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List, Dict, Any, Tuple, Optional
|
|
6
|
+
from collections import defaultdict
|
|
7
|
+
|
|
8
|
+
from .geometry_utils import point_in_polygon, get_bbox_center, line_segments_intersect
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def track_objects_in_zone(results: Any, zone_polygon: List[List[float]]) -> Dict[str, Any]:
|
|
12
|
+
"""
|
|
13
|
+
Track objects within a defined zone.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
results: Detection or tracking results
|
|
17
|
+
zone_polygon: Zone polygon coordinates [[x1,y1], [x2,y2], ...]
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Dict with zone tracking information
|
|
21
|
+
"""
|
|
22
|
+
zone_tracks = []
|
|
23
|
+
zone_polygon_tuples = [(p[0], p[1]) for p in zone_polygon]
|
|
24
|
+
|
|
25
|
+
if isinstance(results, list):
|
|
26
|
+
# Detection format
|
|
27
|
+
for detection in results:
|
|
28
|
+
if _is_detection_in_zone(detection, zone_polygon_tuples):
|
|
29
|
+
bbox = detection.get("bounding_box", detection.get("bbox", {}))
|
|
30
|
+
center = get_bbox_center(bbox)
|
|
31
|
+
|
|
32
|
+
zone_tracks.append({
|
|
33
|
+
**detection,
|
|
34
|
+
"in_zone": True,
|
|
35
|
+
"zone_center": center
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
elif isinstance(results, dict):
|
|
39
|
+
# Frame-based format
|
|
40
|
+
for frame_id, detections in results.items():
|
|
41
|
+
if isinstance(detections, list):
|
|
42
|
+
for detection in detections:
|
|
43
|
+
if _is_detection_in_zone(detection, zone_polygon_tuples):
|
|
44
|
+
bbox = detection.get("bounding_box", detection.get("bbox", {}))
|
|
45
|
+
center = get_bbox_center(bbox)
|
|
46
|
+
|
|
47
|
+
zone_tracks.append({
|
|
48
|
+
**detection,
|
|
49
|
+
"frame_id": frame_id,
|
|
50
|
+
"in_zone": True,
|
|
51
|
+
"zone_center": center
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
"zone_tracks": zone_tracks,
|
|
56
|
+
"count_in_zone": len(zone_tracks)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def detect_line_crossings(results: Dict[str, List[Dict]], line_points: List[List[float]],
|
|
61
|
+
track_history: Optional[Dict] = None) -> Dict[str, Any]:
|
|
62
|
+
"""
|
|
63
|
+
Detect when tracked objects cross a virtual line.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
results: Tracking results in frame format
|
|
67
|
+
line_points: Line coordinates [[x1,y1], [x2,y2]]
|
|
68
|
+
track_history: Optional track position history
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Dict with crossing information
|
|
72
|
+
"""
|
|
73
|
+
if len(line_points) != 2:
|
|
74
|
+
return {"crossings": [], "total_crossings": 0}
|
|
75
|
+
|
|
76
|
+
line_start = tuple(line_points[0])
|
|
77
|
+
line_end = tuple(line_points[1])
|
|
78
|
+
crossings = []
|
|
79
|
+
|
|
80
|
+
if track_history is None:
|
|
81
|
+
track_history = {}
|
|
82
|
+
|
|
83
|
+
for frame_id, detections in results.items():
|
|
84
|
+
if isinstance(detections, list):
|
|
85
|
+
for detection in detections:
|
|
86
|
+
track_id = detection.get("track_id")
|
|
87
|
+
if track_id is None:
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
bbox = detection.get("bounding_box", detection.get("bbox"))
|
|
91
|
+
if not bbox:
|
|
92
|
+
continue
|
|
93
|
+
|
|
94
|
+
center = get_bbox_center(bbox)
|
|
95
|
+
|
|
96
|
+
# Check for line crossing
|
|
97
|
+
if track_id in track_history:
|
|
98
|
+
prev_pos = track_history[track_id][-1] if track_history[track_id] else None
|
|
99
|
+
|
|
100
|
+
if prev_pos and line_segments_intersect(prev_pos, center, line_start, line_end):
|
|
101
|
+
crossings.append({
|
|
102
|
+
"track_id": track_id,
|
|
103
|
+
"frame_id": frame_id,
|
|
104
|
+
"position": center,
|
|
105
|
+
"category": detection.get("category", "unknown"),
|
|
106
|
+
"previous_position": prev_pos
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
# Update track history
|
|
110
|
+
if track_id not in track_history:
|
|
111
|
+
track_history[track_id] = []
|
|
112
|
+
|
|
113
|
+
track_history[track_id].append(center)
|
|
114
|
+
|
|
115
|
+
# Keep only recent positions (last 10)
|
|
116
|
+
if len(track_history[track_id]) > 10:
|
|
117
|
+
track_history[track_id] = track_history[track_id][-10:]
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
"crossings": crossings,
|
|
121
|
+
"total_crossings": len(crossings),
|
|
122
|
+
"track_history": track_history
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def analyze_track_movements(results: Dict[str, List[Dict]]) -> Dict[str, Any]:
|
|
127
|
+
"""
|
|
128
|
+
Analyze movement patterns of tracked objects.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
results: Tracking results in frame format
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Dict with movement analysis
|
|
135
|
+
"""
|
|
136
|
+
track_paths = defaultdict(list)
|
|
137
|
+
track_stats = {}
|
|
138
|
+
|
|
139
|
+
# Collect track positions
|
|
140
|
+
for frame_id, detections in results.items():
|
|
141
|
+
if isinstance(detections, list):
|
|
142
|
+
for detection in detections:
|
|
143
|
+
track_id = detection.get("track_id")
|
|
144
|
+
if track_id is None:
|
|
145
|
+
continue
|
|
146
|
+
|
|
147
|
+
bbox = detection.get("bounding_box", detection.get("bbox"))
|
|
148
|
+
if bbox:
|
|
149
|
+
center = get_bbox_center(bbox)
|
|
150
|
+
track_paths[track_id].append({
|
|
151
|
+
"frame_id": frame_id,
|
|
152
|
+
"position": center,
|
|
153
|
+
"category": detection.get("category", "unknown")
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
# Analyze each track
|
|
157
|
+
for track_id, positions in track_paths.items():
|
|
158
|
+
if len(positions) < 2:
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
# Calculate total distance traveled
|
|
162
|
+
total_distance = 0
|
|
163
|
+
for i in range(1, len(positions)):
|
|
164
|
+
prev_pos = positions[i-1]["position"]
|
|
165
|
+
curr_pos = positions[i]["position"]
|
|
166
|
+
distance = ((curr_pos[0] - prev_pos[0])**2 + (curr_pos[1] - prev_pos[1])**2)**0.5
|
|
167
|
+
total_distance += distance
|
|
168
|
+
|
|
169
|
+
# Calculate average speed (distance per frame)
|
|
170
|
+
avg_speed = total_distance / (len(positions) - 1) if len(positions) > 1 else 0
|
|
171
|
+
|
|
172
|
+
track_stats[track_id] = {
|
|
173
|
+
"total_frames": len(positions),
|
|
174
|
+
"total_distance": total_distance,
|
|
175
|
+
"average_speed": avg_speed,
|
|
176
|
+
"start_position": positions[0]["position"],
|
|
177
|
+
"end_position": positions[-1]["position"],
|
|
178
|
+
"category": positions[0]["category"]
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
"track_paths": dict(track_paths),
|
|
183
|
+
"track_statistics": track_stats,
|
|
184
|
+
"total_tracks": len(track_paths)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def filter_tracks_by_duration(results: Dict[str, List[Dict]], min_duration: int = 5) -> Dict[str, List[Dict]]:
|
|
189
|
+
"""
|
|
190
|
+
Filter tracking results to only include tracks that appear for minimum duration.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
results: Tracking results in frame format
|
|
194
|
+
min_duration: Minimum number of frames a track must appear
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
Filtered tracking results
|
|
198
|
+
"""
|
|
199
|
+
track_counts = defaultdict(int)
|
|
200
|
+
|
|
201
|
+
# Count appearances per track
|
|
202
|
+
for frame_id, detections in results.items():
|
|
203
|
+
if isinstance(detections, list):
|
|
204
|
+
for detection in detections:
|
|
205
|
+
track_id = detection.get("track_id")
|
|
206
|
+
if track_id is not None:
|
|
207
|
+
track_counts[track_id] += 1
|
|
208
|
+
|
|
209
|
+
# Filter tracks that meet minimum duration
|
|
210
|
+
valid_tracks = {track_id for track_id, count in track_counts.items() if count >= min_duration}
|
|
211
|
+
|
|
212
|
+
# Filter results
|
|
213
|
+
filtered_results = {}
|
|
214
|
+
for frame_id, detections in results.items():
|
|
215
|
+
filtered_detections = []
|
|
216
|
+
for detection in detections:
|
|
217
|
+
track_id = detection.get("track_id")
|
|
218
|
+
if track_id is None or track_id in valid_tracks:
|
|
219
|
+
filtered_detections.append(detection)
|
|
220
|
+
|
|
221
|
+
if filtered_detections:
|
|
222
|
+
filtered_results[frame_id] = filtered_detections
|
|
223
|
+
|
|
224
|
+
return filtered_results
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _is_detection_in_zone(detection: Dict[str, Any], zone_polygon: List[Tuple[float, float]]) -> bool:
|
|
228
|
+
"""Check if a detection is within a zone polygon."""
|
|
229
|
+
bbox = detection.get("bounding_box", detection.get("bbox"))
|
|
230
|
+
if not bbox:
|
|
231
|
+
return False
|
|
232
|
+
|
|
233
|
+
center = get_bbox_center(bbox)
|
|
234
|
+
return point_in_polygon(center, zone_polygon)
|
|
File without changes
|