matrice-analytics 0.1.70__py3-none-any.whl → 0.1.96__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 (26) hide show
  1. matrice_analytics/post_processing/__init__.py +8 -2
  2. matrice_analytics/post_processing/config.py +4 -2
  3. matrice_analytics/post_processing/core/base.py +1 -1
  4. matrice_analytics/post_processing/core/config.py +40 -3
  5. matrice_analytics/post_processing/face_reg/face_recognition.py +1014 -201
  6. matrice_analytics/post_processing/face_reg/face_recognition_client.py +171 -29
  7. matrice_analytics/post_processing/face_reg/people_activity_logging.py +19 -0
  8. matrice_analytics/post_processing/post_processor.py +4 -0
  9. matrice_analytics/post_processing/usecases/__init__.py +4 -1
  10. matrice_analytics/post_processing/usecases/advanced_customer_service.py +913 -500
  11. matrice_analytics/post_processing/usecases/color_detection.py +19 -18
  12. matrice_analytics/post_processing/usecases/customer_service.py +356 -9
  13. matrice_analytics/post_processing/usecases/fire_detection.py +241 -23
  14. matrice_analytics/post_processing/usecases/footfall.py +750 -0
  15. matrice_analytics/post_processing/usecases/license_plate_monitoring.py +638 -40
  16. matrice_analytics/post_processing/usecases/people_counting.py +66 -33
  17. matrice_analytics/post_processing/usecases/vehicle_monitoring.py +35 -34
  18. matrice_analytics/post_processing/usecases/weapon_detection.py +2 -1
  19. matrice_analytics/post_processing/utils/alert_instance_utils.py +1018 -0
  20. matrice_analytics/post_processing/utils/business_metrics_manager_utils.py +1338 -0
  21. matrice_analytics/post_processing/utils/incident_manager_utils.py +1754 -0
  22. {matrice_analytics-0.1.70.dist-info → matrice_analytics-0.1.96.dist-info}/METADATA +1 -1
  23. {matrice_analytics-0.1.70.dist-info → matrice_analytics-0.1.96.dist-info}/RECORD +26 -22
  24. {matrice_analytics-0.1.70.dist-info → matrice_analytics-0.1.96.dist-info}/WHEEL +0 -0
  25. {matrice_analytics-0.1.70.dist-info → matrice_analytics-0.1.96.dist-info}/licenses/LICENSE.txt +0 -0
  26. {matrice_analytics-0.1.70.dist-info → matrice_analytics-0.1.96.dist-info}/top_level.txt +0 -0
@@ -7,6 +7,7 @@ in the post-processing pipeline using Matrice Session.
7
7
  """
8
8
 
9
9
  import os
10
+ import re
10
11
  import base64
11
12
  import logging
12
13
  import httpx
@@ -14,6 +15,7 @@ import urllib
14
15
  import urllib.request
15
16
  from typing import List, Dict, Any, Optional
16
17
  from datetime import datetime, timezone
18
+ from pathlib import Path
17
19
 
18
20
  # Import matrice session
19
21
  try:
@@ -29,6 +31,78 @@ class FacialRecognitionClient:
29
31
  Simplified Face Recognition Client using Matrice Session.
30
32
  All API calls are made through the Matrice session RPC interface.
31
33
  """
