Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Maj vio for panda demo #60

Merged
merged 17 commits into from
Dec 27, 2024
Merged
18 changes: 16 additions & 2 deletions deployment/edge/ansible/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,27 @@ export LOCAL_VIO_DIR=
```


### Install sshpass
https://stackoverflow.com/questions/42835626/ansible-to-use-the-ssh-connection-type-with-passwords-you-must-install-the-s
### Install sshpass and the requirements

On MacOS, you can install sshpass using brew:
```bash
brew install sshpass
```

Then install requirements:
```bash
pip install -r requirements.txt
```

### Find the IP of the edge

Under Ubuntu, you can use the following command to find the IP of the edge
```bash
ip a | grep -A 2 'wlx' | grep 'inet ' | awk '{print $2}' | cut -d/ -f1
```

Then change the IP addresses on the inventory file

## docker-compose devices

We are adding those devices to be able to trigger capture from 2 cameras connected on the usb port of your edge.
Expand Down
4 changes: 1 addition & 3 deletions deployment/edge/ansible/files/docker-compose.template.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: '3'

Arthemide marked this conversation as resolved.
Show resolved Hide resolved
services:
edge_model_serving:
container_name: edge_model_serving
Expand Down Expand Up @@ -32,7 +30,7 @@ services:
SERVING_MODEL_URL: http://edge_model_serving:8501
GOOGLE_APPLICATION_CREDENTIALS: /edge_orchestrator/config/secrets/credentials.json
GCP_BUCKET_NAME: tf-vio-bucket
ACTIVE_CONFIG_NAME: marker_classification_with_1_fake_camera
ACTIVE_CONFIG_NAME: duck_detector_with_2_usbcamera

edge_interface:
container_name: edge_interface
Expand Down
18 changes: 18 additions & 0 deletions deployment/edge/gcp/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
## Launch edge locally and upload data on GCP

### Service account for edge_orchestrator
- Create a service account with the roles/storage.admin directly through the GCP UI.
- Export key json file
- Store the key file at this location: `edge_orchestrator/config/secrets/credentials.json`

### Set env variables on docker-compose.yml

API_CONFIG: upload-gcp
GOOGLE_APPLICATION_CREDENTIALS: /edge_orchestrator/config/secrets/credentials.json
GCP_BUCKET_NAME: <bucket-name>

### Launch VIO
```shell
$ make edge_orchestrator
Arthemide marked this conversation as resolved.
Show resolved Hide resolved
```

## Deploy edge on GCP via Kube

Ensure that Terraform has ran before to create the infrastructure.
Expand Down
2 changes: 0 additions & 2 deletions docker-compose.raspberrypi.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: '3'

services:
edge_db:
image: webhippie/mongodb:latest-arm32v7
Expand Down
9 changes: 4 additions & 5 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: '3'

services:
edge_db:
image: mongo:5.0.2
Expand Down Expand Up @@ -34,10 +32,11 @@ services:
- 8000:8000
environment:
EDGE_NAME: vio-edge-1
API_CONFIG: docker
API_CONFIG: upload-gcp
SERVING_MODEL_URL: http://edge_model_serving:8501
Arthemide marked this conversation as resolved.
Show resolved Hide resolved
MONGO_DB_URI: mongodb://edge_db:27017/
POSTGRES_DB_URI: postgresql://vio:vio@hub_monitoring_db:5432/vio
GOOGLE_APPLICATION_CREDENTIALS: /edge_orchestrator/config/secrets/credentials.json
GCP_BUCKET_NAME: tf-vio-bucket
ACTIVE_CONFIG_NAME: marker_classification_with_1_fake_camera
profiles: [edge]

hub_labelizer:
Expand Down
2 changes: 1 addition & 1 deletion edge_model_serving/tflite_serving/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.8.10-slim
FROM python:3.8-slim

WORKDIR /tflite_serving

Expand Down
2 changes: 1 addition & 1 deletion edge_model_serving/tflite_serving/Dockerfile.raspberrypi
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM --platform=linux/arm64 arm64v8/python:3.8.10
FROM --platform=linux/arm64 arm64v8/python:3.8

WORKDIR /tflite_serving

Expand Down
10 changes: 8 additions & 2 deletions edge_orchestrator/.env.template
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
REGISTRY_USERNAME = <your-github-username>
REGISTRY_PASSWORD = <your-personal-access-token-generated-in-github>
EDGE_NAME=
API_CONFIG=
SERVING_MODEL_URL=
GOOGLE_APPLICATION_CREDENTIALS=
GCP_BUCKET_NAME=
ACTIVE_CONFIG_NAME=
REGISTRY_USERNAME=<your-github-username>
REGISTRY_PASSWORD=<your-personal-access-token-generated-in-github>
2 changes: 1 addition & 1 deletion edge_orchestrator/.python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.8.10
3.9
2 changes: 1 addition & 1 deletion edge_orchestrator/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.8.16-slim
FROM python:3.8-slim

WORKDIR /edge_orchestrator

Expand Down
2 changes: 1 addition & 1 deletion edge_orchestrator/Dockerfile.raspberrypi
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM --platform=linux/arm64 arm64v8/python:3.8.16
FROM --platform=linux/arm64 arm64v8/python:3.8
#FROM --platform=linux/arm/v7 arm32v7/python:3.9.16-bullseye

WORKDIR /edge_orchestrator
Expand Down
18 changes: 18 additions & 0 deletions edge_orchestrator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,21 @@ The edge_orchestrator orchestrates the following steps as soon as it is triggere
3. metadata backup
4. model inference on images
5. saving results


## Common errors

# Error: pg_config executable not found.
Arthemide marked this conversation as resolved.
Show resolved Hide resolved

Check if pg_config is installed on your system.

```bash
pg_config --version
```

If not, install it using the following command:

MacOS:
```bash
brew install postgresql
```
15 changes: 14 additions & 1 deletion edge_orchestrator/config/inventory.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@
320
],
"objectness_threshold": 0.3
},
"duck_detector": {
"category": "classification",
"version": 1,
"class_names": [
"OK",
"NOK",
"NOK2"
Arthemide marked this conversation as resolved.
Show resolved Hide resolved
],
"image_resolution": [
224,
224
]
}
},
"camera_rules": [
Expand All @@ -86,4 +99,4 @@
"min_threshold_ok_ratio_rule",
"min_threshold_ko_rule"
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "duck_detector_2_cams",
"cameras": {
"camera_id0": {
"type": "usb_camera",
"source": "/dev/video0",
"position": "front",
"exposition": 100,
"models_graph": {
"model_id4": {
"name": "duck_detector",
"depends_on": []
}
},
"camera_rule": {
"name": "expected_label_rule",
"parameters": {
"expected_label": ["OK"]
}
}
},
"camera_id2": {
"type": "usb_camera",
"source": "/dev/video2",
"position": "front",
"exposition": 100,
"models_graph": {
"model_id4": {
"name": "duck_detector",
"depends_on": []
}
},
"camera_rule": {
"name": "expected_label_rule",
"parameters": {
"expected_label": ["OK"]
}
}
}
},
"item_rule": {
"name": "min_threshold_ko_rule",
"parameters": {
"threshold": 1
}
}
}
3 changes: 3 additions & 0 deletions edge_orchestrator/edge_orchestrator/api_config.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import os

from dotenv import load_dotenv

from edge_orchestrator import logger


def load_config():
load_dotenv()
configuration = os.environ.get("API_CONFIG", "default")
logger.info(f"App running with configuration: {configuration}")

Expand Down
19 changes: 14 additions & 5 deletions edge_orchestrator/edge_orchestrator/application/trigger_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,17 @@ async def upload_job(
station_config: StationConfig = Depends(get_station_config),
background_tasks: BackgroundTasks = None,
):
item = Item.from_nothing()
contents = image.file.read()
item.binaries = {"0": contents}
background_tasks.add_task(uploader.upload, item, station_config.active_config["name"])
return {"item_id": item.id}
if station_config.active_config is None:
return JSONResponse(
status_code=403,
content={
"message": "No active configuration selected! "
"Set the active station configuration before triggering the inspection."
},
)
else:
item = Item.from_nothing()
contents = image.file.read()
item.binaries = {"0": contents}
background_tasks.add_task(uploader.upload, item, station_config.active_config["name"])
Arthemide marked this conversation as resolved.
Show resolved Hide resolved
return {"item_id": item.id}
14 changes: 0 additions & 14 deletions edge_orchestrator/edge_orchestrator/domain/use_cases/supervisor.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from pathlib import Path
from typing import Any, Dict, List, Union

from codecarbon import track_emissions
from PIL import Image

from edge_orchestrator.api_config import (
Expand Down Expand Up @@ -59,9 +58,6 @@ def __init__(
self.edge_station = edge_station
self.telemetry_sink = telemetry_sink

@track_emissions(
Arthemide marked this conversation as resolved.
Show resolved Hide resolved
project_name="save_item_metadata", measure_power_secs=1, log_level="info", output_dir=str(emissions_path)
)
def save_item_metadata(self, fct):
@functools.wraps(fct)
async def wrapper(item: Item, *args):
Expand All @@ -80,7 +76,6 @@ async def inspect(self, item: Item):
tasks = OrderedDict()

@self.save_item_metadata
@track_emissions(project_name="capture", measure_power_secs=1, log_level="info", output_dir=str(emissions_path))
async def capture(item: Item):
if item.binaries is None or len(item.binaries) == 0:
cameras_metadata, binaries = self.edge_station.capture()
Expand All @@ -89,23 +84,14 @@ async def capture(item: Item):
check_capture_according_to_config(item, self.station_config.get_cameras())

@self.save_item_metadata
@track_emissions(
project_name="save_item_binaries", measure_power_secs=1, log_level="info", output_dir=str(emissions_path)
)
async def save_item_binaries(item: Item):
self.binary_storage.save_item_binaries(item, self.station_config.active_config["name"])

@self.save_item_metadata
@track_emissions(
project_name="set_inferences", measure_power_secs=1, log_level="info", output_dir=str(emissions_path)
)
async def set_inferences(item: Item):
item.inferences = await self.get_predictions(item)

@self.save_item_metadata
@track_emissions(
project_name="set_decision", measure_power_secs=1, log_level="info", output_dir=str(emissions_path)
)
async def set_decision(item: Item):
decision = self.apply_business_rules(item)
item.decision = decision.value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ def save_item_metadata(self, fct):
async def wrapper(item: Item, *args):
item.state = args[0].value
Arthemide marked this conversation as resolved.
Show resolved Hide resolved
await fct(item)
self.metadata_storage.save_item_metadata(item)
active_config_name = args[1]
self.metadata_storage.save_item_metadata(item, active_config_name)
Arthemide marked this conversation as resolved.
Show resolved Hide resolved

return wrapper

Expand All @@ -50,12 +51,12 @@ async def set_error_state(item: Item, error_message: str):
logger.info(f"Starting {uploader_state.value}")
try:
logger.info(f"Entering try {uploader_state.value}")
await task_fct(item, uploader_state)
await task_fct(item, uploader_state, active_config_name)
Arthemide marked this conversation as resolved.
Show resolved Hide resolved
except Exception as e:
logger.error(f"Error during {uploader_state.value}: {e}")
await set_error_state(item, str(e))

logger.info(f"End of {uploader_state.value}")

item.state = UploaderState.DONE.value
self.metadata_storage.save_item_metadata(item)
self.metadata_storage.save_item_metadata(item, active_config_name)
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
class GCPBinaryStorage(BinaryStorage):
def __init__(self):
self.storage_client = storage.Client()
self.prefix = os.environ.get("EDGE_NAME", "")

prefix = os.getenv("EDGE_NAME")
if prefix is None:
Arthemide marked this conversation as resolved.
Show resolved Hide resolved
raise Exception("EDGE_NAME environment variable should be set")
self.prefix = prefix
self.bucket = self.storage_client.get_bucket(os.getenv("GCP_BUCKET_NAME"))

def save_item_binaries(self, item: Item, active_config_name: str) -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@
class GCPMetadataStorage(MetadataStorage):
def __init__(self):
self.storage_client = storage.Client()
self.prefix = os.environ.get("EDGE_NAME", "")

prefix = os.getenv("EDGE_NAME")
if prefix is None:
Arthemide marked this conversation as resolved.
Show resolved Hide resolved
raise Exception("EDGE_NAME environment variable should be set")
self.prefix = prefix
self.bucket = self.storage_client.get_bucket(os.getenv("GCP_BUCKET_NAME"))

def save_item_metadata(self, item: Item, active_config_name: str):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,17 @@ def perform_pre_processing(self, model: ModelInfos, binary: bytes):

def perform_post_processing(self, model: ModelInfos, json_outputs: list, binary_name: str) -> dict:
logger.debug(f"model classnames: {model.class_names}")
predictions = json_outputs[0]
number_predictions_classes = len(predictions)
number_model_classes = len(model.class_names)
if number_predictions_classes != number_model_classes:
raise Exception(
Arthemide marked this conversation as resolved.
Show resolved Hide resolved
f"Number of classes in the model ({number_model_classes}) is different from"
"the number of predictions ({number_predictions_classes})"
)
return {
binary_name: {
"label": model.class_names[np.argmax(json_outputs[0])],
"probability": float(np.max(json_outputs[0])),
"label": model.class_names[np.argmax(predictions)],
Arthemide marked this conversation as resolved.
Show resolved Hide resolved
"probability": float(np.max(predictions)),
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def __init__(self, station_configs_folder: Path, inventory: Inventory, data_fold
self.load()

self.active_config = None
config_name = os.environ.get("ACTIVE_CONFIG_NAME", None)
config_name = os.getenv("ACTIVE_CONFIG_NAME")
if config_name is not None:
self.set_station_config(config_name)

Expand Down
Loading
Loading