matrice-analytics 0.1.35__py3-none-any.whl → 0.1.37__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of matrice-analytics might be problematic. Click here for more details.
- matrice_analytics/post_processing/post_processor.py +2 -1
- matrice_analytics/post_processing/usecases/license_plate_monitoring.py +173 -84
- {matrice_analytics-0.1.35.dist-info → matrice_analytics-0.1.37.dist-info}/METADATA +1 -1
- {matrice_analytics-0.1.35.dist-info → matrice_analytics-0.1.37.dist-info}/RECORD +7 -7
- {matrice_analytics-0.1.35.dist-info → matrice_analytics-0.1.37.dist-info}/WHEEL +0 -0
- {matrice_analytics-0.1.35.dist-info → matrice_analytics-0.1.37.dist-info}/licenses/LICENSE.txt +0 -0
- {matrice_analytics-0.1.35.dist-info → matrice_analytics-0.1.37.dist-info}/top_level.txt +0 -0
|
@@ -25,6 +25,7 @@ import numpy as np
|
|
|
25
25
|
import re
|
|
26
26
|
from collections import Counter, defaultdict
|
|
27
27
|
import sys
|
|
28
|
+
import subprocess
|
|
28
29
|
import logging
|
|
29
30
|
import asyncio
|
|
30
31
|
import urllib
|
|
@@ -37,33 +38,96 @@ print(f"Python version: {major_version}.{minor_version}")
|
|
|
37
38
|
os.environ["ORT_LOG_SEVERITY_LEVEL"] = "3"
|
|
38
39
|
|
|
39
40
|
|
|
40
|
-
#
|
|
41
|
+
# Lazy import mechanism for LicensePlateRecognizer
|
|
41
42
|
_OCR_IMPORT_SOURCE = None
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
_LicensePlateRecognizerClass = None
|
|
44
|
+
|
|
45
|
+
def _get_license_plate_recognizer_class():
|
|
46
|
+
"""Lazy load LicensePlateRecognizer with automatic installation fallback."""
|
|
47
|
+
global _OCR_IMPORT_SOURCE, _LicensePlateRecognizerClass
|
|
48
|
+
|
|
49
|
+
if _LicensePlateRecognizerClass is not None:
|
|
50
|
+
return _LicensePlateRecognizerClass
|
|
51
|
+
|
|
52
|
+
# Try to import from local repo first
|
|
53
|
+
try:
|
|
54
|
+
from ..ocr.fast_plate_ocr_py38 import LicensePlateRecognizer
|
|
55
|
+
_OCR_IMPORT_SOURCE = "local_repo"
|
|
56
|
+
_LicensePlateRecognizerClass = LicensePlateRecognizer
|
|
57
|
+
logging.info("Successfully imported LicensePlateRecognizer from local repo")
|
|
58
|
+
return _LicensePlateRecognizerClass
|
|
59
|
+
except ImportError as e:
|
|
60
|
+
logging.debug(f"Could not import from local repo: {e}")
|
|
61
|
+
|
|
62
|
+
# Try to import from installed package
|
|
46
63
|
try:
|
|
47
64
|
from fast_plate_ocr import LicensePlateRecognizer # type: ignore
|
|
48
65
|
_OCR_IMPORT_SOURCE = "installed_package"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
66
|
+
_LicensePlateRecognizerClass = LicensePlateRecognizer
|
|
67
|
+
logging.info("Successfully imported LicensePlateRecognizer from installed package")
|
|
68
|
+
return _LicensePlateRecognizerClass
|
|
69
|
+
except ImportError as e:
|
|
70
|
+
logging.warning(f"Could not import from installed package: {e}")
|
|
71
|
+
|
|
72
|
+
# Try to install with GPU support first
|
|
73
|
+
logging.info("Attempting to install fast-plate-ocr with GPU support...")
|
|
74
|
+
try:
|
|
75
|
+
import subprocess
|
|
76
|
+
result = subprocess.run(
|
|
77
|
+
[sys.executable, "-m", "pip", "install", "fast-plate-ocr[onnx-gpu]", "--no-cache-dir"],
|
|
78
|
+
capture_output=True,
|
|
79
|
+
text=True,
|
|
80
|
+
timeout=300
|
|
81
|
+
)
|
|
82
|
+
if result.returncode == 0:
|
|
83
|
+
logging.info("Successfully installed fast-plate-ocr[onnx-gpu]")
|
|
84
|
+
try:
|
|
85
|
+
from fast_plate_ocr import LicensePlateRecognizer # type: ignore
|
|
86
|
+
_OCR_IMPORT_SOURCE = "installed_package_gpu"
|
|
87
|
+
_LicensePlateRecognizerClass = LicensePlateRecognizer
|
|
88
|
+
logging.info("Successfully imported LicensePlateRecognizer after GPU installation")
|
|
89
|
+
return _LicensePlateRecognizerClass
|
|
90
|
+
except ImportError as e:
|
|
91
|
+
logging.warning(f"Installation succeeded but import failed: {e}")
|
|
92
|
+
else:
|
|
93
|
+
logging.warning(f"GPU installation failed: {result.stderr}")
|
|
94
|
+
except Exception as e:
|
|
95
|
+
logging.warning(f"Error during GPU installation: {e}")
|
|
96
|
+
|
|
97
|
+
# Try to install with CPU support as fallback
|
|
98
|
+
logging.info("Attempting to install fast-plate-ocr with CPU support...")
|
|
99
|
+
try:
|
|
100
|
+
import subprocess
|
|
101
|
+
result = subprocess.run(
|
|
102
|
+
[sys.executable, "-m", "pip", "install", "fast-plate-ocr[onnx]", "--no-cache-dir"],
|
|
103
|
+
capture_output=True,
|
|
104
|
+
text=True,
|
|
105
|
+
timeout=300
|
|
106
|
+
)
|
|
107
|
+
if result.returncode == 0:
|
|
108
|
+
logging.info("Successfully installed fast-plate-ocr[onnx]")
|
|
109
|
+
try:
|
|
110
|
+
from fast_plate_ocr import LicensePlateRecognizer # type: ignore
|
|
111
|
+
_OCR_IMPORT_SOURCE = "installed_package_cpu"
|
|
112
|
+
_LicensePlateRecognizerClass = LicensePlateRecognizer
|
|
113
|
+
logging.info("Successfully imported LicensePlateRecognizer after CPU installation")
|
|
114
|
+
return _LicensePlateRecognizerClass
|
|
115
|
+
except ImportError as e:
|
|
116
|
+
logging.error(f"Installation succeeded but import failed: {e}")
|
|
117
|
+
else:
|
|
118
|
+
logging.error(f"CPU installation failed: {result.stderr}")
|
|
119
|
+
except Exception as e:
|
|
120
|
+
logging.error(f"Error during CPU installation: {e}")
|
|
121
|
+
|
|
122
|
+
# Return None if all attempts failed
|
|
123
|
+
logging.error("All attempts to load or install LicensePlateRecognizer failed")
|
|
124
|
+
_OCR_IMPORT_SOURCE = "unavailable"
|
|
125
|
+
return None
|
|
56
126
|
|
|
57
127
|
# Internal utilities that are still required
|
|
58
128
|
from ..ocr.preprocessing import ImagePreprocessor
|
|
59
129
|
from ..core.config import BaseConfig, AlertConfig, ZoneConfig
|
|
60
130
|
|
|
61
|
-
# (Catch import errors early in the logs)
|
|
62
|
-
try:
|
|
63
|
-
_ = LicensePlateRecognizer # noqa: B018 reference to quiet linters
|
|
64
|
-
except Exception as _e:
|
|
65
|
-
print(f"Warning: fast_plate_ocr could not be imported {_e}")
|
|
66
|
-
|
|
67
131
|
try:
|
|
68
132
|
from matrice_common.session import Session
|
|
69
133
|
HAS_MATRICE_SESSION = True
|
|
@@ -123,6 +187,8 @@ class LicensePlateMonitorLogger:
|
|
|
123
187
|
|
|
124
188
|
def initialize_session(self, config: LicensePlateMonitorConfig) -> None:
|
|
125
189
|
"""Initialize session and fetch server connection info if lpr_server_id is provided."""
|
|
190
|
+
print("[LP_LOGGING] ===== INITIALIZING LP LOGGER SESSION =====")
|
|
191
|
+
print(f"[LP_LOGGING] Config lpr_server_id: {config.lpr_server_id}")
|
|
126
192
|
self.logger.info("[LP_LOGGING] ===== INITIALIZING LP LOGGER SESSION =====")
|
|
127
193
|
self.logger.info(f"[LP_LOGGING] Config lpr_server_id: {config.lpr_server_id}")
|
|
128
194
|
|
|
@@ -204,6 +270,7 @@ class LicensePlateMonitorLogger:
|
|
|
204
270
|
else:
|
|
205
271
|
self.logger.warning("[LP_LOGGING] No lpr_server_id provided in config, skipping server connection info fetch")
|
|
206
272
|
|
|
273
|
+
print("[LP_LOGGING] ===== LP LOGGER SESSION INITIALIZATION COMPLETE =====")
|
|
207
274
|
self.logger.info("[LP_LOGGING] ===== LP LOGGER SESSION INITIALIZATION COMPLETE =====")
|
|
208
275
|
|
|
209
276
|
def _get_public_ip(self) -> str:
|
|
@@ -261,9 +328,11 @@ class LicensePlateMonitorLogger:
|
|
|
261
328
|
time_since_last_log = current_time - last_log_time
|
|
262
329
|
|
|
263
330
|
if time_since_last_log >= cooldown:
|
|
331
|
+
print(f"[LP_LOGGING] ✓ Plate '{plate_text}' ready to log ({time_since_last_log:.1f}s since last)")
|
|
264
332
|
self.logger.info(f"[LP_LOGGING] OK - Plate '{plate_text}' ready to log (last logged {time_since_last_log:.1f}s ago, cooldown={cooldown}s)")
|
|
265
333
|
return True
|
|
266
334
|
else:
|
|
335
|
+
print(f"[LP_LOGGING] ⊗ Plate '{plate_text}' in cooldown ({cooldown - time_since_last_log:.1f}s remaining)")
|
|
267
336
|
self.logger.info(f"[LP_LOGGING] SKIP - Plate '{plate_text}' in cooldown period ({time_since_last_log:.1f}s elapsed, {cooldown - time_since_last_log:.1f}s remaining)")
|
|
268
337
|
return False
|
|
269
338
|
|
|
@@ -325,11 +394,14 @@ class LicensePlateMonitorLogger:
|
|
|
325
394
|
image_data: Base64-encoded JPEG image of the license plate crop
|
|
326
395
|
cooldown: Cooldown period in seconds
|
|
327
396
|
"""
|
|
397
|
+
print(f"[LP_LOGGING] ===== PLATE LOG REQUEST START =====")
|
|
398
|
+
print(f"[LP_LOGGING] Plate: '{plate_text}', Timestamp: {timestamp}")
|
|
328
399
|
self.logger.info(f"[LP_LOGGING] ===== PLATE LOG REQUEST START =====")
|
|
329
400
|
self.logger.info(f"[LP_LOGGING] Plate: '{plate_text}', Timestamp: {timestamp}")
|
|
330
401
|
|
|
331
402
|
# Check cooldown
|
|
332
403
|
if not self.should_log_plate(plate_text, cooldown):
|
|
404
|
+
print(f"[LP_LOGGING] Plate '{plate_text}' NOT SENT - cooldown")
|
|
333
405
|
self.logger.info(f"[LP_LOGGING] Plate '{plate_text}' NOT SENT - skipped due to cooldown period")
|
|
334
406
|
self.logger.info(f"[LP_LOGGING] ===== PLATE LOG REQUEST END (SKIPPED) =====")
|
|
335
407
|
return False
|
|
@@ -340,6 +412,7 @@ class LicensePlateMonitorLogger:
|
|
|
340
412
|
location = camera_info.get("location", "")
|
|
341
413
|
frame_id = stream_info.get("frame_id", "")
|
|
342
414
|
|
|
415
|
+
print(f"[LP_LOGGING] Camera: '{camera_name}', Location: '{location}'")
|
|
343
416
|
self.logger.info(f"[LP_LOGGING] Stream Info - Camera: '{camera_name}', Location: '{location}', Frame ID: '{frame_id}'")
|
|
344
417
|
|
|
345
418
|
# Get project ID from server_info
|
|
@@ -363,20 +436,24 @@ class LicensePlateMonitorLogger:
|
|
|
363
436
|
# Add projectId as query parameter
|
|
364
437
|
endpoint = f'/v1/lpr-server/detections?projectId={project_id}'
|
|
365
438
|
full_url = f"{self.server_base_url}{endpoint}"
|
|
439
|
+
print(f"[LP_LOGGING] Sending POST to: {full_url}")
|
|
366
440
|
self.logger.info(f"[LP_LOGGING] Sending POST request to: {full_url}")
|
|
367
441
|
self.logger.info(f"[LP_LOGGING] Payload: licensePlate='{plate_text}', frameId='{frame_id}', location='{location}', camera='{camera_name}', imageData length={len(image_data) if image_data else 0}")
|
|
368
442
|
|
|
369
443
|
response = await self.session.rpc.post_async(endpoint, payload=payload, base_url=self.server_base_url)
|
|
370
444
|
|
|
445
|
+
print(f"[LP_LOGGING] Response: {response}")
|
|
371
446
|
self.logger.info(f"[LP_LOGGING] API Response received: {response}")
|
|
372
447
|
|
|
373
448
|
# Update timestamp after successful log
|
|
374
449
|
self.update_log_timestamp(plate_text)
|
|
450
|
+
print(f"[LP_LOGGING] ✓ Plate '{plate_text}' SUCCESSFULLY SENT")
|
|
375
451
|
self.logger.info(f"[LP_LOGGING] Plate '{plate_text}' SUCCESSFULLY SENT at {rfc3339_timestamp}")
|
|
376
452
|
self.logger.info(f"[LP_LOGGING] ===== PLATE LOG REQUEST END (SUCCESS) =====")
|
|
377
453
|
return True
|
|
378
454
|
|
|
379
455
|
except Exception as e:
|
|
456
|
+
print(f"[LP_LOGGING] ✗ Plate '{plate_text}' FAILED - {e}")
|
|
380
457
|
self.logger.error(f"[LP_LOGGING] Plate '{plate_text}' NOT SENT - Exception occurred: {e}", exc_info=True)
|
|
381
458
|
self.logger.info(f"[LP_LOGGING] ===== PLATE LOG REQUEST END (FAILED) =====")
|
|
382
459
|
return False
|
|
@@ -384,13 +461,6 @@ class LicensePlateMonitorLogger:
|
|
|
384
461
|
class LicensePlateMonitorUseCase(BaseProcessor):
|
|
385
462
|
CATEGORY_DISPLAY = {"license_plate": "license_plate"}
|
|
386
463
|
|
|
387
|
-
# --------------------------------------------------------------
|
|
388
|
-
# Shared resources (initialised once per process)
|
|
389
|
-
# --------------------------------------------------------------
|
|
390
|
-
_ocr_model: Optional[LicensePlateRecognizer] = None # Fast plate OCR
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
464
|
def __init__(self):
|
|
395
465
|
super().__init__("license_plate_monitor")
|
|
396
466
|
self.category = "license_plate_monitor"
|
|
@@ -420,22 +490,9 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
420
490
|
# Map of track_id -> current dominant plate text
|
|
421
491
|
self.unique_plate_track: Dict[Any, str] = {}
|
|
422
492
|
self.image_preprocessor = ImagePreprocessor()
|
|
423
|
-
#
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
# Using stub - log warning once
|
|
427
|
-
self.logger.error("OCR module not available. LicensePlateRecognizer will not function. Install: pip install fast-plate-ocr[onnx]")
|
|
428
|
-
LicensePlateMonitorUseCase._ocr_model = LicensePlateRecognizer('cct-s-v1-global-model')
|
|
429
|
-
else:
|
|
430
|
-
# Try to load real OCR model
|
|
431
|
-
try:
|
|
432
|
-
LicensePlateMonitorUseCase._ocr_model = LicensePlateRecognizer('cct-s-v1-global-model')
|
|
433
|
-
source_msg = "from local repo" if _OCR_IMPORT_SOURCE == "local_repo" else "from installed package"
|
|
434
|
-
self.logger.info(f"LicensePlateRecognizer loaded successfully {source_msg}")
|
|
435
|
-
except Exception as e:
|
|
436
|
-
self.logger.error(f"Failed to initialize LicensePlateRecognizer: {e}", exc_info=True)
|
|
437
|
-
LicensePlateMonitorUseCase._ocr_model = None
|
|
438
|
-
self.ocr_model = LicensePlateMonitorUseCase._ocr_model
|
|
493
|
+
# OCR model will be lazily initialized when first used
|
|
494
|
+
self.ocr_model = None
|
|
495
|
+
self._ocr_initialization_attempted = False
|
|
439
496
|
# OCR text history for stability checks (text consecutive frame count)
|
|
440
497
|
self._text_history: Dict[str, int] = {}
|
|
441
498
|
|
|
@@ -511,25 +568,30 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
511
568
|
self.logger.error(f"[LP_LOGGING] Plate logging has been DISABLED due to initialization failure")
|
|
512
569
|
return False
|
|
513
570
|
|
|
514
|
-
def _log_detected_plates(self, detections: List[Dict[str, Any]], config: LicensePlateMonitorConfig,
|
|
571
|
+
async def _log_detected_plates(self, detections: List[Dict[str, Any]], config: LicensePlateMonitorConfig,
|
|
515
572
|
stream_info: Optional[Dict[str, Any]], image_bytes: Optional[bytes] = None) -> None:
|
|
516
573
|
"""Log all detected plates to RPC server with cooldown."""
|
|
517
574
|
# Enhanced logging for diagnostics
|
|
575
|
+
print(f"[LP_LOGGING] Starting plate logging check - detections count: {len(detections)}")
|
|
518
576
|
self.logger.info(f"[LP_LOGGING] Starting plate logging check - detections count: {len(detections)}")
|
|
519
577
|
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}")
|
|
520
578
|
|
|
521
579
|
if not self._logging_enabled:
|
|
580
|
+
print("[LP_LOGGING] Plate logging is DISABLED")
|
|
522
581
|
self.logger.warning("[LP_LOGGING] Plate logging is DISABLED - logging_enabled flag is False")
|
|
523
582
|
return
|
|
524
583
|
|
|
525
584
|
if not self.plate_logger:
|
|
585
|
+
print("[LP_LOGGING] Plate logging SKIPPED - plate_logger not initialized")
|
|
526
586
|
self.logger.warning("[LP_LOGGING] Plate logging SKIPPED - plate_logger is not initialized (lpr_server_id may not be configured)")
|
|
527
587
|
return
|
|
528
588
|
|
|
529
589
|
if not stream_info:
|
|
590
|
+
print("[LP_LOGGING] Plate logging SKIPPED - stream_info is None")
|
|
530
591
|
self.logger.warning("[LP_LOGGING] Plate logging SKIPPED - stream_info is None")
|
|
531
592
|
return
|
|
532
593
|
|
|
594
|
+
print("[LP_LOGGING] All pre-conditions met, proceeding with plate logging")
|
|
533
595
|
self.logger.info(f"[LP_LOGGING] All pre-conditions met, proceeding with plate logging")
|
|
534
596
|
|
|
535
597
|
# Get current timestamp
|
|
@@ -569,49 +631,45 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
569
631
|
continue
|
|
570
632
|
plates_to_log.add(plate_text)
|
|
571
633
|
|
|
634
|
+
print(f"[LP_LOGGING] Collected {len(plates_to_log)} unique plates to log: {plates_to_log}")
|
|
572
635
|
self.logger.info(f"[LP_LOGGING] Collected {len(plates_to_log)} unique plates to log: {plates_to_log}")
|
|
573
636
|
if detections_without_text > 0:
|
|
574
637
|
self.logger.warning(f"[LP_LOGGING] {detections_without_text} detections have NO plate_text (OCR may have failed or not run yet)")
|
|
575
638
|
|
|
576
|
-
# Log each unique plate (respecting cooldown)
|
|
639
|
+
# Log each unique plate directly with await (respecting cooldown)
|
|
577
640
|
if plates_to_log:
|
|
578
|
-
|
|
641
|
+
print(f"[LP_LOGGING] Logging {len(plates_to_log)} plates with cooldown={config.plate_log_cooldown}s")
|
|
642
|
+
self.logger.info(f"[LP_LOGGING] Logging {len(plates_to_log)} plates with cooldown={config.plate_log_cooldown}s")
|
|
579
643
|
try:
|
|
580
|
-
#
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
self.logger.info(f"[LP_LOGGING] Creating task for plate: {plate_text}")
|
|
587
|
-
task = self.plate_logger.log_plate(
|
|
644
|
+
# Call log_plate directly with await for each plate
|
|
645
|
+
for plate_text in plates_to_log:
|
|
646
|
+
print(f"[LP_LOGGING] Processing plate: {plate_text}")
|
|
647
|
+
self.logger.info(f"[LP_LOGGING] Processing plate: {plate_text}")
|
|
648
|
+
try:
|
|
649
|
+
result = await self.plate_logger.log_plate(
|
|
588
650
|
plate_text=plate_text,
|
|
589
651
|
timestamp=current_timestamp,
|
|
590
652
|
stream_info=stream_info,
|
|
591
653
|
image_data=image_data,
|
|
592
654
|
cooldown=config.plate_log_cooldown
|
|
593
655
|
)
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
self.logger.error(f"[LP_LOGGING] Task {i} for plate {plate} raised exception: {result}")
|
|
604
|
-
else:
|
|
605
|
-
self.logger.info(f"[LP_LOGGING] Task {i} for plate {plate} completed with result: {result}")
|
|
606
|
-
finally:
|
|
607
|
-
loop.close()
|
|
608
|
-
self.logger.info(f"[LP_LOGGING] Event loop closed")
|
|
656
|
+
status = "SENT" if result else "SKIPPED (cooldown)"
|
|
657
|
+
print(f"[LP_LOGGING] Plate {plate_text}: {status}")
|
|
658
|
+
self.logger.info(f"[LP_LOGGING] Plate {plate_text}: {status}")
|
|
659
|
+
except Exception as e:
|
|
660
|
+
print(f"[LP_LOGGING] ERROR - Plate {plate_text} failed: {e}")
|
|
661
|
+
self.logger.error(f"[LP_LOGGING] Plate {plate_text} raised exception: {e}", exc_info=True)
|
|
662
|
+
|
|
663
|
+
print("[LP_LOGGING] Plate logging complete")
|
|
664
|
+
self.logger.info(f"[LP_LOGGING] Plate logging complete")
|
|
609
665
|
except Exception as e:
|
|
666
|
+
print(f"[LP_LOGGING] CRITICAL ERROR during plate logging: {e}")
|
|
610
667
|
self.logger.error(f"[LP_LOGGING] CRITICAL ERROR during plate logging: {e}", exc_info=True)
|
|
611
668
|
else:
|
|
669
|
+
print("[LP_LOGGING] No plates to log")
|
|
612
670
|
self.logger.info(f"[LP_LOGGING] No plates to log (plates_to_log is empty)")
|
|
613
671
|
|
|
614
|
-
def process(self, data: Any, config: ConfigProtocol, input_bytes: Optional[bytes] = None,
|
|
672
|
+
async def process(self, data: Any, config: ConfigProtocol, input_bytes: Optional[bytes] = None,
|
|
615
673
|
context: Optional[ProcessingContext] = None, stream_info: Optional[Dict[str, Any]] = None) -> ProcessingResult:
|
|
616
674
|
processing_start = time.time()
|
|
617
675
|
try:
|
|
@@ -647,19 +705,8 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
647
705
|
except Exception:
|
|
648
706
|
pass
|
|
649
707
|
|
|
650
|
-
#
|
|
651
|
-
|
|
652
|
-
self.logger.info("Lazy initialisation fallback (should rarely happen)")
|
|
653
|
-
try:
|
|
654
|
-
LicensePlateMonitorUseCase._ocr_model = LicensePlateRecognizer('cct-s-v1-global-model')
|
|
655
|
-
self.ocr_model = LicensePlateMonitorUseCase._ocr_model
|
|
656
|
-
except Exception as e:
|
|
657
|
-
return self.create_error_result(
|
|
658
|
-
f"Failed to initialise OCR model: {e}",
|
|
659
|
-
usecase=self.name,
|
|
660
|
-
category=self.category,
|
|
661
|
-
context=context,
|
|
662
|
-
)
|
|
708
|
+
# OCR model will be lazily initialized when _run_ocr is first called
|
|
709
|
+
# No need to initialize here
|
|
663
710
|
|
|
664
711
|
input_format = match_results_structure(data)
|
|
665
712
|
context.input_format = input_format
|
|
@@ -744,7 +791,8 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
744
791
|
self.logger.info(f"[LP_LOGGING] After OCR update, {len(final_plates)} detections have plate_text: {final_plates}")
|
|
745
792
|
|
|
746
793
|
# Step 9.5: Log detected plates to RPC (optional, only if lpr_server_id is provided)
|
|
747
|
-
|
|
794
|
+
# Direct await since process is now async
|
|
795
|
+
await self._log_detected_plates(processed_data, config, stream_info, input_bytes)
|
|
748
796
|
|
|
749
797
|
# Step 10: Update frame counter
|
|
750
798
|
self._total_frame_counter += 1
|
|
@@ -914,6 +962,39 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
914
962
|
# ------------------------------------------------------------------
|
|
915
963
|
# Fast OCR helpers
|
|
916
964
|
# ------------------------------------------------------------------
|
|
965
|
+
def _ensure_ocr_model_loaded(self) -> bool:
|
|
966
|
+
"""Lazy initialization of OCR model. Returns True if model is available."""
|
|
967
|
+
if self.ocr_model is not None:
|
|
968
|
+
return True
|
|
969
|
+
|
|
970
|
+
if self._ocr_initialization_attempted:
|
|
971
|
+
return False
|
|
972
|
+
|
|
973
|
+
self._ocr_initialization_attempted = True
|
|
974
|
+
|
|
975
|
+
# Try to get the LicensePlateRecognizer class
|
|
976
|
+
LicensePlateRecognizerClass = _get_license_plate_recognizer_class()
|
|
977
|
+
|
|
978
|
+
if LicensePlateRecognizerClass is None:
|
|
979
|
+
self.logger.error("OCR module not available. LicensePlateRecognizer will not function.")
|
|
980
|
+
return False
|
|
981
|
+
|
|
982
|
+
# Try to initialize the OCR model
|
|
983
|
+
try:
|
|
984
|
+
self.ocr_model = LicensePlateRecognizerClass('cct-s-v1-global-model')
|
|
985
|
+
source_msg = {
|
|
986
|
+
"local_repo": "from local repo",
|
|
987
|
+
"installed_package": "from installed package",
|
|
988
|
+
"installed_package_gpu": "from installed package (GPU)",
|
|
989
|
+
"installed_package_cpu": "from installed package (CPU)"
|
|
990
|
+
}.get(_OCR_IMPORT_SOURCE, "from unknown source")
|
|
991
|
+
self.logger.info(f"LicensePlateRecognizer loaded successfully {source_msg}")
|
|
992
|
+
return True
|
|
993
|
+
except Exception as e:
|
|
994
|
+
self.logger.error(f"Failed to initialize LicensePlateRecognizer: {e}", exc_info=True)
|
|
995
|
+
self.ocr_model = None
|
|
996
|
+
return False
|
|
997
|
+
|
|
917
998
|
def _clean_text(self, text: str) -> str:
|
|
918
999
|
"""Sanitise OCR output to keep only alphanumerics and uppercase."""
|
|
919
1000
|
if not text:
|
|
@@ -922,10 +1003,18 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
922
1003
|
|
|
923
1004
|
def _run_ocr(self, crop: np.ndarray) -> str:
|
|
924
1005
|
"""Run OCR on a cropped plate image and return cleaned text or empty string."""
|
|
925
|
-
if crop is None or crop.size == 0
|
|
1006
|
+
if crop is None or crop.size == 0:
|
|
1007
|
+
return ""
|
|
1008
|
+
|
|
1009
|
+
# Lazy load OCR model on first use
|
|
1010
|
+
if not self._ensure_ocr_model_loaded():
|
|
1011
|
+
return ""
|
|
1012
|
+
|
|
1013
|
+
# Double-check model is available
|
|
1014
|
+
if self.ocr_model is None:
|
|
926
1015
|
return ""
|
|
927
1016
|
|
|
928
|
-
# Check if we have a valid OCR model
|
|
1017
|
+
# Check if we have a valid OCR model with run method
|
|
929
1018
|
if not hasattr(self.ocr_model, 'run'):
|
|
930
1019
|
return ""
|
|
931
1020
|
|
|
@@ -13,7 +13,7 @@ matrice_analytics/boundary_drawing_internal/usage/simple_boundary_launcher.py,sh
|
|
|
13
13
|
matrice_analytics/post_processing/README.md,sha256=bDszazvqV5xbGhMM6hDaMctIyk5gox9bADo2IZZ9Goo,13368
|
|
14
14
|
matrice_analytics/post_processing/__init__.py,sha256=dxGBUQaRCGndQmXpYAWqUhDeUZAcxU-_6HFnm3GRDRA,29417
|
|
15
15
|
matrice_analytics/post_processing/config.py,sha256=V0s86qNapyDE6Q81ZS_1uzNqAjz-vc5L-9Tb33XaLEo,6771
|
|
16
|
-
matrice_analytics/post_processing/post_processor.py,sha256=
|
|
16
|
+
matrice_analytics/post_processing/post_processor.py,sha256=F838_vc7p9tjcp-vTMgTbpHqQcLX94xhL9HM06Wvpo8,44384
|
|
17
17
|
matrice_analytics/post_processing/advanced_tracker/README.md,sha256=RM8dynVoUWKn_hTbw9c6jHAbnQj-8hEAXnmuRZr2w1M,22485
|
|
18
18
|
matrice_analytics/post_processing/advanced_tracker/__init__.py,sha256=tAPFzI_Yep5TLX60FDwKqBqppc-EbxSr0wNsQ9DGI1o,423
|
|
19
19
|
matrice_analytics/post_processing/advanced_tracker/base.py,sha256=VqWy4dd5th5LK-JfueTt2_GSEoOi5QQfQxjTNhmQoLc,3580
|
|
@@ -126,7 +126,7 @@ matrice_analytics/post_processing/usecases/leaf.py,sha256=cwgB1ZNxkQFtkk-thSJrkX
|
|
|
126
126
|
matrice_analytics/post_processing/usecases/leaf_disease.py,sha256=bkiLccTdf4KUq3he4eCpBlKXb5exr-WBhQ_oWQ7os68,36225
|
|
127
127
|
matrice_analytics/post_processing/usecases/leak_detection.py,sha256=oOCLLVMuXVeXPHyN8FUrD3U9JYJJwIz-5fcEMgvLdls,40531
|
|
128
128
|
matrice_analytics/post_processing/usecases/license_plate_detection.py,sha256=dsavd92-wnyXCNrCzaRj24zH7BVvLSa09HkYsrOXYDM,50806
|
|
129
|
-
matrice_analytics/post_processing/usecases/license_plate_monitoring.py,sha256=
|
|
129
|
+
matrice_analytics/post_processing/usecases/license_plate_monitoring.py,sha256=Mu8Hu1EqTwYQ1lqDyz3mioDiTDJddIT2F2HbLWeZSfc,89059
|
|
130
130
|
matrice_analytics/post_processing/usecases/litter_monitoring.py,sha256=XaHAUGRBDJg_iVbu8hRMjTR-5TqrLj6ZNCRkInbzZTY,33255
|
|
131
131
|
matrice_analytics/post_processing/usecases/mask_detection.py,sha256=L_s6ZiT5zeXG-BsFcskb3HEG98DhLgqeMSDmCuwOteU,41501
|
|
132
132
|
matrice_analytics/post_processing/usecases/natural_disaster.py,sha256=ehxdPBoYcZWGVDOVn_mHFoz4lIE8LrveAkuXQj0n9XE,44253
|
|
@@ -188,8 +188,8 @@ matrice_analytics/post_processing/utils/format_utils.py,sha256=UTF7A5h9j0_S12xH9
|
|
|
188
188
|
matrice_analytics/post_processing/utils/geometry_utils.py,sha256=BWfdM6RsdJTTLR1GqkWfdwpjMEjTCJyuBxA4zVGKdfk,9623
|
|
189
189
|
matrice_analytics/post_processing/utils/smoothing_utils.py,sha256=78U-yucAcjUiZ0NIAc9NOUSIT0PWP1cqyIPA_Fdrjp0,14699
|
|
190
190
|
matrice_analytics/post_processing/utils/tracking_utils.py,sha256=rWxuotnJ3VLMHIBOud2KLcu4yZfDp7hVPWUtNAq_2xw,8288
|
|
191
|
-
matrice_analytics-0.1.
|
|
192
|
-
matrice_analytics-0.1.
|
|
193
|
-
matrice_analytics-0.1.
|
|
194
|
-
matrice_analytics-0.1.
|
|
195
|
-
matrice_analytics-0.1.
|
|
191
|
+
matrice_analytics-0.1.37.dist-info/licenses/LICENSE.txt,sha256=_uQUZpgO0mRYL5-fPoEvLSbNnLPv6OmbeEDCHXhK6Qc,1066
|
|
192
|
+
matrice_analytics-0.1.37.dist-info/METADATA,sha256=IDJ8DDNi-_VR-xpJKJJ-YFD9OVSWp0Gj7qFrbUDQSjY,14378
|
|
193
|
+
matrice_analytics-0.1.37.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
194
|
+
matrice_analytics-0.1.37.dist-info/top_level.txt,sha256=STAPEU-e-rWTerXaspdi76T_eVRSrEfFpURSP7_Dt8E,18
|
|
195
|
+
matrice_analytics-0.1.37.dist-info/RECORD,,
|
|
File without changes
|
{matrice_analytics-0.1.35.dist-info → matrice_analytics-0.1.37.dist-info}/licenses/LICENSE.txt
RENAMED
|
File without changes
|
|
File without changes
|