34
+
35
+ # Pattern for matching action IDs (hex strings of at least 8 characters)
36
+ ACTION_ID_PATTERN = re.compile(r"^[0-9a-f]{8,}$", re.IGNORECASE)
37
+
38
+ @classmethod
39
+ def _discover_action_id(cls) -> Optional[str]:
40
+ """Discover action_id from current working directory name (and parents)."""
41
+ candidates: List[str] = []
42
+ try:
43
+ cwd = Path.cwd()
44
+ candidates.append(cwd.name)
45
+ for parent in cwd.parents:
46
+ candidates.append(parent.name)
47
+ except Exception:
48
+ pass
49
+
50
+ try:
51
+ usr_src = Path("/usr/src")
52
+ if usr_src.exists():
53
+ for child in usr_src.iterdir():
54
+ if child.is_dir():
55
+ candidates.append(child.name)
56
+ except Exception:
57
+ pass
58
+
59
+ for candidate in candidates:
60
+ if candidate and len(candidate) >= 8 and cls.ACTION_ID_PATTERN.match(candidate):
61
+ return candidate
62
+ return None
63
+
64
+ def _fetch_project_id_from_action(self) -> Optional[str]:
65
+ """
66
+ Fetch project ID from action details using discovered action ID.
67
+
68
+ This method discovers the action ID from the working directory name,
69
+ fetches action details from the API, and extracts the _idProject field.
70
+ If successful, it also updates the MATRICE_PROJECT_ID environment variable.
71
+
72
+ Returns:
73
+ The project ID string if found, None otherwise.
74
+ """
75
+ action_id = self._discover_action_id()
76
+ if not action_id:
77
+ self.logger.warning("[PROJECT_ID] Could not discover action_id from folder name")
78
+ return None
79
+
80
+ self.logger.info(f"[PROJECT_ID] Discovered action_id from folder: {action_id}")
81
+
82
+ try:
83
+ url = f"/v1/actions/action/{action_id}/details"
84
+ self.logger.info(f"[PROJECT_ID] Fetching action details from: {url}")
85
+ response = self.session.rpc.get(url)
86
+
87
+ if response and response.get("success", False) and response.get("code") == 200:
88
+ data = response.get("data", {})
89
+ project_id = data.get("_idProject", "")
90
+
91
+ if project_id:
92
+ self.logger.info(f"[PROJECT_ID] Successfully fetched project ID from action details: {project_id}")
93
+ # Update environment variable so other components can use it
94
+ os.environ["MATRICE_PROJECT_ID"] = project_id
95
+ self.logger.info(f"[PROJECT_ID] Updated MATRICE_PROJECT_ID environment variable: {project_id}")
96
+ return project_id
97
+ else:
98
+ self.logger.warning(f"[PROJECT_ID] _idProject not found in action details for action_id={action_id}")
99
+ else:
100
+ error_msg = response.get('message', 'Unknown error') if response else 'Empty response'
101
+ self.logger.warning(f"[PROJECT_ID] Failed to fetch action details: {error_msg}")
102
+ except Exception as e:
103
+ self.logger.error(f"[PROJECT_ID] Error fetching action details for action_id={action_id}: {e}", exc_info=True)
104
+
105
+ return None
32
106
 
33
107
  def __init__(self, account_number: str = "", access_key: str = "", secret_key: str = "",
34
108
  project_id: str = "", server_id: str = "", session=None):
@@ -75,6 +149,23 @@ class FacialRecognitionClient:
75
149
  except Exception as e:
76
150
  self.logger.error(f"Failed to initialize Matrice session: {e}", exc_info=True)
77
151
  raise
152
+
153
+ # If project_id is still empty, try to fetch from action details
154
+ if not self.project_id:
155
+ self.logger.info("[PROJECT_ID] Project ID is empty, attempting to fetch from action details...")
156
+ fetched_project_id = self._fetch_project_id_from_action()
157
+ if fetched_project_id:
158
+ self.project_id = fetched_project_id
159
+ self.logger.info(f"[PROJECT_ID] Successfully set project_id from action details: {self.project_id}")
160
+ # Update session with the new project_id if possible
161
+ if hasattr(self.session, 'update'):
162
+ try:
163
+ self.session.update(self.project_id)
164
+ self.logger.info(f"[PROJECT_ID] Updated session with project_id: {self.project_id}")
165
+ except Exception as e:
166
+ self.logger.warning(f"[PROJECT_ID] Failed to update session with project_id: {e}")
167
+ else:
168
+ self.logger.warning("[PROJECT_ID] Could not fetch project_id from action details")
78
169
 
79
170
  # Fetch server connection info if server_id is provided
80
171
  if self.server_id:
@@ -93,12 +184,29 @@ class FacialRecognitionClient:
93
184
  self.server_base_url = f"http://{server_host}:{server_port}"
94
185
  self.logger.warning(f"Facial recognition server base URL: {self.server_base_url}")
95
186
 
