matrice-analytics 0.1.35__py3-none-any.whl → 0.1.36__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 -77
- {matrice_analytics-0.1.35.dist-info → matrice_analytics-0.1.36.dist-info}/METADATA +1 -1
- {matrice_analytics-0.1.35.dist-info → matrice_analytics-0.1.36.dist-info}/RECORD +7 -7
- {matrice_analytics-0.1.35.dist-info → matrice_analytics-0.1.36.dist-info}/WHEEL +0 -0
- {matrice_analytics-0.1.35.dist-info → matrice_analytics-0.1.36.dist-info}/licenses/LICENSE.txt +0 -0
- {matrice_analytics-0.1.35.dist-info → matrice_analytics-0.1.36.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
|
|
@@ -420,22 +497,9 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
420
497
|
# Map of track_id -> current dominant plate text
|
|
421
498
|
self.unique_plate_track: Dict[Any, str] = {}
|
|
422
499
|
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
|
|
500
|
+
# OCR model will be lazily initialized when first used
|
|
501
|
+
self.ocr_model = None
|
|
502
|
+
self._ocr_initialization_attempted = False
|
|
439
503
|
# OCR text history for stability checks (text consecutive frame count)
|
|
440
504
|
self._text_history: Dict[str, int] = {}
|
|
441
505
|
|
|
@@ -511,25 +575,30 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
511
575
|
self.logger.error(f"[LP_LOGGING] Plate logging has been DISABLED due to initialization failure")
|
|
512
576
|
return False
|
|
513
577
|
|
|
514
|
-
def _log_detected_plates(self, detections: List[Dict[str, Any]], config: LicensePlateMonitorConfig,
|
|
578
|
+
async def _log_detected_plates(self, detections: List[Dict[str, Any]], config: LicensePlateMonitorConfig,
|
|
515
579
|
stream_info: Optional[Dict[str, Any]], image_bytes: Optional[bytes] = None) -> None:
|
|
516
580
|
"""Log all detected plates to RPC server with cooldown."""
|
|
517
581
|
# Enhanced logging for diagnostics
|
|
582
|
+
print(f"[LP_LOGGING] Starting plate logging check - detections count: {len(detections)}")
|
|
518
583
|
self.logger.info(f"[LP_LOGGING] Starting plate logging check - detections count: {len(detections)}")
|
|
519
584
|
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
585
|
|
|
521
586
|
if not self._logging_enabled:
|
|
587
|
+
print("[LP_LOGGING] Plate logging is DISABLED")
|
|
522
588
|
self.logger.warning("[LP_LOGGING] Plate logging is DISABLED - logging_enabled flag is False")
|
|
523
589
|
return
|
|
524
590
|
|
|
525
591
|
if not self.plate_logger:
|
|
592
|
+
print("[LP_LOGGING] Plate logging SKIPPED - plate_logger not initialized")
|
|
526
593
|
self.logger.warning("[LP_LOGGING] Plate logging SKIPPED - plate_logger is not initialized (lpr_server_id may not be configured)")
|
|
527
594
|
return
|
|
528
595
|
|
|
529
596
|
if not stream_info:
|
|
597
|
+
print("[LP_LOGGING] Plate logging SKIPPED - stream_info is None")
|
|
530
598
|
self.logger.warning("[LP_LOGGING] Plate logging SKIPPED - stream_info is None")
|
|
531
599
|
return
|
|
532
600
|
|
|
601
|
+
print("[LP_LOGGING] All pre-conditions met, proceeding with plate logging")
|
|
533
602
|
self.logger.info(f"[LP_LOGGING] All pre-conditions met, proceeding with plate logging")
|
|
534
603
|
|
|
535
604
|
# Get current timestamp
|
|
@@ -569,49 +638,45 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
569
638
|
continue
|
|
570
639
|
plates_to_log.add(plate_text)
|
|
571
640
|
|
|
641
|
+
print(f"[LP_LOGGING] Collected {len(plates_to_log)} unique plates to log: {plates_to_log}")
|
|
572
642
|
self.logger.info(f"[LP_LOGGING] Collected {len(plates_to_log)} unique plates to log: {plates_to_log}")
|
|
573
643
|
if detections_without_text > 0:
|
|
574
644
|
self.logger.warning(f"[LP_LOGGING] {detections_without_text} detections have NO plate_text (OCR may have failed or not run yet)")
|
|
575
645
|
|
|
576
|
-
# Log each unique plate (respecting cooldown)
|
|
646
|
+
# Log each unique plate directly with await (respecting cooldown)
|
|
577
647
|
if plates_to_log:
|
|
578
|
-
|
|
648
|
+
print(f"[LP_LOGGING] Logging {len(plates_to_log)} plates with cooldown={config.plate_log_cooldown}s")
|
|
649
|
+
self.logger.info(f"[LP_LOGGING] Logging {len(plates_to_log)} plates with cooldown={config.plate_log_cooldown}s")
|
|
579
650
|
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(
|
|
651
|
+
# Call log_plate directly with await for each plate
|
|
652
|
+
for plate_text in plates_to_log:
|
|
653
|
+
print(f"[LP_LOGGING] Processing plate: {plate_text}")
|
|
654
|
+
self.logger.info(f"[LP_LOGGING] Processing plate: {plate_text}")
|
|
655
|
+
try:
|
|
656
|
+
result = await self.plate_logger.log_plate(
|
|
588
657
|
plate_text=plate_text,
|
|
589
658
|
timestamp=current_timestamp,
|
|
590
659
|
stream_info=stream_info,
|
|
591
660
|
image_data=image_data,
|
|
592
661
|
cooldown=config.plate_log_cooldown
|
|
593
662
|
)
|
|
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")
|
|
663
|
+
status = "SENT" if result else "SKIPPED (cooldown)"
|
|
664
|
+
print(f"[LP_LOGGING] Plate {plate_text}: {status}")
|
|
665
|
+
self.logger.info(f"[LP_LOGGING] Plate {plate_text}: {status}")
|
|
666
|
+
except Exception as e:
|
|
667
|
+
print(f"[LP_LOGGING] ERROR - Plate {plate_text} failed: {e}")
|
|
668
|
+
self.logger.error(f"[LP_LOGGING] Plate {plate_text} raised exception: {e}", exc_info=True)
|
|
669
|
+
|
|
670
|
+
print("[LP_LOGGING] Plate logging complete")
|
|
671
|
+
self.logger.info(f"[LP_LOGGING] Plate logging complete")
|
|
609
672
|
except Exception as e:
|
|
673
|
+
print(f"[LP_LOGGING] CRITICAL ERROR during plate logging: {e}")
|
|
610
674
|
self.logger.error(f"[LP_LOGGING] CRITICAL ERROR during plate logging: {e}", exc_info=True)
|
|
611
675
|
else:
|
|
676
|
+
print("[LP_LOGGING] No plates to log")
|
|
612
677
|
self.logger.info(f"[LP_LOGGING] No plates to log (plates_to_log is empty)")
|
|
613
678
|
|
|
614
|
-
def process(self, data: Any, config: ConfigProtocol, input_bytes: Optional[bytes] = None,
|
|
679
|
+
async def process(self, data: Any, config: ConfigProtocol, input_bytes: Optional[bytes] = None,
|
|
615
680
|
context: Optional[ProcessingContext] = None, stream_info: Optional[Dict[str, Any]] = None) -> ProcessingResult:
|
|
616
681
|
processing_start = time.time()
|
|
617
682
|
try:
|
|
@@ -647,19 +712,8 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
647
712
|
except Exception:
|
|
648
713
|
pass
|
|
649
714
|
|
|
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
|
-
)
|
|
715
|
+
# OCR model will be lazily initialized when _run_ocr is first called
|
|
716
|
+
# No need to initialize here
|
|
663
717
|
|
|
664
718
|
input_format = match_results_structure(data)
|
|
665
719
|
context.input_format = input_format
|
|
@@ -744,7 +798,8 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
744
798
|
self.logger.info(f"[LP_LOGGING] After OCR update, {len(final_plates)} detections have plate_text: {final_plates}")
|
|
745
799
|
|
|
746
800
|
# Step 9.5: Log detected plates to RPC (optional, only if lpr_server_id is provided)
|
|
747
|
-
|
|
801
|
+
# Direct await since process is now async
|
|
802
|
+
await self._log_detected_plates(processed_data, config, stream_info, input_bytes)
|
|
748
803
|
|
|
749
804
|
# Step 10: Update frame counter
|
|
750
805
|
self._total_frame_counter += 1
|
|
@@ -914,6 +969,39 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
914
969
|
# ------------------------------------------------------------------
|
|
915
970
|
# Fast OCR helpers
|
|
916
971
|
# ------------------------------------------------------------------
|
|
972
|
+
def _ensure_ocr_model_loaded(self) -> bool:
|
|
973
|
+
"""Lazy initialization of OCR model. Returns True if model is available."""
|
|
974
|
+
if self.ocr_model is not None:
|
|
975
|
+
return True
|
|
976
|
+
|
|
977
|
+
if self._ocr_initialization_attempted:
|
|
978
|
+
return False
|
|
979
|
+
|
|
980
|
+
self._ocr_initialization_attempted = True
|
|
981
|
+
|
|
982
|
+
# Try to get the LicensePlateRecognizer class
|
|
983
|
+
LicensePlateRecognizerClass = _get_license_plate_recognizer_class()
|
|
984
|
+
|
|
985
|
+
if LicensePlateRecognizerClass is None:
|
|
986
|
+
self.logger.error("OCR module not available. LicensePlateRecognizer will not function.")
|
|
987
|
+
return False
|
|
988
|
+
|
|
989
|
+
# Try to initialize the OCR model
|
|
990
|
+
try:
|
|
991
|
+
self.ocr_model = LicensePlateRecognizerClass('cct-s-v1-global-model')
|
|
992
|
+
source_msg = {
|
|
993
|
+
"local_repo": "from local repo",
|
|
994
|
+
"installed_package": "from installed package",
|
|
995
|
+
"installed_package_gpu": "from installed package (GPU)",
|
|
996
|
+
"installed_package_cpu": "from installed package (CPU)"
|
|
997
|
+
}.get(_OCR_IMPORT_SOURCE, "from unknown source")
|
|
998
|
+
self.logger.info(f"LicensePlateRecognizer loaded successfully {source_msg}")
|
|
999
|
+
return True
|
|
1000
|
+
except Exception as e:
|
|
1001
|
+
self.logger.error(f"Failed to initialize LicensePlateRecognizer: {e}", exc_info=True)
|
|
1002
|
+
self.ocr_model = None
|
|
1003
|
+
return False
|
|
1004
|
+
|
|
917
1005
|
def _clean_text(self, text: str) -> str:
|
|
918
1006
|
"""Sanitise OCR output to keep only alphanumerics and uppercase."""
|
|
919
1007
|
if not text:
|
|
@@ -922,10 +1010,18 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
922
1010
|
|
|
923
1011
|
def _run_ocr(self, crop: np.ndarray) -> str:
|
|
924
1012
|
"""Run OCR on a cropped plate image and return cleaned text or empty string."""
|
|
925
|
-
if crop is None or crop.size == 0
|
|
1013
|
+
if crop is None or crop.size == 0:
|
|
1014
|
+
return ""
|
|
1015
|
+
|
|
1016
|
+
# Lazy load OCR model on first use
|
|
1017
|
+
if not self._ensure_ocr_model_loaded():
|
|
1018
|
+
return ""
|
|
1019
|
+
|
|
1020
|
+
# Double-check model is available
|
|
1021
|
+
if self.ocr_model is None:
|
|
926
1022
|
return ""
|
|
927
1023
|
|
|
928
|
-
# Check if we have a valid OCR model
|
|
1024
|
+
# Check if we have a valid OCR model with run method
|
|
929
1025
|
if not hasattr(self.ocr_model, 'run'):
|
|
930
1026
|
return ""
|
|
931
1027
|
|
|
@@ -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=5YvqGwvvocyBZkFgSnCGuvCp4XT_YTrPuO-RHOEjRAk,89336
|
|
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.36.dist-info/licenses/LICENSE.txt,sha256=_uQUZpgO0mRYL5-fPoEvLSbNnLPv6OmbeEDCHXhK6Qc,1066
|
|
192
|
+
matrice_analytics-0.1.36.dist-info/METADATA,sha256=SOV2XH7crdPmqvOuZmjQvlkrVM0BoWI1fK2K2xmlR64,14378
|
|
193
|
+
matrice_analytics-0.1.36.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
194
|
+
matrice_analytics-0.1.36.dist-info/top_level.txt,sha256=STAPEU-e-rWTerXaspdi76T_eVRSrEfFpURSP7_Dt8E,18
|
|
195
|
+
matrice_analytics-0.1.36.dist-info/RECORD,,
|
|
File without changes
|
{matrice_analytics-0.1.35.dist-info → matrice_analytics-0.1.36.dist-info}/licenses/LICENSE.txt
RENAMED
|
File without changes
|
|
File without changes
|