matrice-analytics 0.1.2__py3-none-any.whl → 0.1.31__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.

Files changed (59) hide show
  1. matrice_analytics/post_processing/advanced_tracker/matching.py +3 -3
  2. matrice_analytics/post_processing/advanced_tracker/strack.py +1 -1
  3. matrice_analytics/post_processing/face_reg/compare_similarity.py +5 -5
  4. matrice_analytics/post_processing/face_reg/embedding_manager.py +14 -7
  5. matrice_analytics/post_processing/face_reg/face_recognition.py +123 -34
  6. matrice_analytics/post_processing/face_reg/face_recognition_client.py +332 -82
  7. matrice_analytics/post_processing/face_reg/people_activity_logging.py +29 -22
  8. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/__init__.py +9 -0
  9. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/__init__.py +4 -0
  10. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/cli.py +33 -0
  11. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/dataset_stats.py +139 -0
  12. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/export.py +398 -0
  13. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/train.py +447 -0
  14. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/utils.py +129 -0
  15. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/valid.py +93 -0
  16. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/validate_dataset.py +240 -0
  17. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_augmentation.py +176 -0
  18. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/cli/visualize_predictions.py +96 -0
  19. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/__init__.py +3 -0
  20. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/process.py +246 -0
  21. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/types.py +60 -0
  22. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/core/utils.py +87 -0
  23. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/__init__.py +3 -0
  24. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/config.py +82 -0
  25. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/hub.py +141 -0
  26. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/inference/plate_recognizer.py +323 -0
  27. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/py.typed +0 -0
  28. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/__init__.py +0 -0
  29. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/__init__.py +0 -0
  30. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/augmentation.py +101 -0
  31. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/data/dataset.py +97 -0
  32. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/__init__.py +0 -0
  33. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/config.py +114 -0
  34. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/layers.py +553 -0
  35. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/loss.py +55 -0
  36. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/metric.py +86 -0
  37. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_builders.py +95 -0
  38. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/model/model_schema.py +395 -0
  39. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/__init__.py +0 -0
  40. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/backend_utils.py +38 -0
  41. matrice_analytics/post_processing/ocr/fast_plate_ocr_py38/train/utilities/utils.py +214 -0
  42. matrice_analytics/post_processing/ocr/postprocessing.py +0 -1
  43. matrice_analytics/post_processing/post_processor.py +19 -5
  44. matrice_analytics/post_processing/usecases/color/clip.py +292 -132
  45. matrice_analytics/post_processing/usecases/color/color_mapper.py +2 -2
  46. matrice_analytics/post_processing/usecases/color_detection.py +429 -355
  47. matrice_analytics/post_processing/usecases/drone_traffic_monitoring.py +41 -386
  48. matrice_analytics/post_processing/usecases/flare_analysis.py +1 -56
  49. matrice_analytics/post_processing/usecases/license_plate_detection.py +476 -202
  50. matrice_analytics/post_processing/usecases/license_plate_monitoring.py +252 -11
  51. matrice_analytics/post_processing/usecases/people_counting.py +408 -1431
  52. matrice_analytics/post_processing/usecases/people_counting_bckp.py +1683 -0
  53. matrice_analytics/post_processing/usecases/vehicle_monitoring.py +39 -10
  54. matrice_analytics/post_processing/utils/__init__.py +8 -8
  55. {matrice_analytics-0.1.2.dist-info → matrice_analytics-0.1.31.dist-info}/METADATA +1 -1
  56. {matrice_analytics-0.1.2.dist-info → matrice_analytics-0.1.31.dist-info}/RECORD +59 -24
  57. {matrice_analytics-0.1.2.dist-info → matrice_analytics-0.1.31.dist-info}/WHEEL +0 -0
  58. {matrice_analytics-0.1.2.dist-info → matrice_analytics-0.1.31.dist-info}/licenses/LICENSE.txt +0 -0
  59. {matrice_analytics-0.1.2.dist-info → matrice_analytics-0.1.31.dist-info}/top_level.txt +0 -0