96
- self.session.update(self.server_info.get('projectID', ''))
97
- self.logger.info(f"Updated Matrice session with project ID: {self.server_info.get('projectID', '')}")
187
+ # Update project_id from server_info if available and current project_id is empty
188
+ server_project_id = self.server_info.get('projectID', '')
189
+ if server_project_id:
190
+ if not self.project_id:
191
+ self.project_id = server_project_id
192
+ self.logger.info(f"[PROJECT_ID] Set project_id from server_info: {self.project_id}")
193
+ # Update environment variable
194
+ os.environ["MATRICE_PROJECT_ID"] = self.project_id
195
+ self.logger.info(f"[PROJECT_ID] Updated MATRICE_PROJECT_ID env var from server_info: {self.project_id}")
196
+ self.session.update(server_project_id)
197
+ self.logger.info(f"Updated Matrice session with project ID: {server_project_id}")
198
+ else:
199
+ self.logger.warning("[PROJECT_ID] server_info.projectID is empty")
98
200
  else:
99
201
  self.logger.warning("Failed to fetch facial recognition server connection info")
100
202
  except Exception as e:
101
203
  self.logger.error(f"Error fetching facial recognition server connection info: {e}", exc_info=True)
204
+
205
+ # Final check: log the project_id status
206
+ if self.project_id:
207
+ self.logger.info(f"[PROJECT_ID] Final project_id: {self.project_id}")
208
+ else:
209
+ self.logger.error("[PROJECT_ID] WARNING: project_id is still empty after all initialization attempts!")
102
210
 
103
211
  def _get_public_ip(self) -> str:
104
212
  """Get the public IP address of this machine."""
@@ -286,6 +394,8 @@ class FacialRecognitionClient:
286
394
  employee_id: Optional[str] = None,
287
395
  timestamp: str = datetime.now(timezone.utc).isoformat(),
288
396
  image_data: Optional[str] = None,
397
+ camera_name: Optional[str] = None,
398
+ camera_id: Optional[str] = None,
289
399
  ) -> Dict[str, Any]:
290
400
  """
291
401
  Store people activity data with optional image data
@@ -310,6 +420,8 @@ class FacialRecognitionClient:
310
420
  "timestamp": timestamp,
311
421
  "bbox": bbox,
312
422
  "location": location,
423
+ "camera_name": camera_name,
424
+ "camera_id": camera_id,
313
425
  }
314
426
 
315
427
  # Add optional fields if provided based on API spec
@@ -322,8 +434,8 @@ class FacialRecognitionClient:
322
434
  if image_data:
323
435
  activity_request["imageData"] = image_data
324
436
 
325
- self.logger.info(f"API REQUEST: Storing people activity - type={detection_type}, staff_id={staff_id}, location={location}, has_image={bool(image_data)}")
326
- self.logger.debug(f"Activity request payload: bbox={bbox}, employee_id={employee_id}")
437
+ self.logger.info(f"API REQUEST: Storing people activity - type={detection_type}, staff_id={staff_id}, location={location}, camera_name={camera_name}, camera_id={camera_id}, has_image={bool(image_data)}")
438
+ self.logger.debug(f"Activity request payload: bbox={bbox}, employee_id={employee_id}, camera_name={camera_name}, camera_id={camera_id}")
327
439
 
328
440
  try:
329
441
  response = await self.session.rpc.async_send_request(
@@ -474,11 +586,11 @@ class FacialRecognitionClient:
474
586
  except Exception as e:
475
587
  self.logger.error(f"API ERROR: Get all staff embeddings request failed - {e}", exc_info=True)
476
588
  return {"success": False, "error": str(e)}
477
-
478
- async def update_deployment(self, deployment_id: str) -> Dict[str, Any]:
479
- """Update deployment to notify facial recognition server
589
+
590
+ async def update_deployment_action(self, deployment_id: str) -> Dict[str, Any]:
591
+ """Update deployment action in backend
480
592
 
481
- API: PUT /v1/facial_recognition/update_deployment/:deployment_id
593
+ API: PUT /internal/v1/actions/update_facial_recognition_deployment/:server_id?app_deployment_id=:deployment_id
482
594
 
483
595
  Args:
484
596
  deployment_id: The deployment ID to update
@@ -487,34 +599,34 @@ class FacialRecognitionClient:
487
599
  Dict containing response data
488
600
  """
