matrice-analytics 0.1.60__py3-none-any.whl → 0.1.89__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- matrice_analytics/post_processing/config.py +2 -2
- matrice_analytics/post_processing/core/base.py +1 -1
- matrice_analytics/post_processing/face_reg/embedding_manager.py +8 -8
- matrice_analytics/post_processing/face_reg/face_recognition.py +886 -201
- matrice_analytics/post_processing/face_reg/face_recognition_client.py +68 -2
- matrice_analytics/post_processing/usecases/advanced_customer_service.py +908 -498
- matrice_analytics/post_processing/usecases/color_detection.py +18 -18
- matrice_analytics/post_processing/usecases/customer_service.py +356 -9
- matrice_analytics/post_processing/usecases/fire_detection.py +149 -11
- matrice_analytics/post_processing/usecases/license_plate_monitoring.py +548 -40
- matrice_analytics/post_processing/usecases/people_counting.py +11 -11
- matrice_analytics/post_processing/usecases/vehicle_monitoring.py +34 -34
- matrice_analytics/post_processing/usecases/weapon_detection.py +98 -22
- matrice_analytics/post_processing/utils/alert_instance_utils.py +950 -0
- matrice_analytics/post_processing/utils/business_metrics_manager_utils.py +1245 -0
- matrice_analytics/post_processing/utils/incident_manager_utils.py +1657 -0
- {matrice_analytics-0.1.60.dist-info → matrice_analytics-0.1.89.dist-info}/METADATA +1 -1
- {matrice_analytics-0.1.60.dist-info → matrice_analytics-0.1.89.dist-info}/RECORD +21 -18
- {matrice_analytics-0.1.60.dist-info → matrice_analytics-0.1.89.dist-info}/WHEEL +0 -0
- {matrice_analytics-0.1.60.dist-info → matrice_analytics-0.1.89.dist-info}/licenses/LICENSE.txt +0 -0
- {matrice_analytics-0.1.60.dist-info → matrice_analytics-0.1.89.dist-info}/top_level.txt +0 -0
|
@@ -18,6 +18,8 @@ from ..utils import (
|
|
|
18
18
|
BBoxSmoothingConfig,
|
|
19
19
|
BBoxSmoothingTracker
|
|
20
20
|
)
|
|
21
|
+
# Import alert system utilities
|
|
22
|
+
from ..utils.alert_instance_utils import ALERT_INSTANCE
|
|
21
23
|
# External dependencies
|
|
22
24
|
import cv2
|
|
23
25
|
import numpy as np
|
|
@@ -31,11 +33,15 @@ import asyncio
|
|
|
31
33
|
import urllib
|
|
32
34
|
import urllib.request
|
|
33
35
|
import base64
|
|
36
|
+
from pathlib import Path
|
|
34
37
|
# Get the major and minor version numbers
|
|
35
38
|
major_version = sys.version_info.major
|
|
36
39
|
minor_version = sys.version_info.minor
|
|
37
40
|
print(f"Python version: {major_version}.{minor_version}")
|
|
38
41
|
os.environ["ORT_LOG_SEVERITY_LEVEL"] = "3"
|
|
42
|
+
import base64
|
|
43
|
+
from matrice_common.stream.matrice_stream import MatriceStream, StreamType
|
|
44
|
+
from matrice_common.session import Session
|
|
39
45
|
|
|
40
46
|
|
|
41
47
|
# Lazy import mechanism for LicensePlateRecognizer
|
|
@@ -129,7 +135,6 @@ from ..ocr.preprocessing import ImagePreprocessor
|
|
|
129
135
|
from ..core.config import BaseConfig, AlertConfig, ZoneConfig
|
|
130
136
|
|
|
131
137
|
try:
|
|
132
|
-
from matrice_common.session import Session
|
|
133
138
|
HAS_MATRICE_SESSION = True
|
|
134
139
|
except ImportError:
|
|
135
140
|
HAS_MATRICE_SESSION = False
|
|
@@ -156,6 +161,7 @@ class LicensePlateMonitorConfig(BaseConfig):
|
|
|
156
161
|
ocr_mode:str = field(default_factory=lambda: "numeric") # "alphanumeric" or "numeric" or "alphabetic"
|
|
157
162
|
session: Optional[Session] = None
|
|
158
163
|
lpr_server_id: Optional[str] = None # Optional LPR server ID for remote logging
|
|
164
|
+
redis_server_id: Optional[str] = None # Optional Redis server ID for instant alerts
|
|
159
165
|
plate_log_cooldown: float = 30.0 # Cooldown period in seconds for logging same plate
|
|
160
166
|
|
|
161
167
|
def validate(self) -> List[str]:
|
|
@@ -237,6 +243,7 @@ class LicensePlateMonitorLogger:
|
|
|
237
243
|
# Fetch server connection info if lpr_server_id is provided
|
|
238
244
|
if config.lpr_server_id:
|
|
239
245
|
self.lpr_server_id = config.lpr_server_id
|
|
246
|
+
self.logger.info(f"[LP_LOGGING] CONFIG PRINTTEST: {config}")
|
|
240
247
|
self.logger.info(f"[LP_LOGGING] Fetching LPR server connection info for server ID: {self.lpr_server_id}")
|
|
241
248
|
try:
|
|
242
249
|
self.server_info = self.get_server_connection_info()
|
|
@@ -265,6 +272,7 @@ class LicensePlateMonitorLogger:
|
|
|
265
272
|
self.logger.error("[LP_LOGGING] Failed to fetch LPR server connection info - server_info is None")
|
|
266
273
|
self.logger.error("[LP_LOGGING] This will prevent plate logging from working!")
|
|
267
274
|
except Exception as e:
|
|
275
|
+
#pass
|
|
268
276
|
self.logger.error(f"[LP_LOGGING] Error fetching LPR server connection info: {e}", exc_info=True)
|
|
269
277
|
self.logger.error("[LP_LOGGING] This will prevent plate logging from working!")
|
|
270
278
|
else:
|
|
@@ -284,6 +292,17 @@ class LicensePlateMonitorLogger:
|
|
|
284
292
|
self.logger.error(f"Error fetching external IP: {e}", exc_info=True)
|
|
285
293
|
return "localhost"
|
|
286
294
|
|
|
295
|
+
def _get_backend_base_url(self) -> str:
|
|
296
|
+
"""Resolve backend base URL based on ENV variable: prod/staging/dev."""
|
|
297
|
+
env = os.getenv("ENV", "prod").strip().lower()
|
|
298
|
+
if env in ("prod", "production"):
|
|
299
|
+
host = "prod.backend.app.matrice.ai"
|
|
300
|
+
elif env in ("dev", "development"):
|
|
301
|
+
host = "dev.backend.app.matrice.ai"
|
|
302
|
+
else:
|
|
303
|
+
host = "staging.backend.app.matrice.ai"
|
|
304
|
+
return f"https://{host}"
|
|
305
|
+
|
|
287
306
|
def get_server_connection_info(self) -> Optional[Dict[str, Any]]:
|
|
288
307
|
"""Fetch server connection info from RPC."""
|
|
289
308
|
if not self.lpr_server_id:
|
|
@@ -406,16 +425,21 @@ class LicensePlateMonitorLogger:
|
|
|
406
425
|
self.logger.info(f"[LP_LOGGING] ===== PLATE LOG REQUEST END (SKIPPED) =====")
|
|
407
426
|
return False
|
|
408
427
|
|
|
428
|
+
if not stream_info:
|
|
429
|
+
self.logger.info(f"[LP_LOGGING] Stream info is None, skipping plate log")
|
|
430
|
+
stream_info = {}
|
|
431
|
+
|
|
409
432
|
try:
|
|
410
433
|
camera_info = stream_info.get("camera_info", {})
|
|
411
|
-
camera_name = camera_info.get("camera_name", "")
|
|
412
|
-
location = camera_info.get("location", "")
|
|
434
|
+
camera_name = camera_info.get("camera_name", "default_camera")
|
|
435
|
+
location = camera_info.get("location", "default_location")
|
|
413
436
|
frame_id = stream_info.get("frame_id", "")
|
|
414
437
|
|
|
415
438
|
print(f"[LP_LOGGING] Camera: '{camera_name}', Location: '{location}'")
|
|
416
439
|
self.logger.info(f"[LP_LOGGING] Stream Info - Camera: '{camera_name}', Location: '{location}', Frame ID: '{frame_id}'")
|
|
417
440
|
|
|
418
441
|
# Get project ID from server_info
|
|
442
|
+
self.logger.info(f"[LP_LOGGING] SERVER-INFO: '{self.server_info}'")
|
|
419
443
|
project_id = self.server_info.get('projectID', '') if self.server_info else ''
|
|
420
444
|
self.logger.info(f"[LP_LOGGING] Project ID: '{project_id}'")
|
|
421
445
|
|
|
@@ -438,7 +462,7 @@ class LicensePlateMonitorLogger:
|
|
|
438
462
|
full_url = f"{self.server_base_url}{endpoint}"
|
|
439
463
|
print(f"[LP_LOGGING] Sending POST to: {full_url}")
|
|
440
464
|
self.logger.info(f"[LP_LOGGING] Sending POST request to: {full_url}")
|
|
441
|
-
self.logger.info(f"[LP_LOGGING] Payload: licensePlate='{plate_text}', frameId='{frame_id}', location='{location}', camera='{camera_name}'
|
|
465
|
+
self.logger.info(f"[LP_LOGGING] Payload: licensePlate='{plate_text}', frameId='{frame_id}', location='{location}', camera='{camera_name}'")
|
|
442
466
|
|
|
443
467
|
response = await self.session.rpc.post_async(endpoint, payload=payload, base_url=self.server_base_url)
|
|
444
468
|
|
|
@@ -509,9 +533,330 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
509
533
|
|
|
510
534
|
# Initialize plate logger (optional, only used if lpr_server_id is provided)
|
|
511
535
|
self.plate_logger: Optional[LicensePlateMonitorLogger] = None
|
|
512
|
-
self._logging_enabled = True
|
|
536
|
+
self._logging_enabled = True # False //ToDo: DISABLED FOR NOW, ENABLED FOR PRODUCTION. ##
|
|
513
537
|
self._plate_logger_initialized = False # Track if plate logger has been initialized
|
|
514
|
-
|
|
538
|
+
|
|
539
|
+
# Initialize instant alert manager (will be lazily initialized on first process() call)
|
|
540
|
+
self.alert_manager: Optional[ALERT_INSTANCE] = None
|
|
541
|
+
self._alert_manager_initialized = False # Track initialization to do it only once
|
|
542
|
+
|
|
543
|
+
def set_alert_manager(self, alert_manager: ALERT_INSTANCE) -> None:
|
|
544
|
+
"""
|
|
545
|
+
Set the alert manager instance for instant alerts.
|
|
546
|
+
|
|
547
|
+
Args:
|
|
548
|
+
alert_manager: ALERT_INSTANCE instance configured with Redis/Kafka clients
|
|
549
|
+
"""
|
|
550
|
+
self.alert_manager = alert_manager
|
|
551
|
+
self.logger.info("Alert manager set for license plate monitoring")
|
|
552
|
+
|
|
553
|
+
def _discover_action_id(self) -> Optional[str]:
|
|
554
|
+
"""Discover action_id from current working directory name (and parents), similar to face_recognition flow."""
|
|
555
|
+
try:
|
|
556
|
+
import re as _re
|
|
557
|
+
pattern = _re.compile(r"^[0-9a-f]{8,}$", _re.IGNORECASE)
|
|
558
|
+
candidates: List[str] = []
|
|
559
|
+
try:
|
|
560
|
+
cwd = Path.cwd()
|
|
561
|
+
candidates.append(cwd.name)
|
|
562
|
+
for parent in cwd.parents:
|
|
563
|
+
candidates.append(parent.name)
|
|
564
|
+
except Exception:
|
|
565
|
+
pass
|
|
566
|
+
|
|
567
|
+
try:
|
|
568
|
+
usr_src = Path("/usr/src")
|
|
569
|
+
if usr_src.exists():
|
|
570
|
+
for child in usr_src.iterdir():
|
|
571
|
+
if child.is_dir():
|
|
572
|
+
candidates.append(child.name)
|
|
573
|
+
except Exception:
|
|
574
|
+
pass
|
|
575
|
+
|
|
576
|
+
for candidate in candidates:
|
|
577
|
+
if candidate and len(candidate) >= 8 and pattern.match(candidate):
|
|
578
|
+
return candidate
|
|
579
|
+
except Exception:
|
|
580
|
+
pass
|
|
581
|
+
return None
|
|
582
|
+
|
|
583
|
+
def _get_backend_base_url(self) -> str:
|
|
584
|
+
"""Resolve backend base URL based on ENV variable: prod/staging/dev."""
|
|
585
|
+
env = os.getenv("ENV", "prod").strip().lower()
|
|
586
|
+
if env in ("prod", "production"):
|
|
587
|
+
host = "prod.backend.app.matrice.ai"
|
|
588
|
+
elif env in ("dev", "development"):
|
|
589
|
+
host = "dev.backend.app.matrice.ai"
|
|
590
|
+
else:
|
|
591
|
+
host = "staging.backend.app.matrice.ai"
|
|
592
|
+
return f"https://{host}"
|
|
593
|
+
|
|
594
|
+
def _mask_value(self, value: Optional[str]) -> str:
|
|
595
|
+
"""Mask sensitive values for logging/printing."""
|
|
596
|
+
if not value:
|
|
597
|
+
return ""
|
|
598
|
+
if len(value) <= 4:
|
|
599
|
+
return "*" * len(value)
|
|
600
|
+
return value[:2] + "*" * (len(value) - 4) + value[-2:]
|
|
601
|
+
|
|
602
|
+
def _get_public_ip(self) -> str:
|
|
603
|
+
"""Get the public IP address of this machine."""
|
|
604
|
+
self.logger.info("Fetching public IP address...")
|
|
605
|
+
try:
|
|
606
|
+
public_ip = urllib.request.urlopen("https://v4.ident.me", timeout=120).read().decode("utf8").strip()
|
|
607
|
+
#self.logger.info(f"Successfully fetched external IP: {public_ip}")
|
|
608
|
+
return public_ip
|
|
609
|
+
except Exception as e:
|
|
610
|
+
#self.logger.error(f"Error fetching external IP: {e}", exc_info=True)
|
|
611
|
+
return "localhost"
|
|
612
|
+
|
|
613
|
+
def _initialize_alert_manager_once(self, config: LicensePlateMonitorConfig) -> None:
|
|
614
|
+
"""
|
|
615
|
+
Initialize alert manager ONCE with Redis OR Kafka clients (Environment based).
|
|
616
|
+
Called from process() on first invocation.
|
|
617
|
+
Uses config.session (existing session from pipeline).
|
|
618
|
+
"""
|
|
619
|
+
if self._alert_manager_initialized:
|
|
620
|
+
return
|
|
621
|
+
|
|
622
|
+
try:
|
|
623
|
+
# Import required modules
|
|
624
|
+
import base64
|
|
625
|
+
from matrice_common.stream.matrice_stream import MatriceStream, StreamType
|
|
626
|
+
|
|
627
|
+
# Use existing session from config (same pattern as plate_logger)
|
|
628
|
+
if not config.session:
|
|
629
|
+
account_number = os.getenv("MATRICE_ACCOUNT_NUMBER", "")
|
|
630
|
+
access_key_id = os.getenv("MATRICE_ACCESS_KEY_ID", "")
|
|
631
|
+
secret_key = os.getenv("MATRICE_SECRET_ACCESS_KEY", "")
|
|
632
|
+
project_id = os.getenv("MATRICE_PROJECT_ID", "")
|
|
633
|
+
|
|
634
|
+
self.session = Session(
|
|
635
|
+
account_number=account_number,
|
|
636
|
+
access_key=access_key_id,
|
|
637
|
+
secret_key=secret_key,
|
|
638
|
+
project_id=project_id,
|
|
639
|
+
)
|
|
640
|
+
config.session = self.session
|
|
641
|
+
if not self.session:
|
|
642
|
+
self.logger.warning("[ALERT] No session in config OR manual, skipping alert manager initialization")
|
|
643
|
+
self._alert_manager_initialized = True
|
|
644
|
+
return
|
|
645
|
+
|
|
646
|
+
rpc = config.session.rpc
|
|
647
|
+
|
|
648
|
+
# Determine environment: Localhost vs Cloud
|
|
649
|
+
# We use LPR server info to determine if we are local or cloud, similar to face_recognition_client
|
|
650
|
+
is_localhost = False
|
|
651
|
+
lpr_server_id = config.lpr_server_id
|
|
652
|
+
print("--------------------------------CONFIG-PRINT---------------------------")
|
|
653
|
+
print(config)
|
|
654
|
+
print("--------------------------------CONFIG-PRINT---------------------------")
|
|
655
|
+
if lpr_server_id:
|
|
656
|
+
try:
|
|
657
|
+
# Fetch LPR server info to compare IPs
|
|
658
|
+
response = rpc.get(f"/v1/actions/lpr_servers/{lpr_server_id}")
|
|
659
|
+
if response.get("success", False) and response.get("data"):
|
|
660
|
+
server_data = response.get("data", {})
|
|
661
|
+
server_host = server_data.get("host", "")
|
|
662
|
+
public_ip = self._get_public_ip()
|
|
663
|
+
|
|
664
|
+
# Check if server_host indicates localhost
|
|
665
|
+
localhost_indicators = ["localhost", "127.0.0.1", "0.0.0.0"]
|
|
666
|
+
if server_host in localhost_indicators or server_host == public_ip:
|
|
667
|
+
is_localhost = True
|
|
668
|
+
self.logger.info(f"[ALERT] Detected Localhost environment (Public IP={public_ip}, Server IP={server_host})")
|
|
669
|
+
else:
|
|
670
|
+
is_localhost = False
|
|
671
|
+
self.logger.info(f"[ALERT] Detected Cloud environment (Public IP={public_ip}, Server IP={server_host})")
|
|
672
|
+
else:
|
|
673
|
+
self.logger.warning(f"[ALERT] Failed to fetch LPR server info for environment detection, defaulting to Cloud mode")
|
|
674
|
+
except Exception as e:
|
|
675
|
+
self.logger.warning(f"[ALERT] Error detecting environment: {e}, defaulting to Cloud mode")
|
|
676
|
+
else:
|
|
677
|
+
self.logger.info("[ALERT] No LPR server ID, defaulting to Cloud mode")
|
|
678
|
+
|
|
679
|
+
# ------------------------------------------------------------------
|
|
680
|
+
# Discover action_id and fetch action details (STRICT API-DRIVEN)
|
|
681
|
+
# ------------------------------------------------------------------
|
|
682
|
+
action_id = self._discover_action_id()
|
|
683
|
+
if not action_id:
|
|
684
|
+
self.logger.error("[ALERT] Could not discover action_id from working directory or parents")
|
|
685
|
+
print("----- ALERT ACTION DISCOVERY -----")
|
|
686
|
+
print("action_id: NOT FOUND")
|
|
687
|
+
print("----------------------------------")
|
|
688
|
+
self._alert_manager_initialized = True
|
|
689
|
+
return
|
|
690
|
+
|
|
691
|
+
try:
|
|
692
|
+
action_url = f"/v1/actions/action/{action_id}/details"
|
|
693
|
+
action_resp = rpc.get(action_url)
|
|
694
|
+
if not (action_resp and action_resp.get("success", False)):
|
|
695
|
+
raise RuntimeError(action_resp.get("message", "Unknown error") if isinstance(action_resp, dict) else "Unknown error")
|
|
696
|
+
action_doc = action_resp.get("data", {}) if isinstance(action_resp, dict) else {}
|
|
697
|
+
action_details = action_doc.get("actionDetails", {}) if isinstance(action_doc, dict) else {}
|
|
698
|
+
|
|
699
|
+
# server id and type extraction (robust to variants)
|
|
700
|
+
server_id = (
|
|
701
|
+
action_details.get("serverId")
|
|
702
|
+
or action_details.get("server_id")
|
|
703
|
+
or action_details.get("serverID")
|
|
704
|
+
or action_details.get("redis_server_id")
|
|
705
|
+
or action_details.get("kafka_server_id")
|
|
706
|
+
)
|
|
707
|
+
server_type = (
|
|
708
|
+
action_details.get("serverType")
|
|
709
|
+
or action_details.get("server_type")
|
|
710
|
+
or action_details.get("type")
|
|
711
|
+
)
|
|
712
|
+
|
|
713
|
+
# Persist identifiers for future
|
|
714
|
+
self._action_id = action_id
|
|
715
|
+
self._deployment_id = action_details.get("_idDeployment") or action_details.get("deployment_id")
|
|
716
|
+
self._app_deployment_id = action_details.get("app_deployment_id")
|
|
717
|
+
self._instance_id = action_details.get("instanceID") or action_details.get("instanceId")
|
|
718
|
+
self._external_ip = action_details.get("externalIP") or action_details.get("externalIp")
|
|
719
|
+
|
|
720
|
+
print("----- ALERT ACTION DETAILS -----")
|
|
721
|
+
print(f"action_id: {action_id}")
|
|
722
|
+
print(f"server_type: {server_type}")
|
|
723
|
+
print(f"server_id: {server_id}")
|
|
724
|
+
print(f"deployment_id: {self._deployment_id}")
|
|
725
|
+
print(f"app_deployment_id: {self._app_deployment_id}")
|
|
726
|
+
print(f"instance_id: {self._instance_id}")
|
|
727
|
+
print(f"external_ip: {self._external_ip}")
|
|
728
|
+
print("--------------------------------")
|
|
729
|
+
self.logger.info(f"[ALERT] Action details fetched | action_id={action_id}, server_type={server_type}, server_id={server_id}")
|
|
730
|
+
self.logger.debug(f"[ALERT] Full action_details: {action_details}")
|
|
731
|
+
except Exception as e:
|
|
732
|
+
self.logger.error(f"[ALERT] Failed to fetch action details for action_id={action_id}: {e}", exc_info=True)
|
|
733
|
+
print("----- ALERT ACTION DETAILS ERROR -----")
|
|
734
|
+
print(f"action_id: {action_id}")
|
|
735
|
+
print(f"error: {e}")
|
|
736
|
+
print("--------------------------------------")
|
|
737
|
+
self._alert_manager_initialized = True
|
|
738
|
+
return
|
|
739
|
+
|
|
740
|
+
redis_client = None
|
|
741
|
+
kafka_client = None
|
|
742
|
+
|
|
743
|
+
# STRICT SWITCH: Only Redis if localhost, Only Kafka if cloud
|
|
744
|
+
if is_localhost:
|
|
745
|
+
# Initialize Redis client (ONLY) using STRICT API by instanceID
|
|
746
|
+
instance_id = getattr(self, "_instance_id", None)
|
|
747
|
+
if not instance_id:
|
|
748
|
+
self.logger.error("[ALERT] Localhost mode but instance_id missing in action details for Redis initialization")
|
|
749
|
+
else:
|
|
750
|
+
try:
|
|
751
|
+
backend_base = self._get_backend_base_url()
|
|
752
|
+
url = f"/v1/actions/get_redis_server_by_instance_id/{instance_id}"
|
|
753
|
+
self.logger.info(f"[ALERT] Initializing Redis client via API for Localhost mode (instance_id={instance_id})")
|
|
754
|
+
response = rpc.get(url)
|
|
755
|
+
if isinstance(response, dict) and response.get("success", False):
|
|
756
|
+
data = response.get("data", {})
|
|
757
|
+
host = data.get("host")
|
|
758
|
+
port = data.get("port")
|
|
759
|
+
username = data.get("username")
|
|
760
|
+
password = data.get("password", "")
|
|
761
|
+
db_index = data.get("db", 0)
|
|
762
|
+
conn_timeout = data.get("connection_timeout", 120)
|
|
763
|
+
|
|
764
|
+
print("----- REDIS SERVER PARAMS -----")
|
|
765
|
+
print(f"server_type: {server_type}")
|
|
766
|
+
print(f"instance_id: {instance_id}")
|
|
767
|
+
print(f"host: {host}")
|
|
768
|
+
print(f"port: {port}")
|
|
769
|
+
print(f"username: {username}")
|
|
770
|
+
print(f"password: {password}")
|
|
771
|
+
print(f"db: {db_index}")
|
|
772
|
+
print(f"connection_timeout: {conn_timeout}")
|
|
773
|
+
print("--------------------------------")
|
|
774
|
+
|
|
775
|
+
self.logger.info(f"[ALERT] Redis server params | instance_id={instance_id}, host={host}, port={port}, user={username}, db={db_index}")
|
|
776
|
+
|
|
777
|
+
# Initialize without gating on status
|
|
778
|
+
redis_client = MatriceStream(
|
|
779
|
+
StreamType.REDIS,
|
|
780
|
+
host=host,
|
|
781
|
+
port=int(port),
|
|
782
|
+
password=password,
|
|
783
|
+
username=username,
|
|
784
|
+
db=db_index,
|
|
785
|
+
connection_timeout=conn_timeout
|
|
786
|
+
)
|
|
787
|
+
redis_client.setup("alert_instant_config_request")
|
|
788
|
+
self.logger.info("[ALERT] Redis client initialized successfully")
|
|
789
|
+
else:
|
|
790
|
+
self.logger.warning(f"[ALERT] Failed to fetch Redis server info: {response.get('message', 'Unknown error') if isinstance(response, dict) else 'Unknown error'}")
|
|
791
|
+
except Exception as e:
|
|
792
|
+
self.logger.warning(f"[ALERT] Redis initialization failed: {e}")
|
|
793
|
+
|
|
794
|
+
else:
|
|
795
|
+
# Initialize Kafka client (ONLY) using STRICT API (global info endpoint)
|
|
796
|
+
try:
|
|
797
|
+
backend_base = self._get_backend_base_url()
|
|
798
|
+
url = f"/v1/actions/get_kafka_info"
|
|
799
|
+
self.logger.info("[ALERT] Initializing Kafka client via API for Cloud mode")
|
|
800
|
+
response = rpc.get(url)
|
|
801
|
+
if isinstance(response, dict) and response.get("success", False):
|
|
802
|
+
data = response.get("data", {})
|
|
803
|
+
enc_ip = data.get("ip")
|
|
804
|
+
enc_port = data.get("port")
|
|
805
|
+
ip_addr = None
|
|
806
|
+
port = None
|
|
807
|
+
try:
|
|
808
|
+
ip_addr = base64.b64decode(str(enc_ip)).decode("utf-8")
|
|
809
|
+
except Exception:
|
|
810
|
+
ip_addr = enc_ip
|
|
811
|
+
try:
|
|
812
|
+
port = base64.b64decode(str(enc_port)).decode("utf-8")
|
|
813
|
+
except Exception:
|
|
814
|
+
port = enc_port
|
|
815
|
+
|
|
816
|
+
print("----- KAFKA SERVER PARAMS -----")
|
|
817
|
+
print(f"server_type: {server_type}")
|
|
818
|
+
print(f"ipAddress: {ip_addr}")
|
|
819
|
+
print(f"port: {port}")
|
|
820
|
+
print("--------------------------------")
|
|
821
|
+
|
|
822
|
+
self.logger.info(f"[ALERT] Kafka server params | ip={ip_addr}, port={port}")
|
|
823
|
+
|
|
824
|
+
bootstrap_servers = f"{ip_addr}:{port}"
|
|
825
|
+
kafka_client = MatriceStream(
|
|
826
|
+
StreamType.KAFKA,
|
|
827
|
+
bootstrap_servers=bootstrap_servers,
|
|
828
|
+
sasl_mechanism="SCRAM-SHA-256",
|
|
829
|
+
sasl_username="matrice-sdk-user",
|
|
830
|
+
sasl_password="matrice-sdk-password",
|
|
831
|
+
security_protocol="SASL_PLAINTEXT"
|
|
832
|
+
)
|
|
833
|
+
kafka_client.setup("alert_instant_config_request", consumer_group_id="py_analytics_lpr_alerts")
|
|
834
|
+
self.logger.info(f"[ALERT] Kafka client initialized successfully (servers={bootstrap_servers})")
|
|
835
|
+
else:
|
|
836
|
+
self.logger.warning(f"[ALERT] Failed to fetch Kafka server info: {response.get('message', 'Unknown error') if isinstance(response, dict) else 'Unknown error'}")
|
|
837
|
+
except Exception as e:
|
|
838
|
+
self.logger.warning(f"[ALERT] Kafka initialization failed: {e}")
|
|
839
|
+
|
|
840
|
+
# Create alert manager if client is available
|
|
841
|
+
if redis_client or kafka_client:
|
|
842
|
+
self.alert_manager = ALERT_INSTANCE(
|
|
843
|
+
redis_client=redis_client,
|
|
844
|
+
kafka_client=kafka_client,
|
|
845
|
+
config_topic="alert_instant_config_request",
|
|
846
|
+
trigger_topic="alert_instant_triggered",
|
|
847
|
+
polling_interval=10, # Poll every 10 seconds
|
|
848
|
+
logger=self.logger
|
|
849
|
+
)
|
|
850
|
+
self.alert_manager.start()
|
|
851
|
+
transport = "Redis" if redis_client else "Kafka"
|
|
852
|
+
self.logger.info(f"[ALERT] Alert manager initialized and started with {transport} (polling every 10s)")
|
|
853
|
+
else:
|
|
854
|
+
self.logger.warning(f"[ALERT] No {'Redis' if is_localhost else 'Kafka'} client available for {'Localhost' if is_localhost else 'Cloud'} mode, alerts disabled")
|
|
855
|
+
|
|
856
|
+
except Exception as e:
|
|
857
|
+
self.logger.error(f"[ALERT] Alert manager initialization failed: {e}", exc_info=True)
|
|
858
|
+
finally:
|
|
859
|
+
self._alert_manager_initialized = True # Mark as initialized (don't retry every frame)
|
|
515
860
|
|
|
516
861
|
def reset_tracker(self) -> None:
|
|
517
862
|
"""Reset the advanced tracker instance."""
|
|
@@ -537,6 +882,149 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
537
882
|
self.reset_tracker()
|
|
538
883
|
self.reset_plate_tracking()
|
|
539
884
|
self.logger.info("All plate tracking state reset")
|
|
885
|
+
|
|
886
|
+
def _send_instant_alerts(
|
|
887
|
+
self,
|
|
888
|
+
detections: List[Dict[str, Any]],
|
|
889
|
+
stream_info: Optional[Dict[str, Any]],
|
|
890
|
+
config: LicensePlateMonitorConfig
|
|
891
|
+
) -> None:
|
|
892
|
+
"""
|
|
893
|
+
Send detection events to the instant alert system.
|
|
894
|
+
|
|
895
|
+
This method processes detections and sends them to the alert manager
|
|
896
|
+
for evaluation against active alert configurations.
|
|
897
|
+
|
|
898
|
+
Args:
|
|
899
|
+
detections: List of detection dictionaries with plate_text
|
|
900
|
+
stream_info: Stream information containing camera_id and other metadata
|
|
901
|
+
config: License plate monitoring configuration
|
|
902
|
+
"""
|
|
903
|
+
self.logger.info(f"[ALERT_DEBUG] ========== SEND INSTANT ALERTS ==========")
|
|
904
|
+
|
|
905
|
+
if not self.alert_manager:
|
|
906
|
+
self.logger.debug("[ALERT_DEBUG] Alert manager not configured, skipping instant alerts")
|
|
907
|
+
return
|
|
908
|
+
|
|
909
|
+
if not detections:
|
|
910
|
+
self.logger.debug("[ALERT_DEBUG] No detections to send to alert manager")
|
|
911
|
+
return
|
|
912
|
+
|
|
913
|
+
self.logger.info(f"[ALERT_DEBUG] Processing {len(detections)} detection(s) for alerts")
|
|
914
|
+
|
|
915
|
+
# Extract metadata directly from stream_info with empty string defaults
|
|
916
|
+
# No complex nested checks - if not found, pass empty string (no errors)
|
|
917
|
+
camera_id = ""
|
|
918
|
+
app_deployment_id = ""
|
|
919
|
+
application_id = ""
|
|
920
|
+
camera_name = ""
|
|
921
|
+
|
|
922
|
+
if stream_info:
|
|
923
|
+
self.logger.debug(f"[ALERT_DEBUG] stream_info keys: {list(stream_info.keys())}")
|
|
924
|
+
# Direct extraction with safe defaults
|
|
925
|
+
camera_id = stream_info.get("camera_id", "")
|
|
926
|
+
if not camera_id and "camera_info" in stream_info:
|
|
927
|
+
camera_id = stream_info.get("camera_info", {}).get("camera_id", "")
|
|
928
|
+
|
|
929
|
+
camera_name = stream_info.get("camera_name", "")
|
|
930
|
+
if not camera_name and "camera_info" in stream_info:
|
|
931
|
+
camera_name = stream_info.get("camera_info", {}).get("camera_name", "")
|
|
932
|
+
|
|
933
|
+
app_deployment_id = stream_info.get("app_deployment_id", "")
|
|
934
|
+
application_id = stream_info.get("application_id", stream_info.get("app_id", ""))
|
|
935
|
+
|
|
936
|
+
self.logger.debug(f"[ALERT_DEBUG] Extracted metadata from stream_info:")
|
|
937
|
+
self.logger.debug(f"[ALERT_DEBUG] - camera_id: '{camera_id}'")
|
|
938
|
+
self.logger.debug(f"[ALERT_DEBUG] - camera_name: '{camera_name}'")
|
|
939
|
+
self.logger.debug(f"[ALERT_DEBUG] - app_deployment_id: '{app_deployment_id}'")
|
|
940
|
+
self.logger.debug(f"[ALERT_DEBUG] - application_id: '{application_id}'")
|
|
941
|
+
else:
|
|
942
|
+
self.logger.warning("[ALERT_DEBUG] stream_info is None")
|
|
943
|
+
|
|
944
|
+
# Process each detection with a valid plate_text
|
|
945
|
+
sent_count = 0
|
|
946
|
+
skipped_count = 0
|
|
947
|
+
for i, detection in enumerate(detections):
|
|
948
|
+
self.logger.debug(f"[ALERT_DEBUG] --- Processing detection #{i+1} ---")
|
|
949
|
+
self.logger.debug(f"[ALERT_DEBUG] Detection keys: {list(detection.keys())}")
|
|
950
|
+
|
|
951
|
+
plate_text = detection.get('plate_text', '.')
|
|
952
|
+
if plate_text:
|
|
953
|
+
plate_text = plate_text.strip()
|
|
954
|
+
else:
|
|
955
|
+
plate_text = ''
|
|
956
|
+
self.logger.debug(f"[ALERT_DEBUG] Plate text: '{plate_text}'")
|
|
957
|
+
|
|
958
|
+
if not plate_text or plate_text == '':
|
|
959
|
+
self.logger.debug(f"[ALERT_DEBUG] Skipping detection #{i+1} - no plate_text")
|
|
960
|
+
skipped_count += 1
|
|
961
|
+
continue
|
|
962
|
+
|
|
963
|
+
# Extract detection metadata
|
|
964
|
+
confidence = detection.get('score', detection.get('confidence', 0.0))
|
|
965
|
+
bbox = detection.get('bbox', detection.get('bounding_box', []))
|
|
966
|
+
|
|
967
|
+
self.logger.debug(f"[ALERT_DEBUG] Confidence: {confidence}")
|
|
968
|
+
self.logger.debug(f"[ALERT_DEBUG] BBox: {bbox}")
|
|
969
|
+
|
|
970
|
+
# Build coordinates dict
|
|
971
|
+
coordinates = {}
|
|
972
|
+
if isinstance(bbox, dict):
|
|
973
|
+
# Handle dict format bbox
|
|
974
|
+
if 'xmin' in bbox:
|
|
975
|
+
coordinates = {
|
|
976
|
+
"x": int(bbox.get('xmin', 0)),
|
|
977
|
+
"y": int(bbox.get('ymin', 0)),
|
|
978
|
+
"width": int(bbox.get('xmax', 0) - bbox.get('xmin', 0)),
|
|
979
|
+
"height": int(bbox.get('ymax', 0) - bbox.get('ymin', 0))
|
|
980
|
+
}
|
|
981
|
+
elif 'x' in bbox:
|
|
982
|
+
coordinates = {
|
|
983
|
+
"x": int(bbox.get('x', 0)),
|
|
984
|
+
"y": int(bbox.get('y', 0)),
|
|
985
|
+
"width": int(bbox.get('width', 0)),
|
|
986
|
+
"height": int(bbox.get('height', 0))
|
|
987
|
+
}
|
|
988
|
+
elif isinstance(bbox, list) and len(bbox) >= 4:
|
|
989
|
+
x1, y1, x2, y2 = bbox[:4]
|
|
990
|
+
coordinates = {
|
|
991
|
+
"x": int(x1),
|
|
992
|
+
"y": int(y1),
|
|
993
|
+
"width": int(x2 - x1),
|
|
994
|
+
"height": int(y2 - y1)
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
self.logger.debug(f"[ALERT_DEBUG] Coordinates: {coordinates}")
|
|
998
|
+
|
|
999
|
+
# Build detection event for alert system
|
|
1000
|
+
detection_event = {
|
|
1001
|
+
"camera_id": camera_id,
|
|
1002
|
+
"app_deployment_id": app_deployment_id,
|
|
1003
|
+
"application_id": application_id,
|
|
1004
|
+
"detectionType": "license_plate",
|
|
1005
|
+
"plateNumber": plate_text,
|
|
1006
|
+
"confidence": float(confidence),
|
|
1007
|
+
"frameUrl": "", # Will be filled by analytics publisher if needed
|
|
1008
|
+
"coordinates": coordinates,
|
|
1009
|
+
"cameraName": camera_name,
|
|
1010
|
+
"vehicleType": detection.get('vehicle_type', ''),
|
|
1011
|
+
"vehicleColor": detection.get('vehicle_color', ''),
|
|
1012
|
+
"timestamp": datetime.now(timezone.utc).isoformat()
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
self.logger.info(f"[ALERT_DEBUG] Detection event #{i+1} built: {detection_event}")
|
|
1016
|
+
|
|
1017
|
+
# Send to alert manager for evaluation
|
|
1018
|
+
try:
|
|
1019
|
+
self.logger.info(f"[ALERT_DEBUG] Sending detection event #{i+1} to alert manager...")
|
|
1020
|
+
self.alert_manager.process_detection_event(detection_event)
|
|
1021
|
+
self.logger.info(f"[ALERT_DEBUG] ✓ Sent detection event to alert manager: plate={plate_text}, confidence={confidence:.2f}")
|
|
1022
|
+
sent_count += 1
|
|
1023
|
+
except Exception as e:
|
|
1024
|
+
self.logger.error(f"[ALERT_DEBUG] ❌ Error sending detection event to alert manager: {e}", exc_info=True)
|
|
1025
|
+
|
|
1026
|
+
self.logger.info(f"[ALERT_DEBUG] Summary: {sent_count} sent, {skipped_count} skipped")
|
|
1027
|
+
self.logger.info(f"[ALERT_DEBUG] ========== INSTANT ALERTS PROCESSED ==========")
|
|
540
1028
|
|
|
541
1029
|
def _initialize_plate_logger(self, config: LicensePlateMonitorConfig) -> bool:
|
|
542
1030
|
"""Initialize the plate logger if lpr_server_id is provided. Returns True if successful."""
|
|
@@ -576,6 +1064,7 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
576
1064
|
self.logger.info(f"[LP_LOGGING] Starting plate logging check - detections count: {len(detections)}")
|
|
577
1065
|
self.logger.info(f"[LP_LOGGING] Logging enabled: {self._logging_enabled}, Plate logger exists: {self.plate_logger is not None}, Stream info exists: {stream_info is not None}")
|
|
578
1066
|
|
|
1067
|
+
#self._logging_enabled = False # ToDo: DISABLED FOR NOW, ENABLED FOR PRODUCTION
|
|
579
1068
|
if not self._logging_enabled:
|
|
580
1069
|
print("[LP_LOGGING] Plate logging is DISABLED")
|
|
581
1070
|
self.logger.warning("[LP_LOGGING] Plate logging is DISABLED - logging_enabled flag is False")
|
|
@@ -586,10 +1075,10 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
586
1075
|
self.logger.warning("[LP_LOGGING] Plate logging SKIPPED - plate_logger is not initialized (lpr_server_id may not be configured)")
|
|
587
1076
|
return
|
|
588
1077
|
|
|
589
|
-
if not stream_info:
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
1078
|
+
# if not stream_info:
|
|
1079
|
+
# print("[LP_LOGGING] Plate logging SKIPPED - stream_info is None")
|
|
1080
|
+
# self.logger.warning("[LP_LOGGING] Plate logging SKIPPED - stream_info is None")
|
|
1081
|
+
# return
|
|
593
1082
|
|
|
594
1083
|
print("[LP_LOGGING] All pre-conditions met, proceeding with plate logging")
|
|
595
1084
|
self.logger.info(f"[LP_LOGGING] All pre-conditions met, proceeding with plate logging")
|
|
@@ -617,6 +1106,7 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
617
1106
|
else:
|
|
618
1107
|
self.logger.warning(f"[LP_LOGGING] Failed to decode image bytes")
|
|
619
1108
|
except Exception as e:
|
|
1109
|
+
#pass
|
|
620
1110
|
self.logger.error(f"[LP_LOGGING] Exception while encoding frame image: {e}", exc_info=True)
|
|
621
1111
|
else:
|
|
622
1112
|
self.logger.info(f"[LP_LOGGING] No image_bytes provided, sending without image")
|
|
@@ -657,14 +1147,18 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
657
1147
|
print(f"[LP_LOGGING] Plate {plate_text}: {status}")
|
|
658
1148
|
self.logger.info(f"[LP_LOGGING] Plate {plate_text}: {status}")
|
|
659
1149
|
except Exception as e:
|
|
1150
|
+
#pass
|
|
660
1151
|
print(f"[LP_LOGGING] ERROR - Plate {plate_text} failed: {e}")
|
|
661
1152
|
self.logger.error(f"[LP_LOGGING] Plate {plate_text} raised exception: {e}", exc_info=True)
|
|
662
1153
|
|
|
663
1154
|
print("[LP_LOGGING] Plate logging complete")
|
|
664
1155
|
self.logger.info(f"[LP_LOGGING] Plate logging complete")
|
|
665
1156
|
except Exception as e:
|
|
1157
|
+
print(f"[LP_LOGGING] CRITICAL ERROR during plate logging: {e}")
|
|
1158
|
+
|
|
666
1159
|
print(f"[LP_LOGGING] CRITICAL ERROR during plate logging: {e}")
|
|
667
1160
|
self.logger.error(f"[LP_LOGGING] CRITICAL ERROR during plate logging: {e}", exc_info=True)
|
|
1161
|
+
pass
|
|
668
1162
|
else:
|
|
669
1163
|
print("[LP_LOGGING] No plates to log")
|
|
670
1164
|
self.logger.info(f"[LP_LOGGING] No plates to log (plates_to_log is empty)")
|
|
@@ -693,11 +1187,16 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
693
1187
|
else:
|
|
694
1188
|
self.logger.error(f"[LP_LOGGING] Plate logger initialization FAILED - plates will NOT be sent")
|
|
695
1189
|
elif self._plate_logger_initialized:
|
|
696
|
-
|
|
1190
|
+
self.logger.debug(f"[LP_LOGGING] Plate logger already initialized, skipping re-initialization")
|
|
697
1191
|
elif not config.lpr_server_id:
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
1192
|
+
if self._total_frame_counter == 0: #Only log once at start
|
|
1193
|
+
self.logger.warning(f"[LP_LOGGING] Plate logging will be DISABLED - no lpr_server_id provided in config")
|
|
1194
|
+
|
|
1195
|
+
# Initialize alert manager once (lazy initialization on first call)
|
|
1196
|
+
if not self._alert_manager_initialized:
|
|
1197
|
+
self._initialize_alert_manager_once(config)
|
|
1198
|
+
self.logger.info(f"[ALERT] CONFIG OF ALERT SHOULD BE PRINTED")
|
|
1199
|
+
|
|
701
1200
|
# Normalize alert_config if provided as a plain dict (JS JSON)
|
|
702
1201
|
if isinstance(getattr(config, 'alert_config', None), dict):
|
|
703
1202
|
try:
|
|
@@ -718,13 +1217,13 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
718
1217
|
# print("---------CONFIDENCE FILTERING",config.confidence_threshold)
|
|
719
1218
|
# print("---------DATA1--------------",data)
|
|
720
1219
|
processed_data = filter_by_confidence(data, config.confidence_threshold)
|
|
721
|
-
|
|
1220
|
+
self.logger.debug(f"Applied confidence filtering with threshold {config.confidence_threshold}")
|
|
722
1221
|
|
|
723
1222
|
# Step 2: Apply category mapping if provided
|
|
724
1223
|
if config.index_to_category:
|
|
725
1224
|
processed_data = apply_category_mapping(processed_data, config.index_to_category)
|
|
726
1225
|
#self.logger.debug("Applied category mapping")
|
|
727
|
-
|
|
1226
|
+
print("---------DATA2-STREAM--------------",stream_info)
|
|
728
1227
|
# Step 3: Filter to target categories (handle dict or list)
|
|
729
1228
|
if isinstance(processed_data, dict):
|
|
730
1229
|
processed_data = processed_data.get("detections", [])
|
|
@@ -775,25 +1274,29 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
775
1274
|
#print("---------DATA5--------------",processed_data)
|
|
776
1275
|
# Step 8: Perform OCR on media
|
|
777
1276
|
ocr_analysis = self._analyze_ocr_in_media(processed_data, input_bytes, config)
|
|
778
|
-
self.logger.info(f"[LP_LOGGING] OCR analysis completed, found {len(ocr_analysis)} results")
|
|
1277
|
+
#self.logger.info(f"[LP_LOGGING] OCR analysis completed, found {len(ocr_analysis)} results")
|
|
779
1278
|
ocr_plates_found = [r.get('plate_text') for r in ocr_analysis if r.get('plate_text')]
|
|
780
|
-
if ocr_plates_found:
|
|
781
|
-
|
|
782
|
-
else:
|
|
783
|
-
|
|
1279
|
+
# if ocr_plates_found:
|
|
1280
|
+
# self.logger.info(f"[LP_LOGGING] OCR detected plates: {ocr_plates_found}")
|
|
1281
|
+
# else:
|
|
1282
|
+
# self.logger.warning(f"[LP_LOGGING] OCR did not detect any valid plate texts")
|
|
784
1283
|
|
|
785
1284
|
# Step 9: Update plate texts
|
|
786
1285
|
processed_data = self._update_detections_with_ocr(processed_data, ocr_analysis)
|
|
787
1286
|
self._update_plate_texts(processed_data)
|
|
788
|
-
|
|
1287
|
+
print("[LP_LOGGING]DEBUG -1")
|
|
1288
|
+
|
|
789
1289
|
# Log final detection state before sending
|
|
790
1290
|
final_plates = [d.get('plate_text') for d in processed_data if d.get('plate_text')]
|
|
791
1291
|
self.logger.info(f"[LP_LOGGING] After OCR update, {len(final_plates)} detections have plate_text: {final_plates}")
|
|
792
|
-
|
|
1292
|
+
|
|
793
1293
|
# Step 9.5: Log detected plates to RPC (optional, only if lpr_server_id is provided)
|
|
794
1294
|
# Direct await since process is now async
|
|
795
1295
|
await self._log_detected_plates(processed_data, config, stream_info, input_bytes)
|
|
796
|
-
|
|
1296
|
+
print("[LP_LOGGING]DEBUG -2")
|
|
1297
|
+
# Step 9.6: Send detections to instant alert system (if configured)
|
|
1298
|
+
self._send_instant_alerts(processed_data, stream_info, config)
|
|
1299
|
+
print("[LP_LOGGING]DEBUG -3")
|
|
797
1300
|
# Step 10: Update frame counter
|
|
798
1301
|
self._total_frame_counter += 1
|
|
799
1302
|
|
|
@@ -809,6 +1312,7 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
809
1312
|
# Step 12: Calculate summaries
|
|
810
1313
|
counting_summary = self._count_categories(processed_data, config)
|
|
811
1314
|
counting_summary['total_counts'] = self.get_total_counts()
|
|
1315
|
+
print("[LP_LOGGING]DEBUG -4")
|
|
812
1316
|
|
|
813
1317
|
# Step 13: Generate alerts and summaries
|
|
814
1318
|
alerts = self._check_alerts(counting_summary, frame_number, config)
|
|
@@ -856,7 +1360,7 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
856
1360
|
processing_fps = (1.0 / proc_time) if proc_time > 0 else None
|
|
857
1361
|
# Log the performance metrics using the module-level logger
|
|
858
1362
|
print("latency in ms:",processing_latency_ms,"| Throughput fps:",processing_fps,"| Frame_Number:",self._total_frame_counter)
|
|
859
|
-
|
|
1363
|
+
print("[LP_LOGGING]DEBUG -5")
|
|
860
1364
|
return result
|
|
861
1365
|
|
|
862
1366
|
except Exception as e:
|
|
@@ -1162,35 +1666,39 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
1162
1666
|
human_text_lines = []
|
|
1163
1667
|
#print("counting_summary", counting_summary)
|
|
1164
1668
|
human_text_lines.append(f"CURRENT FRAME @ {current_timestamp}:")
|
|
1669
|
+
sum_of_current_frame_detections = sum(per_category_count.values())
|
|
1670
|
+
|
|
1165
1671
|
if total_detections > 0:
|
|
1672
|
+
#for cat, count in per_category_count.items():
|
|
1673
|
+
human_text_lines.append(f"\t- License Plates Detected: {sum_of_current_frame_detections}")
|
|
1166
1674
|
category_counts = [f"{count} {cat}" for cat, count in per_category_count.items()]
|
|
1167
1675
|
detection_text = category_counts[0] + " detected" if len(category_counts) == 1 else f"{', '.join(category_counts[:-1])}, and {category_counts[-1]} detected"
|
|
1168
|
-
human_text_lines.append(f"\t- {detection_text}")
|
|
1169
|
-
#
|
|
1676
|
+
#human_text_lines.append(f"\t- {detection_text}")
|
|
1677
|
+
#Show dominant per-track license plates for current frame
|
|
1170
1678
|
seen = set()
|
|
1171
1679
|
display_texts = []
|
|
1172
1680
|
for det in counting_summary.get("detections", []):
|
|
1173
1681
|
t = det.get("track_id")
|
|
1174
1682
|
dom = det.get("plate_text")
|
|
1175
|
-
if not dom or not (self._min_plate_len <= len(dom) <=
|
|
1683
|
+
if not dom or not (self._min_plate_len <= len(dom) <= 5):
|
|
1176
1684
|
continue
|
|
1177
1685
|
if t in seen:
|
|
1178
1686
|
continue
|
|
1179
1687
|
seen.add(t)
|
|
1180
1688
|
display_texts.append(dom)
|
|
1181
|
-
if display_texts:
|
|
1182
|
-
|
|
1689
|
+
# if display_texts:
|
|
1690
|
+
# human_text_lines.append(f"\t- License Plates: {', '.join(display_texts)}")
|
|
1183
1691
|
else:
|
|
1184
|
-
human_text_lines.append(f"\t-
|
|
1692
|
+
human_text_lines.append(f"\t- License Plates Detected: 0")
|
|
1185
1693
|
|
|
1186
1694
|
human_text_lines.append("")
|
|
1187
|
-
human_text_lines.append(f"TOTAL SINCE {start_timestamp}:")
|
|
1188
|
-
human_text_lines.append(f"\t- Total Detected: {cumulative_total}")
|
|
1695
|
+
# human_text_lines.append(f"TOTAL SINCE {start_timestamp}:")
|
|
1696
|
+
# human_text_lines.append(f"\t- Total Detected: {cumulative_total}")
|
|
1189
1697
|
|
|
1190
|
-
if self._unique_plate_texts:
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1698
|
+
# if self._unique_plate_texts:
|
|
1699
|
+
# human_text_lines.append("\t- Unique License Plates:")
|
|
1700
|
+
# for text in sorted(self._unique_plate_texts.values()):
|
|
1701
|
+
# human_text_lines.append(f"\t\t- {text}")
|
|
1194
1702
|
|
|
1195
1703
|
current_counts = [{"category": cat, "count": count} for cat, count in per_category_count.items() if count > 0 or total_detections > 0]
|
|
1196
1704
|
total_counts_list = [{"category": cat, "count": count} for cat, count in total_counts.items() if count > 0 or cumulative_total > 0]
|
|
@@ -1200,10 +1708,10 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
1200
1708
|
for detection in counting_summary.get("detections", []):
|
|
1201
1709
|
dom = detection.get("plate_text", "")
|
|
1202
1710
|
if not dom:
|
|
1203
|
-
dom = "
|
|
1711
|
+
dom = ""
|
|
1204
1712
|
bbox = detection.get("bounding_box", {})
|
|
1205
|
-
category = detection.get("category", "
|
|
1206
|
-
|
|
1713
|
+
category = detection.get("category", "")
|
|
1714
|
+
#egmentation = detection.get("masks", detection.get("segmentation", detection.get("mask", [])))
|
|
1207
1715
|
detection_obj = self.create_detection_object(category, bbox, segmentation=None, plate_text=dom)
|
|
1208
1716
|
detections.append(detection_obj)
|
|
1209
1717
|
|