@@ -24,17 +24,36 @@ import numpy as np
24
24
  #import torch
25
25
  import re
26
26
  from collections import Counter, defaultdict
27
- #from turbojpeg import TurboJPEG, TJPF_RGB
27
+ import sys
28
+ import logging
29
+ import asyncio
30
+ import urllib
31
+ import urllib.request
32
+ # Get the major and minor version numbers
33
+ major_version = sys.version_info.major
34
+ minor_version = sys.version_info.minor
35
+ print(f"Python version: {major_version}.{minor_version}")
28
36
  os.environ["ORT_LOG_SEVERITY_LEVEL"] = "3"
29
- # Fast license-plate OCR (replaces EasyOCR)
30
- # Attempt to import fast_plate_ocr; fall back to a stub if unavailable
31
- try:
32
- from fast_plate_ocr import LicensePlateRecognizer # type: ignore
33
- except: # pragma: no cover – optional dependency may be absent
34
- class LicensePlateRecognizer: # type: ignore
35
- """Stub fallback when fast_plate_ocr is not installed."""
36
- def __init__(self, *args, **kwargs):
37
- print("fast_plate_ocr is required for LicensePlateMonitorUseCase but is not installed.")
37
+
38
+
39
+ _ENABLE_OCR_BOOTSTRAP = os.getenv("MATRICE_ENABLE_OCR_BOOTSTRAP", "0")
40
+ if _ENABLE_OCR_BOOTSTRAP == "1":
41
+ try:
42
+ from ..ocr.fast_plate_ocr_py38 import LicensePlateRecognizer
43
+ except:
44
+ class LicensePlateRecognizer:
45
+ """Stub fallback when fast_plate_ocr is not installed."""
46
+ def __init__(self, *args, **kwargs):
47
+ print("fast_plate_ocr from the local library py_inferenceis required for LicensePlateMonitorUseCase as platform is determined as python3.8 Jetson")
48
+ else:
49
+ try:
50
+ from fast_plate_ocr import LicensePlateRecognizer # type: ignore
51
+ except: # pragma: no cover – optional dependency may be absent
52
+ logging.error("fast_plate_ocr is required for LicensePlateMonitorUseCase but is not installed.")
53
+ class LicensePlateRecognizer: # type: ignore
54
+ """Stub fallback when fast_plate_ocr is not installed."""
55
+ def __init__(self, *args, **kwargs):
56
+ print("fast_plate_ocr is required for LicensePlateMonitorUseCase but is not installed.")
38
57
 
39
58
  # Internal utilities that are still required
40
59
  from ..ocr.preprocessing import ImagePreprocessor
@@ -46,6 +65,12 @@ try:
46
65
  except Exception as _e:
47
66
  print(f"Warning: fast_plate_ocr could not be imported ⇒ {_e}")
48
67
 
68
+ try:
69
+ from matrice_common.session import Session
70
+ HAS_MATRICE_SESSION = True
71
+ except ImportError:
72
+ HAS_MATRICE_SESSION = False
73
+ logging.warning("Matrice session not available")
49
74
 
50
75
  @dataclass
51
76
  class LicensePlateMonitorConfig(BaseConfig):
@@ -66,7 +91,10 @@ class LicensePlateMonitorConfig(BaseConfig):
66
91
  language: List[str] = field(default_factory=lambda: ['en'])
67
92
  country: str = field(default_factory=lambda: 'us')
68
93
  ocr_mode:str = field(default_factory=lambda: "numeric") # "alphanumeric" or "numeric" or "alphabetic"
69
-
94
+ session: Optional[Session] = None
95
+ lpr_server_id: Optional[str] = None # Optional LPR server ID for remote logging
96
+ plate_log_cooldown: float = 30.0 # Cooldown period in seconds for logging same plate
97
+
70
98
  def validate(self) -> List[str]:
71
99
  """Validate configuration parameters."""
72
100
  errors = super().validate()
