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,317 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Advanced helper utilities for specialized post-processing operations.
|
|
3
|
+
These functions provide advanced image/video processing and tracking utilities
|
|
4
|
+
not available in the basic refactored system.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import time
|
|
8
|
+
import math
|
|
9
|
+
import io
|
|
10
|
+
import numpy as np
|
|
11
|
+
from typing import List, Dict, Any, Tuple, Optional, Set
|
|
12
|
+
from collections import defaultdict
|
|
13
|
+
|
|
14
|
+
# Try to import optional dependencies
|
|
15
|
+
try:
|
|
16
|
+
from PIL import Image
|
|
17
|
+
PIL_AVAILABLE = True
|
|
18
|
+
except ImportError:
|
|
19
|
+
PIL_AVAILABLE = False
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
import cv2
|
|
23
|
+
CV2_AVAILABLE = True
|
|
24
|
+
except ImportError:
|
|
25
|
+
CV2_AVAILABLE = False
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def line_segments_intersect(p1: Tuple[float, float], p2: Tuple[float, float],
|
|
29
|
+
p3: Tuple[float, float], p4: Tuple[float, float]) -> bool:
|
|
30
|
+
"""Check if two line segments intersect."""
|
|
31
|
+
def ccw(A, B, C):
|
|
32
|
+
return (C[1] - A[1]) * (B[0] - A[0]) > (B[1] - A[1]) * (C[0] - A[0])
|
|
33
|
+
|
|
34
|
+
return ccw(p1, p3, p4) != ccw(p2, p3, p4) and ccw(p1, p2, p3) != ccw(p1, p2, p4)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def calculate_bbox_fingerprint(bbox: Dict[str, float], category: str = "unknown") -> str:
|
|
38
|
+
"""Generate a fingerprint for bbox deduplication."""
|
|
39
|
+
if not bbox:
|
|
40
|
+
return f"{category}_empty"
|
|
41
|
+
|
|
42
|
+
# Normalize bbox format
|
|
43
|
+
if "xmin" in bbox:
|
|
44
|
+
x1, y1, x2, y2 = bbox["xmin"], bbox["ymin"], bbox["xmax"], bbox["ymax"]
|
|
45
|
+
elif "x1" in bbox:
|
|
46
|
+
x1, y1, x2, y2 = bbox["x1"], bbox["y1"], bbox["x2"], bbox["y2"]
|
|
47
|
+
else:
|
|
48
|
+
values = list(bbox.values())
|
|
49
|
+
x1, y1, x2, y2 = values[0], values[1], values[2], values[3]
|
|
50
|
+
|
|
51
|
+
# Round to reduce minor variations
|
|
52
|
+
x1, y1, x2, y2 = round(x1, 1), round(y1, 1), round(x2, 1), round(y2, 1)
|
|
53
|
+
|
|
54
|
+
return f"{category}_{x1}_{y1}_{x2}_{y2}"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def clean_expired_tracks(track_timestamps: Dict, track_last_seen: Dict,
|
|
58
|
+
current_timestamp: float, expiry_time: float) -> None:
|
|
59
|
+
"""Clean expired tracks from tracking dictionaries."""
|
|
60
|
+
expired_tracks = []
|
|
61
|
+
|
|
62
|
+
for track_id, last_seen in track_last_seen.items():
|
|
63
|
+
if current_timestamp - last_seen > expiry_time:
|
|
64
|
+
expired_tracks.append(track_id)
|
|
65
|
+
|
|
66
|
+
for track_id in expired_tracks:
|
|
67
|
+
track_timestamps.pop(track_id, None)
|
|
68
|
+
track_last_seen.pop(track_id, None)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def generate_summary_statistics(data: Dict[str, Any]) -> Dict[str, Any]:
|
|
72
|
+
"""Generate comprehensive summary statistics from tracking data."""
|
|
73
|
+
summary = {
|
|
74
|
+
"total_objects": 0,
|
|
75
|
+
"objects_by_category": defaultdict(int),
|
|
76
|
+
"unique_tracks": set(),
|
|
77
|
+
"tracks_by_category": defaultdict(set),
|
|
78
|
+
"frame_count": 0,
|
|
79
|
+
"time_range": {"start": None, "end": None},
|
|
80
|
+
"activity_periods": []
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if isinstance(data, dict):
|
|
84
|
+
for frame_id, detections in data.items():
|
|
85
|
+
if isinstance(detections, list):
|
|
86
|
+
summary["frame_count"] += 1
|
|
87
|
+
frame_time = None
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
frame_time = float(frame_id)
|
|
91
|
+
if summary["time_range"]["start"] is None or frame_time < summary["time_range"]["start"]:
|
|
92
|
+
summary["time_range"]["start"] = frame_time
|
|
93
|
+
if summary["time_range"]["end"] is None or frame_time > summary["time_range"]["end"]:
|
|
94
|
+
summary["time_range"]["end"] = frame_time
|
|
95
|
+
except:
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
for detection in detections:
|
|
99
|
+
summary["total_objects"] += 1
|
|
100
|
+
category = detection.get("category", "unknown")
|
|
101
|
+
summary["objects_by_category"][category] += 1
|
|
102
|
+
|
|
103
|
+
if "track_id" in detection:
|
|
104
|
+
track_id = detection["track_id"]
|
|
105
|
+
summary["unique_tracks"].add(track_id)
|
|
106
|
+
summary["tracks_by_category"][category].add(track_id)
|
|
107
|
+
|
|
108
|
+
# Convert sets to counts for JSON serialization
|
|
109
|
+
summary["unique_tracks"] = len(summary["unique_tracks"])
|
|
110
|
+
summary["tracks_by_category"] = {k: len(v) for k, v in summary["tracks_by_category"].items()}
|
|
111
|
+
summary["objects_by_category"] = dict(summary["objects_by_category"])
|
|
112
|
+
|
|
113
|
+
return summary
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def bytes_to_image(image_bytes: bytes, return_format: str = "pil") -> Optional[Any]:
|
|
117
|
+
"""Convert image bytes to PIL Image or numpy array."""
|
|
118
|
+
if not image_bytes:
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
if return_format.lower() == "pil":
|
|
123
|
+
if not PIL_AVAILABLE:
|
|
124
|
+
raise ImportError("PIL is required for PIL format. Install with: pip install Pillow")
|
|
125
|
+
return Image.open(io.BytesIO(image_bytes))
|
|
126
|
+
|
|
127
|
+
elif return_format.lower() == "cv2":
|
|
128
|
+
if not CV2_AVAILABLE:
|
|
129
|
+
raise ImportError("OpenCV is required for CV2 format. Install with: pip install opencv-python")
|
|
130
|
+
|
|
131
|
+
# Convert bytes to numpy array
|
|
132
|
+
nparr = np.frombuffer(image_bytes, np.uint8)
|
|
133
|
+
# Decode image
|
|
134
|
+
img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
|
|
135
|
+
return img
|
|
136
|
+
|
|
137
|
+
elif return_format.lower() == "numpy":
|
|
138
|
+
if PIL_AVAILABLE:
|
|
139
|
+
pil_img = Image.open(io.BytesIO(image_bytes))
|
|
140
|
+
return np.array(pil_img)
|
|
141
|
+
else:
|
|
142
|
+
raise ImportError("PIL is required for numpy conversion. Install with: pip install Pillow")
|
|
143
|
+
|
|
144
|
+
else:
|
|
145
|
+
raise ValueError(f"Unsupported return format: {return_format}. Use 'pil', 'cv2', or 'numpy'")
|
|
146
|
+
|
|
147
|
+
except Exception as e:
|
|
148
|
+
print(f"Error converting image bytes: {e}")
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def bytes_to_video_frame(video_bytes: bytes, frame_number: int = 0, return_format: str = "cv2") -> Optional[Any]:
|
|
153
|
+
"""Extract a specific frame from video bytes."""
|
|
154
|
+
if not video_bytes or not CV2_AVAILABLE:
|
|
155
|
+
if not CV2_AVAILABLE:
|
|
156
|
+
raise ImportError("OpenCV is required for video processing. Install with: pip install opencv-python")
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
# Create temporary file in memory
|
|
161
|
+
import tempfile
|
|
162
|
+
import os
|
|
163
|
+
|
|
164
|
+
with tempfile.NamedTemporaryFile(suffix='.mp4', delete=False) as temp_file:
|
|
165
|
+
temp_file.write(video_bytes)
|
|
166
|
+
temp_path = temp_file.name
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
# Open video
|
|
170
|
+
cap = cv2.VideoCapture(temp_path)
|
|
171
|
+
|
|
172
|
+
# Set frame position
|
|
173
|
+
cap.set(cv2.CAP_PROP_POS_FRAMES, frame_number)
|
|
174
|
+
|
|
175
|
+
# Read frame
|
|
176
|
+
ret, frame = cap.read()
|
|
177
|
+
cap.release()
|
|
178
|
+
|
|
179
|
+
if not ret:
|
|
180
|
+
return None
|
|
181
|
+
|
|
182
|
+
if return_format.lower() == "cv2":
|
|
183
|
+
return frame
|
|
184
|
+
elif return_format.lower() == "pil":
|
|
185
|
+
if not PIL_AVAILABLE:
|
|
186
|
+
raise ImportError("PIL is required for PIL format. Install with: pip install Pillow")
|
|
187
|
+
# Convert BGR to RGB for PIL
|
|
188
|
+
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
|
189
|
+
return Image.fromarray(rgb_frame)
|
|
190
|
+
elif return_format.lower() == "numpy":
|
|
191
|
+
return frame
|
|
192
|
+
else:
|
|
193
|
+
raise ValueError(f"Unsupported return format: {return_format}")
|
|
194
|
+
|
|
195
|
+
finally:
|
|
196
|
+
# Clean up temporary file
|
|
197
|
+
if os.path.exists(temp_path):
|
|
198
|
+
os.unlink(temp_path)
|
|
199
|
+
|
|
200
|
+
except Exception as e:
|
|
201
|
+
print(f"Error extracting video frame: {e}")
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def get_image_dimensions(image_bytes: bytes) -> Optional[Tuple[int, int]]:
|
|
206
|
+
"""Get image dimensions (width, height) from image bytes."""
|
|
207
|
+
if not image_bytes:
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
try:
|
|
211
|
+
if PIL_AVAILABLE:
|
|
212
|
+
img = Image.open(io.BytesIO(image_bytes))
|
|
213
|
+
return img.size # PIL returns (width, height)
|
|
214
|
+
elif CV2_AVAILABLE:
|
|
215
|
+
nparr = np.frombuffer(image_bytes, np.uint8)
|
|
216
|
+
img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
|
|
217
|
+
if img is not None:
|
|
218
|
+
height, width = img.shape[:2]
|
|
219
|
+
return (width, height)
|
|
220
|
+
else:
|
|
221
|
+
raise ImportError("Either PIL or OpenCV is required. Install with: pip install Pillow opencv-python")
|
|
222
|
+
|
|
223
|
+
except Exception as e:
|
|
224
|
+
print(f"Error getting image dimensions: {e}")
|
|
225
|
+
|
|
226
|
+
return None
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def is_valid_image_bytes(image_bytes: bytes) -> bool:
|
|
230
|
+
"""Check if bytes represent a valid image."""
|
|
231
|
+
if not image_bytes:
|
|
232
|
+
return False
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
if PIL_AVAILABLE:
|
|
236
|
+
Image.open(io.BytesIO(image_bytes))
|
|
237
|
+
return True
|
|
238
|
+
elif CV2_AVAILABLE:
|
|
239
|
+
nparr = np.frombuffer(image_bytes, np.uint8)
|
|
240
|
+
img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
|
|
241
|
+
return img is not None
|
|
242
|
+
else:
|
|
243
|
+
# Basic check for common image headers
|
|
244
|
+
if image_bytes.startswith(b'\xff\xd8\xff'): # JPEG
|
|
245
|
+
return True
|
|
246
|
+
elif image_bytes.startswith(b'\x89PNG\r\n\x1a\n'): # PNG
|
|
247
|
+
return True
|
|
248
|
+
elif image_bytes.startswith(b'GIF87a') or image_bytes.startswith(b'GIF89a'): # GIF
|
|
249
|
+
return True
|
|
250
|
+
elif image_bytes.startswith(b'BM'): # BMP
|
|
251
|
+
return True
|
|
252
|
+
return False
|
|
253
|
+
|
|
254
|
+
except Exception:
|
|
255
|
+
return False
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def get_image_format(image_bytes: bytes) -> Optional[str]:
|
|
259
|
+
"""Detect image format from bytes."""
|
|
260
|
+
if not image_bytes:
|
|
261
|
+
return None
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
if PIL_AVAILABLE:
|
|
265
|
+
img = Image.open(io.BytesIO(image_bytes))
|
|
266
|
+
return img.format.lower() if img.format else None
|
|
267
|
+
else:
|
|
268
|
+
# Basic format detection
|
|
269
|
+
if image_bytes.startswith(b'\xff\xd8\xff'):
|
|
270
|
+
return 'jpeg'
|
|
271
|
+
elif image_bytes.startswith(b'\x89PNG\r\n\x1a\n'):
|
|
272
|
+
return 'png'
|
|
273
|
+
elif image_bytes.startswith(b'GIF87a') or image_bytes.startswith(b'GIF89a'):
|
|
274
|
+
return 'gif'
|
|
275
|
+
elif image_bytes.startswith(b'BM'):
|
|
276
|
+
return 'bmp'
|
|
277
|
+
elif image_bytes.startswith(b'RIFF') and b'WEBP' in image_bytes[:12]:
|
|
278
|
+
return 'webp'
|
|
279
|
+
return None
|
|
280
|
+
|
|
281
|
+
except Exception:
|
|
282
|
+
return None
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def convert_detection_to_tracking_format(detections: List[Dict], frame_id: str = "0") -> Dict:
|
|
286
|
+
"""Convert detection format to tracking format."""
|
|
287
|
+
tracking_results = {frame_id: []}
|
|
288
|
+
|
|
289
|
+
for detection in detections:
|
|
290
|
+
tracking_detection = {
|
|
291
|
+
"track_id": detection.get("track_id", 0),
|
|
292
|
+
"category": detection.get("category", "unknown"),
|
|
293
|
+
"confidence": detection.get("confidence", 0.0),
|
|
294
|
+
"bounding_box": detection.get("bounding_box", detection.get("bbox", {}))
|
|
295
|
+
}
|
|
296
|
+
tracking_results[frame_id].append(tracking_detection)
|
|
297
|
+
|
|
298
|
+
return tracking_results
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def convert_tracking_to_detection_format(tracking_results: Dict) -> List[Dict]:
|
|
302
|
+
"""Convert tracking format to detection format."""
|
|
303
|
+
detections = []
|
|
304
|
+
|
|
305
|
+
for frame_id, frame_detections in tracking_results.items():
|
|
306
|
+
if isinstance(frame_detections, list):
|
|
307
|
+
for detection in frame_detections:
|
|
308
|
+
detection_item = {
|
|
309
|
+
"category": detection.get("category", "unknown"),
|
|
310
|
+
"confidence": detection.get("confidence", 0.0),
|
|
311
|
+
"bounding_box": detection.get("bounding_box", detection.get("bbox", {}))
|
|
312
|
+
}
|
|
313
|
+
if "track_id" in detection:
|
|
314
|
+
detection_item["track_id"] = detection["track_id"]
|
|
315
|
+
detections.append(detection_item)
|
|
316
|
+
|
|
317
|
+
return detections
|