Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
parkpow
GitHub Repository: parkpow/deep-license-plate-recognition
Path: blob/master/webhooks/webhook_tester/front_rear_tester.py
1093 views
1
import argparse
2
import json
3
import os
4
from dataclasses import dataclass
5
from datetime import datetime, timezone
6
from typing import Any
7
8
import requests
9
10
"""
11
Expected output when running the tester against properly configured middleware:
12
13
--- Scenario 1: Normal pair (front then rear, plate in DB, correct make/model) ---
14
Response: {"message":"Event buffered, waiting for pair"}
15
Response: {"message":"Processed camera pair"}
16
17
--- Scenario 2: Solo front camera (plate in DB, correct make/model) ---
18
Response: {"message":"Processed camera pair"}
19
20
--- Scenario 3: Solo rear camera (plate in DB, correct make/model) ---
21
Response: {"message":"Processed camera pair"}
22
23
--- Scenario 4: Failed front camera (front fails, rear works, plate in DB, correct make/model) ---
24
Response: {"message":"Event buffered, waiting for pair"}
25
26
--- Scenario 5: Failed rear camera (front works, rear fails, plate in DB, correct make/model) ---
27
Response: {"message":"Event buffered, waiting for pair"}
28
29
--- Scenario 6: Overwrite unpaired event (not in DB) ---
30
Response: {"message":"Event buffered, waiting for pair"}
31
Response: {"message":"Event buffered, waiting for pair"}
32
33
--- Scenario 7: Plate not in DB ---
34
Response: {"message":"Event buffered, waiting for pair"}
35
36
--- Scenario 8: Make/model mismatch (plate in DB, wrong make/model) ---
37
Response: {"message":"Event buffered, waiting for pair"}
38
39
--- Scenario 9: Camera not configured (plate in DB, correct make/model) ---
40
Response: {"message":"Camera not configured in any pair"}
41
"""
42
43
CONFIG_PATH = os.path.join(
44
os.path.dirname(__file__), "../middleware/protocols/config/front_rear_config.json"
45
)
46
47
# Plates known to be in the DB (from user-provided CSV)
48
IN_DB_PLATES = [
49
("34A23126", "TOYOTA", "YARIS"),
50
("34A36837", "MITSUBISHI", "LANCER"),
51
("89C20339", "ISUZU", "GIGA"),
52
]
53
54
DEFAULT_REGION = "us-ca"
55
56
57
@dataclass
58
class TestConfig:
59
"""Test configuration and camera pairs."""
60
61
endpoint: str
62
token: str
63
normal_pair: dict[str, str] | None
64
solo_front: dict[str, str] | None
65
solo_rear: dict[str, str] | None
66
failed_front: dict[str, str] | None
67
failed_rear: dict[str, str] | None
68
in_db_plate: str
69
in_db_make: str
70
in_db_model: str
71
region: str = DEFAULT_REGION
72
73
74
def load_config() -> dict[str, Any]:
75
"""Load middleware configuration from JSON file."""
76
with open(CONFIG_PATH) as f:
77
return json.load(f)
78
79
80
def find_camera_pairs(config: dict[str, Any]) -> dict[str, dict[str, str] | None]:
81
"""Find and return all configured camera pairs."""
82
pairs = config.get("camera_pairs", [])
83
84
return {
85
"normal_pair": next(
86
(
87
p
88
for p in pairs
89
if p.get("front") == "camera-front" and p.get("rear") == "camera-rear"
90
),
91
None,
92
),
93
"solo_front": next(
94
(
95
p
96
for p in pairs
97
if p.get("front") == "camera-solo-front" and not p.get("rear")
98
),
99
None,
100
),
101
"solo_rear": next(
102
(
103
p
104
for p in pairs
105
if not p.get("front") and p.get("rear") == "camera-solo-rear"
106
),
107
None,
108
),
109
"failed_front": next(
110
(
111
p
112
for p in pairs
113
if p.get("front") == "camera-failed-front"
114
and p.get("rear") == "camera-working-rear"
115
),
116
None,
117
),
118
"failed_rear": next(
119
(
120
p
121
for p in pairs
122
if p.get("front") == "camera-working-front"
123
and p.get("rear") == "camera-failed-rear"
124
),
125
None,
126
),
127
}
128
129
130
def format_timestamp(dt: datetime) -> str:
131
"""Format datetime as ISO8601 timestamp string."""
132
return dt.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
133
134
135
def build_event(
136
camera_id: str,
137
plate: str,
138
region_code: str,
139
timestamp: str,
140
endpoint: str,
141
make: str = "Porsche",
142
model: str = "911",
143
orientation: str = "Front",
144
) -> dict[str, Any]:
145
"""Build a front_rear event payload matching stream structure."""
146
filename = f"{camera_id}_screenshots/image.jpg"
147
return {
148
"hook": {
149
"target": endpoint,
150
"id": camera_id,
151
"event": "recognition",
152
"filename": filename,
153
},
154
"data": {
155
"camera_id": camera_id,
156
"filename": filename,
157
"timestamp": timestamp,
158
"timestamp_local": timestamp,
159
"timestamp_camera": None,
160
"results": [
161
{
162
"box": {"xmax": 412, "xmin": 337, "ymax": 305, "ymin": 270},
163
"candidates": [
164
{"plate": plate, "score": 0.902},
165
{"plate": "plbrec", "score": 0.758},
166
],
167
"color": [
168
{"color": "red", "score": 0.699},
169
{"color": "black", "score": 0.134},
170
{"color": "blue", "score": 0.03},
171
],
172
"dscore": 0.757,
173
"model_make": [{"make": make, "model": model, "score": 0.43}],
174
"orientation": [
175
{"orientation": orientation, "score": 0.883},
176
{
177
"orientation": "Rear"
178
if orientation == "Front"
179
else "Front",
180
"score": 0.07,
181
},
182
{"orientation": "Unknown", "score": 0.047},
183
],
184
"plate": plate,
185
"region": {"code": region_code, "score": 0.179},
186
"score": 0.902,
187
"vehicle": {
188
"box": {"xmax": 590, "xmin": 155, "ymax": 373, "ymin": 71},
189
"score": 0.709,
190
"type": "Sedan",
191
},
192
"direction": 210,
193
"source_url": "/user-data/video.mp4",
194
"position_sec": 23.47,
195
}
196
],
197
},
198
}
199
200
201
def send_event(url: str, token: str, event: dict[str, Any]) -> requests.Response:
202
"""Send event to middleware endpoint with optional image attachment."""
203
files = {}
204
image_path = "./small.jpg"
205
if os.path.exists(image_path):
206
files["upload"] = ("small.jpg", open(image_path, "rb"), "image/jpeg")
207
208
data = {"json": json.dumps(event)}
209
headers = {"Authorization": f"Token {token}"} if token else {}
210
211
try:
212
resp = requests.post(url, data=data, files=files, headers=headers, timeout=30)
213
return resp
214
finally:
215
if "upload" in files:
216
files["upload"][1].close()
217
218
219
def print_result(
220
resp: requests.Response,
221
plate: str | None = None,
222
make: str | None = None,
223
model: str | None = None,
224
camera: str | None = None,
225
) -> None:
226
"""Print test result with event metadata."""
227
if plate or make or model or camera:
228
print(f"[camera={camera} plate={plate} make={make} model={model}]")
229
print(f"Status code: {resp.status_code}")
230
print(f"Response: {resp.text}")
231
232
233
def send_and_print(
234
test_cfg: TestConfig,
235
camera_id: str,
236
plate: str,
237
timestamp: str,
238
orientation: str,
239
make: str | None = None,
240
model: str | None = None,
241
description: str | None = None,
242
) -> None:
243
"""Helper to build, send event, and print result."""
244
if description:
245
print(description)
246
247
event = build_event(
248
camera_id=camera_id,
249
plate=plate,
250
region_code=test_cfg.region,
251
timestamp=timestamp,
252
endpoint=test_cfg.endpoint,
253
make=make or "Porsche",
254
model=model or "911",
255
orientation=orientation,
256
)
257
258
resp = send_event(test_cfg.endpoint, test_cfg.token, event)
259
print_result(resp, plate, make, model, camera_id)
260
261
262
def run_scenarios(args: argparse.Namespace, config: dict[str, Any]) -> None:
263
"""Execute all test scenarios against the middleware."""
264
now = datetime.now(timezone.utc)
265
timestamp = format_timestamp(now)
266
in_db_plate, in_db_make, in_db_model = IN_DB_PLATES[0]
267
268
camera_pairs = find_camera_pairs(config)
269
test_cfg = TestConfig(
270
endpoint=args.endpoint,
271
token=args.token,
272
in_db_plate=in_db_plate,
273
in_db_make=in_db_make,
274
in_db_model=in_db_model,
275
region=DEFAULT_REGION,
276
normal_pair=camera_pairs["normal_pair"],
277
solo_front=camera_pairs["solo_front"],
278
solo_rear=camera_pairs["solo_rear"],
279
failed_front=camera_pairs["failed_front"],
280
failed_rear=camera_pairs["failed_rear"],
281
)
282
283
# Scenario 1: Normal pair (front then rear, plate in DB, correct make/model)
284
if test_cfg.normal_pair:
285
print(
286
"\n--- Scenario 1: Normal pair (front then rear, plate in DB, correct make/model) ---"
287
)
288
print("Sending front event...")
289
send_and_print(
290
test_cfg,
291
test_cfg.normal_pair["front"],
292
test_cfg.in_db_plate,
293
timestamp,
294
"Front",
295
test_cfg.in_db_make,
296
test_cfg.in_db_model,
297
)
298
print("Sending rear event...")
299
send_and_print(
300
test_cfg,
301
test_cfg.normal_pair["rear"],
302
test_cfg.in_db_plate,
303
timestamp,
304
"Rear",
305
test_cfg.in_db_make,
306
test_cfg.in_db_model,
307
)
308
309
# Scenario 2: Solo front (in DB plate, correct make/model)
310
if test_cfg.solo_front:
311
print(
312
"\n--- Scenario 2: Solo front camera (plate in DB, correct make/model) ---"
313
)
314
print("Sending solo front event...")
315
send_and_print(
316
test_cfg,
317
test_cfg.solo_front["front"],
318
test_cfg.in_db_plate,
319
timestamp,
320
"Front",
321
test_cfg.in_db_make,
322
test_cfg.in_db_model,
323
)
324
325
# Scenario 3: Solo rear (in DB plate, correct make/model)
326
if test_cfg.solo_rear:
327
print(
328
"\n--- Scenario 3: Solo rear camera (plate in DB, correct make/model) ---"
329
)
330
print("Sending solo rear event...")
331
send_and_print(
332
test_cfg,
333
test_cfg.solo_rear["rear"],
334
test_cfg.in_db_plate,
335
timestamp,
336
"Rear",
337
test_cfg.in_db_make,
338
test_cfg.in_db_model,
339
)
340
341
# Scenario 4: Failed front (in DB plate, correct make/model)
342
if test_cfg.failed_front:
343
print(
344
"\n--- Scenario 4: Failed front camera (front fails, rear works, plate in DB, correct make/model) ---"
345
)
346
print("Sending rear event only...")
347
send_and_print(
348
test_cfg,
349
test_cfg.failed_front["rear"],
350
test_cfg.in_db_plate,
351
timestamp,
352
"Rear",
353
test_cfg.in_db_make,
354
test_cfg.in_db_model,
355
)
356
357
# Scenario 5: Failed rear (in DB plate, correct make/model)
358
if test_cfg.failed_rear:
359
print(
360
"\n--- Scenario 5: Failed rear camera (front works, rear fails, plate in DB, correct make/model) ---"
361
)
362
print("Sending front event only...")
363
send_and_print(
364
test_cfg,
365
test_cfg.failed_rear["front"],
366
test_cfg.in_db_plate,
367
timestamp,
368
"Front",
369
test_cfg.in_db_make,
370
test_cfg.in_db_model,
371
)
372
373
# Scenario 6: Overwrite event (new plate before pair completes, not in DB)
374
if test_cfg.normal_pair:
375
print("\n--- Scenario 6: Overwrite unpaired event (not in DB) ---")
376
print("Sending front event with OLD123...")
377
send_and_print(
378
test_cfg, test_cfg.normal_pair["front"], "OLD123", timestamp, "Front"
379
)
380
print("Sending front event with NEW456 (should overwrite)...")
381
send_and_print(
382
test_cfg, test_cfg.normal_pair["front"], "NEW456", timestamp, "Front"
383
)
384
385
# Scenario 7: Plate not in DB
386
if test_cfg.normal_pair:
387
print("\n--- Scenario 7: Plate not in DB ---")
388
print("Sending front event with unknown plate...")
389
send_and_print(
390
test_cfg, test_cfg.normal_pair["front"], "NOTINDB", timestamp, "Front"
391
)
392
393
# Scenario 8: Make/model mismatch (in DB plate, wrong make/model)
394
if test_cfg.normal_pair:
395
print(
396
"\n--- Scenario 8: Make/model mismatch (plate in DB, wrong make/model) ---"
397
)
398
print("Sending front event with mismatched make/model...")
399
send_and_print(
400
test_cfg,
401
test_cfg.normal_pair["front"],
402
test_cfg.in_db_plate,
403
timestamp,
404
"Front",
405
"Toyota",
406
"Corolla",
407
)
408
409
# Scenario 9: Camera not configured (in DB plate, correct make/model)
410
print(
411
"\n--- Scenario 9: Camera not configured (plate in DB, correct make/model) ---"
412
)
413
print("Sending event from unknown camera...")
414
send_and_print(
415
test_cfg,
416
"unknown-camera",
417
test_cfg.in_db_plate,
418
timestamp,
419
"Front",
420
test_cfg.in_db_make,
421
test_cfg.in_db_model,
422
)
423
424
425
def parse_args() -> argparse.Namespace:
426
"""Parse command line arguments."""
427
parser = argparse.ArgumentParser(
428
description="Test front-rear middleware event scenarios (config-driven)"
429
)
430
parser.add_argument("--endpoint", required=True, help="Webhook endpoint URL")
431
parser.add_argument("--token", required=True, help="Authorization token (required)")
432
return parser.parse_args()
433
434
435
if __name__ == "__main__":
436
args = parse_args()
437
config = load_config()
438
try:
439
run_scenarios(args, config)
440
except KeyboardInterrupt:
441
print("\nStopping...")
442
except Exception as e:
443
print(f"\n--> An error occurred: {e}")
444
print("--> The test failed.")
445
446