@@ -84,6 +112,149 @@ class LicensePlateMonitorConfig(BaseConfig):
84
112
  errors.append("smoothing_confidence_range_factor must be positive")
85
113
  return errors
86
114
 
115
+ class LicensePlateMonitorLogger:
116
+ def __init__(self):
117
+ self.session = None
118
+ self.logger = logging.getLogger(__name__)
119
+ self.lpr_server_id = None
120
+ self.server_info = None
121
+ self.plate_log_timestamps: Dict[str, float] = {} # Track last log time per plate
122
+ self.server_base_url = None
123
+ self.public_ip = self._get_public_ip()
124
+
125
+ def initialize_session(self, config: LicensePlateMonitorConfig) -> None:
126
+ """Initialize session and fetch server connection info if lpr_server_id is provided."""
127
+ # Use existing session if provided, otherwise create new one
128
+ if self.session:
129
+ return
130
+ if config.session:
131
+ self.session = config.session
132
+ if not self.session:
133
+ # Initialize Matrice session
134
+ if not HAS_MATRICE_SESSION:
135
+ raise ImportError("Matrice session is required for License Plate Monitoring")
136
+ try:
137
+ self.session = Session(
138
+ account_number=os.getenv("MATRICE_ACCOUNT_NUMBER", ""),
139
+ access_key=os.getenv("MATRICE_ACCESS_KEY_ID", ""),
140
+ secret_key=os.getenv("MATRICE_SECRET_ACCESS_KEY", ""),
141
+ project_id=os.getenv("MATRICE_PROJECT_ID", ""),
142
+ )
143
+ self.logger.info("Initialized new Matrice session for License Plate Monitoring")
144
+ except Exception as e:
145
+ self.logger.error(f"Failed to initialize Matrice session: {e}", exc_info=True)
146
+ raise
147
+
148
+ # Fetch server connection info if lpr_server_id is provided
149
+ if config.lpr_server_id:
150
+ self.lpr_server_id = config.lpr_server_id
151
+ try:
152
+ self.server_info = self.get_server_connection_info()
153
+ if self.server_info:
154
+ self.logger.info(f"Successfully fetched LPR server info: {self.server_info.get('name', 'Unknown')}")
155
+ # Compare server host with public IP to determine if it's localhost
156
+ server_host = self.server_info.get('host', 'localhost')
157
+ server_port = self.server_info.get('port', 8200)
158
+
159
+ if server_host == self.public_ip:
160
+ self.server_base_url = f"http://localhost:{server_port}"
161
+ self.logger.warning(f"Server host matches public IP, using localhost: {self.server_base_url}")
162
+ else:
163
+ self.server_base_url = f"https://{server_host}:{server_port}"
164
+ self.logger.warning(f"LPR server base URL: {self.server_base_url}")
165
+
166
+ self.session.update(self.server_info.get('projectID', ''))
167
+ self.logger.info(f"Updated Matrice session with project ID: {self.server_info.get('projectID', '')}")
168
+ else:
169
+ self.logger.warning("Failed to fetch LPR server connection info")
170
+ except Exception as e:
171
+ self.logger.error(f"Error fetching LPR server connection info: {e}", exc_info=True)
172
+
173
+ def _get_public_ip(self) -> str:
174
+ """Get the public IP address of this machine."""
175
+ try:
176
+ public_ip = urllib.request.urlopen("https://v4.ident.me", timeout=120).read().decode("utf8").strip()
177
+ self.logger.warning(f"Successfully fetched external IP: {public_ip}")
178
+ return public_ip
179
+ except Exception as e:
180
+ self.logger.error(f"Error fetching external IP: {e}", exc_info=True)
181
+ return "localhost"
182
+
183
+ def get_server_connection_info(self) -> Optional[Dict[str, Any]]:
184
+ """Fetch server connection info from RPC."""
185
+ if not self.lpr_server_id:
186
+ return None
187
+
188
+ try:
189
+ response = self.session.rpc.get(f"/v1/actions/lpr_servers/{self.lpr_server_id}")
190
+ if response.get("success", False) and response.get("code") == 200:
191
+ # Response format:
192
+ # {'success': True,
193
+ # 'code': 200,
194
+ # 'message': 'Success',
195
+ # 'serverTime': '2025-10-19T04:58:04Z',
196
+ # 'data': {'id': '68f07e515cd5c6134a075384',
197
+ # 'name': 'lpr-server-1',
198
+ # 'host': '106.219.122.19',
199
+ # 'port': 8200,
200
+ # 'status': 'created',
201
+ # 'accountNumber': '3823255831182978487149732',
202
+ # 'projectID': '68ca6372ab79ba13ef699ba6',
203
+ # 'region': 'United States',
204
+ # 'isShared': False}}
205
+ return response.get("data", {})
206
+ else:
207
+ self.logger.warning(f"Failed to fetch server info: {response.get('message', 'Unknown error')}")
208
+ return None
209
+ except Exception as e:
210
+ self.logger.error(f"Exception while fetching server connection info: {e}", exc_info=True)
211
+ return None
212
+
213
+ def should_log_plate(self, plate_text: str, cooldown: float) -> bool:
214
+ """Check if enough time has passed since last log for this plate."""
215
+ current_time = time.time()
216
+ last_log_time = self.plate_log_timestamps.get(plate_text, 0)
217
+
218
+ if current_time - last_log_time >= cooldown:
219
+ return True
220
+ return False
221
+
222
+ def update_log_timestamp(self, plate_text: str) -> None:
223
+ """Update the last log timestamp for a plate."""
224
+ self.plate_log_timestamps[plate_text] = time.time()
225
+
226
+ async def log_plate(self, plate_text: str, timestamp: str, stream_info: Dict[str, Any], cooldown: float = 30.0) -> bool:
227
+ """Log plate to RPC server with cooldown period."""
228
+ # Check cooldown
229
+ if not self.should_log_plate(plate_text, cooldown):
230
+ self.logger.debug(f"Plate {plate_text} skipped due to cooldown period")
231
+ return False
232
+
233
+ try:
234
+ camera_info = stream_info.get("camera_info", {})
235
+ camera_name = camera_info.get("camera_name", "")
236
+ location = camera_info.get("location", "")
237
+ frame_id = stream_info.get("frame_id", "")
238
+
239
+ payload = {
240
+ 'licensePlate': plate_text,
241
+ 'frameId': frame_id,
242
+ 'location': location,
243
+ 'camera': camera_name,
244
+ 'captureTimestamp': timestamp
245
+ }
246
+
247
+ await self.session.rpc.post_async('/v1/lpr-server/detections', payload=payload, base_url=self.server_base_url)
248
+
249
+ # Update timestamp after successful log
250
+ self.update_log_timestamp(plate_text)
251
+ self.logger.info(f"Successfully logged plate: {plate_text} at {timestamp}")
252
+ return True
253
+
254
+ except Exception as e:
255
+ self.logger.error(f"Failed to log plate {plate_text}: {e}", exc_info=True)
256
+ return False
257
+
87
258
  class LicensePlateMonitorUseCase(BaseProcessor):
