Skip to content

Commit 96f5282

Browse files
committed
v1.5.19
1 parent 5babc36 commit 96f5282

File tree

21 files changed

+173
-47
lines changed

21 files changed

+173
-47
lines changed

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,11 +295,10 @@ The `.env` [file](https://docs.docker.com/compose/environment-variables/set-envi
295295
./scripts/sh-services edit <service>
296296
```
297297
and follow the prompts to edit `.env` variables interactively.
298-
299298
2. Add a user-specific file with an `.env` extension to the `conf/` folder, e.g. `sio/conf/0009-debug.env`:
300299
```bash
301300
echo "MY_VARIABLE=24" > sio/conf/0009-debug.env
302-
echo "SIO_DOCKER_TAG=r250127" >> sio/conf/0009-debug.env
301+
echo "SIO_DOCKER_TAG=r250201" >> sio/conf/0009-debug.env
303302

304303
./scripts/sh-services merge sio
305304
```

RELEASE.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Release Notes
22

3+
## V1.5.19
4+
5+
- Update SIO to r250201
6+
- Add vehicle MMCG to the UI
7+
- Add webhook output via POST
8+
39
## V1.5.18
410

511
- Add important MCP information

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v1.5.18
1+
v1.5.19

deployment-examples/ALPRDemo/common/Database.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33
from datetime import datetime, timedelta
44

55
class LicensePlate:
6-
def __init__(self, object_id, region, plate_string, detection_time, source_id, x, y, w, h, imageId):
6+
def __init__(self, object_id, make, model, color, region, plate_string, detection_time, source_id, x, y, w, h, imageId):
77
self.object_id = object_id
88
self.region = region
99
self.plate_string = plate_string
1010
self.detection_time = detection_time
1111
self.source_id = source_id
12+
self.make = make
13+
self.model = model
14+
self.color = color
1215
self.x = x
1316
self.y = y
1417
self.w = w
@@ -18,6 +21,9 @@ def __init__(self, object_id, region, plate_string, detection_time, source_id, x
1821
def to_dict(self):
1922
return {
2023
"oid" : self.object_id,
24+
"make" : self.make,
25+
"model" : self.model,
26+
"color" : self.color,
2127
"string" : self.plate_string,
2228
"region" : self.region,
2329
"time" : self.detection_time,
@@ -38,6 +44,9 @@ def create_table(self):
3844
CREATE TABLE IF NOT EXISTS plates (
3945
id INTEGER PRIMARY KEY AUTOINCREMENT,
4046
object_id TEXT UNIQUE,
47+
make TEXT,
48+
model TEXT,
49+
color TEXT,
4150
region TEXT,
4251
plate_string TEXT,
4352
detection_time INTEGER, -- Epoch time
@@ -53,10 +62,13 @@ def create_table(self):
5362
def add_detection(self, license_plate):
5463
with self.conn:
5564
self.conn.execute('''
56-
INSERT OR IGNORE INTO plates (object_id, region, plate_string, detection_time, source_id, x, y, w, h, imageId)
57-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
65+
INSERT OR IGNORE INTO plates (object_id, make, model, color, region, plate_string, detection_time, source_id, x, y, w, h, imageId)
66+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5867
ON CONFLICT(id)
5968
DO UPDATE SET
69+
make = excluded.make,
70+
model = excluded.model,
71+
color = excluded.color,
6072
region = excluded.region,
6173
plate_string = excluded.plate_string,
6274
detection_time = excluded.detection_time,
@@ -66,7 +78,8 @@ def add_detection(self, license_plate):
6678
h = excluded.h,
6779
imageId = excluded.imageId;
6880
''', (
69-
license_plate.object_id, license_plate.region, license_plate.plate_string,
81+
license_plate.object_id, license_plate.make, license_plate.model,
82+
license_plate.color, license_plate.region, license_plate.plate_string,
7083
int(license_plate.detection_time), license_plate.source_id,
7184
license_plate.x, license_plate.y, license_plate.w, license_plate.h,
7285
license_plate.imageId
@@ -123,6 +136,9 @@ def close(self):
123136
current_time = int(datetime.now().timestamp())
124137
license_plate = LicensePlate(
125138
object_id='123ABC',
139+
make = 'toyota',
140+
model = 'corolla',
141+
color = 'white',
126142
region='FL',
127143
plate_string='ABC1234',
128144
detection_time=current_time,

deployment-examples/ALPRDemo/config/sio-pipelines.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"pipeline" : "./share/pipelines/VehicleAnalytics/VehicleAnalyticsRTSP.yaml",
44
"restartPolicy" : "restart",
55
"parameters" : {
6-
"VIDEO_IN" : "rtsp://live555_svc:554/StreetVideo1.mkv",
6+
"VIDEO_IN" : "rtsp://live555_svc:554/Turn-01.mkv",
77
"boxFilterConfig" : "/config/sio-box-filter.json",
88
"detectionModel" : "gen7es",
99
"lptModel" : "gen7es",
@@ -30,7 +30,7 @@
3030
"pipeline" : "./share/pipelines/VehicleAnalytics/VehicleAnalyticsRTSP.yaml",
3131
"restartPolicy" : "restart",
3232
"parameters" : {
33-
"VIDEO_IN" : "rtsp://live555_svc:554/StreetVideo2.mkv",
33+
"VIDEO_IN" : "rtsp://live555_svc:554/Turn-02.mkv",
3434
"boxFilterConfig" : "/config/sio-box-filter.json",
3535
"detectionModel" : "gen7es",
3636
"lptModel" : "gen7es",

deployment-examples/ALPRDemo/consumer/SIO.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,24 @@ def getLPInfo(self, lps, lpKey):
3737

3838
return lpString, lpRegion, lpBox
3939

40+
# -------------------------------------------------------------------------------
41+
# Get Vehicle attributes
42+
# -------------------------------------------------------------------------------
43+
def getVehicleInfo(self, vehicles, vehicleKey):
44+
vehicle = vehicles[vehicleKey]
45+
makeModel = vehicle.get("attributes", {}).get("vehicleType", {}).get("value", {})
46+
makeModelScore = vehicle.get("attributes", {}).get("vehicleType", {}).get("value", {})
47+
if isinstance(makeModel, dict):
48+
make = makeModel.get("make",{})
49+
model = makeModel.get("model",{}) + " " + makeModel.get("generation",{})
50+
else:
51+
make = makeModel
52+
model = ""
53+
color = vehicle.get("attributes", {}).get("color", {}).get("value", {})
54+
vehicleBox = self.getBox(vehicle)
55+
56+
return make, model, color, vehicleBox, makeModelScore
57+
4058
# -------------------------------------------------------------------------------
4159
# Get image ID associated with current message or None
4260
# -------------------------------------------------------------------------------
@@ -54,25 +72,34 @@ def parseSIOMessage(self, message):
5472

5573
mc = message.get("metaClasses", {})
5674
lps = mc.get("licensePlates", {})
75+
vehicles = mc.get("vehicles", {})
5776

5877
# Get image associated with the frame (this isn't guaranteed)
5978
imageId = self.getFrameImageID(message)
6079

6180
# Process license plates
6281
for lpKey in lps.keys():
6382
lpString, lpRegion, lpBox = self.getLPInfo(lps, lpKey)
64-
self.onLicensePlate(sourceId, lpKey, frameTimestamp, lpString, lpRegion, lpBox, imageId)
83+
links = lps[lpKey].get("links",{})[0]
84+
make = ""; model = ""; color = ""; vehicleBox = ""
85+
if "vehicles" == links.get("metaClass",{}):
86+
vehicleId = links.get("id",{})
87+
make, model, color, vehicleBox, makeModelScore = self.getVehicleInfo(vehicles, vehicleId)
88+
89+
self.onLicensePlate(sourceId, lpKey, frameTimestamp, lpString, lpRegion, lpBox,
90+
make, model, color, makeModelScore, vehicleBox, imageId)
6591

6692

6793
# -------------------------------------------------------------------------------
68-
def onLicensePlate(self, sourceId, uid, frameTimestamp, lpString, lpRegion, lpBox, imageId):
94+
def onLicensePlate(self, sourceId, uid, frameTimestamp, lpString, lpRegion, lpBox, make, model, color, makeModelScore, vBox, imageId):
6995
frameTimestampValue = int(frameTimestamp)
7096
timestamp_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(frameTimestampValue/1000))
7197

72-
lp = LicensePlate(uid, lpRegion, lpString, frameTimestamp, sourceId, lpBox[0], lpBox[1], lpBox[2], lpBox[3], imageId)
98+
lp = LicensePlate(uid, make, model, color, lpRegion, lpString, frameTimestamp, sourceId,
99+
lpBox[0], lpBox[1], lpBox[2], lpBox[3], imageId)
73100
try:
74101
self.db.add_detection(lp)
75-
print(f"{timestamp_str} Got LP source={sourceId}, uid={uid} time={frameTimestamp} string={lpString} region={lpRegion} box={lpBox} imageId={imageId}")
102+
print(f"{timestamp_str} Got LP source={sourceId}, uid={uid} time={frameTimestamp} make={make} model={model} color={color} string={lpString} region={lpRegion} box={lpBox} imageId={imageId}")
76103
except:
77104
print(f"{timestamp_str} Failed to add to DB: source={sourceId}, uid={uid} time={frameTimestamp} string={lpString} region={lpRegion} box={lpBox}")
78105
print(f"Failed to insert/update LP: {traceback.format_exc()}")

deployment-examples/ALPRDemo/docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ services:
2323

2424
# ========================= SIO ALPR Analytics =========================
2525
analytics_svc:
26-
image: us-central1-docker.pkg.dev/ext-edge-analytics/docker/sio:${SIO_RELEASE-r250127}${SIO_DOCKER_TAG_VARIANT}
26+
image: us-central1-docker.pkg.dev/ext-edge-analytics/docker/sio:${SIO_RELEASE-r250201}${SIO_DOCKER_TAG_VARIANT}
2727
restart: unless-stopped
2828
environment:
2929
# Location where SIO will place generated model engine files

deployment-examples/ALPRDemo/ui/python/ALPRUI.py

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
import cv2
1414
from datetime import datetime
1515
from SIOParser import SIOParser
16+
import argparse
1617

1718
kDefaultTimeout = 5
19+
kDefaultIpAddress = "127.0.0.1"
1820

1921
def epoch_to_offset(epoch_timestamp):
2022
return datetime.utcfromtimestamp(epoch_timestamp/1000).strftime('%H:%M:%S')
@@ -86,11 +88,11 @@ def onCancel(self, event):
8688

8789
class MainFrame(wx.Frame):
8890
# =========================================================================
89-
def __init__(self, *args, **kw):
91+
def __init__(self, ipAddress, *args, **kw):
9092
super(MainFrame, self).__init__(*args, **kw)
9193

9294
self.settings = {
93-
"api_ip": "10.10.10.20",
95+
"api_ip": ipAddress,
9496
"api_port": "8888",
9597
"refresh_rate": 10,
9698
"max_entries": 50,
@@ -154,7 +156,6 @@ def initUI(self):
154156
search_sizer.Add(self.search_button, 0, wx.ALIGN_CENTER | wx.ALL, 5)
155157
self.search_tab.SetSizer(search_sizer)
156158

157-
158159
# Layout for the File tab
159160
file_sizer = wx.BoxSizer(wx.VERTICAL)
160161
self.uploaded_files_list = wx.ListCtrl(self.file_tab, style=wx.LC_REPORT)
@@ -172,17 +173,22 @@ def initUI(self):
172173
self.file_tab.SetSizer(file_sizer)
173174
self.uploaded_files_results = {}
174175

175-
176176
self.list_box = self.initListCtrl(self.panel)
177177
self.image_ctrl = wx.StaticBitmap(self.panel)
178-
self.lp_ctrl = wx.StaticBitmap(self.panel, size=(100,60))
178+
self.lp_ctrl = wx.StaticBitmap(self.panel, size=(200,100))
179+
180+
# Create a horizontal sizer for image_ctrl and lp_ctrl
181+
self.image_lp_sizer = wx.BoxSizer(wx.HORIZONTAL)
182+
self.image_lp_sizer.Add(self.image_ctrl, 1, wx.EXPAND | wx.ALL, 5) # image_ctrl takes more space
183+
self.image_lp_sizer.Add(self.lp_ctrl, 1, wx.ALIGN_CENTER | wx.ALL, 5) # lp_ctrl stays small
179184

180185
# Layout for the main panel
181186
main_sizer = wx.BoxSizer(wx.VERTICAL)
182187
main_sizer.Add(self.notebook, 1, wx.EXPAND)
183188
main_sizer.Add(self.list_box, 1, wx.EXPAND | wx.ALL, 5)
184-
main_sizer.Add(self.image_ctrl, 1, wx.EXPAND | wx.ALL, 5)
185-
main_sizer.Add(self.lp_ctrl, 1, wx.ALIGN_CENTER | wx.ALL, 5)
189+
main_sizer.Add(self.image_lp_sizer, 1, wx.EXPAND | wx.ALL, 5)
190+
# main_sizer.Add(self.image_ctrl, 1, wx.EXPAND | wx.ALL, 5)
191+
# main_sizer.Add(self.lp_ctrl, 1, wx.ALIGN_CENTER | wx.ALL, 5)
186192
self.panel.SetSizer(main_sizer)
187193

188194
# Bind events
@@ -267,9 +273,10 @@ def clearSharedUIState(self):
267273
def initListCtrl(self,parent):
268274
ctrl = wx.ListCtrl(parent, style=wx.LC_REPORT)
269275
ctrl.InsertColumn(0, 'Time')
270-
ctrl.InsertColumn(1, 'Plate/State')
271-
ctrl.InsertColumn(2, 'Source')
272-
ctrl.InsertColumn(3, 'UID')
276+
ctrl.InsertColumn(1, 'Make/Model/Color')
277+
ctrl.InsertColumn(2, 'Plate/State')
278+
ctrl.InsertColumn(3, 'Source')
279+
ctrl.InsertColumn(4, 'UID')
273280
# Bind the single-click event
274281
ctrl.Bind(wx.EVT_LIST_ITEM_SELECTED, self.onListItemSelected)
275282
return ctrl
@@ -481,11 +488,15 @@ def populateListWithData(self,data,ctrl,isoffset):
481488
else:
482489
dt = epoch_to_string(int(entry['time']))
483490
ctrl.InsertItem(index, f"{dt}")
484-
ctrl.SetItem(index, 1, f"{entry['string']}/{entry['region']}")
485-
ctrl.SetItem(index, 2, f"{entry['sourceId']}")
486-
ctrl.SetItem(index, 3, f"{entry['oid']}")
491+
ctrl.SetItem(index, 1, f"{entry['make']}/{entry['model']}/{entry['color']}")
492+
ctrl.SetItem(index, 2, f"{entry['string']}/{entry['region']}")
493+
ctrl.SetItem(index, 3, f"{entry['sourceId']}")
494+
ctrl.SetItem(index, 4, f"{entry['oid']}")
487495
index = index + 1
488496

497+
for i in range(4): # Adjust all columns
498+
ctrl.SetColumnWidth(i, wx.LIST_AUTOSIZE)
499+
489500
# =========================================================================
490501
def apiRoot(self):
491502
return f"http://{self.settings['api_ip']}:{self.settings['api_port']}"
@@ -628,8 +639,20 @@ def BringToFront(self):
628639
print("Error bringing window to front:", e)
629640

630641

631-
app = wx.App(False)
632-
frame = MainFrame(None, title="ALPR Demo", size=(800, 600))
633-
frame.Bind(wx.EVT_CLOSE, frame.onClose)
634-
frame.Show()
635-
app.MainLoop()
642+
def main():
643+
644+
# Load args
645+
parser = argparse.ArgumentParser(description='Run ALPR Demo Client UI')
646+
parser.add_argument('-i', '--ipAddress', type=str, help='The Server IP Address', default=kDefaultIpAddress)
647+
648+
args = parser.parse_args()
649+
ipAddress = args.ipAddress
650+
651+
app = wx.App(False)
652+
frame = MainFrame(ipAddress, None, title="ALPR Demo", size=(800, 600))
653+
frame.Bind(wx.EVT_CLOSE, frame.onClose)
654+
frame.Show()
655+
app.MainLoop()
656+
657+
if __name__ == "__main__":
658+
main()

deployment-examples/ALPRDemo/ui/python/SIOParser.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,24 @@ def getLPInfo(self, lps, lpKey):
2828

2929
return lpString, lpRegion, lpBox
3030

31+
# -------------------------------------------------------------------------------
32+
# Get Vehicle attributes
33+
# -------------------------------------------------------------------------------
34+
def getVehicleInfo(self, vehicles, vehicleKey):
35+
vehicle = vehicles[vehicleKey]
36+
makeModel = vehicle.get("attributes", {}).get("vehicleType", {}).get("value", {})
37+
makeModelScore = vehicle.get("attributes", {}).get("vehicleType", {}).get("attributeScore", {})
38+
if isinstance(makeModel, dict):
39+
make = makeModel.get("make",{})
40+
model = makeModel.get("model",{}) + " " + makeModel.get("generation",{})
41+
else:
42+
make = makeModel
43+
model = ""
44+
color = vehicle.get("attributes", {}).get("color", {}).get("value", {})
45+
vehicleBox = self.getBox(vehicle)
46+
47+
return make, model, color, vehicleBox, makeModelScore
48+
3149
# -------------------------------------------------------------------------------
3250
def parseSIOResult(self, result):
3351
frames = sorted([int(key) for key in result])
@@ -38,27 +56,38 @@ def parseSIOResult(self, result):
3856

3957
mc = message.get("metaClasses", {})
4058
lps = mc.get("licensePlates", {})
41-
59+
vehicles = mc.get("vehicles", {})
4260

4361
# Process license plates
4462
for lpKey in lps.keys():
63+
links = lps[lpKey].get("links",{})[0]
64+
make = ""; model = ""; color = ""; vehicleBox = ""
65+
if "vehicles" == links.get("metaClass",{}):
66+
vehicleId = links.get("id",{})
67+
make, model, color, vehicleBox, makeModelScore = self.getVehicleInfo(vehicles, vehicleId)
68+
4569
lpString, lpRegion, lpBox = self.getLPInfo(lps, lpKey)
4670
if lpKey in self.lps:
4771
if self.lps[lpKey][3] == lpString and self.lps[lpKey][2] == lpRegion:
4872
# Nothing changed about the plate (but the box may have gotten worse, so keep an earlier one)
4973
continue
50-
self.onLicensePlate(sourceId, frame, lpKey, frameTimestamp, lpString, lpRegion, lpBox)
74+
75+
self.onLicensePlate(sourceId, frame, lpKey, frameTimestamp, lpString,
76+
lpRegion, lpBox, make, model, color, makeModelScore, vehicleBox)
5177

5278

5379
# -------------------------------------------------------------------------------
54-
def onLicensePlate(self, sourceid, frameid, uid, frameTimestamp, lpString, lpRegion, lpBox):
80+
def onLicensePlate(self, sourceid, frameid, uid, frameTimestamp,
81+
lpString, lpRegion, lpBox, make, model, color, makeModelScore, vehicleBox):
5582
frameTimestampValue = int(frameTimestamp)
56-
self.lps[uid] = ( sourceid, frameid, lpRegion, lpString, frameTimestamp, lpBox )
83+
self.lps[uid] = ( sourceid, frameid, lpRegion, lpString, frameTimestamp, lpBox,
84+
make, model, color, makeModelScore, vehicleBox )
5785

5886
# -------------------------------------------------------------------------------
5987
def getLPs(self):
6088
res = []
6189
for k in self.lps:
6290
lp = self.lps[k]
63-
res.append( { 'time': lp[4], 'string' : lp[3], 'region' : lp[2], 'sourceId' : lp[0], 'box' : lp[5], 'oid' : k, 'frameid' : lp[1] } )
91+
res.append( { 'time': lp[4], 'string' : lp[3], 'region' : lp[2], 'sourceId' : lp[0], 'box' : lp[5],
92+
'oid' : k, 'frameid' : lp[1], 'make' : lp[6], 'model' : lp[7], 'color' : lp[8], 'vBox' : lp[9] } )
6493
return res

deployment-examples/SIOOnDemandAnalytics/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ When deploying on Jetson devices, additional configuration is required:
4747
```bash
4848
sed -i 's|"VIDEO_IN".*:.*"rtsp://.*"|"VIDEO_IN": "rtsp://sh-ui-backend:8555/live"|' config/analytics/pipelines.json
4949
```
50+
6. Execute the following command to get it up and running:
51+
```bash
52+
docker compose up
53+
```
5054

5155
## Testing the API
5256

0 commit comments

Comments
 (0)