Made-with: Cursor
6.9 KiB
Module: main
Purpose
FastAPI application entry point — exposes HTTP API for object detection on images and video media, health checks, and Server-Sent Events (SSE) streaming of detection results.
Public Interface
API Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /health |
Returns AI engine availability status |
| POST | /detect |
Single image detection (multipart file upload) |
| POST | /detect/{media_id} |
Start async detection on media from loader service |
| GET | /detect/stream |
SSE stream of detection events |
DTOs (Pydantic Models)
| Model | Fields | Description |
|---|---|---|
DetectionDto |
centerX, centerY, width, height, classNum, label, confidence | Single detection result |
DetectionEvent |
annotations (list[DetectionDto]), mediaId, mediaStatus, mediaPercent | SSE event payload |
HealthResponse |
status, aiAvailability, errorMessage | Health check response |
AIConfigDto |
frame_period_recognition, frame_recognition_seconds, probability_threshold, tracking_*, model_batch_size, big_image_tile_overlap_percent, altitude, focal_length, sensor_width, paths | Configuration input for media detection |
Class: TokenManager
| Method | Signature | Description |
|---|---|---|
__init__ |
(str access_token, str refresh_token) |
Stores tokens |
get_valid_token |
() -> str |
Returns access_token; auto-refreshes if expiring within 60s |
Internal Logic
/health
Returns HealthResponse with status="healthy" always. aiAvailability reflects the engine's AIAvailabilityStatus. On exception, returns aiAvailability="None".
/detect (single image)
- Reads uploaded file bytes
- Parses optional JSON config
- Runs
inference.detect_single_imagein ThreadPoolExecutor (max 2 workers) - Returns list of DetectionDto
Error mapping: RuntimeError("not available") → 503, RuntimeError → 422, ValueError → 400.
/detect/{media_id} (async media)
- Checks for duplicate active detection (409 if already running)
- Extracts auth tokens from Authorization header and x-refresh-token header
- Creates
asyncio.Taskfor background detection - Detection runs
inference.run_detectin ThreadPoolExecutor - Callbacks push
DetectionEventto all SSE queues - If auth token present, also POSTs annotations to the Annotations service
- Returns immediately:
{"status": "started", "mediaId": media_id}
/detect/stream (SSE)
- Creates asyncio.Queue per client (maxsize=100)
- Yields
data: {json}\n\nSSE format - Cleans up queue on disconnect
Token Management
- Decodes JWT exp claim from base64 payload (no signature verification)
- Auto-refreshes via POST to
{ANNOTATIONS_URL}/auth/refreshwhen within 60s of expiry
Annotations Service Integration
Detections posts results to the Annotations service (POST {ANNOTATIONS_URL}/annotations) server-to-server during async media detection (F3). This only happens when an auth token is present in the original request.
Endpoint: POST {ANNOTATIONS_URL}/annotations
Headers:
| Header | Value |
|---|---|
| Authorization | Bearer {accessToken} (forwarded from the original client request) |
| Content-Type | application/json |
Request body — payload sent by Detections:
| Field | Type | Description |
|---|---|---|
| mediaId | string | ID of the media being processed |
| source | int | 0 (AnnotationSource.AI) |
| videoTime | string | Video playback position formatted from ms as "HH:MM:SS" — mapped to Annotations.Time |
| detections | list | Detection results for this batch (see below) |
| image | string (base64) | Optional — base64-encoded frame image bytes |
userId is not included in the payload. The Annotations service resolves the user identity from the Bearer JWT.
Detection object (as sent by Detections):
| Field | Type | Description |
|---|---|---|
| centerX | float | X center, normalized 0.0–1.0 |
| centerY | float | Y center, normalized 0.0–1.0 |
| width | float | Width, normalized 0.0–1.0 |
| height | float | Height, normalized 0.0–1.0 |
| classNum | int | Detection class number |
| label | string | Human-readable class name |
| confidence | float | Model confidence 0.0–1.0 |
The Annotations API contract (CreateAnnotationRequest) also accepts description (string), affiliation (AffiliationEnum), and combatReadiness (CombatReadinessEnum) on each Detection, but the Detections service does not populate these — the Annotations service uses defaults.
Responses from Annotations service:
| Status | Condition |
|---|---|
| 201 | Annotation created |
| 400 | Neither image nor mediaId provided |
| 404 | MediaId not found in Annotations DB |
Failure handling: POST failures are silently caught — detection processing continues regardless. Annotations that fail to post are not retried.
Downstream pipeline (Annotations service side):
- Saves annotation to local PostgreSQL (image → XxHash64 ID, label file in YOLO format)
- Publishes SSE event to UI via
GET /annotations/events - Enqueues annotation ID to
annotations_queue_recordsbuffer table (unless SilentDetection mode is enabled in system settings) FailsafeProducer(BackgroundService) drains the buffer to RabbitMQ Stream (azaion-annotations) using MessagePack + Gzip
Token refresh for long-running video:
For video detection that may outlast the JWT lifetime, the TokenManager auto-refreshes via POST {ANNOTATIONS_URL}/auth/refresh when the token is within 60s of expiry. The refresh token is provided by the client in the X-Refresh-Token request header.
Dependencies
- External:
asyncio,base64,json,os,time,concurrent.futures,typing,requests,fastapi,pydantic - Internal:
inference(lazy import),constants_inf(label lookup),loader_http_client(client instantiation)
Consumers
None (entry point).
Data Models
DetectionDto,DetectionEvent,HealthResponse,AIConfigDto— Pydantic models for APITokenManager— JWT token lifecycle
Configuration
| Env Var | Default | Description |
|---|---|---|
LOADER_URL |
http://loader:8080 |
Loader service base URL |
ANNOTATIONS_URL |
http://annotations:8080 |
Annotations service base URL |
External Integrations
| Service | Protocol | Purpose |
|---|---|---|
| Loader | HTTP (via LoaderHttpClient) | Model loading |
| Annotations | HTTP POST | Auth refresh (/auth/refresh), annotation posting (/annotations) |
Security
- Bearer token from request headers, refreshed via Annotations service
- JWT exp decoded (base64, no signature verification) — token validation is not performed locally
- No CORS configuration
- No rate limiting
- No input validation on media_id path parameter beyond string type
Tests
None found.