-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initialize edge streamlit interface (#59)
* Initialize edge streamlit page --------- Co-authored-by: Thibaut <[email protected]> Co-authored-by: Andre Arockiasamy Amalor Raj <[email protected]> Co-authored-by: gireg.roussel <[email protected]> Co-authored-by: LouisonR-octo <[email protected]>
- Loading branch information
1 parent
559fb37
commit 8225c59
Showing
9 changed files
with
295 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
48 changes: 48 additions & 0 deletions
48
edge_orchestrator/config/station_configs/yolo_coco_nano_with_2_fake_camera.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
{ | ||
"name": "yolo_coco", | ||
"cameras": { | ||
"camera_id3": { | ||
"type": "fake", | ||
"source": "people_dataset", | ||
"position": "back", | ||
"exposition": 100, | ||
"models_graph": { | ||
"model_id4": { | ||
"name": "yolo_coco_nano", | ||
"depends_on": [] | ||
} | ||
}, | ||
"camera_rule": { | ||
"name": "expected_label_rule", | ||
"parameters": { | ||
"expected_label": ["person"] | ||
} | ||
} | ||
}, | ||
"camera_id4": { | ||
"type": "fake", | ||
"source": "people_dataset", | ||
"position": "back", | ||
"exposition": 100, | ||
"models_graph": { | ||
"model_id4": { | ||
"name": "yolo_coco_nano", | ||
"depends_on": [] | ||
} | ||
}, | ||
"camera_rule": { | ||
"name": "expected_label_rule", | ||
"parameters": { | ||
"expected_label": ["person"] | ||
} | ||
} | ||
} | ||
}, | ||
"item_rule": { | ||
"name": "min_threshold_ko_rule", | ||
"parameters": { | ||
"threshold": 1 | ||
} | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
FROM python:3.9-slim | ||
|
||
# let print debug messages log on the console | ||
ENV PYTHONUNBUFFERED=1 | ||
ENV PYTHONIOENCODING=UTF-8 | ||
|
||
ENV ORCHESTRATOR_URL=http://edge_orchestrator:8000/api/v1/ | ||
|
||
WORKDIR /app | ||
|
||
RUN apt-get update && apt-get install -y \ | ||
build-essential \ | ||
curl \ | ||
software-properties-common \ | ||
git \ | ||
&& rm -rf /var/lib/apt/lists/* | ||
|
||
COPY requirements.txt . | ||
|
||
RUN pip install -r requirements.txt | ||
|
||
COPY app.py . | ||
COPY prediction_boxes.py . | ||
|
||
EXPOSE 8501 | ||
|
||
HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health | ||
|
||
ENTRYPOINT ["streamlit", "run", "app.py"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
SHELL := /bin/bash | ||
.SHELLFLAGS = -ec | ||
.ONESHELL: | ||
.SILENT: | ||
|
||
.PHONY: help | ||
help: | ||
echo "❓ Use \`make <target>\`" | ||
grep -E '^\.PHONY: [a-zA-Z0-9_-]+ .*?## .*$$' $(MAKEFILE_LIST) | \ | ||
awk 'BEGIN {FS = "(: |##)"}; {printf "\033[36m%-30s\033[0m %s\n", $$2, $$3}' | ||
|
||
.PHONY: env ## Create a virtual environment | ||
env: | ||
python3 -m venv env | ||
. env/bin/activate | ||
pip install --upgrade pip | ||
pip install -r requirements.txt | ||
|
||
.PHONY: run ## Run the streamlit app | ||
run: | ||
streamlit run app.py | ||
|
||
.PHONY: linting ## 🐍 Lint Python files to conform to the PEP 8 style guide | ||
linting: | ||
black . | ||
isort . --gitignore | ||
autoflake -i --remove-all-unused-imports -r --ignore-init-module-imports . --exclude .venv |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import json | ||
import os | ||
import time | ||
import requests | ||
import streamlit as st | ||
from prediction_boxes import filtering_items_that_have_predictions, plot_predictions | ||
from PIL import Image | ||
from io import BytesIO | ||
|
||
# Page configuration | ||
st.set_page_config(page_title="VIO-edge", page_icon="🔦", layout="wide") | ||
|
||
URL_ORCH = os.getenv("ORCHESTRATOR_URL", "http://localhost:8000/api/v1/") | ||
|
||
url_config = URL_ORCH + "configs" | ||
url_active_config = URL_ORCH + "configs/active" | ||
url_trigger = URL_ORCH + "trigger" | ||
|
||
|
||
def main(): | ||
active_config = json.loads(requests.get(url_active_config).text) | ||
if active_config: | ||
st.session_state.active_config = active_config | ||
|
||
if "active_config" not in st.session_state: | ||
st.session_state.active_config = None | ||
if "trigger" not in st.session_state: | ||
st.session_state.trigger = False | ||
if "item_id" not in st.session_state: | ||
st.session_state.item_id = None | ||
|
||
col1, col2, col3 = st.columns(3) | ||
|
||
configs = json.loads(requests.get(url_config).text) | ||
|
||
active_config_index = 0 | ||
if st.session_state.active_config: | ||
active_config_name = st.session_state.active_config.get("name") | ||
active_config_index = next( | ||
( | ||
index | ||
for (index, config) in enumerate(configs.values()) | ||
if config["name"] == active_config_name | ||
), | ||
0, | ||
) | ||
option = col1.selectbox( | ||
"Select an option", | ||
tuple(configs), | ||
index=active_config_index, | ||
label_visibility="collapsed", | ||
) | ||
|
||
if col2.button("Active", use_container_width=True): | ||
st.session_state.item_id = None | ||
body = {"config_name": option} | ||
requests.post(url=url_active_config, json=body) | ||
st.session_state.active_config = json.loads( | ||
requests.get(url_active_config).text | ||
) | ||
|
||
if st.session_state.active_config: | ||
active_config_name = st.session_state.active_config.get("name") | ||
col2.write(f"active config: {active_config_name}") | ||
|
||
if st.session_state.active_config: | ||
if col3.button("Trigger", use_container_width=True): | ||
st.session_state.trigger = True | ||
response = requests.post(url_trigger) | ||
item_id = response.json().get("item_id") | ||
st.session_state.item_id = item_id | ||
col3.write(f"item id: {item_id}") | ||
|
||
columns = st.columns(2) | ||
|
||
if st.session_state.item_id and (st.session_state.active_config is not None): | ||
time.sleep(5) | ||
|
||
url_metadata = URL_ORCH + f"items/{st.session_state.item_id}" | ||
response = requests.get(url_metadata) | ||
metadata = response.json() | ||
decision = metadata["decision"] | ||
inferences = metadata["inferences"] | ||
|
||
print("decision", decision) | ||
print("inferences", inferences) | ||
|
||
cameras = st.session_state.active_config["cameras"] | ||
for i, camera in enumerate(cameras): | ||
url_binaries = ( | ||
URL_ORCH + f"items/{st.session_state.item_id}/binaries/{camera}" | ||
) | ||
response = requests.get(url_binaries) | ||
image = response.content | ||
# If metadata is not empty, we plot the predictions | ||
if filtering_items_that_have_predictions(metadata, camera): | ||
image = Image.open(BytesIO(image)) | ||
image = plot_predictions(image, camera, metadata) | ||
columns[i].image(image, channels="BGR", width=450) | ||
if inferences.get(camera): | ||
columns[i].markdown(inferences[camera]) | ||
|
||
st.markdown( | ||
f"<h1 style='text-align: center; color: #e67e22;'>{decision}</h1>", | ||
unsafe_allow_html=True, | ||
) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
from PIL import Image, ImageDraw, ImageFont | ||
|
||
|
||
def filtering_items_that_have_predictions(metadata: dict, camera_id: str): | ||
if metadata == {} or metadata is None: | ||
return False | ||
elif metadata["inferences"] == {}: | ||
return False | ||
for model_results in metadata["inferences"][camera_id].values(): | ||
if model_results == "NO_DECISION" or model_results == {}: | ||
return False | ||
for prediction in model_results.values(): | ||
if "location" not in prediction: | ||
return False | ||
return True | ||
|
||
|
||
def plot_predictions(img: Image, camera_id: str, metadata: dict): | ||
if metadata["inferences"] == {}: | ||
return img | ||
camera_prediction_metadata = metadata["inferences"][camera_id] | ||
models = camera_prediction_metadata.keys() | ||
for model in models: | ||
detected_objects = camera_prediction_metadata[model].values() | ||
for detected_object in detected_objects: | ||
bbox = detected_object["location"] | ||
label = detected_object["label"] | ||
img = draw_bbox(img, bbox, label) | ||
|
||
return img | ||
|
||
|
||
def draw_bbox(img, bbox, label): | ||
draw = ImageDraw.Draw(img) | ||
width, height = img.size | ||
|
||
# Convert normalized coordinates to pixel values | ||
top_left_x = int(bbox[0] * width) | ||
top_left_y = int(bbox[1] * height) | ||
bottom_right_x = int(bbox[2] * width) | ||
bottom_right_y = int(bbox[3] * height) | ||
|
||
# Draw the bounding box | ||
draw.rectangle( | ||
[top_left_x, top_left_y, bottom_right_x, bottom_right_y], outline="red", width=2 | ||
) | ||
|
||
# Load a font | ||
font = ImageFont.load_default() | ||
|
||
# Calculate text size and position | ||
text_size = draw.textbbox((0, 0), label, font=font)[2:] | ||
text_x = top_left_x | ||
text_y = top_left_y - text_size[1] if top_left_y - text_size[1] > 0 else top_left_y | ||
|
||
# Draw the label background | ||
draw.rectangle( | ||
[text_x, text_y, text_x + text_size[0], text_y + text_size[1]], fill="red" | ||
) | ||
|
||
# Draw the label text | ||
draw.text((text_x, text_y), label, fill="white", font=font) | ||
|
||
return img |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
opencv-python-headless==4.7.0.72 | ||
streamlit==1.41.0 | ||
numpy==1.24.1 |