Skip to content

Commit dbb0b33

Browse files
committed
Initial release
1 parent 71dad76 commit dbb0b33

File tree

17 files changed

+674
-1
lines changed

17 files changed

+674
-1
lines changed

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Auto detect text files and perform LF normalization
2+
* text=auto

.github/workflows/docker.yml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: Docker
2+
3+
on:
4+
schedule:
5+
- cron: "0 1 * * *" # run at 1 AM UTC
6+
7+
workflow_dispatch:
8+
9+
jobs:
10+
docker:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- uses: actions/checkout@v2
15+
16+
- name: Set up QEMU
17+
uses: docker/setup-qemu-action@v1
18+
19+
- name: Set up Docker Buildx
20+
uses: docker/setup-buildx-action@v1
21+
22+
- name: Login to GitHub Container Registry
23+
uses: docker/login-action@v1
24+
with:
25+
registry: ghcr.io
26+
username: ${{ github.repository_owner }}
27+
password: ${{ secrets.GITHUB_TOKEN }}
28+
29+
- name: Build linux/amd64
30+
id: docker_build
31+
uses: docker/build-push-action@v2
32+
with:
33+
context: .
34+
file: ./Dockerfile
35+
platforms: linux/amd64
36+
37+
- name: Build linux/arm64
38+
id: docker_build_arm64
39+
uses: docker/build-push-action@v2
40+
with:
41+
context: .
42+
file: ./Dockerfile
43+
platforms: linux/arm64
44+
45+
- name: Build linux/arm
46+
id: docker_build_arm
47+
uses: docker/build-push-action@v2
48+
with:
49+
context: .
50+
file: ./Dockerfile
51+
platforms: linux/arm
52+
53+
- name: Push images
54+
id: docker_push
55+
uses: docker/build-push-action@v2
56+
with:
57+
platforms: linux/amd64,linux/arm64,linux/arm
58+
push: true
59+
tags: ghcr.io/redisgrafana/redis-opencv:latest
60+
61+
- name: Image digest
62+
run: echo ${{ steps.docker_build.outputs.digest }}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules/
2+
.DS_Store
3+
data/

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Change Log
2+
3+
## v1.0.0 (IN PROGRESS)
4+
5+
### Features / Enhancements
6+
7+
- Initial release