489
601
  if not deployment_id:
490
- self.logger.warning("No deployment_id provided for update_deployment")
602
+ self.logger.warning("No deployment_id provided for update_deployment_action")
491
603
  return {"success": False, "error": "deployment_id is required"}
492
604
 
493
- self.logger.info(f"API REQUEST: Updating deployment - deployment_id={deployment_id}")
605
+ self.logger.info(f"API REQUEST: Updating deployment action - deployment_id={deployment_id}")
494
606
 
495
- # Use Matrice session for async RPC call
607
+ # Use Matrice session for async RPC call to backend (not facial recognition server).
496
608
  try:
497
609
  response = await self.session.rpc.async_send_request(
498
610
  method="PUT",
499
- path=f"/v1/facial_recognition/update_deployment/{deployment_id}",
611
+ path=f"/v1/actions/update_facial_recognition_deployment/{self.server_id}?app_deployment_id={deployment_id}",
500
612
  payload={},
501
- base_url=self.server_base_url
613
+ base_url="https://prod.backend.app.matrice.ai"
502
614
  )
503
615
 
504
616
  if response.get('success', False):
505
- self.logger.info(f"API RESPONSE: Deployment updated successfully - deployment_id={deployment_id}")
617
+ self.logger.info(f"API RESPONSE: Deployment action updated successfully - deployment_id={deployment_id}")
506
618
  else:
507
- self.logger.warning(f"Failed to update deployment for deployment_id={deployment_id}: {response.get('error', 'Unknown error')}")
619
+ self.logger.warning(f"Failed to update deployment action for deployment_id={deployment_id}: {response.get('error', 'Unknown error')}")
508
620
 
509
621
  return self._handle_response(response)
510
622
  except Exception as e:
511
- self.logger.error(f"API ERROR: Update deployment request failed - deployment_id={deployment_id} - {e}", exc_info=True)
623
+ self.logger.error(f"API ERROR: Update deployment action request failed - deployment_id={deployment_id} - {e}", exc_info=True)
512
624
  return {"success": False, "error": str(e)}
513
625
 
514
- async def update_deployment_action(self, deployment_id: str) -> Dict[str, Any]:
515
- """Update deployment action in backend
626
+ async def update_deployment(self, deployment_id: str) -> Dict[str, Any]:
627
+ """Update deployment to notify facial recognition server
516
628
 
517
- API: PUT /internal/v1/actions/update_facial_recognition_deployment/:server_id?app_deployment_id=:deployment_id
629
+ API: PUT /v1/facial_recognition/update_deployment/:deployment_id
518
630
 
519
631
  Args:
520
632
  deployment_id: The deployment ID to update
@@ -523,28 +635,28 @@ class FacialRecognitionClient:
523
635
  Dict containing response data
