Files
detections/_docs/02_document/modules/main.md
T
2026-03-23 14:07:54 +02:00

169 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.01.0 |
| centerY | float | Y center, normalized 0.01.0 |
| width | float | Width, normalized 0.01.0 |
| height | float | Height, normalized 0.01.0 |
| classNum | int | Detection class number |
| label | string | Human-readable class name |
| confidence | float | Model confidence 0.01.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.