Dockerfile

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
FROM redislabs/redisai:latest-cpu-x64-bionic as redisai
2+
FROM redislabs/redistimeseries:latest as redistimeseries
3+
FROM redislabs/redisgears:latest
4+
5+
ENV LD_LIBRARY_PATH=/usr/lib/redis/modules
6+
7+
ARG MODULES=/var/opt/redislabs/lib/modules
8+
ARG RG=${MODULES}/redisgears.so
9+
ARG REDIS="redis-server --loadmodule ${RG} PythonHomeDir /opt/redislabs/lib/modules/python3"
10+
11+
ARG DEPS="python python3-pip python3-setuptools libglib2.0-0 libsm6 libxrender1 libxext6 libgomp1"
12+
ARG REQ="numpy \
13+
Pillow \
14+
opencv-python"
15+
16+
# Set up a build environment
17+
WORKDIR /data
18+
RUN set -ex;\
19+
deps="$DEPS";\
20+
apt-get update;\
21+
apt-get install -y --no-install-recommends $deps;
22+
23+
# Copy RedisTimeSeries
24+
COPY --from=redistimeseries ${LD_LIBRARY_PATH}/*.so ${LD_LIBRARY_PATH}/
25+
26+
# Copy RedisAI
27+
COPY --from=redisai ${LD_LIBRARY_PATH}/redisai.so ${LD_LIBRARY_PATH}/
28+
COPY --from=redisai ${LD_LIBRARY_PATH}/backends ${LD_LIBRARY_PATH}/backends
29+
30+
# Start Redis and install Deps
31+
RUN nohup bash -c "${REDIS}&" && sleep 4 && redis-cli RG.PYEXECUTE "GearsBuilder().run()" REQUIREMENTS $REQ \
32+
&& redis-cli save
33+
34+
ENTRYPOINT ["redis-server"]
35+
CMD ["--loadmodule", "/usr/lib/redis/modules/redistimeseries.so", \
36+
"--loadmodule", "/usr/lib/redis/modules/redisai.so", \
37+
"--loadmodule", "/var/opt/redislabs/lib/modules/redisgears.so", \
38+
"PythonHomeDir", "/opt/redislabs/lib/modules/python3"]

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@
186186
same "printed page" as the copyright notice for easier
187187
identification within third-party archives.
188188

189-
Copyright [yyyy] [name of copyright owner]
189+
Copyright 2021 Mikhail Volkov
190190

191191
Licensed under the Apache License, Version 2.0 (the "License");
192192
you may not use this file except in compliance with the License.

ai/init.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import argparse
2+
import redis
3+
from urllib.parse import urlparse
4+
5+
if __name__ == '__main__':
6+
# Parse arguments
7+
parser = argparse.ArgumentParser()
8+
parser.add_argument('-d', '--device', help='CPU or GPU',
9+
type=str, default='CPU')
10+
parser.add_argument('-u', '--url', help='Redis URL',
11+
type=str, default='redis://127.0.0.1:6379')
12+
args = parser.parse_args()
13+
14+
# Set up Redis connection
15+
url = urlparse(args.url)
16+
conn = redis.Redis(host=url.hostname, port=url.port)
17+
if not conn.ping():
18+
raise Exception('Redis unavailable')
19+
20+
# Load the RedisAI model
21+
print('Loading model - ', end='')
22+
with open('./tiny-yolo-voc.pb', 'rb') as f:
23+
model = f.read()
24+
res = conn.execute_command('AI.MODELSET', 'yolo:model', 'TF',
25+
args.device, 'INPUTS', 'input', 'OUTPUTS', 'output', 'BLOB', model)
26+
print(res)
27+
28+
# Load the PyTorch post processing boxes script
29+
print('Loading script - ', end='')
30+
with open('./yolo_boxes.py', 'rb') as f:
31+
script = f.read()
32+
res = conn.execute_command(
33+
'AI.SCRIPTSET', 'yolo:script', args.device, 'SOURCE', script)
34+
print(res)

ai/tiny-yolo-voc.pb

60.5 MB
Binary file not shown.

ai/yolo_boxes.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
def nms(boxes):
2+
# assuming only one image in one batch
3+
boxes = boxes.squeeze()
4+
nms_thresh = 0.45
5+
conf_thresh = 0.2
6+
no_of_valid_elems = (boxes[:, 4] > conf_thresh).nonzero().numel()
7+
boxes_confs_inv = 1 - boxes[:, 4]
8+
_, sort_ids = torch.sort(boxes_confs_inv)
9+
x1 = boxes[:, 0]
10+
y1 = boxes[:, 1]
11+
x2 = boxes[:, 2]
12+
y2 = boxes[:, 3]
13+
area = (x2 - x1 + 1) * (y2 - y1 + 1)
14+
for index in range(no_of_valid_elems):
15+
i = sort_ids[index]
16+
new_ind = index + 1
17+
if float(boxes[i, 4]) > conf_thresh:
18+
xx1 = torch.max(x1[i], x1[sort_ids[new_ind:]])
19+
yy1 = torch.max(y1[i], y1[sort_ids[new_ind:]])
20+
xx2 = torch.min(x2[i], x2[sort_ids[new_ind:]])
21+
yy2 = torch.min(y2[i], y2[sort_ids[new_ind:]])
22+
w = torch.max(torch.zeros(1, device=boxes.device), xx2 - xx1 + 1)
23+
h = torch.max(torch.zeros(1, device=boxes.device), yy2 - yy1 + 1)
24+
overlap = (w * h) / area[sort_ids[new_ind:]]
25+
higher_nms_ind = (overlap > nms_thresh).nonzero()
26+
boxes[sort_ids[new_ind:][higher_nms_ind]
27+
] = torch.zeros(7, device=boxes.device)
28+
return boxes.unsqueeze(0)
29+
30+
31+
def get_region_boxes(output):
32+
conf_thresh = 0.2
33+
num_classes = 20
34+
num_anchors = 5
35+
anchor_step = 2
36+
anchors_ = [1.08, 1.19, 3.42, 4.41, 6.63,
37+
11.38, 9.42, 5.11, 16.62, 10.52]
38+
39+
anchors = torch.empty(num_anchors * 2, device=output.device)
40+
for i in range(num_anchors * 2):
41+
anchors[i] = anchors_[i]
42+
43+
batch = output.size(0)
44+
# assert(output.size(1) == (5+num_classes)*num_anchors)
45+
h = output.size(2)
46+
w = output.size(3)
47+
48+
output = output.view(batch*num_anchors, 5+num_classes, h*w).transpose(0,
49+
1).contiguous().view(5+num_classes, batch*num_anchors*h*w)
50+
51+
grid_x = torch.linspace(0, w-1, w, device=output.device).repeat(h,
52+
1).repeat(batch*num_anchors, 1, 1).view(batch*num_anchors*h*w)
53+
grid_y = torch.linspace(0, h-1, h, device=output.device).repeat(w,
54+
1).t().repeat(batch*num_anchors, 1, 1).view(batch*num_anchors*h*w)
55+
xs = torch.sigmoid(output[0]) + grid_x
56+
ys = torch.sigmoid(output[1]) + grid_y
57+
58+
anchor_w = anchors.view(num_anchors, anchor_step).index_select(
59+
1, torch.zeros(1, device=output.device).long())
60+
anchor_h = anchors.view(num_anchors, anchor_step).index_select(
61+
1, torch.ones(1, device=output.device).long())
62+
anchor_w = anchor_w.repeat(batch, 1).repeat(
63+
1, 1, h*w).view(batch*num_anchors*h*w)
64+
anchor_h = anchor_h.repeat(batch, 1).repeat(
65+
1, 1, h*w).view(batch*num_anchors*h*w)
66+
ws = torch.exp(output[2]) * anchor_w.float()
67+
hs = torch.exp(output[3]) * anchor_h.float()
68+
69+
det_confs = torch.sigmoid(output[4])
70+
71+
cls_confs = torch.softmax(output[5: 5+num_classes].transpose(0, 1), dim=1)
72+
cls_max_confs, cls_max_ids = torch.max(cls_confs, 1)
73+
cls_max_confs = cls_max_confs.view(-1)
74+
cls_max_ids = cls_max_ids.view(-1)
75+
76+
sz_hw = h * w
77+
boxes = torch.zeros(batch, h * w * num_anchors, 7)
78+
# assuming only one image in a batch
79+
x1 = xs / w
80+
y1 = ys / h
81+
x2 = ws / w
82+
y2 = hs / h
83+
higher_confs = ((det_confs * cls_max_confs) > conf_thresh).nonzero()
84+
no_selected_elems = higher_confs.numel()
85+
if no_selected_elems > 0:
86+
boxes[:, 0:no_selected_elems] = torch.stack(
87+
[x1, y1, x2, y2, det_confs, cls_max_confs, cls_max_ids.float()], dim=1)[higher_confs.squeeze()]
88+
return boxes
89+
90+
91+
def boxes_from_tf(output):
92+
boxes = get_region_boxes(output.permute(0, 3, 1, 2).contiguous())
93+
boxes = nms(boxes)
94+
return boxes

docker-compose.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
version: "3.4"
2+
3+
services:
4+
redis:
5+
container_name: redis
6+
image: ghcr.io/redisgrafana/redis-opencv:latest
7+
ports:
8+
- 6379:6379
9+
10+
grafana:
11+
container_name: grafana
12+
image: ghcr.io/redisgrafana/redis-app:latest
13+
ports:
14+
- "3000:3000"
15+
environment:
16+
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
17+
- GF_AUTH_ANONYMOUS_ENABLED=true
18+
- GF_AUTH_BASIC_ENABLED=false
19+
- GF_ENABLE_GZIP=true
20+
- GF_USERS_DEFAULT_THEME=light
21+
volumes:
22+
- ./provisioning:/etc/grafana/provisioning
23+
- ./dashboards:/var/lib/grafana/dashboards

0 commit comments

Comments
 (0)