524
636
  """
525
637
  if not deployment_id:
526
- self.logger.warning("No deployment_id provided for update_deployment_action")
638
+ self.logger.warning("No deployment_id provided for update_deployment")
527
639
  return {"success": False, "error": "deployment_id is required"}
528
640
 
529
- self.logger.info(f"API REQUEST: Updating deployment action - deployment_id={deployment_id}")
641
+ self.logger.info(f"API REQUEST: Updating deployment - deployment_id={deployment_id}")
530
642
 
531
- # Use Matrice session for async RPC call to backend (not facial recognition server)
643
+ # Use Matrice session for async RPC call
532
644
  try:
533
645
  response = await self.session.rpc.async_send_request(
534
646
  method="PUT",
535
- path=f"/internal/v1/actions/update_facial_recognition_deployment/{self.server_id}?app_deployment_id={deployment_id}",
647
+ path=f"/v1/facial_recognition/update_deployment/{deployment_id}",
536
648
  payload={},
537
- base_url="https://prod.backend.app.matrice.ai"
649
+ base_url=self.server_base_url
538
650
  )
539
651
 
540
652
  if response.get('success', False):
541
- self.logger.info(f"API RESPONSE: Deployment action updated successfully - deployment_id={deployment_id}")
653
+ self.logger.info(f"API RESPONSE: Deployment updated successfully - deployment_id={deployment_id}")
542
654
  else:
543
- self.logger.warning(f"Failed to update deployment action for deployment_id={deployment_id}: {response.get('error', 'Unknown error')}")
655
+ self.logger.warning(f"Failed to update deployment for deployment_id={deployment_id}: {response.get('error', 'Unknown error')}")
544
656
 
545
657
  return self._handle_response(response)
546
658
  except Exception as e:
547
- self.logger.error(f"API ERROR: Update deployment action request failed - deployment_id={deployment_id} - {e}", exc_info=True)
659
+ self.logger.error(f"API ERROR: Update deployment request failed - deployment_id={deployment_id} - {e}", exc_info=True)
548
660
  return {"success": False, "error": str(e)}
549
661
 
550
662
  async def enroll_unknown_person(self, embedding: List[float], image_source: str = None, timestamp: str = None, location: str = None, employee_id: str = None) -> Dict[str, Any]:
@@ -602,17 +714,47 @@ class FacialRecognitionClient:
602
714
  payload={},
603
715
  base_url=self.server_base_url
604
716
  )
605
-
717
+
606
718
  if response.get('success', False):
607
719
  self.logger.info(f"API RESPONSE: Service is healthy")
608
720
  else:
609
721
  self.logger.warning(f"Health check failed: {response.get('error', 'Unknown error')}")
610
-
722
+
611
723
  return self._handle_response(response)
612
724
  except Exception as e:
613
725
  self.logger.error(f"API ERROR: Health check request failed - {e}", exc_info=True)
614
726
  return {"success": False, "error": str(e)}
615
727
 
728
+ async def get_redis_details(self) -> Dict[str, Any]:
729
+ """Get Redis connection details from facial recognition server
730
+
731
+ API: GET /v1/facial_recognition/get_redis_details
732
+
733
+ Returns:
734
+ Dict containing Redis connection details (REDIS_IP, REDIS_PORT, REDIS_PASSWORD)
735
+ """
736
+
737
+ self.logger.info(f"API REQUEST: Getting Redis connection details")
738
+
739
+ # Use Matrice session for async RPC call
740
+ try:
741
+ response = await self.session.rpc.async_send_request(
742
+ method="GET",
743
+ path=f"/v1/facial_recognition/get_redis_details",
744
+ payload={},
745
+ base_url=self.server_base_url
746
+ )
747
+
748
+ if response.get('success', False):
749
+ self.logger.info(f"API RESPONSE: Redis details retrieved successfully")
750
+ else:
751
+ self.logger.warning(f"Failed to get Redis details: {response.get('error', 'Unknown error')}")
752
+
753
+ return self._handle_response(response)
754
+ except Exception as e:
755
+ self.logger.error(f"API ERROR: Get Redis details request failed - {e}", exc_info=True)
756
+ return {"success": False, "error": str(e)}
757
+
616
758
  def _handle_response(self, response: Dict[str, Any]) -> Dict[str, Any]:
617
759
  """Handle RPC response and errors"""
618
760
  try:
@@ -4,6 +4,7 @@ import time
4
4
  import threading
5
5
  import queue
6
6
  import base64
7
+ import os
7
8
  from typing import Dict, Optional, Set
8
9
  import numpy as np
9
10
  import cv2
@@ -18,6 +19,15 @@ class PeopleActivityLogging:
18
19
  self.face_client = face_client
19
20
  self.logger = logging.getLogger(__name__)
20
21
 
22
+ # Log project ID information for observability and debugging
23
+ face_client_project_id = getattr(self.face_client, "project_id", None) if self.face_client else None
24
+ env_project_id = os.getenv("MATRICE_PROJECT_ID", "")
25
+ self.logger.info(
26
+ "[PROJECT_ID] PeopleActivityLogging initialized "
27
+ f"with face_client.project_id='{face_client_project_id}', "
28
+ f"MATRICE_PROJECT_ID env='{env_project_id}'"
29
+ )
30
+
21
31
  # Use thread-safe queue for cross-thread communication (Python 3.8 compatibility)
22
32
  self.activity_queue = queue.Queue()
23
33
 
@@ -95,6 +105,8 @@ class PeopleActivityLogging:
95
105
  detection: Dict,
96
106
  current_frame: Optional[np.ndarray] = None,
97
107
  location: str = "",
108
+ camera_name: str = "",
109
+ camera_id: str = "",
98
110
  ):
99
111
  """Enqueue a detection for background processing"""
100
112
  try:
@@ -103,6 +115,8 @@ class PeopleActivityLogging:
103
115
  "detection": detection,
104
116
  "current_frame": current_frame,
105
117
  "location": location,
118
+ "camera_name": camera_name,
119
+ "camera_id": camera_id,
106
120
  "timestamp": datetime.now(timezone.utc).isoformat(),
107
121
  "employee_id": detection.get("employee_id", None),
108
122
  "staff_id": detection.get("person_id")
@@ -178,7 +192,10 @@ class PeopleActivityLogging:
178
192
  location = activity_data["location"]
179
193
  staff_id = activity_data["staff_id"]
180
194
  timestamp = activity_data["timestamp"]
195
+ camera_name = activity_data.get("camera_name", "")
196
+ camera_id = activity_data.get("camera_id", "")
181
197
 
198
+ self.logger.debug(f"Processing activity - location: '{location}', camera_name: '{camera_name}', camera_id: '{camera_id}'")
182
199
  try:
183
200
  if not self.face_client:
184
201
  self.logger.warning("Face client not available for activity logging")
@@ -211,6 +228,8 @@ class PeopleActivityLogging:
211
228
  employee_id=employee_id,
212
229
  timestamp=timestamp,
213
230
  image_data=image_data,
231
+ camera_name=camera_name,
232
+ camera_id=camera_id,
214
233
  )
215
234
 
216
235
  if response and response.get("success", False):
@@ -99,6 +99,7 @@ from .usecases import (
99
99
  UndergroundPipelineDefectUseCase,
100
100
  SusActivityUseCase,
101
101
  NaturalDisasterUseCase,
102
+ FootFallUseCase,
102
103
  # Put all IMAGE based usecases here
103
104
  BloodCancerDetectionUseCase,
104
105
  SkinCancerClassificationUseCase,
@@ -569,6 +570,9 @@ class PostProcessor:
569
570
  registry.register_use_case(
570
571
  "environmental", "natural_disaster_detection", NaturalDisasterUseCase
571
572
  )
573
+ registry.register_use_case(
574
+ "retail", "footfall", FootFallUseCase
575
+ )
572
576
 
573
577
  # Put all IMAGE based usecases here
574
578
  registry.register_use_case(
@@ -85,6 +85,8 @@ from .pcb_defect_detection import PCBDefectConfig, PCBDefectUseCase
85
85
  from .underground_pipeline_defect_detection import UndergroundPipelineDefectConfig,UndergroundPipelineDefectUseCase
86
86
  from .suspicious_activity_detection import SusActivityConfig, SusActivityUseCase
87
87
  from .natural_disaster import NaturalDisasterConfig, NaturalDisasterUseCase
88
+ from .footfall import FootFallConfig, FootFallUseCase
89
+
88
90
 
89
91
  #Put all IMAGE based usecases here
90
92
  from .blood_cancer_detection_img import BloodCancerDetectionConfig, BloodCancerDetectionUseCase
@@ -172,6 +174,7 @@ __all__ = [
172
174
  'UndergroundPipelineDefectUseCase',
173
175
  'SusActivityUseCase',
174
176
  'NaturalDisasterUseCase',
177
+ 'FootFallUseCase',
175
178
 
176
179
  #Put all IMAGE based usecases here
177
180
  'BloodCancerDetectionUseCase',
@@ -254,7 +257,7 @@ __all__ = [
254
257
  'PCBDefectConfig',
255
258
  'SusActivityConfig',
256
259
  'NaturalDisasterConfig',
257
-
260
+ 'FootFallConfig',
258
261
  #Put all IMAGE based usecase CONFIGS here
259
262
  'BloodCancerDetectionConfig',
260
263
  'SkinCancerClassificationConfig',