Refactor code structure for improved readability and maintainability
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
FROM nvidia/cuda:12.1.1-cudnn8-runtime-ubuntu22.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python3 python3-pip python3-dev \
|
||||
libgl1 libglib2.0-0 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN ln -sf /usr/bin/python3 /usr/bin/python
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY workers/detect/requirements.txt .
|
||||
RUN pip3 install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Bake in the trained construction-material model and point the worker at it.
|
||||
COPY workers/detect/best.pt .
|
||||
ENV YOLO_MODEL=/app/best.pt
|
||||
|
||||
COPY workers/detect/handler.py .
|
||||
|
||||
CMD ["python3", "-u", "handler.py"]
|
||||
@@ -0,0 +1,37 @@
|
||||
# Construction-Material Detection Worker (YOLOv8)
|
||||
|
||||
RunPod Serverless worker running Ultralytics YOLOv8 object detection for a
|
||||
custom-trained construction-material classifier.
|
||||
|
||||
## Build & Deploy
|
||||
- Dockerfile path: `/workers/detect/Dockerfile`
|
||||
- Build context: repo ROOT (the COPY paths are prefixed with `workers/detect/`).
|
||||
- GPU: light/medium — NVIDIA T4 or A4000 is plenty.
|
||||
|
||||
## Model
|
||||
The trained construction-material model (`best.pt`, 43 MB) is **baked into the
|
||||
image** (`COPY workers/detect/best.pt` → `ENV YOLO_MODEL=/app/best.pt`), so the
|
||||
worker detects your materials out of the box — no volume upload needed.
|
||||
|
||||
- `YOLO_MODEL` — override only if you want to swap models (default `/app/best.pt`).
|
||||
To update the model later, replace `workers/detect/best.pt` and push (RunPod rebuilds).
|
||||
|
||||
## App-side env var
|
||||
Point your application at the deployed endpoint with:
|
||||
- `RUNPOD_YOLO_URL` = your RunPod serverless endpoint URL.
|
||||
|
||||
## Contract
|
||||
Input:
|
||||
```json
|
||||
{ "input": { "image": "<base64>" } }
|
||||
```
|
||||
The base64 may include a `data:image/...;base64,` prefix; it is stripped.
|
||||
|
||||
Output (pixel coordinates):
|
||||
```json
|
||||
{ "detections": [
|
||||
{ "name": "brick", "confidence": 0.94,
|
||||
"box": { "x1": 10.0, "y1": 20.0, "x2": 110.0, "y2": 220.0 } }
|
||||
] }
|
||||
```
|
||||
On error: `{ "error": "<Type>: <message>" }`
|
||||
Binary file not shown.
@@ -0,0 +1,64 @@
|
||||
_V = "/runpod-volume"
|
||||
import os
|
||||
if os.path.isdir(_V):
|
||||
os.environ.setdefault("HF_HOME", os.path.join(_V, "huggingface"))
|
||||
|
||||
import io
|
||||
import base64
|
||||
import runpod
|
||||
from PIL import Image
|
||||
|
||||
_model = None
|
||||
|
||||
|
||||
def _load_model():
|
||||
global _model
|
||||
if _model is None:
|
||||
from ultralytics import YOLO
|
||||
weights = os.environ.get("YOLO_MODEL", "yolov8n.pt")
|
||||
_model = YOLO(weights)
|
||||
return _model
|
||||
|
||||
|
||||
def handler(event):
|
||||
try:
|
||||
data = (event or {}).get("input") or {}
|
||||
image_b64 = data.get("image")
|
||||
if not image_b64:
|
||||
return {"error": "ValueError: missing 'image' in input"}
|
||||
|
||||
if "," in image_b64:
|
||||
image_b64 = image_b64.split(",", 1)[1]
|
||||
image_bytes = base64.b64decode(image_b64)
|
||||
image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
|
||||
|
||||
# Custom construction models are often less confident than COCO — use a low
|
||||
# detection floor (env YOLO_CONF, default 0.15) so real brick/pipe/steel hits
|
||||
# come through instead of being dropped by ultralytics' default 0.25.
|
||||
conf = float(data.get("conf") or os.environ.get("YOLO_CONF", "0.15"))
|
||||
model = _load_model()
|
||||
results = model.predict(image, conf=conf, verbose=False)
|
||||
|
||||
detections = []
|
||||
for result in results:
|
||||
boxes = getattr(result, "boxes", None)
|
||||
if boxes is None:
|
||||
continue
|
||||
names = result.names if getattr(result, "names", None) else model.names
|
||||
for box in boxes:
|
||||
cls = int(box.cls[0])
|
||||
conf = float(box.conf[0])
|
||||
xyxy = box.xyxy[0].tolist()
|
||||
x1, y1, x2, y2 = [float(v) for v in xyxy]
|
||||
detections.append({
|
||||
"name": str(names[cls]),
|
||||
"confidence": conf,
|
||||
"box": {"x1": x1, "y1": y1, "x2": x2, "y2": y2},
|
||||
})
|
||||
|
||||
return {"detections": detections}
|
||||
except Exception as exc:
|
||||
return {"error": f"{type(exc).__name__}: {exc}"}
|
||||
|
||||
|
||||
runpod.serverless.start({"handler": handler})
|
||||
@@ -0,0 +1,3 @@
|
||||
runpod==1.7.9
|
||||
ultralytics==8.3.40
|
||||
pillow==10.4.0
|
||||
@@ -0,0 +1 @@
|
||||
{"input": {"image": "iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAIAAAB7GkOtAAAFp0lEQVR42u3VMREAMAgAMfRVRO2woK1GagIJCCB3UfDLR74PwEIhAYABAGAAABgAAAYAgAEAYAAAGAAABgCAAQBgAAAYAAAGAIABAGAAABgAAAYAgAEAYAAAGAAABgCAAQBgAAAYAIABAGAAABgAAAYAgAEAYAAAGAAABgCAAQBgAAAYAAAGAIABAGAAABgAAAYAgAEAYAAAGAAABgCAAQBgAAAYAIABqABgAAAYAAAGAIABAGAAABgAAAYAgAEAYAAAGAAABgCAAQBgAAAYAAAGAIABAGAAABgAAAYAgAEAYAAAGAAABgBgAAAYAAAGAIABAGAAABgAAAYAgAEAYAAAGAAABgCAAQBgAAAYAAAGAIABAGAAABgAAAYAgAEAYAAAGAAABgBgACoAGAAABgCAAQBgAAAYAAAGAIABAGAAABgAAAYAgAEAYAAAGAAABgCAAQBgAAAYAAAGAIABAGAAABgAAAYAgAEAGAAABgCAAQBgAAAYAAAGAIABAGAAABgAAAYAgAEAYAAAGAAABgCAAQBgAAAYAAAGAIABAGAAABgAAAYAgAEAGAAABgCAAQBgAAAYAAAGAIABAGAAABgAAAYAgAEAYAAAGAAABgCAAQBgAAAYAAAGAIABAGAAABgAAAYAgAEAYAAABgCAAQBgAAAYAAAGAIABAGAAABgAAAYAgAEAYAAAGAAABgCAAQBgAAAYAAAGAIABAGAAABgAAAYAgAEAYAAABgCAAQBgAAAYAAAGAIABAGAAABgAAAYAgAEAYAAAGAAABgCAAQBgAAAYAAAGAIABAGAAABgAAAYAgAEAYAAAGACAAQBgAAAYAAAGAIABAGAAABgAAAYAgAEAYAAAGAAABgCAAQBgAAAYAAAGAIABAGAAABgAAAYAgAEAYAAAGACAAQBgAAAYAAAGAIABAGAAABgAAAYAgAEAYAAAGAAABgCAAQBgAAAYAAAGAIABAGAAABgAAAYAwDiAWweAhQwAwAAAMAAADAAAAwDAAAAwAAAMAAADAMAAADAAAAwAAAMAwAAAMAAADAAAAwDAAAAwAAAMAAADAMAAADAAAAwAwAAAMAAADAAAAwDAAAAwAAAMAAADAMAAADAAAAwAAAMAwAAAMAAADAAAAwDAAAAwAAAMAAADAMAAADAAAAwAwAAkADAAAAwAAAMAwAAAMAAADAAAAwDAAAAwAAAMAAADAMAAADAAAAwAAAMAwAAAMAAADAAAAwDAAAAwAAAMAAADADAAAAwAAAMAwAAAMAAADAAAAwDAAAAwAAAMAAADAMAAADAAAAwAAAMAwAAAMAAADAAAAwDAAAAwAAAMAAADADAAFQAMAAADAMAAADAAAAwAAAMAwAAAMAAADAAAAwDAAAAwAAAMAAADAMAAADAAAAwAAAMAwAAAMAAADAAAAwDAAAAMAAADAMAAADAAAAwAAAMAwAAAMAAADAAAAwDAAAAwAAAMAAADAMAAADAAAAwAAAMAwAAAMAAADAAAAwDAAAAMQAUAAwDAAAAwAAAMAAADAMAAADAAAAwAAAMAwAAAMAAADAAAAwDAAAAwAAAMAAADAMAAADAAAAwAAAMAwAAAMAAAAwDAAAAwAAAMAAADAMAAADAAAAwAAAMAwAAAMAAADAAAAwDAAAAwAAAMAAADAMAAADAAAAwAAAMAwAAAMAAAAwDAAAAwAAAMAAADAMAAADAAAAwAAAMAwAAAMAAADAAAAwDAAAAwAAAMAAADAMAAADAAAAwAAAMAwAAAMAAADADAAAAwAAAMAAADAMAAADAAAAwAAAMAwAAAMAAADAAAAwDAAAAwAAAMAAADAMAAADAAAAwAAAMAwAAAMAAADADAAAAwAAAMAAADAMAAADAAAAwAAAMAwAAAMAAADAAAAwDAAAAwAAAMAAADAMAAADAAAAwAAAMAYNQ/e2eZ8ltazgAAAABJRU5ErkJggg=="}}
|
||||
Reference in New Issue
Block a user