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