mirror of
https://github.com/azaion/detections.git
synced 2026-04-22 05:26:32 +00:00
Update Docker configurations and dependencies for Jetson deployment
- Added image specifications for services in `docker-compose.demo-jetson.yml` and `docker-compose.jetson.yml` to streamline deployment. - Updated `Dockerfile.gpu` to use the development version of the CUDA runtime for enhanced compatibility. - Modified `Dockerfile.jetson` to switch to a newer JetPack base image and adjusted the requirements file to include additional dependencies for improved functionality. - Removed obsolete deployment scripts and calibration cache generation script to clean up the project structure. Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
.venv
|
||||
.venv-e2e
|
||||
_docs
|
||||
e2e
|
||||
tests
|
||||
data
|
||||
demo
|
||||
Logs
|
||||
build
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.pyd
|
||||
*.so
|
||||
*.egg-info
|
||||
.pytest_cache
|
||||
.git
|
||||
*.md
|
||||
scripts
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM nvidia/cuda:12.2.0-runtime-ubuntu22.04
|
||||
FROM nvidia/cuda:12.2.0-devel-ubuntu22.04
|
||||
RUN apt-get update && apt-get install -y python3 python3-pip python3-dev gcc libgl1 libglib2.0-0 && rm -rf /var/lib/apt/lists/*
|
||||
WORKDIR /app
|
||||
COPY requirements.txt requirements-gpu.txt ./
|
||||
|
||||
+2
-5
@@ -1,17 +1,14 @@
|
||||
FROM nvcr.io/nvidia/l4t-base:r36.3.0
|
||||
FROM nvcr.io/nvidia/l4t-jetpack:r36.2.0
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
python3 python3-pip python3-dev gcc \
|
||||
libgl1 libglib2.0-0 \
|
||||
python3-libnvinfer python3-libnvinfer-dev \
|
||||
python3-pycuda \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN python3 -c "import tensorrt" || \
|
||||
(echo "TensorRT Python bindings not found; check PYTHONPATH for JetPack installation" && exit 1)
|
||||
|
||||
WORKDIR /app
|
||||
COPY requirements-jetson.txt ./
|
||||
COPY requirements.txt requirements-jetson.txt ./
|
||||
RUN pip3 install --no-cache-dir -r requirements-jetson.txt
|
||||
COPY . .
|
||||
RUN python3 setup.py build_ext --inplace
|
||||
|
||||
@@ -2,6 +2,7 @@ name: detections-demo-jetson
|
||||
|
||||
services:
|
||||
loader:
|
||||
image: ${REGISTRY:-docker.azaion.com}/detections-loader-mock:${TAG:-latest}
|
||||
build:
|
||||
context: ./e2e/mocks/loader
|
||||
ports:
|
||||
@@ -12,6 +13,7 @@ services:
|
||||
- demo-net
|
||||
|
||||
annotations:
|
||||
image: ${REGISTRY:-docker.azaion.com}/detections-annotations-mock:${TAG:-latest}
|
||||
build:
|
||||
context: ./e2e/mocks/annotations
|
||||
ports:
|
||||
@@ -20,6 +22,7 @@ services:
|
||||
- demo-net
|
||||
|
||||
detections:
|
||||
image: ${REGISTRY:-docker.azaion.com}/detections-jetson:${TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.jetson
|
||||
|
||||
@@ -2,9 +2,7 @@ name: detections-jetson
|
||||
|
||||
services:
|
||||
detections:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.jetson
|
||||
image: ${REGISTRY:-docker.azaion.com}/detections-jetson:${TAG:-latest}
|
||||
ports:
|
||||
- "8080:8080"
|
||||
runtime: nvidia
|
||||
|
||||
+14
-1
@@ -1 +1,14 @@
|
||||
-r requirements.txt
|
||||
fastapi==0.135.2
|
||||
uvicorn[standard]==0.42.0
|
||||
PyJWT==2.12.1
|
||||
h11==0.16.0
|
||||
python-multipart==0.0.22
|
||||
Cython==3.2.4
|
||||
opencv-python==4.10.0.84
|
||||
numpy==2.2.6
|
||||
pynvml==12.0.0
|
||||
requests==2.32.4
|
||||
loguru==0.7.3
|
||||
av==14.2.0
|
||||
xxhash==3.5.0
|
||||
pycuda
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ h11==0.16.0
|
||||
python-multipart==0.0.22
|
||||
Cython==3.2.4
|
||||
opencv-python==4.10.0.84
|
||||
numpy==2.3.0
|
||||
numpy==2.2.6
|
||||
onnxruntime==1.22.0
|
||||
pynvml==12.0.0
|
||||
requests==2.32.4
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Deploy Azaion detections demo stack to a Jetson over SSH.
|
||||
#
|
||||
# Usage:
|
||||
# JETSON_HOST=192.168.x.x bash scripts/deploy_demo_jetson.sh \
|
||||
# --onnx /local/path/azaion.onnx \
|
||||
# --classes /local/path/classes.json \
|
||||
# [--int8-cache /local/path/azaion.int8_calib.cache] \
|
||||
# [--calibration-images /local/path/images/]
|
||||
#
|
||||
# Optional env vars:
|
||||
# JETSON_HOST (required) IP or hostname of the Jetson
|
||||
# JETSON_USER SSH user (default: jetson)
|
||||
# REMOTE_DIR Path on Jetson to deploy into (default: ~/detections)
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
JETSON_HOST="${JETSON_HOST:-}"
|
||||
JETSON_USER="${JETSON_USER:-jetson}"
|
||||
REMOTE_DIR="${REMOTE_DIR:-~/detections}"
|
||||
|
||||
ONNX_PATH=""
|
||||
CLASSES_PATH=""
|
||||
INT8_CACHE_PATH=""
|
||||
CALIBRATION_IMAGES=""
|
||||
|
||||
usage() {
|
||||
echo "Usage: JETSON_HOST=<ip> bash $0 --onnx <path> --classes <path> [options]"
|
||||
echo ""
|
||||
echo "Required:"
|
||||
echo " --onnx <path> Local path to azaion.onnx"
|
||||
echo " --classes <path> Local path to classes.json"
|
||||
echo ""
|
||||
echo "Optional:"
|
||||
echo " --int8-cache <path> Local path to azaion.int8_calib.cache (skips calibration)"
|
||||
echo " --calibration-images <dir> Local image directory; rsync to Jetson and run INT8 calibration"
|
||||
echo " --help Show this message"
|
||||
echo ""
|
||||
echo "Env vars:"
|
||||
echo " JETSON_HOST (required) Jetson IP or hostname"
|
||||
echo " JETSON_USER SSH user (default: jetson)"
|
||||
echo " REMOTE_DIR Deploy directory on Jetson (default: ~/detections)"
|
||||
exit 0
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--onnx) ONNX_PATH="$2"; shift 2 ;;
|
||||
--classes) CLASSES_PATH="$2"; shift 2 ;;
|
||||
--int8-cache) INT8_CACHE_PATH="$2"; shift 2 ;;
|
||||
--calibration-images) CALIBRATION_IMAGES="$2"; shift 2 ;;
|
||||
--help) usage ;;
|
||||
*) echo "Unknown argument: $1"; usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -z "$JETSON_HOST" ]] && { echo "ERROR: JETSON_HOST is required"; exit 1; }
|
||||
[[ -z "$ONNX_PATH" ]] && { echo "ERROR: --onnx is required"; exit 1; }
|
||||
[[ -z "$CLASSES_PATH" ]] && { echo "ERROR: --classes is required"; exit 1; }
|
||||
[[ -f "$ONNX_PATH" ]] || { echo "ERROR: ONNX file not found: $ONNX_PATH"; exit 1; }
|
||||
[[ -f "$CLASSES_PATH" ]] || { echo "ERROR: classes.json not found: $CLASSES_PATH"; exit 1; }
|
||||
|
||||
SSH="ssh ${JETSON_USER}@${JETSON_HOST}"
|
||||
SCP="scp"
|
||||
|
||||
echo "=== Azaion Demo — Jetson Deployment ==="
|
||||
echo " Host: ${JETSON_USER}@${JETSON_HOST}"
|
||||
echo " Remote dir: ${REMOTE_DIR}"
|
||||
echo ""
|
||||
|
||||
# ── 1. Sync project ─────────────────────────────────────────────────────────
|
||||
echo "--- Syncing project files ---"
|
||||
$SSH "mkdir -p ${REMOTE_DIR}/demo/models"
|
||||
rsync -az --exclude='.git' --exclude='__pycache__' --exclude='*.pyc' \
|
||||
--exclude='*.egg-info' --exclude='.venv' --exclude='demo/models' \
|
||||
"${PROJECT_ROOT}/" "${JETSON_USER}@${JETSON_HOST}:${REMOTE_DIR}/"
|
||||
|
||||
# ── 2. Upload model artifacts ────────────────────────────────────────────────
|
||||
echo "--- Uploading model artifacts ---"
|
||||
$SCP "$ONNX_PATH" "${JETSON_USER}@${JETSON_HOST}:${REMOTE_DIR}/demo/models/azaion.onnx"
|
||||
$SCP "$CLASSES_PATH" "${JETSON_USER}@${JETSON_HOST}:${REMOTE_DIR}/demo/models/classes.json"
|
||||
|
||||
if [[ -n "$INT8_CACHE_PATH" ]]; then
|
||||
[[ -f "$INT8_CACHE_PATH" ]] || { echo "ERROR: INT8 cache not found: $INT8_CACHE_PATH"; exit 1; }
|
||||
echo "--- Uploading INT8 calibration cache ---"
|
||||
$SCP "$INT8_CACHE_PATH" "${JETSON_USER}@${JETSON_HOST}:${REMOTE_DIR}/demo/models/azaion.int8_calib.cache"
|
||||
fi
|
||||
|
||||
# ── 3. Optional: run INT8 calibration on Jetson ──────────────────────────────
|
||||
if [[ -n "$CALIBRATION_IMAGES" ]] && [[ -z "$INT8_CACHE_PATH" ]]; then
|
||||
[[ -d "$CALIBRATION_IMAGES" ]] || { echo "ERROR: calibration images dir not found: $CALIBRATION_IMAGES"; exit 1; }
|
||||
echo "--- Syncing calibration images to Jetson ---"
|
||||
$SSH "mkdir -p ${REMOTE_DIR}/demo/calibration"
|
||||
rsync -az "${CALIBRATION_IMAGES}/" "${JETSON_USER}@${JETSON_HOST}:${REMOTE_DIR}/demo/calibration/"
|
||||
|
||||
echo "--- Building detections image for calibration ---"
|
||||
$SSH "cd ${REMOTE_DIR} && docker compose -f docker-compose.demo-jetson.yml build detections"
|
||||
|
||||
echo "--- Running INT8 calibration (this takes several minutes) ---"
|
||||
$SSH "cd ${REMOTE_DIR} && docker compose -f docker-compose.demo-jetson.yml run --rm \
|
||||
-v ${REMOTE_DIR}/demo/calibration:/calibration \
|
||||
detections \
|
||||
python3 scripts/generate_int8_cache.py \
|
||||
--images-dir /calibration \
|
||||
--onnx /models/azaion.onnx \
|
||||
--output /models/azaion.int8_calib.cache"
|
||||
echo "--- Calibration cache written to ${REMOTE_DIR}/demo/models/azaion.int8_calib.cache ---"
|
||||
fi
|
||||
|
||||
# ── 4. Start services ────────────────────────────────────────────────────────
|
||||
echo "--- Building and starting services ---"
|
||||
$SSH "cd ${REMOTE_DIR} && docker compose -f docker-compose.demo-jetson.yml up -d --build"
|
||||
|
||||
# ── 5. Health check ───────────────────────────────────────────────────────────
|
||||
echo "--- Health check ---"
|
||||
HEALTH_URL="http://${JETSON_HOST}:8080/health"
|
||||
MAX_RETRIES=15
|
||||
RETRY_INTERVAL=5
|
||||
|
||||
for i in $(seq 1 $MAX_RETRIES); do
|
||||
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$HEALTH_URL" 2>/dev/null || true)
|
||||
if [[ "$STATUS" == "200" ]]; then
|
||||
echo ""
|
||||
echo "=== Demo is live at http://${JETSON_HOST}:8080 ==="
|
||||
echo ""
|
||||
echo "Endpoints:"
|
||||
echo " POST http://${JETSON_HOST}:8080/detect/image"
|
||||
echo " POST http://${JETSON_HOST}:8080/detect/video"
|
||||
echo " GET http://${JETSON_HOST}:8080/health"
|
||||
echo ""
|
||||
echo "Note: On first start the service converts azaion.onnx to a TRT engine."
|
||||
echo " Check /health until AI status shows 'enabled'."
|
||||
exit 0
|
||||
fi
|
||||
echo " Waiting for service… (${i}/${MAX_RETRIES}, HTTP ${STATUS})"
|
||||
sleep "$RETRY_INTERVAL"
|
||||
done
|
||||
|
||||
echo "ERROR: Health check failed after $((MAX_RETRIES * RETRY_INTERVAL))s"
|
||||
echo "Check logs with: ssh ${JETSON_USER}@${JETSON_HOST} \"cd ${REMOTE_DIR} && docker compose -f docker-compose.demo-jetson.yml logs detections\""
|
||||
exit 1
|
||||
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Sample a random subset of images from a YOLO dataset for INT8 calibration.
|
||||
|
||||
Run locally (on your dev machine) before deploying to Jetson:
|
||||
|
||||
python3 scripts/jetson/sample_calibration_images.py \
|
||||
--dataset /path/to/dataset-2025-05-22 \
|
||||
--output /tmp/calibration \
|
||||
--num-samples 500
|
||||
|
||||
The output directory can then be passed directly to deploy_demo_jetson.sh
|
||||
via --calibration-images, or to generate_int8_cache.py via --images-dir.
|
||||
"""
|
||||
import argparse
|
||||
import random
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--dataset", required=True, help="Root of the YOLO dataset (must contain images/)")
|
||||
parser.add_argument("--output", required=True, help="Destination directory for sampled images")
|
||||
parser.add_argument("--num-samples", type=int, default=500)
|
||||
parser.add_argument("--seed", type=int, default=42)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def collect_images(dataset_root: Path) -> list[Path]:
|
||||
images_dir = dataset_root / "images"
|
||||
if not images_dir.is_dir():
|
||||
print(f"ERROR: {images_dir} not found", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
images: list[Path] = []
|
||||
for pattern in ("**/*.jpg", "**/*.jpeg", "**/*.png"):
|
||||
images += sorted(images_dir.glob(pattern))
|
||||
return images
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
dataset_root = Path(args.dataset)
|
||||
output_dir = Path(args.output)
|
||||
|
||||
images = collect_images(dataset_root)
|
||||
if not images:
|
||||
print(f"ERROR: no images found in {dataset_root / 'images'}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
rng = random.Random(args.seed)
|
||||
sample = rng.sample(images, min(args.num_samples, len(images)))
|
||||
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
for src in sample:
|
||||
shutil.copy2(src, output_dir / src.name)
|
||||
|
||||
print(f"Sampled {len(sample)} images → {output_dir}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user