88
259
  CATEGORY_DISPLAY = {"license_plate": "license_plate"}
89
260
 
@@ -145,6 +316,10 @@ class LicensePlateMonitorUseCase(BaseProcessor):
145
316
  self._ocr_mode = None
146
317
  #self.jpeg = TurboJPEG()
147
318
 
319
+ # Initialize plate logger (optional, only used if lpr_server_id is provided)
320
+ self.plate_logger: Optional[LicensePlateMonitorLogger] = None
321
+ self._logging_enabled = True
322
+
148
323
 
149
324
  def reset_tracker(self) -> None:
150
325
  """Reset the advanced tracker instance."""
@@ -170,6 +345,65 @@ class LicensePlateMonitorUseCase(BaseProcessor):
170
345
  self.reset_tracker()
171
346
  self.reset_plate_tracking()
172
347
  self.logger.info("All plate tracking state reset")
348
+
349
+ def _initialize_plate_logger(self, config: LicensePlateMonitorConfig) -> None:
350
+ """Initialize the plate logger if lpr_server_id is provided."""
351
+ if not config.lpr_server_id:
352
+ self._logging_enabled = False
353
+ self.logger.info("Plate logging disabled: no lpr_server_id provided")
354
+ return
355
+
356
+ try:
357
+ if self.plate_logger is None:
358
+ self.plate_logger = LicensePlateMonitorLogger()
359
+
360
+ self.plate_logger.initialize_session(config)
361
+ self._logging_enabled = True
362
+ self.logger.info(f"Plate logging enabled with server ID: {config.lpr_server_id}")
363
+ except Exception as e:
364
+ self.logger.error(f"Failed to initialize plate logger: {e}", exc_info=True)
365
+ self._logging_enabled = False
366
+
367
+ def _log_detected_plates(self, detections: List[Dict[str, Any]], config: LicensePlateMonitorConfig,
368
+ stream_info: Optional[Dict[str, Any]]) -> None:
369
+ """Log all detected plates to RPC server with cooldown."""
370
+ if not self._logging_enabled or not self.plate_logger or not stream_info:
371
+ return
372
+
373
+ # Get current timestamp
374
+ current_timestamp = self._get_current_timestamp_str(stream_info, precision=True)
375
+
376
+ # Collect all unique plates from current detections
377
+ plates_to_log = set()
378
+ for det in detections:
379
+ plate_text = det.get('plate_text')
380
+ if not plate_text:
381
+ continue
382
+ plates_to_log.add(plate_text)
383
+
384
+ # Log each unique plate (respecting cooldown)
385
+ if plates_to_log:
386
+ try:
387
+ # Run async logging tasks
388
+ loop = asyncio.new_event_loop()
389
+ asyncio.set_event_loop(loop)
390
+ try:
391
+ tasks = []
392
+ for plate_text in plates_to_log:
393
+ task = self.plate_logger.log_plate(
394
+ plate_text=plate_text,
395
+ timestamp=current_timestamp,
396
+ stream_info=stream_info,
397
+ cooldown=config.plate_log_cooldown
398
+ )
399
+ tasks.append(task)
400
+
401
+ # Run all logging tasks concurrently
402
+ loop.run_until_complete(asyncio.gather(*tasks, return_exceptions=True))
403
+ finally:
404
+ loop.close()
405
+ except Exception as e:
406
+ self.logger.error(f"Error during plate logging: {e}", exc_info=True)
173
407
 
