matrice-analytics 0.1.34__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 +318 -114
- {matrice_analytics-0.1.34.dist-info → matrice_analytics-0.1.36.dist-info}/METADATA +1 -1
- {matrice_analytics-0.1.34.dist-info → matrice_analytics-0.1.36.dist-info}/RECORD +7 -7
- {matrice_analytics-0.1.34.dist-info → matrice_analytics-0.1.36.dist-info}/WHEEL +0 -0
- {matrice_analytics-0.1.34.dist-info → matrice_analytics-0.1.36.dist-info}/licenses/LICENSE.txt +0 -0
- {matrice_analytics-0.1.34.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,60 +187,91 @@ 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."""
|
|
126
|
-
|
|
190
|
+
print("[LP_LOGGING] ===== INITIALIZING LP LOGGER SESSION =====")
|
|
191
|
+
print(f"[LP_LOGGING] Config lpr_server_id: {config.lpr_server_id}")
|
|
192
|
+
self.logger.info("[LP_LOGGING] ===== INITIALIZING LP LOGGER SESSION =====")
|
|
193
|
+
self.logger.info(f"[LP_LOGGING] Config lpr_server_id: {config.lpr_server_id}")
|
|
127
194
|
|
|
128
195
|
# Use existing session if provided, otherwise create new one
|
|
129
|
-
if self.session:
|
|
130
|
-
self.logger.info("Session already initialized, skipping initialization")
|
|
196
|
+
if self.session and self.server_info and self.server_base_url:
|
|
197
|
+
self.logger.info("[LP_LOGGING] Session already initialized with server info, skipping re-initialization")
|
|
198
|
+
self.logger.info(f"[LP_LOGGING] Using existing server: {self.server_base_url}")
|
|
131
199
|
return
|
|
200
|
+
elif self.session:
|
|
201
|
+
self.logger.info("[LP_LOGGING] Session exists but server info missing, continuing initialization...")
|
|
202
|
+
else:
|
|
203
|
+
self.logger.info("[LP_LOGGING] No existing session, initializing from scratch...")
|
|
204
|
+
|
|
132
205
|
if config.session:
|
|
133
206
|
self.session = config.session
|
|
134
|
-
self.logger.info("Using provided session from config")
|
|
207
|
+
self.logger.info("[LP_LOGGING] Using provided session from config")
|
|
208
|
+
|
|
135
209
|
if not self.session:
|
|
136
210
|
# Initialize Matrice session
|
|
137
211
|
if not HAS_MATRICE_SESSION:
|
|
138
|
-
self.logger.error("Matrice session module not available")
|
|
212
|
+
self.logger.error("[LP_LOGGING] Matrice session module not available")
|
|
139
213
|
raise ImportError("Matrice session is required for License Plate Monitoring")
|
|
140
214
|
try:
|
|
141
|
-
self.logger.info("Creating new Matrice session...")
|
|
215
|
+
self.logger.info("[LP_LOGGING] Creating new Matrice session from environment variables...")
|
|
216
|
+
account_number = os.getenv("MATRICE_ACCOUNT_NUMBER", "")
|
|
217
|
+
access_key_id = os.getenv("MATRICE_ACCESS_KEY_ID", "")
|
|
218
|
+
secret_key = os.getenv("MATRICE_SECRET_ACCESS_KEY", "")
|
|
219
|
+
project_id = os.getenv("MATRICE_PROJECT_ID", "")
|
|
220
|
+
|
|
221
|
+
self.logger.info(f"[LP_LOGGING] Account Number: {'SET' if account_number else 'NOT SET'}")
|
|
222
|
+
self.logger.info(f"[LP_LOGGING] Access Key ID: {'SET' if access_key_id else 'NOT SET'}")
|
|
223
|
+
self.logger.info(f"[LP_LOGGING] Secret Key: {'SET' if secret_key else 'NOT SET'}")
|
|
224
|
+
self.logger.info(f"[LP_LOGGING] Project ID: {'SET' if project_id else 'NOT SET'}")
|
|
225
|
+
|
|
142
226
|
self.session = Session(
|
|
143
|
-
account_number=
|
|
144
|
-
access_key=
|
|
145
|
-
secret_key=
|
|
146
|
-
project_id=
|
|
227
|
+
account_number=account_number,
|
|
228
|
+
access_key=access_key_id,
|
|
229
|
+
secret_key=secret_key,
|
|
230
|
+
project_id=project_id,
|
|
147
231
|
)
|
|
148
|
-
self.logger.info("Successfully initialized new Matrice session
|
|
232
|
+
self.logger.info("[LP_LOGGING] Successfully initialized new Matrice session")
|
|
149
233
|
except Exception as e:
|
|
150
|
-
self.logger.error(f"Failed to initialize Matrice session: {e}", exc_info=True)
|
|
234
|
+
self.logger.error(f"[LP_LOGGING] Failed to initialize Matrice session: {e}", exc_info=True)
|
|
151
235
|
raise
|
|
152
236
|
|
|
153
237
|
# Fetch server connection info if lpr_server_id is provided
|
|
154
238
|
if config.lpr_server_id:
|
|
155
239
|
self.lpr_server_id = config.lpr_server_id
|
|
156
|
-
self.logger.info(f"Fetching LPR server connection info for server ID: {self.lpr_server_id}")
|
|
240
|
+
self.logger.info(f"[LP_LOGGING] Fetching LPR server connection info for server ID: {self.lpr_server_id}")
|
|
157
241
|
try:
|
|
158
242
|
self.server_info = self.get_server_connection_info()
|
|
159
243
|
if self.server_info:
|
|
160
|
-
self.logger.info(f"Successfully fetched LPR server info
|
|
244
|
+
self.logger.info(f"[LP_LOGGING] Successfully fetched LPR server info")
|
|
245
|
+
self.logger.info(f"[LP_LOGGING] - Name: {self.server_info.get('name', 'Unknown')}")
|
|
246
|
+
self.logger.info(f"[LP_LOGGING] - Host: {self.server_info.get('host', 'Unknown')}")
|
|
247
|
+
self.logger.info(f"[LP_LOGGING] - Port: {self.server_info.get('port', 'Unknown')}")
|
|
248
|
+
self.logger.info(f"[LP_LOGGING] - Status: {self.server_info.get('status', 'Unknown')}")
|
|
249
|
+
self.logger.info(f"[LP_LOGGING] - Project ID: {self.server_info.get('projectID', 'Unknown')}")
|
|
250
|
+
|
|
161
251
|
# Compare server host with public IP to determine if it's localhost
|
|
162
252
|
server_host = self.server_info.get('host', 'localhost')
|
|
163
253
|
server_port = self.server_info.get('port', 8200)
|
|
164
254
|
|
|
165
255
|
if server_host == self.public_ip:
|
|
166
256
|
self.server_base_url = f"http://localhost:{server_port}"
|
|
167
|
-
self.logger.info(f"Server host matches public IP ({self.public_ip}), using localhost: {self.server_base_url}")
|
|
257
|
+
self.logger.info(f"[LP_LOGGING] Server host matches public IP ({self.public_ip}), using localhost: {self.server_base_url}")
|
|
168
258
|
else:
|
|
169
|
-
self.server_base_url = f"
|
|
170
|
-
self.logger.info(f"LPR server base URL configured: {self.server_base_url}")
|
|
259
|
+
self.server_base_url = f"http://{server_host}:{server_port}"
|
|
260
|
+
self.logger.info(f"[LP_LOGGING] LPR server base URL configured: {self.server_base_url}")
|
|
171
261
|
|
|
172
262
|
self.session.update(self.server_info.get('projectID', ''))
|
|
173
|
-
self.logger.info(f"Updated Matrice session with project ID: {self.server_info.get('projectID', '')}")
|
|
263
|
+
self.logger.info(f"[LP_LOGGING] Updated Matrice session with project ID: {self.server_info.get('projectID', '')}")
|
|
174
264
|
else:
|
|
175
|
-
self.logger.
|
|
265
|
+
self.logger.error("[LP_LOGGING] Failed to fetch LPR server connection info - server_info is None")
|
|
266
|
+
self.logger.error("[LP_LOGGING] This will prevent plate logging from working!")
|
|
176
267
|
except Exception as e:
|
|
177
|
-
self.logger.error(f"Error fetching LPR server connection info: {e}", exc_info=True)
|
|
268
|
+
self.logger.error(f"[LP_LOGGING] Error fetching LPR server connection info: {e}", exc_info=True)
|
|
269
|
+
self.logger.error("[LP_LOGGING] This will prevent plate logging from working!")
|
|
178
270
|
else:
|
|
179
|
-
self.logger.
|
|
271
|
+
self.logger.warning("[LP_LOGGING] No lpr_server_id provided in config, skipping server connection info fetch")
|
|
272
|
+
|
|
273
|
+
print("[LP_LOGGING] ===== LP LOGGER SESSION INITIALIZATION COMPLETE =====")
|
|
274
|
+
self.logger.info("[LP_LOGGING] ===== LP LOGGER SESSION INITIALIZATION COMPLETE =====")
|
|
180
275
|
|
|
181
276
|
def _get_public_ip(self) -> str:
|
|
182
277
|
"""Get the public IP address of this machine."""
|
|
@@ -233,10 +328,12 @@ class LicensePlateMonitorLogger:
|
|
|
233
328
|
time_since_last_log = current_time - last_log_time
|
|
234
329
|
|
|
235
330
|
if time_since_last_log >= cooldown:
|
|
236
|
-
|
|
331
|
+
print(f"[LP_LOGGING] ✓ Plate '{plate_text}' ready to log ({time_since_last_log:.1f}s since last)")
|
|
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)")
|
|
237
333
|
return True
|
|
238
334
|
else:
|
|
239
|
-
|
|
335
|
+
print(f"[LP_LOGGING] ⊗ Plate '{plate_text}' in cooldown ({cooldown - time_since_last_log:.1f}s remaining)")
|
|
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)")
|
|
240
337
|
return False
|
|
241
338
|
|
|
242
339
|
def update_log_timestamp(self, plate_text: str) -> None:
|
|
@@ -297,11 +394,16 @@ class LicensePlateMonitorLogger:
|
|
|
297
394
|
image_data: Base64-encoded JPEG image of the license plate crop
|
|
298
395
|
cooldown: Cooldown period in seconds
|
|
299
396
|
"""
|
|
300
|
-
|
|
397
|
+
print(f"[LP_LOGGING] ===== PLATE LOG REQUEST START =====")
|
|
398
|
+
print(f"[LP_LOGGING] Plate: '{plate_text}', Timestamp: {timestamp}")
|
|
399
|
+
self.logger.info(f"[LP_LOGGING] ===== PLATE LOG REQUEST START =====")
|
|
400
|
+
self.logger.info(f"[LP_LOGGING] Plate: '{plate_text}', Timestamp: {timestamp}")
|
|
301
401
|
|
|
302
402
|
# Check cooldown
|
|
303
403
|
if not self.should_log_plate(plate_text, cooldown):
|
|
304
|
-
|
|
404
|
+
print(f"[LP_LOGGING] Plate '{plate_text}' NOT SENT - cooldown")
|
|
405
|
+
self.logger.info(f"[LP_LOGGING] Plate '{plate_text}' NOT SENT - skipped due to cooldown period")
|
|
406
|
+
self.logger.info(f"[LP_LOGGING] ===== PLATE LOG REQUEST END (SKIPPED) =====")
|
|
305
407
|
return False
|
|
306
408
|
|
|
307
409
|
try:
|
|
@@ -310,11 +412,16 @@ class LicensePlateMonitorLogger:
|
|
|
310
412
|
location = camera_info.get("location", "")
|
|
311
413
|
frame_id = stream_info.get("frame_id", "")
|
|
312
414
|
|
|
415
|
+
print(f"[LP_LOGGING] Camera: '{camera_name}', Location: '{location}'")
|
|
416
|
+
self.logger.info(f"[LP_LOGGING] Stream Info - Camera: '{camera_name}', Location: '{location}', Frame ID: '{frame_id}'")
|
|
417
|
+
|
|
313
418
|
# Get project ID from server_info
|
|
314
419
|
project_id = self.server_info.get('projectID', '') if self.server_info else ''
|
|
420
|
+
self.logger.info(f"[LP_LOGGING] Project ID: '{project_id}'")
|
|
315
421
|
|
|
316
422
|
# Format timestamp to RFC3339 format (2006-01-02T15:04:05Z)
|
|
317
423
|
rfc3339_timestamp = self._format_timestamp_rfc3339(timestamp)
|
|
424
|
+
self.logger.info(f"[LP_LOGGING] Formatted timestamp: {timestamp} -> {rfc3339_timestamp}")
|
|
318
425
|
|
|
319
426
|
payload = {
|
|
320
427
|
'licensePlate': plate_text,
|
|
@@ -328,19 +435,27 @@ class LicensePlateMonitorLogger:
|
|
|
328
435
|
|
|
329
436
|
# Add projectId as query parameter
|
|
330
437
|
endpoint = f'/v1/lpr-server/detections?projectId={project_id}'
|
|
331
|
-
|
|
438
|
+
full_url = f"{self.server_base_url}{endpoint}"
|
|
439
|
+
print(f"[LP_LOGGING] Sending POST to: {full_url}")
|
|
440
|
+
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}', imageData length={len(image_data) if image_data else 0}")
|
|
332
442
|
|
|
333
443
|
response = await self.session.rpc.post_async(endpoint, payload=payload, base_url=self.server_base_url)
|
|
334
444
|
|
|
335
|
-
|
|
445
|
+
print(f"[LP_LOGGING] Response: {response}")
|
|
446
|
+
self.logger.info(f"[LP_LOGGING] API Response received: {response}")
|
|
336
447
|
|
|
337
448
|
# Update timestamp after successful log
|
|
338
449
|
self.update_log_timestamp(plate_text)
|
|
339
|
-
|
|
450
|
+
print(f"[LP_LOGGING] ✓ Plate '{plate_text}' SUCCESSFULLY SENT")
|
|
451
|
+
self.logger.info(f"[LP_LOGGING] Plate '{plate_text}' SUCCESSFULLY SENT at {rfc3339_timestamp}")
|
|
452
|
+
self.logger.info(f"[LP_LOGGING] ===== PLATE LOG REQUEST END (SUCCESS) =====")
|
|
340
453
|
return True
|
|
341
454
|
|
|
342
455
|
except Exception as e:
|
|
343
|
-
|
|
456
|
+
print(f"[LP_LOGGING] ✗ Plate '{plate_text}' FAILED - {e}")
|
|
457
|
+
self.logger.error(f"[LP_LOGGING] Plate '{plate_text}' NOT SENT - Exception occurred: {e}", exc_info=True)
|
|
458
|
+
self.logger.info(f"[LP_LOGGING] ===== PLATE LOG REQUEST END (FAILED) =====")
|
|
344
459
|
return False
|
|
345
460
|
|
|
346
461
|
class LicensePlateMonitorUseCase(BaseProcessor):
|
|
@@ -382,23 +497,10 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
382
497
|
# Map of track_id -> current dominant plate text
|
|
383
498
|
self.unique_plate_track: Dict[Any, str] = {}
|
|
384
499
|
self.image_preprocessor = ImagePreprocessor()
|
|
385
|
-
#
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
self.logger.error("OCR module not available. LicensePlateRecognizer will not function. Install: pip install fast-plate-ocr[onnx]")
|
|
390
|
-
LicensePlateMonitorUseCase._ocr_model = LicensePlateRecognizer('cct-s-v1-global-model')
|
|
391
|
-
else:
|
|
392
|
-
# Try to load real OCR model
|
|
393
|
-
try:
|
|
394
|
-
LicensePlateMonitorUseCase._ocr_model = LicensePlateRecognizer('cct-s-v1-global-model')
|
|
395
|
-
source_msg = "from local repo" if _OCR_IMPORT_SOURCE == "local_repo" else "from installed package"
|
|
396
|
-
self.logger.info(f"LicensePlateRecognizer loaded successfully {source_msg}")
|
|
397
|
-
except Exception as e:
|
|
398
|
-
self.logger.error(f"Failed to initialize LicensePlateRecognizer: {e}", exc_info=True)
|
|
399
|
-
LicensePlateMonitorUseCase._ocr_model = None
|
|
400
|
-
self.ocr_model = LicensePlateMonitorUseCase._ocr_model
|
|
401
|
-
# OCR text history for stability checks (text → consecutive frame count)
|
|
500
|
+
# OCR model will be lazily initialized when first used
|
|
501
|
+
self.ocr_model = None
|
|
502
|
+
self._ocr_initialization_attempted = False
|
|
503
|
+
# OCR text history for stability checks (text consecutive frame count)
|
|
402
504
|
self._text_history: Dict[str, int] = {}
|
|
403
505
|
|
|
404
506
|
self.start_timer = None
|
|
@@ -415,6 +517,7 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
415
517
|
# Initialize plate logger (optional, only used if lpr_server_id is provided)
|
|
416
518
|
self.plate_logger: Optional[LicensePlateMonitorLogger] = None
|
|
417
519
|
self._logging_enabled = True
|
|
520
|
+
self._plate_logger_initialized = False # Track if plate logger has been initialized
|
|
418
521
|
|
|
419
522
|
|
|
420
523
|
def reset_tracker(self) -> None:
|
|
@@ -442,30 +545,62 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
442
545
|
self.reset_plate_tracking()
|
|
443
546
|
self.logger.info("All plate tracking state reset")
|
|
444
547
|
|
|
445
|
-
def _initialize_plate_logger(self, config: LicensePlateMonitorConfig) ->
|
|
446
|
-
"""Initialize the plate logger if lpr_server_id is provided."""
|
|
548
|
+
def _initialize_plate_logger(self, config: LicensePlateMonitorConfig) -> bool:
|
|
549
|
+
"""Initialize the plate logger if lpr_server_id is provided. Returns True if successful."""
|
|
550
|
+
self.logger.info(f"[LP_LOGGING] _initialize_plate_logger called with lpr_server_id: {config.lpr_server_id}")
|
|
551
|
+
|
|
447
552
|
if not config.lpr_server_id:
|
|
448
553
|
self._logging_enabled = False
|
|
449
|
-
self.
|
|
450
|
-
|
|
554
|
+
self._plate_logger_initialized = False
|
|
555
|
+
self.logger.warning("[LP_LOGGING] Plate logging disabled: no lpr_server_id provided")
|
|
556
|
+
return False
|
|
451
557
|
|
|
452
558
|
try:
|
|
453
559
|
if self.plate_logger is None:
|
|
560
|
+
self.logger.info("[LP_LOGGING] Creating new LicensePlateMonitorLogger instance")
|
|
454
561
|
self.plate_logger = LicensePlateMonitorLogger()
|
|
562
|
+
else:
|
|
563
|
+
self.logger.info("[LP_LOGGING] Using existing LicensePlateMonitorLogger instance")
|
|
455
564
|
|
|
565
|
+
self.logger.info("[LP_LOGGING] Initializing session for plate logger")
|
|
456
566
|
self.plate_logger.initialize_session(config)
|
|
457
567
|
self._logging_enabled = True
|
|
458
|
-
self.
|
|
568
|
+
self._plate_logger_initialized = True
|
|
569
|
+
self.logger.info(f"[LP_LOGGING] SUCCESS - Plate logging ENABLED with server ID: {config.lpr_server_id}")
|
|
570
|
+
return True
|
|
459
571
|
except Exception as e:
|
|
460
|
-
self.logger.error(f"Failed to initialize plate logger: {e}", exc_info=True)
|
|
572
|
+
self.logger.error(f"[LP_LOGGING] ERROR - Failed to initialize plate logger: {e}", exc_info=True)
|
|
461
573
|
self._logging_enabled = False
|
|
574
|
+
self._plate_logger_initialized = False
|
|
575
|
+
self.logger.error(f"[LP_LOGGING] Plate logging has been DISABLED due to initialization failure")
|
|
576
|
+
return False
|
|
462
577
|
|
|
463
|
-
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,
|
|
464
579
|
stream_info: Optional[Dict[str, Any]], image_bytes: Optional[bytes] = None) -> None:
|
|
465
580
|
"""Log all detected plates to RPC server with cooldown."""
|
|
466
|
-
|
|
581
|
+
# Enhanced logging for diagnostics
|
|
582
|
+
print(f"[LP_LOGGING] Starting plate logging check - detections count: {len(detections)}")
|
|
583
|
+
self.logger.info(f"[LP_LOGGING] Starting plate logging check - detections count: {len(detections)}")
|
|
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}")
|
|
585
|
+
|
|
586
|
+
if not self._logging_enabled:
|
|
587
|
+
print("[LP_LOGGING] Plate logging is DISABLED")
|
|
588
|
+
self.logger.warning("[LP_LOGGING] Plate logging is DISABLED - logging_enabled flag is False")
|
|
467
589
|
return
|
|
468
590
|
|
|
591
|
+
if not self.plate_logger:
|
|
592
|
+
print("[LP_LOGGING] Plate logging SKIPPED - plate_logger not initialized")
|
|
593
|
+
self.logger.warning("[LP_LOGGING] Plate logging SKIPPED - plate_logger is not initialized (lpr_server_id may not be configured)")
|
|
594
|
+
return
|
|
595
|
+
|
|
596
|
+
if not stream_info:
|
|
597
|
+
print("[LP_LOGGING] Plate logging SKIPPED - stream_info is None")
|
|
598
|
+
self.logger.warning("[LP_LOGGING] Plate logging SKIPPED - stream_info is None")
|
|
599
|
+
return
|
|
600
|
+
|
|
601
|
+
print("[LP_LOGGING] All pre-conditions met, proceeding with plate logging")
|
|
602
|
+
self.logger.info(f"[LP_LOGGING] All pre-conditions met, proceeding with plate logging")
|
|
603
|
+
|
|
469
604
|
# Get current timestamp
|
|
470
605
|
current_timestamp = self._get_current_timestamp_str(stream_info, precision=True)
|
|
471
606
|
|
|
@@ -483,44 +618,65 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
483
618
|
if success:
|
|
484
619
|
# Convert to base64
|
|
485
620
|
image_data = base64.b64encode(jpeg_buffer.tobytes()).decode('utf-8')
|
|
486
|
-
self.logger.
|
|
621
|
+
self.logger.info(f"[LP_LOGGING] Encoded frame image as base64, length: {len(image_data)}")
|
|
622
|
+
else:
|
|
623
|
+
self.logger.warning(f"[LP_LOGGING] Failed to encode JPEG image")
|
|
624
|
+
else:
|
|
625
|
+
self.logger.warning(f"[LP_LOGGING] Failed to decode image bytes")
|
|
487
626
|
except Exception as e:
|
|
488
|
-
self.logger.error(f"
|
|
627
|
+
self.logger.error(f"[LP_LOGGING] Exception while encoding frame image: {e}", exc_info=True)
|
|
628
|
+
else:
|
|
629
|
+
self.logger.info(f"[LP_LOGGING] No image_bytes provided, sending without image")
|
|
489
630
|
|
|
490
631
|
# Collect all unique plates from current detections
|
|
491
632
|
plates_to_log = set()
|
|
633
|
+
detections_without_text = 0
|
|
492
634
|
for det in detections:
|
|
493
635
|
plate_text = det.get('plate_text')
|
|
494
636
|
if not plate_text:
|
|
637
|
+
detections_without_text += 1
|
|
495
638
|
continue
|
|
496
639
|
plates_to_log.add(plate_text)
|
|
497
640
|
|
|
498
|
-
|
|
641
|
+
print(f"[LP_LOGGING] Collected {len(plates_to_log)} unique plates to log: {plates_to_log}")
|
|
642
|
+
self.logger.info(f"[LP_LOGGING] Collected {len(plates_to_log)} unique plates to log: {plates_to_log}")
|
|
643
|
+
if detections_without_text > 0:
|
|
644
|
+
self.logger.warning(f"[LP_LOGGING] {detections_without_text} detections have NO plate_text (OCR may have failed or not run yet)")
|
|
645
|
+
|
|
646
|
+
# Log each unique plate directly with await (respecting cooldown)
|
|
499
647
|
if plates_to_log:
|
|
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")
|
|
500
650
|
try:
|
|
501
|
-
#
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
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(
|
|
508
657
|
plate_text=plate_text,
|
|
509
658
|
timestamp=current_timestamp,
|
|
510
659
|
stream_info=stream_info,
|
|
511
660
|
image_data=image_data,
|
|
512
661
|
cooldown=config.plate_log_cooldown
|
|
513
662
|
)
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
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")
|
|
520
672
|
except Exception as e:
|
|
521
|
-
|
|
673
|
+
print(f"[LP_LOGGING] CRITICAL ERROR during plate logging: {e}")
|
|
674
|
+
self.logger.error(f"[LP_LOGGING] CRITICAL ERROR during plate logging: {e}", exc_info=True)
|
|
675
|
+
else:
|
|
676
|
+
print("[LP_LOGGING] No plates to log")
|
|
677
|
+
self.logger.info(f"[LP_LOGGING] No plates to log (plates_to_log is empty)")
|
|
522
678
|
|
|
523
|
-
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,
|
|
524
680
|
context: Optional[ProcessingContext] = None, stream_info: Optional[Dict[str, Any]] = None) -> ProcessingResult:
|
|
525
681
|
processing_start = time.time()
|
|
526
682
|
try:
|
|
@@ -535,9 +691,19 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
535
691
|
return self.create_error_result("input_bytes (video/image) is required for license plate monitoring",
|
|
536
692
|
usecase=self.name, category=self.category, context=context)
|
|
537
693
|
|
|
538
|
-
# Initialize plate logger if lpr_server_id is provided (optional flow)
|
|
539
|
-
if
|
|
540
|
-
self.
|
|
694
|
+
# Initialize plate logger once if lpr_server_id is provided (optional flow)
|
|
695
|
+
if not self._plate_logger_initialized and config.lpr_server_id:
|
|
696
|
+
self.logger.info(f"[LP_LOGGING] First-time initialization - lpr_server_id: {config.lpr_server_id}")
|
|
697
|
+
success = self._initialize_plate_logger(config)
|
|
698
|
+
if success:
|
|
699
|
+
self.logger.info(f"[LP_LOGGING] Plate logger initialized successfully and ready to send plates")
|
|
700
|
+
else:
|
|
701
|
+
self.logger.error(f"[LP_LOGGING] Plate logger initialization FAILED - plates will NOT be sent")
|
|
702
|
+
elif self._plate_logger_initialized:
|
|
703
|
+
self.logger.debug(f"[LP_LOGGING] Plate logger already initialized, skipping re-initialization")
|
|
704
|
+
elif not config.lpr_server_id:
|
|
705
|
+
if self._total_frame_counter == 0: # Only log once at start
|
|
706
|
+
self.logger.warning(f"[LP_LOGGING] Plate logging will be DISABLED - no lpr_server_id provided in config")
|
|
541
707
|
|
|
542
708
|
# Normalize alert_config if provided as a plain dict (JS JSON)
|
|
543
709
|
if isinstance(getattr(config, 'alert_config', None), dict):
|
|
@@ -546,19 +712,8 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
546
712
|
except Exception:
|
|
547
713
|
pass
|
|
548
714
|
|
|
549
|
-
#
|
|
550
|
-
|
|
551
|
-
self.logger.info("Lazy initialisation fallback (should rarely happen)")
|
|
552
|
-
try:
|
|
553
|
-
LicensePlateMonitorUseCase._ocr_model = LicensePlateRecognizer('cct-s-v1-global-model')
|
|
554
|
-
self.ocr_model = LicensePlateMonitorUseCase._ocr_model
|
|
555
|
-
except Exception as e:
|
|
556
|
-
return self.create_error_result(
|
|
557
|
-
f"Failed to initialise OCR model: {e}",
|
|
558
|
-
usecase=self.name,
|
|
559
|
-
category=self.category,
|
|
560
|
-
context=context,
|
|
561
|
-
)
|
|
715
|
+
# OCR model will be lazily initialized when _run_ocr is first called
|
|
716
|
+
# No need to initialize here
|
|
562
717
|
|
|
563
718
|
input_format = match_results_structure(data)
|
|
564
719
|
context.input_format = input_format
|
|
@@ -627,16 +782,24 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
627
782
|
#print("---------DATA5--------------",processed_data)
|
|
628
783
|
# Step 8: Perform OCR on media
|
|
629
784
|
ocr_analysis = self._analyze_ocr_in_media(processed_data, input_bytes, config)
|
|
630
|
-
|
|
631
|
-
|
|
785
|
+
self.logger.info(f"[LP_LOGGING] OCR analysis completed, found {len(ocr_analysis)} results")
|
|
786
|
+
ocr_plates_found = [r.get('plate_text') for r in ocr_analysis if r.get('plate_text')]
|
|
787
|
+
if ocr_plates_found:
|
|
788
|
+
self.logger.info(f"[LP_LOGGING] OCR detected plates: {ocr_plates_found}")
|
|
789
|
+
else:
|
|
790
|
+
self.logger.warning(f"[LP_LOGGING] OCR did not detect any valid plate texts")
|
|
632
791
|
|
|
633
792
|
# Step 9: Update plate texts
|
|
634
|
-
#print("---------DATA6--------------",processed_data)
|
|
635
793
|
processed_data = self._update_detections_with_ocr(processed_data, ocr_analysis)
|
|
636
794
|
self._update_plate_texts(processed_data)
|
|
637
795
|
|
|
796
|
+
# Log final detection state before sending
|
|
797
|
+
final_plates = [d.get('plate_text') for d in processed_data if d.get('plate_text')]
|
|
798
|
+
self.logger.info(f"[LP_LOGGING] After OCR update, {len(final_plates)} detections have plate_text: {final_plates}")
|
|
799
|
+
|
|
638
800
|
# Step 9.5: Log detected plates to RPC (optional, only if lpr_server_id is provided)
|
|
639
|
-
|
|
801
|
+
# Direct await since process is now async
|
|
802
|
+
await self._log_detected_plates(processed_data, config, stream_info, input_bytes)
|
|
640
803
|
|
|
641
804
|
# Step 10: Update frame counter
|
|
642
805
|
self._total_frame_counter += 1
|
|
@@ -806,6 +969,39 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
806
969
|
# ------------------------------------------------------------------
|
|
807
970
|
# Fast OCR helpers
|
|
808
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
|
+
|
|
809
1005
|
def _clean_text(self, text: str) -> str:
|
|
810
1006
|
"""Sanitise OCR output to keep only alphanumerics and uppercase."""
|
|
811
1007
|
if not text:
|
|
@@ -814,10 +1010,18 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
814
1010
|
|
|
815
1011
|
def _run_ocr(self, crop: np.ndarray) -> str:
|
|
816
1012
|
"""Run OCR on a cropped plate image and return cleaned text or empty string."""
|
|
817
|
-
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:
|
|
818
1022
|
return ""
|
|
819
1023
|
|
|
820
|
-
# Check if we have a valid OCR model
|
|
1024
|
+
# Check if we have a valid OCR model with run method
|
|
821
1025
|
if not hasattr(self.ocr_model, 'run'):
|
|
822
1026
|
return ""
|
|
823
1027
|
|
|
@@ -926,7 +1130,7 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
926
1130
|
if not dominant_text:
|
|
927
1131
|
dominant_text = self.unique_plate_track.get(tid)
|
|
928
1132
|
|
|
929
|
-
# Enforce length
|
|
1133
|
+
# Enforce length 56 and uniqueness per frame
|
|
930
1134
|
if dominant_text and self._min_plate_len <= len(dominant_text) <= 6:
|
|
931
1135
|
unique_texts.add(dominant_text)
|
|
932
1136
|
valid_detections.append({
|
|
@@ -1373,7 +1577,7 @@ class LicensePlateMonitorUseCase(BaseProcessor):
|
|
|
1373
1577
|
"""Format a timestamp so that exactly two digits follow the decimal point (milliseconds).
|
|
1374
1578
|
|
|
1375
1579
|
The input can be either:
|
|
1376
|
-
1. A numeric Unix timestamp (``float`` / ``int``)
|
|
1580
|
+
1. A numeric Unix timestamp (``float`` / ``int``) it will first be converted to a
|
|
1377
1581
|
string in the format ``YYYY-MM-DD-HH:MM:SS.ffffff UTC``.
|
|
1378
1582
|
2. A string already following the same layout.
|
|
1379
1583
|
|
|
@@ -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.34.dist-info → matrice_analytics-0.1.36.dist-info}/licenses/LICENSE.txt
RENAMED
|
File without changes
|
|
File without changes
|