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,150 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility functions for post-processing operations.
|
|
3
|
+
|
|
4
|
+
This module provides organized utility functions for common post-processing tasks
|
|
5
|
+
like geometry calculations, format conversions, counting, tracking, and filtering.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .geometry_utils import (
|
|
9
|
+
point_in_polygon,
|
|
10
|
+
get_bbox_center,
|
|
11
|
+
calculate_distance,
|
|
12
|
+
calculate_bbox_overlap,
|
|
13
|
+
calculate_iou,
|
|
14
|
+
get_bbox_area,
|
|
15
|
+
normalize_bbox,
|
|
16
|
+
denormalize_bbox,
|
|
17
|
+
line_segments_intersect
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from .format_utils import (
|
|
21
|
+
convert_to_coco_format,
|
|
22
|
+
convert_to_yolo_format,
|
|
23
|
+
convert_to_tracking_format,
|
|
24
|
+
convert_detection_to_tracking_format,
|
|
25
|
+
convert_tracking_to_detection_format,
|
|
26
|
+
match_results_structure
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
from .filter_utils import (
|
|
30
|
+
filter_by_confidence,
|
|
31
|
+
filter_by_categories,
|
|
32
|
+
calculate_bbox_fingerprint,
|
|
33
|
+
clean_expired_tracks,
|
|
34
|
+
remove_duplicate_detections,
|
|
35
|
+
apply_category_mapping,
|
|
36
|
+
filter_by_area
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
from .counting_utils import (
|
|
40
|
+
count_objects_by_category,
|
|
41
|
+
count_objects_in_zones,
|
|
42
|
+
count_unique_tracks,
|
|
43
|
+
calculate_counting_summary
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
from .tracking_utils import (
|
|
47
|
+
track_objects_in_zone,
|
|
48
|
+
detect_line_crossings,
|
|
49
|
+
analyze_track_movements,
|
|
50
|
+
filter_tracks_by_duration
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
from .smoothing_utils import (
|
|
54
|
+
bbox_smoothing,
|
|
55
|
+
BBoxSmoothingConfig,
|
|
56
|
+
BBoxSmoothingTracker,
|
|
57
|
+
create_bbox_smoothing_tracker,
|
|
58
|
+
create_default_smoothing_config
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# from .color_utils import (
|
|
62
|
+
# extract_major_colors
|
|
63
|
+
# )
|
|
64
|
+
|
|
65
|
+
# Configuration utilities for easy setup
|
|
66
|
+
from ..core.config_utils import (
|
|
67
|
+
create_people_counting_config,
|
|
68
|
+
create_intrusion_detection_config,
|
|
69
|
+
create_proximity_detection_config,
|
|
70
|
+
create_customer_service_config,
|
|
71
|
+
create_advanced_customer_service_config,
|
|
72
|
+
create_basic_counting_tracking_config,
|
|
73
|
+
create_zone_from_bbox,
|
|
74
|
+
create_polygon_zone,
|
|
75
|
+
create_config_from_template,
|
|
76
|
+
validate_zone_polygon,
|
|
77
|
+
get_use_case_examples,
|
|
78
|
+
create_retail_store_zones,
|
|
79
|
+
create_office_zones
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
__all__ = [
|
|
83
|
+
# Geometry utilities
|
|
84
|
+
'point_in_polygon',
|
|
85
|
+
'get_bbox_center',
|
|
86
|
+
'calculate_distance',
|
|
87
|
+
'calculate_bbox_overlap',
|
|
88
|
+
'calculate_iou',
|
|
89
|
+
'get_bbox_area',
|
|
90
|
+
'normalize_bbox',
|
|
91
|
+
'denormalize_bbox',
|
|
92
|
+
'line_segments_intersect',
|
|
93
|
+
|
|
94
|
+
# Format utilities
|
|
95
|
+
'convert_to_coco_format',
|
|
96
|
+
'convert_to_yolo_format',
|
|
97
|
+
'convert_to_tracking_format',
|
|
98
|
+
'convert_detection_to_tracking_format',
|
|
99
|
+
'convert_tracking_to_detection_format',
|
|
100
|
+
'match_results_structure',
|
|
101
|
+
|
|
102
|
+
# Filter utilities
|
|
103
|
+
'filter_by_confidence',
|
|
104
|
+
'filter_by_categories',
|
|
105
|
+
'calculate_bbox_fingerprint',
|
|
106
|
+
'clean_expired_tracks',
|
|
107
|
+
'remove_duplicate_detections',
|
|
108
|
+
'apply_category_mapping',
|
|
109
|
+
'filter_by_area',
|
|
110
|
+
|
|
111
|
+
# Counting utilities
|
|
112
|
+
'count_objects_by_category',
|
|
113
|
+
'count_objects_in_zones',
|
|
114
|
+
'count_unique_tracks',
|
|
115
|
+
'calculate_counting_summary',
|
|
116
|
+
|
|
117
|
+
# Tracking utilities
|
|
118
|
+
'track_objects_in_zone',
|
|
119
|
+
'detect_line_crossings',
|
|
120
|
+
'analyze_track_movements',
|
|
121
|
+
'filter_tracks_by_duration',
|
|
122
|
+
|
|
123
|
+
# Smoothing utilities
|
|
124
|
+
'bbox_smoothing',
|
|
125
|
+
'BBoxSmoothingConfig',
|
|
126
|
+
'BBoxSmoothingTracker',
|
|
127
|
+
'create_bbox_smoothing_tracker',
|
|
128
|
+
'create_default_smoothing_config',
|
|
129
|
+
|
|
130
|
+
# # Color utilities
|
|
131
|
+
# 'extract_major_colors',
|
|
132
|
+
# 'rgb_to_lab',
|
|
133
|
+
# 'lab_distance',
|
|
134
|
+
# 'find_nearest_color',
|
|
135
|
+
|
|
136
|
+
# Configuration utilities
|
|
137
|
+
'create_people_counting_config',
|
|
138
|
+
'create_customer_service_config',
|
|
139
|
+
'create_intrusion_detection_config',
|
|
140
|
+
'create_proximity_detection_config',
|
|
141
|
+
'create_advanced_customer_service_config',
|
|
142
|
+
'create_basic_counting_tracking_config',
|
|
143
|
+
'create_zone_from_bbox',
|
|
144
|
+
'create_polygon_zone',
|
|
145
|
+
'create_config_from_template',
|
|
146
|
+
'validate_zone_polygon',
|
|
147
|
+
'get_use_case_examples',
|
|
148
|
+
'create_retail_store_zones',
|
|
149
|
+
'create_office_zones'
|
|
150
|
+
]
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Advanced counting utilities with time-based tracking and deduplication for post-processing.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from typing import List, Dict, Any, Tuple, Optional, Set
|
|
7
|
+
from collections import defaultdict
|
|
8
|
+
from .format_utils import match_results_structure
|
|
9
|
+
from .geometry_utils import point_in_polygon, get_bbox_center
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def calculate_bbox_overlap(bbox1: Dict, bbox2: Dict) -> float:
|
|
13
|
+
"""Calculate overlap between two bounding boxes."""
|
|
14
|
+
# Convert to consistent format
|
|
15
|
+
if 'xmin' in bbox1:
|
|
16
|
+
x1_min, y1_min, x1_max, y1_max = bbox1['xmin'], bbox1['ymin'], bbox1['xmax'], bbox1['ymax']
|
|
17
|
+
else:
|
|
18
|
+
x1_min, y1_min, x1_max, y1_max = bbox1.get('x1', 0), bbox1.get('y1', 0), bbox1.get('x2', 0), bbox1.get('y2', 0)
|
|
19
|
+
|
|
20
|
+
if 'xmin' in bbox2:
|
|
21
|
+
x2_min, y2_min, x2_max, y2_max = bbox2['xmin'], bbox2['ymin'], bbox2['xmax'], bbox2['ymax']
|
|
22
|
+
else:
|
|
23
|
+
x2_min, y2_min, x2_max, y2_max = bbox2.get('x1', 0), bbox2.get('y1', 0), bbox2.get('x2', 0), bbox2.get('y2', 0)
|
|
24
|
+
|
|
25
|
+
# Calculate intersection
|
|
26
|
+
x_left = max(x1_min, x2_min)
|
|
27
|
+
y_top = max(y1_min, y2_min)
|
|
28
|
+
x_right = min(x1_max, x2_max)
|
|
29
|
+
y_bottom = min(y1_max, y2_max)
|
|
30
|
+
|
|
31
|
+
if x_right < x_left or y_bottom < y_top:
|
|
32
|
+
return 0.0
|
|
33
|
+
|
|
34
|
+
intersection_area = (x_right - x_left) * (y_bottom - y_top)
|
|
35
|
+
|
|
36
|
+
# Calculate union
|
|
37
|
+
bbox1_area = (x1_max - x1_min) * (y1_max - y1_min)
|
|
38
|
+
bbox2_area = (x2_max - x2_min) * (y2_max - y2_min)
|
|
39
|
+
union_area = bbox1_area + bbox2_area - intersection_area
|
|
40
|
+
|
|
41
|
+
if union_area == 0:
|
|
42
|
+
return 0.0
|
|
43
|
+
|
|
44
|
+
return intersection_area / union_area
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def calculate_bbox_fingerprint(bbox: Dict, category: str = "") -> str:
|
|
48
|
+
"""Calculate a fingerprint for bbox deduplication."""
|
|
49
|
+
if not bbox:
|
|
50
|
+
return f"{category}_empty"
|
|
51
|
+
|
|
52
|
+
# Normalize bbox coordinates
|
|
53
|
+
if 'xmin' in bbox:
|
|
54
|
+
x1, y1, x2, y2 = bbox['xmin'], bbox['ymin'], bbox['xmax'], bbox['ymax']
|
|
55
|
+
else:
|
|
56
|
+
x1, y1, x2, y2 = bbox.get('x1', 0), bbox.get('y1', 0), bbox.get('x2', 0), bbox.get('y2', 0)
|
|
57
|
+
|
|
58
|
+
# Round to reduce minor variations
|
|
59
|
+
x1, y1, x2, y2 = round(x1, 1), round(y1, 1), round(x2, 1), round(y2, 1)
|
|
60
|
+
|
|
61
|
+
return f"{category}_{x1}_{y1}_{x2}_{y2}"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def clean_expired_tracks(track_timestamps: Dict, track_last_seen: Dict,
|
|
65
|
+
current_timestamp: float, expiry_seconds: int):
|
|
66
|
+
"""Clean expired tracks from tracking dictionaries."""
|
|
67
|
+
expired_tracks = []
|
|
68
|
+
|
|
69
|
+
for track_id, last_seen in track_last_seen.items():
|
|
70
|
+
if current_timestamp - last_seen > expiry_seconds:
|
|
71
|
+
expired_tracks.append(track_id)
|
|
72
|
+
|
|
73
|
+
for track_id in expired_tracks:
|
|
74
|
+
track_timestamps.pop(track_id, None)
|
|
75
|
+
track_last_seen.pop(track_id, None)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class CountingLibrary:
|
|
79
|
+
"""Library class for handling object counting operations with time-based tracking."""
|
|
80
|
+
|
|
81
|
+
def __init__(self,
|
|
82
|
+
time_window_seconds: int = 3600,
|
|
83
|
+
track_expiry_seconds: int = 300,
|
|
84
|
+
enable_time_based_counting: bool = True,
|
|
85
|
+
enable_bbox_deduplication: bool = True,
|
|
86
|
+
bbox_similarity_threshold: float = 0.8):
|
|
87
|
+
"""Initialize counting library with configuration."""
|
|
88
|
+
self.zone_counters = defaultdict(int)
|
|
89
|
+
self.dwell_times = defaultdict(dict)
|
|
90
|
+
|
|
91
|
+
# Time-based tracking for incremental counting
|
|
92
|
+
self.unique_tracks_seen = set() # All unique track IDs ever seen
|
|
93
|
+
self.track_timestamps = {} # track_id -> first_seen_timestamp
|
|
94
|
+
self.track_last_seen = {} # track_id -> last_seen_timestamp
|
|
95
|
+
self.zone_unique_tracks = defaultdict(set) # zone_name -> set of unique track IDs
|
|
96
|
+
self.zone_track_timestamps = defaultdict(dict) # zone_name -> {track_id: first_seen}
|
|
97
|
+
|
|
98
|
+
# Bounding box-based deduplication
|
|
99
|
+
self.bbox_fingerprints = {} # track_id -> bbox_fingerprint for deduplication
|
|
100
|
+
self.seen_bbox_fingerprints = set() # Set of all bbox fingerprints seen
|
|
101
|
+
self.category_bbox_fingerprints = defaultdict(set) # category -> set of bbox fingerprints
|
|
102
|
+
|
|
103
|
+
# Configuration
|
|
104
|
+
self.time_window = time_window_seconds
|
|
105
|
+
self.track_expiry_time = track_expiry_seconds
|
|
106
|
+
self.enable_time_based_counting = enable_time_based_counting
|
|
107
|
+
self.bbox_similarity_threshold = bbox_similarity_threshold
|
|
108
|
+
self.enable_bbox_deduplication = enable_bbox_deduplication
|
|
109
|
+
|
|
110
|
+
def set_time_window(self, time_window_seconds: int):
|
|
111
|
+
"""Set the time window for statistics collection."""
|
|
112
|
+
self.time_window = time_window_seconds
|
|
113
|
+
|
|
114
|
+
def count_objects(self, results: Any, identification_keys: List[str] = None,
|
|
115
|
+
current_timestamp: Optional[float] = None) -> Tuple[Any, Dict]:
|
|
116
|
+
"""Count objects with metadata, supporting incremental time-based counting."""
|
|
117
|
+
# Use default identification keys if not provided
|
|
118
|
+
if identification_keys is None:
|
|
119
|
+
identification_keys = ["track_id"]
|
|
120
|
+
|
|
121
|
+
if current_timestamp is None:
|
|
122
|
+
current_timestamp = time.time()
|
|
123
|
+
|
|
124
|
+
# Clean expired tracks if time-based counting is enabled
|
|
125
|
+
if self.enable_time_based_counting:
|
|
126
|
+
clean_expired_tracks(self.track_timestamps, self.track_last_seen,
|
|
127
|
+
current_timestamp, self.track_expiry_time)
|
|
128
|
+
|
|
129
|
+
metadata = {"total": 0, "by_category": defaultdict(int)}
|
|
130
|
+
results_type = match_results_structure(results)
|
|
131
|
+
|
|
132
|
+
if results_type == "detection":
|
|
133
|
+
metadata["total"] = len(results)
|
|
134
|
+
for result in results:
|
|
135
|
+
category = result.get("category", "unknown")
|
|
136
|
+
metadata["by_category"][category] += 1
|
|
137
|
+
|
|
138
|
+
elif results_type == "classification":
|
|
139
|
+
metadata["total"] = len(results)
|
|
140
|
+
|
|
141
|
+
elif results_type == "object_tracking":
|
|
142
|
+
current_unique_tracks = set()
|
|
143
|
+
new_tracks_this_frame = set()
|
|
144
|
+
unique_detections_per_category = defaultdict(set) # For proper category counting
|
|
145
|
+
|
|
146
|
+
# Keep track of processed detections to avoid duplicates
|
|
147
|
+
processed_detections = []
|
|
148
|
+
|
|
149
|
+
for frame_id, detections in results.items():
|
|
150
|
+
if isinstance(detections, list):
|
|
151
|
+
for detection in detections:
|
|
152
|
+
# Skip if this detection is a duplicate of an already processed detection
|
|
153
|
+
if self.enable_bbox_deduplication and self._is_duplicate_detection(detection, processed_detections):
|
|
154
|
+
continue
|
|
155
|
+
|
|
156
|
+
# Add to processed detections to check future duplicates
|
|
157
|
+
processed_detections.append(detection)
|
|
158
|
+
|
|
159
|
+
for key in identification_keys:
|
|
160
|
+
if key in detection:
|
|
161
|
+
track_id = detection[key]
|
|
162
|
+
current_unique_tracks.add(track_id)
|
|
163
|
+
|
|
164
|
+
# Track time-based information
|
|
165
|
+
if self.enable_time_based_counting:
|
|
166
|
+
if track_id not in self.unique_tracks_seen:
|
|
167
|
+
self.unique_tracks_seen.add(track_id)
|
|
168
|
+
self.track_timestamps[track_id] = current_timestamp
|
|
169
|
+
new_tracks_this_frame.add(track_id)
|
|
170
|
+
|
|
171
|
+
# Update last seen time
|
|
172
|
+
self.track_last_seen[track_id] = current_timestamp
|
|
173
|
+
|
|
174
|
+
category = detection.get("category", "unknown")
|
|
175
|
+
|
|
176
|
+
# Use bounding box fingerprint for unique category counting
|
|
177
|
+
if ("bounding_box" in detection or "bbox" in detection) and self.enable_bbox_deduplication:
|
|
178
|
+
bbox = detection.get("bounding_box", detection.get("bbox", {}))
|
|
179
|
+
bbox_fingerprint = calculate_bbox_fingerprint(bbox, category)
|
|
180
|
+
unique_detections_per_category[category].add(bbox_fingerprint)
|
|
181
|
+
else:
|
|
182
|
+
# Fallback to track_id based counting
|
|
183
|
+
unique_detections_per_category[category].add(track_id)
|
|
184
|
+
|
|
185
|
+
break # Only use first matching identification key
|
|
186
|
+
|
|
187
|
+
# Update category counts based on unique detections
|
|
188
|
+
for category, unique_fingerprints in unique_detections_per_category.items():
|
|
189
|
+
metadata["by_category"][category] = len(unique_fingerprints)
|
|
190
|
+
|
|
191
|
+
# Set counts based on counting mode
|
|
192
|
+
if self.enable_time_based_counting:
|
|
193
|
+
metadata["total"] = len(self.unique_tracks_seen)
|
|
194
|
+
metadata["current_frame_unique"] = len(current_unique_tracks)
|
|
195
|
+
metadata["new_tracks_this_frame"] = len(new_tracks_this_frame)
|
|
196
|
+
metadata["total_tracks_in_time_window"] = len(self._get_tracks_in_time_window(current_timestamp))
|
|
197
|
+
else:
|
|
198
|
+
metadata["total"] = len(current_unique_tracks)
|
|
199
|
+
|
|
200
|
+
# Convert defaultdict to regular dict for JSON serialization
|
|
201
|
+
metadata["by_category"] = dict(metadata["by_category"])
|
|
202
|
+
|
|
203
|
+
# Add time-based metadata
|
|
204
|
+
if self.enable_time_based_counting:
|
|
205
|
+
metadata["time_based_counting"] = {
|
|
206
|
+
"enabled": True,
|
|
207
|
+
"time_window_seconds": self.time_window,
|
|
208
|
+
"track_expiry_seconds": self.track_expiry_time,
|
|
209
|
+
"current_timestamp": current_timestamp,
|
|
210
|
+
"active_tracks": len([t for t in self.track_last_seen.values()
|
|
211
|
+
if current_timestamp - t <= self.track_expiry_time])
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return results, metadata
|
|
215
|
+
|
|
216
|
+
def count_in_zones(self, results: Dict, zones: Dict[str, List[Tuple[float, float]]] = None,
|
|
217
|
+
current_timestamp: Optional[float] = None) -> Dict:
|
|
218
|
+
"""Count objects in defined zones with configurable rules and time-based tracking."""
|
|
219
|
+
if zones is None:
|
|
220
|
+
zones = {}
|
|
221
|
+
|
|
222
|
+
if current_timestamp is None:
|
|
223
|
+
current_timestamp = time.time()
|
|
224
|
+
|
|
225
|
+
# Clean expired tracks for each zone
|
|
226
|
+
if self.enable_time_based_counting:
|
|
227
|
+
for zone_name in zones.keys():
|
|
228
|
+
self._clean_expired_zone_tracks(zone_name, current_timestamp)
|
|
229
|
+
|
|
230
|
+
zone_counts = {}
|
|
231
|
+
|
|
232
|
+
for zone_name, zone_polygon in zones.items():
|
|
233
|
+
if zone_name not in self.zone_counters:
|
|
234
|
+
self.zone_counters[zone_name] = 0
|
|
235
|
+
|
|
236
|
+
current_count = 0
|
|
237
|
+
current_frame_tracks = set()
|
|
238
|
+
new_zone_tracks = set()
|
|
239
|
+
|
|
240
|
+
if isinstance(results, dict):
|
|
241
|
+
for frame_id, detections in results.items():
|
|
242
|
+
if isinstance(detections, list):
|
|
243
|
+
for detection in detections:
|
|
244
|
+
if "bounding_box" in detection or "bbox" in detection:
|
|
245
|
+
bbox = detection.get("bounding_box", detection.get("bbox", {}))
|
|
246
|
+
center = get_bbox_center(bbox)
|
|
247
|
+
|
|
248
|
+
if point_in_polygon(center, zone_polygon):
|
|
249
|
+
# Get track ID for uniqueness
|
|
250
|
+
track_id = None
|
|
251
|
+
for key in ["track_id"]: # Default identification key
|
|
252
|
+
if key in detection:
|
|
253
|
+
track_id = detection[key]
|
|
254
|
+
break
|
|
255
|
+
|
|
256
|
+
if track_id is not None:
|
|
257
|
+
current_frame_tracks.add(track_id)
|
|
258
|
+
|
|
259
|
+
# Time-based zone tracking
|
|
260
|
+
if self.enable_time_based_counting:
|
|
261
|
+
if track_id not in self.zone_unique_tracks[zone_name]:
|
|
262
|
+
self.zone_unique_tracks[zone_name].add(track_id)
|
|
263
|
+
self.zone_track_timestamps[zone_name][track_id] = current_timestamp
|
|
264
|
+
new_zone_tracks.add(track_id)
|
|
265
|
+
|
|
266
|
+
current_count += 1
|
|
267
|
+
|
|
268
|
+
# Set zone counts based on counting mode
|
|
269
|
+
if self.enable_time_based_counting:
|
|
270
|
+
zone_tracks_in_window = self._get_zone_tracks_in_time_window(zone_name, current_timestamp)
|
|
271
|
+
zone_counts[zone_name] = {
|
|
272
|
+
"current_frame": len(current_frame_tracks),
|
|
273
|
+
"new_this_frame": len(new_zone_tracks),
|
|
274
|
+
"total_unique": len(self.zone_unique_tracks[zone_name]),
|
|
275
|
+
"in_time_window": len(zone_tracks_in_window),
|
|
276
|
+
"time_window_seconds": self.time_window
|
|
277
|
+
}
|
|
278
|
+
else:
|
|
279
|
+
zone_counts[zone_name] = {
|
|
280
|
+
"current_frame": len(current_frame_tracks),
|
|
281
|
+
"total": len(current_frame_tracks)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return zone_counts
|
|
285
|
+
|
|
286
|
+
def get_unique_count_by_keys(self, results: Any, keys: List[str] = None) -> Dict[str, int]:
|
|
287
|
+
"""Get unique count based on specified keys."""
|
|
288
|
+
if keys is None:
|
|
289
|
+
keys = ["track_id"]
|
|
290
|
+
|
|
291
|
+
unique_values = set()
|
|
292
|
+
results_type = match_results_structure(results)
|
|
293
|
+
|
|
294
|
+
if results_type == "object_tracking":
|
|
295
|
+
for frame_id, detections in results.items():
|
|
296
|
+
if isinstance(detections, list):
|
|
297
|
+
for detection in detections:
|
|
298
|
+
for key in keys:
|
|
299
|
+
if key in detection:
|
|
300
|
+
unique_values.add(detection[key])
|
|
301
|
+
break
|
|
302
|
+
|
|
303
|
+
return {"unique_count": len(unique_values), "keys_used": keys}
|
|
304
|
+
|
|
305
|
+
def get_counting_statistics(self, current_timestamp: Optional[float] = None) -> Dict[str, Any]:
|
|
306
|
+
"""Get comprehensive counting statistics."""
|
|
307
|
+
if current_timestamp is None:
|
|
308
|
+
current_timestamp = time.time()
|
|
309
|
+
|
|
310
|
+
stats = {
|
|
311
|
+
"total_unique_tracks": len(self.unique_tracks_seen),
|
|
312
|
+
"active_tracks": len([t for t in self.track_last_seen.values()
|
|
313
|
+
if current_timestamp - t <= self.track_expiry_time]),
|
|
314
|
+
"tracks_in_time_window": len(self._get_tracks_in_time_window(current_timestamp)),
|
|
315
|
+
"zone_statistics": {},
|
|
316
|
+
"configuration": {
|
|
317
|
+
"time_window_seconds": self.time_window,
|
|
318
|
+
"track_expiry_seconds": self.track_expiry_time,
|
|
319
|
+
"time_based_counting_enabled": self.enable_time_based_counting,
|
|
320
|
+
"bbox_deduplication_enabled": self.enable_bbox_deduplication,
|
|
321
|
+
"bbox_similarity_threshold": self.bbox_similarity_threshold
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
# Add zone statistics
|
|
326
|
+
for zone_name in self.zone_unique_tracks.keys():
|
|
327
|
+
zone_tracks_in_window = self._get_zone_tracks_in_time_window(zone_name, current_timestamp)
|
|
328
|
+
stats["zone_statistics"][zone_name] = {
|
|
329
|
+
"total_unique_tracks": len(self.zone_unique_tracks[zone_name]),
|
|
330
|
+
"tracks_in_time_window": len(zone_tracks_in_window)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return stats
|
|
334
|
+
|
|
335
|
+
def reset_counters(self, reset_zones: bool = True, reset_time_tracking: bool = True):
|
|
336
|
+
"""Reset counting state."""
|
|
337
|
+
if reset_zones:
|
|
338
|
+
self.zone_counters.clear()
|
|
339
|
+
self.dwell_times.clear()
|
|
340
|
+
self.zone_unique_tracks.clear()
|
|
341
|
+
self.zone_track_timestamps.clear()
|
|
342
|
+
|
|
343
|
+
if reset_time_tracking:
|
|
344
|
+
self.unique_tracks_seen.clear()
|
|
345
|
+
self.track_timestamps.clear()
|
|
346
|
+
self.track_last_seen.clear()
|
|
347
|
+
self.bbox_fingerprints.clear()
|
|
348
|
+
self.seen_bbox_fingerprints.clear()
|
|
349
|
+
self.category_bbox_fingerprints.clear()
|
|
350
|
+
|
|
351
|
+
def _get_tracks_in_time_window(self, current_timestamp: float) -> Set[str]:
|
|
352
|
+
"""Get tracks that were first seen within the time window."""
|
|
353
|
+
cutoff_time = current_timestamp - self.time_window
|
|
354
|
+
return {track_id for track_id, first_seen in self.track_timestamps.items()
|
|
355
|
+
if first_seen >= cutoff_time}
|
|
356
|
+
|
|
357
|
+
def _get_zone_tracks_in_time_window(self, zone_name: str, current_timestamp: float) -> Set[str]:
|
|
358
|
+
"""Get zone tracks that were first seen within the time window."""
|
|
359
|
+
cutoff_time = current_timestamp - self.time_window
|
|
360
|
+
zone_timestamps = self.zone_track_timestamps.get(zone_name, {})
|
|
361
|
+
return {track_id for track_id, first_seen in zone_timestamps.items()
|
|
362
|
+
if first_seen >= cutoff_time}
|
|
363
|
+
|
|
364
|
+
def _clean_expired_zone_tracks(self, zone_name: str, current_timestamp: float):
|
|
365
|
+
"""Clean expired tracks from zone tracking."""
|
|
366
|
+
if zone_name not in self.zone_track_timestamps:
|
|
367
|
+
return
|
|
368
|
+
|
|
369
|
+
expired_tracks = []
|
|
370
|
+
zone_timestamps = self.zone_track_timestamps[zone_name]
|
|
371
|
+
|
|
372
|
+
for track_id, first_seen in zone_timestamps.items():
|
|
373
|
+
if current_timestamp - first_seen > self.track_expiry_time:
|
|
374
|
+
expired_tracks.append(track_id)
|
|
375
|
+
|
|
376
|
+
for track_id in expired_tracks:
|
|
377
|
+
self.zone_unique_tracks[zone_name].discard(track_id)
|
|
378
|
+
self.zone_track_timestamps[zone_name].pop(track_id, None)
|
|
379
|
+
|
|
380
|
+
def _is_duplicate_detection(self, detection: Dict[str, Any], current_detections: List[Dict[str, Any]]) -> bool:
|
|
381
|
+
"""Check if detection is a duplicate based on bbox similarity."""
|
|
382
|
+
if not self.enable_bbox_deduplication:
|
|
383
|
+
return False
|
|
384
|
+
|
|
385
|
+
detection_bbox = detection.get("bounding_box", detection.get("bbox"))
|
|
386
|
+
if not detection_bbox:
|
|
387
|
+
return False
|
|
388
|
+
|
|
389
|
+
detection_category = detection.get("category", "unknown")
|
|
390
|
+
|
|
391
|
+
for existing_detection in current_detections:
|
|
392
|
+
existing_bbox = existing_detection.get("bounding_box", existing_detection.get("bbox"))
|
|
393
|
+
existing_category = existing_detection.get("category", "unknown")
|
|
394
|
+
|
|
395
|
+
if existing_bbox and detection_category == existing_category:
|
|
396
|
+
overlap = calculate_bbox_overlap(detection_bbox, existing_bbox)
|
|
397
|
+
if overlap >= self.bbox_similarity_threshold:
|
|
398
|
+
return True
|
|
399
|
+
|
|
400
|
+
return False
|