174
408
  def process(self, data: Any, config: ConfigProtocol, input_bytes: Optional[bytes] = None,
175
409
  context: Optional[ProcessingContext] = None, stream_info: Optional[Dict[str, Any]] = None) -> ProcessingResult:
@@ -191,6 +425,10 @@ class LicensePlateMonitorUseCase(BaseProcessor):
191
425
  print(config)
192
426
  print("--------------------------------------")
193
427
 
428
+ # Initialize plate logger if lpr_server_id is provided (optional flow)
429
+ if config.lpr_server_id and self._logging_enabled:
430
+ self._initialize_plate_logger(config)
431
+
194
432
  # Normalize alert_config if provided as a plain dict (JS JSON)
195
433
  if isinstance(getattr(config, 'alert_config', None), dict):
196
434
  try:
@@ -287,6 +525,9 @@ class LicensePlateMonitorUseCase(BaseProcessor):
287
525
  processed_data = self._update_detections_with_ocr(processed_data, ocr_analysis)
288
526
  self._update_plate_texts(processed_data)
289
527
 
528
+ # Step 9.5: Log detected plates to RPC (optional, only if lpr_server_id is provided)
529
+ self._log_detected_plates(processed_data, config, stream_info)
530
+
290
531
  # Step 10: Update frame counter
291
532
  self._total_frame_counter += 1
292
533