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.
Files changed (196) hide show
  1. matrice_analytics/__init__.py +28 -0
  2. matrice_analytics/boundary_drawing_internal/README.md +305 -0
  3. matrice_analytics/boundary_drawing_internal/__init__.py +45 -0
  4. matrice_analytics/boundary_drawing_internal/boundary_drawing_internal.py +1207 -0
  5. matrice_analytics/boundary_drawing_internal/boundary_drawing_tool.py +429 -0
  6. matrice_analytics/boundary_drawing_internal/boundary_tool_template.html +1036 -0
  7. matrice_analytics/boundary_drawing_internal/data/.gitignore +12 -0
  8. matrice_analytics/boundary_drawing_internal/example_usage.py +206 -0
  9. matrice_analytics/boundary_drawing_internal/usage/README.md +110 -0
  10. matrice_analytics/boundary_drawing_internal/usage/boundary_drawer_launcher.py +102 -0
  11. matrice_analytics/boundary_drawing_internal/usage/simple_boundary_launcher.py +107 -0
  12. matrice_analytics/post_processing/README.md +455 -0
  13. matrice_analytics/post_processing/__init__.py +732 -0
  14. matrice_analytics/post_processing/advanced_tracker/README.md +650 -0
  15. matrice_analytics/post_processing/advanced_tracker/__init__.py +17 -0
  16. matrice_analytics/post_processing/advanced_tracker/base.py +99 -0
  17. matrice_analytics/post_processing/advanced_tracker/config.py +77 -0
  18. matrice_analytics/post_processing/advanced_tracker/kalman_filter.py +370 -0
  19. matrice_analytics/post_processing/advanced_tracker/matching.py +195 -0
  20. matrice_analytics/post_processing/advanced_tracker/strack.py +230 -0
  21. matrice_analytics/post_processing/advanced_tracker/tracker.py +367 -0
  22. matrice_analytics/post_processing/config.py +146 -0
  23. matrice_analytics/post_processing/core/__init__.py +63 -0
  24. matrice_analytics/post_processing/core/base.py +704 -0
  25. matrice_analytics/post_processing/core/config.py +3291 -0
  26. matrice_analytics/post_processing/core/config_utils.py +925 -0
  27. matrice_analytics/post_processing/face_reg/__init__.py +43 -0
  28. matrice_analytics/post_processing/face_reg/compare_similarity.py +556 -0
  29. matrice_analytics/post_processing/face_reg/embedding_manager.py +950 -0
  30. matrice_analytics/post_processing/face_reg/face_recognition.py +2234 -0
  31. matrice_analytics/post_processing/face_reg/face_recognition_client.py +606 -0
  32. matrice_analytics/post_processing/face_reg/people_activity_logging.py +321 -0
  33. matrice_analytics/post_processing/ocr/__init__.py +0 -0
  34. matrice_analytics/post_processing/ocr/easyocr_extractor.py +250 -0
  35. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/__init__.py +9 -0
  36. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/__init__.py +4 -0
  37. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/cli.py +33 -0
  38. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/dataset_stats.py +139 -0
  39. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/export.py +398 -0
  40. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/train.py +447 -0
  41. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/utils.py +129 -0
  42. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/valid.py +93 -0
  43. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/validate_dataset.py +240 -0
  44. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_augmentation.py +176 -0
  45. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_predictions.py +96 -0
  46. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/__init__.py +3 -0
  47. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/process.py +246 -0
  48. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/types.py +60 -0
  49. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/utils.py +87 -0
  50. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/__init__.py +3 -0
  51. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/config.py +82 -0
  52. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/hub.py +141 -0
  53. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/plate_recognizer.py +323 -0
  54. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/py.typed +0 -0
  55. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/__init__.py +0 -0
  56. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/__init__.py +0 -0
  57. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/augmentation.py +101 -0
  58. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/dataset.py +97 -0
  59. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/__init__.py +0 -0
  60. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/config.py +114 -0
  61. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/layers.py +553 -0
  62. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/loss.py +55 -0
  63. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/metric.py +86 -0
  64. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_builders.py +95 -0
  65. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_schema.py +395 -0
  66. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/__init__.py +0 -0
  67. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/backend_utils.py +38 -0
  68. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/utils.py +214 -0
  69. matrice_analytics/post_processing/ocr/postprocessing.py +270 -0
  70. matrice_analytics/post_processing/ocr/preprocessing.py +52 -0
  71. matrice_analytics/post_processing/post_processor.py +1175 -0
  72. matrice_analytics/post_processing/test_cases/__init__.py +1 -0
  73. matrice_analytics/post_processing/test_cases/run_tests.py +143 -0
  74. matrice_analytics/post_processing/test_cases/test_advanced_customer_service.py +841 -0
  75. matrice_analytics/post_processing/test_cases/test_basic_counting_tracking.py +523 -0
  76. matrice_analytics/post_processing/test_cases/test_comprehensive.py +531 -0
  77. matrice_analytics/post_processing/test_cases/test_config.py +852 -0
  78. matrice_analytics/post_processing/test_cases/test_customer_service.py +585 -0
  79. matrice_analytics/post_processing/test_cases/test_data_generators.py +583 -0
  80. matrice_analytics/post_processing/test_cases/test_people_counting.py +510 -0
  81. matrice_analytics/post_processing/test_cases/test_processor.py +524 -0
  82. matrice_analytics/post_processing/test_cases/test_usecases.py +165 -0
  83. matrice_analytics/post_processing/test_cases/test_utilities.py +356 -0
  84. matrice_analytics/post_processing/test_cases/test_utils.py +743 -0
  85. matrice_analytics/post_processing/usecases/Histopathological_Cancer_Detection_img.py +604 -0
  86. matrice_analytics/post_processing/usecases/__init__.py +267 -0
  87. matrice_analytics/post_processing/usecases/abandoned_object_detection.py +797 -0
  88. matrice_analytics/post_processing/usecases/advanced_customer_service.py +1601 -0
  89. matrice_analytics/post_processing/usecases/age_detection.py +842 -0
  90. matrice_analytics/post_processing/usecases/age_gender_detection.py +1085 -0
  91. matrice_analytics/post_processing/usecases/anti_spoofing_detection.py +656 -0
  92. matrice_analytics/post_processing/usecases/assembly_line_detection.py +841 -0
  93. matrice_analytics/post_processing/usecases/banana_defect_detection.py +624 -0
  94. matrice_analytics/post_processing/usecases/basic_counting_tracking.py +667 -0
  95. matrice_analytics/post_processing/usecases/blood_cancer_detection_img.py +881 -0
  96. matrice_analytics/post_processing/usecases/car_damage_detection.py +834 -0
  97. matrice_analytics/post_processing/usecases/car_part_segmentation.py +946 -0
  98. matrice_analytics/post_processing/usecases/car_service.py +1601 -0
  99. matrice_analytics/post_processing/usecases/cardiomegaly_classification.py +864 -0
  100. matrice_analytics/post_processing/usecases/cell_microscopy_segmentation.py +897 -0
  101. matrice_analytics/post_processing/usecases/chicken_pose_detection.py +648 -0
  102. matrice_analytics/post_processing/usecases/child_monitoring.py +814 -0
  103. matrice_analytics/post_processing/usecases/color/clip.py +660 -0
  104. matrice_analytics/post_processing/usecases/color/clip_processor/merges.txt +48895 -0
  105. matrice_analytics/post_processing/usecases/color/clip_processor/preprocessor_config.json +28 -0
  106. matrice_analytics/post_processing/usecases/color/clip_processor/special_tokens_map.json +30 -0
  107. matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer.json +245079 -0
  108. matrice_analytics/post_processing/usecases/color/clip_processor/tokenizer_config.json +32 -0
  109. matrice_analytics/post_processing/usecases/color/clip_processor/vocab.json +1 -0
  110. matrice_analytics/post_processing/usecases/color/color_map_utils.py +70 -0
  111. matrice_analytics/post_processing/usecases/color/color_mapper.py +468 -0
  112. matrice_analytics/post_processing/usecases/color_detection.py +1936 -0
  113. matrice_analytics/post_processing/usecases/color_map_utils.py +70 -0
  114. matrice_analytics/post_processing/usecases/concrete_crack_detection.py +827 -0
  115. matrice_analytics/post_processing/usecases/crop_weed_detection.py +781 -0
  116. matrice_analytics/post_processing/usecases/customer_service.py +1008 -0
  117. matrice_analytics/post_processing/usecases/defect_detection_products.py +936 -0
  118. matrice_analytics/post_processing/usecases/distracted_driver_detection.py +822 -0
  119. matrice_analytics/post_processing/usecases/drone_traffic_monitoring.py +585 -0
  120. matrice_analytics/post_processing/usecases/drowsy_driver_detection.py +829 -0
  121. matrice_analytics/post_processing/usecases/dwell_detection.py +829 -0
  122. matrice_analytics/post_processing/usecases/emergency_vehicle_detection.py +827 -0
  123. matrice_analytics/post_processing/usecases/face_emotion.py +813 -0
  124. matrice_analytics/post_processing/usecases/face_recognition.py +827 -0
  125. matrice_analytics/post_processing/usecases/fashion_detection.py +835 -0
  126. matrice_analytics/post_processing/usecases/field_mapping.py +902 -0
  127. matrice_analytics/post_processing/usecases/fire_detection.py +1146 -0
  128. matrice_analytics/post_processing/usecases/flare_analysis.py +836 -0
  129. matrice_analytics/post_processing/usecases/flower_segmentation.py +1006 -0
  130. matrice_analytics/post_processing/usecases/gas_leak_detection.py +837 -0
  131. matrice_analytics/post_processing/usecases/gender_detection.py +832 -0
  132. matrice_analytics/post_processing/usecases/human_activity_recognition.py +871 -0
  133. matrice_analytics/post_processing/usecases/intrusion_detection.py +1672 -0
  134. matrice_analytics/post_processing/usecases/leaf.py +821 -0
  135. matrice_analytics/post_processing/usecases/leaf_disease.py +840 -0
  136. matrice_analytics/post_processing/usecases/leak_detection.py +837 -0
  137. matrice_analytics/post_processing/usecases/license_plate_detection.py +1188 -0
  138. matrice_analytics/post_processing/usecases/license_plate_monitoring.py +1781 -0
  139. matrice_analytics/post_processing/usecases/litter_monitoring.py +717 -0
  140. matrice_analytics/post_processing/usecases/mask_detection.py +869 -0
  141. matrice_analytics/post_processing/usecases/natural_disaster.py +907 -0
  142. matrice_analytics/post_processing/usecases/parking.py +787 -0
  143. matrice_analytics/post_processing/usecases/parking_space_detection.py +822 -0
  144. matrice_analytics/post_processing/usecases/pcb_defect_detection.py +888 -0
  145. matrice_analytics/post_processing/usecases/pedestrian_detection.py +808 -0
  146. matrice_analytics/post_processing/usecases/people_counting.py +706 -0
  147. matrice_analytics/post_processing/usecases/people_counting_bckp.py +1683 -0
  148. matrice_analytics/post_processing/usecases/people_tracking.py +1842 -0
  149. matrice_analytics/post_processing/usecases/pipeline_detection.py +605 -0
  150. matrice_analytics/post_processing/usecases/plaque_segmentation_img.py +874 -0
  151. matrice_analytics/post_processing/usecases/pothole_segmentation.py +915 -0
  152. matrice_analytics/post_processing/usecases/ppe_compliance.py +645 -0
  153. matrice_analytics/post_processing/usecases/price_tag_detection.py +822 -0
  154. matrice_analytics/post_processing/usecases/proximity_detection.py +1901 -0
  155. matrice_analytics/post_processing/usecases/road_lane_detection.py +623 -0
  156. matrice_analytics/post_processing/usecases/road_traffic_density.py +832 -0
  157. matrice_analytics/post_processing/usecases/road_view_segmentation.py +915 -0
  158. matrice_analytics/post_processing/usecases/shelf_inventory_detection.py +583 -0
  159. matrice_analytics/post_processing/usecases/shoplifting_detection.py +822 -0
  160. matrice_analytics/post_processing/usecases/shopping_cart_analysis.py +899 -0
  161. matrice_analytics/post_processing/usecases/skin_cancer_classification_img.py +864 -0
  162. matrice_analytics/post_processing/usecases/smoker_detection.py +833 -0
  163. matrice_analytics/post_processing/usecases/solar_panel.py +810 -0
  164. matrice_analytics/post_processing/usecases/suspicious_activity_detection.py +1030 -0
  165. matrice_analytics/post_processing/usecases/template_usecase.py +380 -0
  166. matrice_analytics/post_processing/usecases/theft_detection.py +648 -0
  167. matrice_analytics/post_processing/usecases/traffic_sign_monitoring.py +724 -0
  168. matrice_analytics/post_processing/usecases/underground_pipeline_defect_detection.py +775 -0
  169. matrice_analytics/post_processing/usecases/underwater_pollution_detection.py +842 -0
  170. matrice_analytics/post_processing/usecases/vehicle_monitoring.py +1029 -0
  171. matrice_analytics/post_processing/usecases/warehouse_object_segmentation.py +899 -0
  172. matrice_analytics/post_processing/usecases/waterbody_segmentation.py +923 -0
  173. matrice_analytics/post_processing/usecases/weapon_detection.py +771 -0
  174. matrice_analytics/post_processing/usecases/weld_defect_detection.py +615 -0
  175. matrice_analytics/post_processing/usecases/wildlife_monitoring.py +898 -0
  176. matrice_analytics/post_processing/usecases/windmill_maintenance.py +834 -0
  177. matrice_analytics/post_processing/usecases/wound_segmentation.py +856 -0
  178. matrice_analytics/post_processing/utils/__init__.py +150 -0
  179. matrice_analytics/post_processing/utils/advanced_counting_utils.py +400 -0
  180. matrice_analytics/post_processing/utils/advanced_helper_utils.py +317 -0
  181. matrice_analytics/post_processing/utils/advanced_tracking_utils.py +461 -0
  182. matrice_analytics/post_processing/utils/alerting_utils.py +213 -0
  183. matrice_analytics/post_processing/utils/category_mapping_utils.py +94 -0
  184. matrice_analytics/post_processing/utils/color_utils.py +592 -0
  185. matrice_analytics/post_processing/utils/counting_utils.py +182 -0
  186. matrice_analytics/post_processing/utils/filter_utils.py +261 -0
  187. matrice_analytics/post_processing/utils/format_utils.py +293 -0
  188. matrice_analytics/post_processing/utils/geometry_utils.py +300 -0
  189. matrice_analytics/post_processing/utils/smoothing_utils.py +358 -0
  190. matrice_analytics/post_processing/utils/tracking_utils.py +234 -0
  191. matrice_analytics/py.typed +0 -0
  192. matrice_analytics-0.1.60.dist-info/METADATA +481 -0
  193. matrice_analytics-0.1.60.dist-info/RECORD +196 -0
  194. matrice_analytics-0.1.60.dist-info/WHEEL +5 -0
  195. matrice_analytics-0.1.60.dist-info/licenses/LICENSE.txt +21 -0
  196. matrice_analytics-0.1.60.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