File size: 3,617 Bytes
58aefd4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | """Prometheus API — FastAPI service backing the React dashboard.
Run from the project root:
uvicorn api.main:app --reload --port 8000
Design notes:
* Detection reuses the existing pipeline (src.detection.detector via
api.services.detection_service) — one source of truth for inference.
* Heavy deps (torch/ultralytics) are imported lazily inside the detection
service, so /api/health, /api/metrics and the catalog endpoints respond
instantly even on a machine without a model loaded.
"""
from __future__ import annotations
import os
# Anaconda + torch + OpenCV each ship their own OpenMP runtime (libiomp5md.dll);
# loading them together (as the video job does) collides and crashes the whole
# process natively — uncatchable, the server just dies mid-job. Allowing the
# duplicate runtime is the standard pragmatic fix for inference. MUST be set
# before torch/cv2/numpy are imported anywhere, so it lives at the very top.
os.environ.setdefault("KMP_DUPLICATE_LIB_OK", "TRUE")
import sys # noqa: E402
from pathlib import Path # noqa: E402
from fastapi import FastAPI # noqa: E402
from fastapi.middleware.cors import CORSMiddleware # noqa: E402
PROJECT_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(PROJECT_ROOT))
from api.routes import catalog, detection, population, survey, telemetry # noqa: E402
from api.schemas import SystemInfo # noqa: E402
from api.services import detection_service # noqa: E402
app = FastAPI(
title="Prometheus API",
description="Aerial wildlife intelligence for the Malilangwe Trust.",
version="0.1.0",
)
# CORS origins are configurable via the ALLOWED_ORIGINS env var (comma-separated)
# so production can lock the API to just the dashboard's domain WITHOUT a code
# change — set it at deploy time. Defaults to "*" for local dev.
# e.g. ALLOWED_ORIGINS=https://prometheus.pages.dev,https://app.example.com
_origins_env = os.environ.get("ALLOWED_ORIGINS", "*").strip()
_allow_origins = ["*"] if _origins_env in ("", "*") else [
o.strip() for o in _origins_env.split(",") if o.strip()
]
app.add_middleware(
CORSMiddleware,
allow_origins=_allow_origins,
allow_credentials=False,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(detection.router)
app.include_router(catalog.router)
app.include_router(population.router)
app.include_router(survey.router)
app.include_router(telemetry.router)
@app.get("/api/health", response_model=SystemInfo, tags=["system"])
def health() -> SystemInfo:
cuda = False
device = "cpu"
try:
import torch
cuda = bool(torch.cuda.is_available())
device = "cuda" if cuda else "cpu"
except Exception: # noqa: BLE001 — torch absent is a valid (CPU-only) state
pass
return SystemInfo(
status="ok",
version="0.1.0",
device=device,
torch_cuda=cuda,
models_available=len(detection_service.available_models()),
project="Prometheus · Malilangwe Trust",
)
# Single-app deploy: if a built dashboard is present in web/, serve it from this
# same server so UI + API + weights live at one origin (one private host). The
# dashboard build uses a relative API base, so its /api/* calls are same-origin
# and carry the host's auth. Mounted LAST so the /api/* routes above win.
# Locally without web/, this is skipped and the server is API-only.
_WEB = PROJECT_ROOT / "web"
if (_WEB / "index.html").exists():
from fastapi.staticfiles import StaticFiles # noqa: E402
app.mount("/", StaticFiles(directory=str(_WEB), html=True), name="web")
|