mirror of
https://github.com/azaion/detections.git
synced 2026-04-22 22:06:32 +00:00
86d8e7e22d
Made-with: Cursor
169 lines
6.9 KiB
Markdown
169 lines
6.9 KiB
Markdown
# 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)
|
||
|
||
1. Reads uploaded file bytes
|
||
2. Parses optional JSON config
|
||
3. Runs `inference.detect_single_image` in ThreadPoolExecutor (max 2 workers)
|
||
4. Returns list of DetectionDto
|
||
|
||
Error mapping: RuntimeError("not available") → 503, RuntimeError → 422, ValueError → 400.
|
||
|
||
### `/detect/{media_id}` (async media)
|
||
|
||
1. Checks for duplicate active detection (409 if already running)
|
||
2. Extracts auth tokens from Authorization header and x-refresh-token header
|
||
3. Creates `asyncio.Task` for background detection
|
||
4. Detection runs `inference.run_detect` in ThreadPoolExecutor
|
||
5. Callbacks push `DetectionEvent` to all SSE queues
|
||
6. If auth token present, also POSTs annotations to the Annotations service
|
||
7. Returns immediately: `{"status": "started", "mediaId": media_id}`
|
||
|
||
### `/detect/stream` (SSE)
|
||
|
||
- Creates asyncio.Queue per client (maxsize=100)
|
||
- Yields `data: {json}\n\n` SSE 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/refresh` when 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):**
|
||
|
||
1. Saves annotation to local PostgreSQL (image → XxHash64 ID, label file in YOLO format)
|
||
2. Publishes SSE event to UI via `GET /annotations/events`
|
||
3. Enqueues annotation ID to `annotations_queue_records` buffer table (unless SilentDetection mode is enabled in system settings)
|
||
4. `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 API
|
||
- `TokenManager` — 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.
|