Refactor constants management to use Pydantic BaseModel for configuration

- Replaced module-level path variables in constants.py with a structured Pydantic Config class.
- Updated all relevant modules (train.py, augmentation.py, exports.py, dataset-visualiser.py, manual_run.py) to access paths through the new config structure.
- Fixed bugs related to image processing and model saving.
- Enhanced test infrastructure to accommodate the new configuration approach.

This refactor improves code maintainability and clarity by centralizing configuration management.
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-03-27 18:18:30 +02:00
parent b68c07b540
commit 142c6c4de8
106 changed files with 5706 additions and 654 deletions
@@ -0,0 +1,97 @@
# Module: annotation-queue/annotation_queue_dto
## Purpose
Data transfer objects for the annotation queue consumer. Defines message types for annotation CRUD events received from a RabbitMQ Streams queue.
## Public Interface
### AnnotationClass (local copy)
Same as dto/annotationClass but reads `classes.json` from current working directory and adds `opencv_color` BGR field.
### AnnotationStatus (Enum)
| Member | Value |
|--------|-------|
| Created | 10 |
| Edited | 20 |
| Validated | 30 |
| Deleted | 40 |
### SourceEnum (Enum)
| Member | Value |
|--------|-------|
| AI | 0 |
| Manual | 1 |
### RoleEnum (Enum)
| Member | Value | Description |
|--------|-------|-------------|
| Operator | 10 | Regular annotator |
| Validator | 20 | Annotation validator |
| CompanionPC | 30 | Companion device |
| Admin | 40 | Administrator |
| ApiAdmin | 1000 | API-level admin |
`RoleEnum.is_validator() -> bool`: Returns True for Validator, Admin, ApiAdmin.
### Detection
| Field | Type |
|-------|------|
| `annotation_name` | str |
| `cls` | int |
| `x`, `y`, `w`, `h` | float |
| `confidence` | float (optional) |
### AnnotationCreatedMessageNarrow
Lightweight message with only `name` and `createdEmail` (from msgpack fields 1, 2).
### AnnotationMessage
Full annotation message deserialized from msgpack:
| Field | Type | Source |
|-------|------|--------|
| `createdDate` | datetime | msgpack field 0 (Timestamp) |
| `name` | str | field 1 |
| `originalMediaName` | str | field 2 |
| `time` | timedelta | field 3 (microseconds/10) |
| `imageExtension` | str | field 4 |
| `detections` | list[Detection] | field 5 (JSON string) |
| `image` | bytes | field 6 |
| `createdRole` | RoleEnum | field 7 |
| `createdEmail` | str | field 8 |
| `source` | SourceEnum | field 9 |
| `status` | AnnotationStatus | field 10 |
### AnnotationBulkMessage
Bulk operation message for validate/delete:
| Field | Type | Source |
|-------|------|--------|
| `annotation_names` | list[str] | msgpack field 0 |
| `annotation_status` | AnnotationStatus | field 1 |
| `createdEmail` | str | field 2 |
| `createdDate` | datetime | field 3 (Timestamp) |
## Internal Logic
- All messages are deserialized from msgpack binary using positional integer keys.
- Detections within AnnotationMessage are stored as a JSON string inside the msgpack payload.
- Module-level `annotation_classes = AnnotationClass.read_json()` is loaded at import time for Detection.__str__ formatting.
## Dependencies
- `msgpack` (external) — binary message deserialization
- `json`, `datetime`, `enum` (stdlib)
## Consumers
annotation-queue/annotation_queue_handler
## Data Models
AnnotationClass, AnnotationStatus, SourceEnum, RoleEnum, Detection, AnnotationCreatedMessageNarrow, AnnotationMessage, AnnotationBulkMessage.
## Configuration
Reads `classes.json` from current working directory.
## External Integrations
None (pure data classes).
## Security
None.
## Tests
None.
@@ -0,0 +1,59 @@
# Module: annotation-queue/annotation_queue_handler
## Purpose
Async consumer for the Azaion annotation queue (RabbitMQ Streams). Listens for annotation CRUD events and writes/moves image+label files on the filesystem.
## Public Interface
### AnnotationQueueHandler
| Method | Signature | Returns | Description |
|--------|-----------|---------|-------------|
| `__init__` | `()` | — | Reads config.yaml, creates directories, initializes rstream Consumer, reads offset |
| `start` | `async ()` | — | Starts consumer, subscribes to queue stream, runs event loop |
| `on_message` | `(message: AMQPMessage, context: MessageContext)` | — | Message callback: routes by AnnotationStatus to save/validate/delete |
| `save_annotation` | `(ann: AnnotationMessage)` | — | Writes label file + image to data or seed directory based on role |
| `validate` | `(msg: AnnotationBulkMessage)` | — | Moves annotations from seed to data directory |
| `delete` | `(msg: AnnotationBulkMessage)` | — | Moves annotations to deleted directory |
### AnnotationQueueHandler.AnnotationName (inner class)
Helper that pre-computes file paths for an annotation name across data/seed directories.
## Internal Logic
- **Queue protocol**: Subscribes to a RabbitMQ Streams queue using rstream library with AMQP message decoding. Resumes from a persisted offset stored in `offset.yaml`.
- **Message routing** (via `application_properties['AnnotationStatus']`):
- `Created` / `Edited``save_annotation`: If validator role, writes to data dir; else writes to seed dir. For Created status, also saves the image bytes. For Edited by validator, moves image from seed to data.
- `Validated``validate`: Bulk-moves all named annotations from seed to data directory.
- `Deleted``delete`: Bulk-moves all named annotations to the deleted directory.
- **Offset tracking**: After each message, increments offset and persists to `offset.yaml`.
- **Directory layout**:
- `{root}/data/images/` + `{root}/data/labels/` — validated annotations
- `{root}/data-seed/images/` + `{root}/data-seed/labels/` — unvalidated annotations
- `{root}/data_deleted/images/` + `{root}/data_deleted/labels/` — soft-deleted annotations
- **Logging**: TimedRotatingFileHandler with daily rotation, 7-day retention, logs to `logs/` directory.
## Dependencies
- `annotation_queue_dto` — AnnotationStatus, AnnotationMessage, AnnotationBulkMessage
- `rstream` (external) — RabbitMQ Streams consumer
- `yaml` (external) — config and offset persistence
- `asyncio`, `os`, `shutil`, `sys`, `logging`, `datetime` (stdlib)
## Consumers
None (entry point — runs via `__main__`).
## Data Models
Uses AnnotationMessage, AnnotationBulkMessage from annotation_queue_dto.
## Configuration
- `config.yaml`: API creds (url, email, password), queue config (host, port, consumer_user, consumer_pw, name), directory structure (root, data, data_seed, data_processed, data_deleted, images, labels)
- `offset.yaml`: persisted queue consumer offset
## External Integrations
- RabbitMQ Streams queue (rstream library) on host `188.245.120.247:5552`
- Filesystem: `/azaion/data/`, `/azaion/data-seed/`, `/azaion/data_deleted/`
## Security
- Queue credentials in `config.yaml` (hardcoded — security concern)
- No encryption of annotation data at rest
## Tests
None.
+64
View File
@@ -0,0 +1,64 @@
# Module: api_client
## Purpose
HTTP client for the Azaion backend API. Handles authentication, file upload/download with encryption, and split-resource management (big/small model parts).
## Public Interface
### ApiCredentials
| Field | Type | Description |
|-------|------|-------------|
| `url` | str | API base URL |
| `email` | str | Login email |
| `password` | str | Login password |
### ApiClient
| Method | Signature | Returns | Description |
|--------|-----------|---------|-------------|
| `__init__` | `()` | — | Reads `config.yaml` for API creds, reads `cdn.yaml` via `load_bytes`, initializes CDNManager |
| `login` | `()` | — | POST `/login` → stores JWT token |
| `upload_file` | `(filename: str, file_bytes: bytearray, folder: str)` | — | Uploads file to API resource endpoint |
| `load_bytes` | `(filename: str, folder: str) -> bytes` | Decrypted bytes | Downloads encrypted resource from API, decrypts with hardware-bound key |
| `load_big_small_resource` | `(resource_name: str, folder: str, key: str) -> bytes` | Decrypted bytes | Reassembles a split resource: big part from local disk + small part from API, decrypts combined |
| `upload_big_small_resource` | `(resource: bytes, resource_name: str, folder: str, key: str)` | — | Encrypts resource, splits into big (CDN) + small (API), uploads both |
## Internal Logic
- **Authentication**: JWT-based. Auto-login on first request, re-login on 401/403.
- **load_bytes**: Sends hardware fingerprint in request payload. Server returns encrypted bytes. Client decrypts using key derived from credentials + hardware hash.
- **Split resource pattern**: Large files (models) are split into two parts:
- `*.small` — first N bytes (min of `SMALL_SIZE_KB * 1024` or 20% of encrypted size) — stored on API server
- `*.big` — remainder — stored on CDN (S3)
- This split ensures the model cannot be reconstructed from either storage alone.
- **CDN initialization**: On construction, `cdn.yaml` is loaded via `load_bytes` (from API, encrypted), then used to initialize `CDNManager`.
## Dependencies
- `constants` — config file paths, size thresholds, model folder name
- `cdn_manager` — CDNCredentials, CDNManager for S3 operations
- `hardware_service``get_hardware_info()` for hardware fingerprint
- `security` — encryption/decryption, key derivation
- `requests` (external) — HTTP client
- `yaml` (external) — config parsing
- `io`, `json`, `os` (stdlib)
## Consumers
exports, train, start_inference
## Data Models
`ApiCredentials` — API connection credentials.
## Configuration
- `config.yaml` — API URL, email, password
- `cdn.yaml` — CDN credentials (loaded encrypted from API at init time)
## External Integrations
- Azaion REST API (`POST /login`, `POST /resources/{folder}`, `POST /resources/get/{folder}`)
- S3-compatible CDN via CDNManager
## Security
- JWT token-based authentication with auto-refresh on 401/403
- Hardware-bound encryption for downloaded resources
- Split model storage prevents single-point compromise
- Credentials read from `config.yaml` (hardcoded in file — security concern)
## Tests
None.
+56
View File
@@ -0,0 +1,56 @@
# Module: augmentation
## Purpose
Image augmentation pipeline that takes raw annotated images and produces multiple augmented variants for training data expansion. Runs continuously in a loop.
## Public Interface
### Augmentator
| Method | Signature | Returns | Description |
|--------|-----------|---------|-------------|
| `__init__` | `()` | — | Initializes augmentation transforms and counters |
| `augment_annotations` | `(from_scratch: bool = False)` | — | Processes all unprocessed images from `data/images``data-processed/images` |
| `augment_annotation` | `(image_file)` | — | Processes a single image file: reads image + labels, augments, saves results |
| `augment_inner` | `(img_ann: ImageLabel) -> list[ImageLabel]` | List of augmented images | Generates 1 original + 7 augmented variants |
| `correct_bboxes` | `(labels) -> list` | Corrected labels | Clips bounding boxes to image boundaries, removes tiny boxes |
| `read_labels` | `(labels_path) -> list[list]` | Parsed YOLO labels | Reads YOLO-format label file into list of [x, y, w, h, class_id] |
## Internal Logic
- **Augmentation pipeline** (albumentations Compose):
1. HorizontalFlip (p=0.6)
2. RandomBrightnessContrast (p=0.4)
3. Affine: scale 0.81.2, rotate ±35°, shear ±10° (p=0.8)
4. MotionBlur (p=0.1)
5. HueSaturationValue (p=0.4)
- Each image produces **8 outputs**: 1 original copy + 7 augmented variants
- Naming: `{stem}_{1..7}.jpg` for augmented, original keeps its name
- **Bbox correction**: clips bounding boxes that extend outside image borders, removes boxes smaller than `correct_min_bbox_size` (0.01 of image dimension)
- **Incremental processing**: skips images already present in `processed_images_dir`
- **Concurrent**: uses `ThreadPoolExecutor` for parallel processing
- **Continuous mode**: `__main__` runs augmentation in an infinite loop with 5-minute sleep between rounds
## Dependencies
- `constants` — directory paths (data_images_dir, data_labels_dir, processed_*)
- `dto/imageLabel` — ImageLabel container class
- `albumentations` (external) — augmentation transforms
- `cv2` (external) — image read/write
- `numpy` (external) — image array handling
- `concurrent.futures`, `os`, `shutil`, `time`, `datetime`, `pathlib` (stdlib)
## Consumers
manual_run
## Data Models
Uses `ImageLabel` from `dto/imageLabel`.
## Configuration
Hardcoded augmentation parameters (probabilities, ranges). Directory paths from `constants`.
## External Integrations
Filesystem I/O: reads from `/azaion/data/`, writes to `/azaion/data-processed/`.
## Security
None.
## Tests
None.
+51
View File
@@ -0,0 +1,51 @@
# Module: cdn_manager
## Purpose
Manages file upload and download to/from an S3-compatible CDN (MinIO/similar) using separate credentials for upload and download operations.
## Public Interface
### CDNCredentials
| Field | Type | Description |
|-------|------|-------------|
| `host` | str | CDN endpoint URL |
| `downloader_access_key` | str | S3 access key for downloads |
| `downloader_access_secret` | str | S3 secret for downloads |
| `uploader_access_key` | str | S3 access key for uploads |
| `uploader_access_secret` | str | S3 secret for uploads |
### CDNManager
| Method | Signature | Returns | Description |
|--------|-----------|---------|-------------|
| `__init__` | `(credentials: CDNCredentials)` | — | Creates two boto3 S3 clients (download + upload) |
| `upload` | `(bucket: str, filename: str, file_bytes: bytearray) -> bool` | True on success | Uploads bytes to S3 bucket |
| `download` | `(bucket: str, filename: str) -> bool` | True on success | Downloads file from S3 to current directory |
## Internal Logic
- Maintains two separate boto3 S3 clients with different credentials (read vs write separation)
- Upload uses `upload_fileobj` with in-memory BytesIO wrapper
- Download uses `download_file` (saves directly to disk with same filename)
- Both methods catch all exceptions, print error, return bool
## Dependencies
- `boto3` (external) — S3 client
- `io`, `sys`, `yaml`, `os` (stdlib) — Note: `sys`, `yaml`, `os` are imported but unused
## Consumers
api_client, exports, train, start_inference
## Data Models
`CDNCredentials` — plain data class holding S3 access credentials.
## Configuration
Credentials loaded from `cdn.yaml` by callers (not by this module directly).
## External Integrations
- S3-compatible object storage (configured via `CDNCredentials.host`)
## Security
- Separate read/write credentials enforce least-privilege access
- Credentials passed in at construction time, not hardcoded here
## Tests
None.
+59
View File
@@ -0,0 +1,59 @@
# Module: constants
## Purpose
Centralizes all filesystem path constants, config file names, file extensions, and size thresholds used across the training pipeline.
## Public Interface
| Name | Type | Value/Description |
|------|------|-------------------|
| `azaion` | str | Root directory: `/azaion` |
| `prefix` | str | Naming prefix: `azaion-` |
| `data_dir` | str | `/azaion/data` |
| `data_images_dir` | str | `/azaion/data/images` |
| `data_labels_dir` | str | `/azaion/data/labels` |
| `processed_dir` | str | `/azaion/data-processed` |
| `processed_images_dir` | str | `/azaion/data-processed/images` |
| `processed_labels_dir` | str | `/azaion/data-processed/labels` |
| `corrupted_dir` | str | `/azaion/data-corrupted` |
| `corrupted_images_dir` | str | `/azaion/data-corrupted/images` |
| `corrupted_labels_dir` | str | `/azaion/data-corrupted/labels` |
| `sample_dir` | str | `/azaion/data-sample` |
| `datasets_dir` | str | `/azaion/datasets` |
| `models_dir` | str | `/azaion/models` |
| `date_format` | str | `%Y-%m-%d` |
| `checkpoint_file` | str | `checkpoint.txt` |
| `checkpoint_date_format` | str | `%Y-%m-%d %H:%M:%S` |
| `CONFIG_FILE` | str | `config.yaml` |
| `JPG_EXT` | str | `.jpg` |
| `TXT_EXT` | str | `.txt` |
| `OFFSET_FILE` | str | `offset.yaml` |
| `SMALL_SIZE_KB` | int | `3` (KB threshold for split-upload small part) |
| `CDN_CONFIG` | str | `cdn.yaml` |
| `MODELS_FOLDER` | str | `models` |
| `CURRENT_PT_MODEL` | str | `/azaion/models/azaion.pt` |
| `CURRENT_ONNX_MODEL` | str | `/azaion/models/azaion.onnx` |
## Internal Logic
Pure constant definitions using `os.path.join`. No functions, no classes, no dynamic behavior.
## Dependencies
- `os.path` (stdlib)
## Consumers
api_client, augmentation, exports, train, manual_run, start_inference, dataset-visualiser
## Data Models
None.
## Configuration
Defines `CONFIG_FILE = 'config.yaml'` and `CDN_CONFIG = 'cdn.yaml'` — the filenames for runtime configuration. Does not read them.
## External Integrations
None.
## Security
None.
## Tests
None.
@@ -0,0 +1,43 @@
# Module: convert-annotations
## Purpose
Standalone script that converts annotation files from external formats (Pascal VOC XML, oriented bounding box text) to YOLO format.
## Public Interface
| Function | Signature | Returns | Description |
|----------|-----------|---------|-------------|
| `convert` | `(folder, dest_folder, read_annotations, ann_format)` | — | Generic converter: reads images + annotations from folder, writes YOLO format to dest |
| `minmax2yolo` | `(width, height, xmin, xmax, ymin, ymax) -> tuple` | (cx, cy, w, h) | Converts pixel min/max coords to normalized YOLO center format |
| `read_pascal_voc` | `(width, height, s: str) -> list[str]` | YOLO label lines | Parses Pascal VOC XML, maps class names to IDs, outputs YOLO lines |
| `read_bbox_oriented` | `(width, height, s: str) -> list[str]` | YOLO label lines | Parses 14-column oriented bbox format, outputs YOLO lines (hardcoded class 2) |
| `rename_images` | `(folder)` | — | Renames files by trimming last 7 chars + replacing extension with .png |
## Internal Logic
- **convert()**: Iterates image files in source folder, reads corresponding annotation file, calls format-specific reader, copies image and writes YOLO label to destination.
- **Pascal VOC**: Parses XML `<object>` elements, maps class names via `name_class_map` (Truck→1, Car/Taxi→2), filters forbidden classes (Motorcycle). Default class = 1.
- **Oriented bbox**: 14-column space-separated format, extracts min/max from columns 613, hardcodes class to 2.
- **Validation**: Skips labels where normalized coordinates exceed 1.0 (out of bounds).
## Dependencies
- `cv2` (external) — image reading for dimensions
- `xml.etree.cElementTree` (stdlib) — Pascal VOC XML parsing
- `os`, `shutil`, `pathlib` (stdlib)
## Consumers
None (standalone script).
## Data Models
None.
## Configuration
Hardcoded class mappings: `name_class_map = {'Truck': 1, 'Car': 2, 'Taxi': 2}`, `forbidden_classes = ['Motorcycle']`.
## External Integrations
Filesystem I/O only.
## Security
None.
## Tests
None.
@@ -0,0 +1,41 @@
# Module: dataset-visualiser
## Purpose
Interactive tool for visually inspecting annotated images from datasets or the processed folder, displaying bounding boxes with class colors.
## Public Interface
| Function | Signature | Description |
|----------|-----------|-------------|
| `visualise_dataset` | `()` | Iterates images in a specific dataset folder, shows each with annotations. Waits for keypress. |
| `visualise_processed_folder` | `()` | Shows images from the processed folder with annotations. |
## Internal Logic
- **visualise_dataset()**: Hardcoded to a specific dataset date (`2024-06-18`), iterates from index 35247 onward. Reads image + labels, calls `ImageLabel.visualize()`, waits for user input to advance.
- **visualise_processed_folder()**: Lists all processed images, shows the first one.
- Both functions use `read_labels()` imported from a `preprocessing` module **which does not exist** in the codebase — this is a broken import.
## Dependencies
- `constants` — directory paths (datasets_dir, prefix, processed_*)
- `dto/annotationClass` — AnnotationClass for class colors
- `dto/imageLabel` — ImageLabel for visualization
- `preprocessing`**MISSING MODULE** (read_labels function)
- `cv2` (external), `matplotlib` (external), `os`, `pathlib` (stdlib)
## Consumers
None (standalone script).
## Data Models
Uses ImageLabel, AnnotationClass.
## Configuration
Hardcoded dataset path and start index.
## External Integrations
Filesystem I/O, matplotlib interactive display.
## Security
None.
## Tests
None.
@@ -0,0 +1,49 @@
# Module: dto/annotationClass
## Purpose
Defines the `AnnotationClass` data model and `WeatherMode` enum used in the training pipeline. Reads annotation class definitions from `classes.json`.
## Public Interface
### WeatherMode (Enum)
| Member | Value | Description |
|--------|-------|-------------|
| `Norm` | 0 | Normal weather |
| `Wint` | 20 | Winter conditions |
| `Night` | 40 | Night conditions |
### AnnotationClass
| Field/Method | Type/Signature | Description |
|-------------|----------------|-------------|
| `id` | int | Class ID (weather_offset + base_id) |
| `name` | str | Class name (with weather suffix if non-Norm) |
| `color` | str | Hex color string (e.g. `#ff0000`) |
| `color_tuple` | property → tuple | RGB tuple parsed from hex color |
| `read_json()` | static → dict[int, AnnotationClass] | Reads `classes.json`, expands across weather modes, returns dict keyed by ID |
## Internal Logic
- `read_json()` locates `classes.json` relative to the parent directory of the `dto/` package
- For each of the 3 weather modes, creates an AnnotationClass per entry in `classes.json` with offset IDs (0, 20, 40)
- This produces up to 80 classes total (17 base × 3 modes = 51, but the system reserves 80 slots)
- `color_tuple` strips the first 3 characters of the color string and parses hex pairs
## Dependencies
- `json`, `enum`, `os.path` (stdlib)
## Consumers
train (for YAML generation), dataset-visualiser (for visualization colors)
## Data Models
`AnnotationClass` — annotation class with ID, name, color. `WeatherMode` — enum for weather conditions.
## Configuration
Reads `classes.json` from project root (relative path from `dto/` parent).
## External Integrations
None.
## Security
None.
## Tests
None directly; used transitively by `tests/imagelabel_visualize_test.py`.
@@ -0,0 +1,41 @@
# Module: dto/imageLabel
## Purpose
Container class for an image with its YOLO-format bounding box labels, plus a visualization method for debugging annotations.
## Public Interface
### ImageLabel
| Field/Method | Type/Signature | Description |
|-------------|----------------|-------------|
| `image_path` | str | Filesystem path to the image |
| `image` | numpy.ndarray | OpenCV image array |
| `labels_path` | str | Filesystem path to the labels file |
| `labels` | list[list] | List of YOLO bboxes: [x_center, y_center, width, height, class_id] |
| `visualize` | `(annotation_classes: dict) -> None` | Draws bounding boxes on image and displays via matplotlib |
## Internal Logic
- `visualize()` converts BGR→RGB, iterates labels, converts normalized YOLO coordinates to pixel coordinates, draws colored rectangles using `annotation_classes[class_num].color_tuple`, displays with matplotlib.
- Labels use YOLO format: center_x, center_y, width, height (all normalized 01), class_id as last element.
## Dependencies
- `cv2` (external) — image manipulation
- `matplotlib.pyplot` (external) — image display
## Consumers
augmentation (as augmented image container), dataset-visualiser (for visualization)
## Data Models
`ImageLabel` — image + labels container.
## Configuration
None.
## External Integrations
None.
## Security
None.
## Tests
Used by `tests/imagelabel_visualize_test.py`.
+53
View File
@@ -0,0 +1,53 @@
# Module: exports
## Purpose
Model export utilities: converts trained YOLO .pt models to ONNX, TensorRT, and RKNN formats. Also handles encrypted model upload (split big/small pattern) and data sampling.
## Public Interface
| Function | Signature | Returns | Description |
|----------|-----------|---------|-------------|
| `export_rknn` | `(model_path: str)` | — | Exports YOLO model to RKNN format (RK3588 target), cleans up temp folder |
| `export_onnx` | `(model_path: str, batch_size: int = 4)` | — | Exports YOLO model to ONNX (1280px, NMS enabled, GPU device 0) |
| `export_tensorrt` | `(model_path: str)` | — | Exports YOLO model to TensorRT engine (batch=4, half precision, NMS) |
| `form_data_sample` | `(destination_path: str, size: int = 500, write_txt_log: bool = False)` | — | Creates a random sample of processed images |
| `show_model` | `(model: str = None)` | — | Opens model visualization in netron |
| `upload_model` | `(model_path: str, filename: str, size_small_in_kb: int = 3)` | — | Encrypts model, splits big/small, uploads to API + CDN |
## Internal Logic
- **export_onnx**: Removes existing ONNX file if present, exports at 1280px with NMS baked in and simplification.
- **export_tensorrt**: Uses YOLO's built-in TensorRT export (batch=4, FP16, NMS, simplify).
- **export_rknn**: Exports to RKNN format targeting RK3588 SoC, moves result file and cleans temp directory.
- **upload_model**: Encrypts with `Security.get_model_encryption_key()`, splits encrypted bytes at 30%/70% boundary (or `size_small_in_kb * 1024`), uploads small part to API, big part to CDN.
- **form_data_sample**: Randomly shuffles processed images, copies first N to destination folder.
## Dependencies
- `constants` — directory paths, model paths, config file names
- `api_client` — ApiClient, ApiCredentials for upload
- `cdn_manager` — CDNManager, CDNCredentials for CDN upload
- `security` — model encryption key, encrypt_to
- `utils` — Dotdict for config access
- `ultralytics` (external) — YOLO model
- `netron` (external) — model visualization
- `yaml`, `os`, `shutil`, `random`, `pathlib` (stdlib)
## Consumers
train (export_tensorrt, upload_model, export_onnx)
## Data Models
None.
## Configuration
Reads `config.yaml` for API credentials (in `upload_model`), `cdn.yaml` for CDN credentials.
## External Integrations
- Ultralytics YOLO export pipeline
- Netron model viewer
- Azaion API + CDN for model upload
## Security
- Models are encrypted with AES-256-CBC before upload
- Split storage (big on CDN, small on API) prevents single-point compromise
## Tests
None.
@@ -0,0 +1,38 @@
# Module: hardware_service
## Purpose
Collects hardware fingerprint information (CPU, GPU, RAM, drive serial) from the host machine for use in hardware-bound encryption key derivation.
## Public Interface
| Function | Signature | Returns |
|----------|-----------|---------|
| `get_hardware_info` | `() -> str` | Formatted string: `CPU: {cpu}. GPU: {gpu}. Memory: {memory}. DriveSerial: {drive_serial}` |
## Internal Logic
- Detects OS via `os.name` (`nt` for Windows, else Linux)
- **Windows**: PowerShell commands to query `Win32_Processor`, `Win32_VideoController`, `Win32_OperatingSystem`, disk serial
- **Linux**: `lscpu`, `lspci`, `free`, `/sys/block/sda/device/` serial
- Parses multi-line output: first line = CPU, second = GPU, second-to-last = memory, last = drive serial
- Handles multiple GPUs by taking first GPU and last two lines for memory/drive
## Dependencies
- `os`, `subprocess` (stdlib)
## Consumers
api_client (used in `load_bytes` to generate hardware string for encryption)
## Data Models
None.
## Configuration
None.
## External Integrations
Executes OS-level shell commands to query hardware.
## Security
The hardware fingerprint is used as input to `Security.get_hw_hash()` and subsequently `Security.get_api_encryption_key()`, binding API encryption to the specific machine.
## Tests
None.
@@ -0,0 +1,55 @@
# Module: inference/dto
## Purpose
Data transfer objects for the inference subsystem: Detection, Annotation, and a local copy of AnnotationClass/WeatherMode.
## Public Interface
### Detection
| Field | Type | Description |
|-------|------|-------------|
| `x` | float | Normalized center X |
| `y` | float | Normalized center Y |
| `w` | float | Normalized width |
| `h` | float | Normalized height |
| `cls` | int | Class ID |
| `confidence` | float | Detection confidence score |
| `overlaps(det2, iou_threshold) -> bool` | method | IoU-based overlap check |
### Annotation
| Field | Type | Description |
|-------|------|-------------|
| `frame` | numpy.ndarray | Video frame image |
| `time` | int/float | Timestamp in the video |
| `detections` | list[Detection] | Detected objects in this frame |
### AnnotationClass (duplicate)
Same as `dto/annotationClass.AnnotationClass` but with an additional `opencv_color` field (BGR tuple). Reads from `classes.json` relative to `inference/` parent directory.
### WeatherMode (duplicate)
Same as `dto/annotationClass.WeatherMode`.
## Internal Logic
- `Detection.overlaps()` computes IoU between two bounding boxes and returns True if above threshold.
- `AnnotationClass` here adds `opencv_color` as a pre-computed BGR tuple from the hex color for efficient OpenCV rendering.
## Dependencies
- `json`, `enum`, `os.path` (stdlib)
## Consumers
inference/inference
## Data Models
Detection, Annotation, AnnotationClass, WeatherMode.
## Configuration
Reads `classes.json` from project root.
## External Integrations
None.
## Security
None.
## Tests
None.
@@ -0,0 +1,48 @@
# Module: inference/inference
## Purpose
High-level video inference pipeline. Orchestrates preprocessing → engine inference → postprocessing → visualization for object detection on video streams.
## Public Interface
### Inference
| Method | Signature | Returns | Description |
|--------|-----------|---------|-------------|
| `__init__` | `(engine: InferenceEngine, confidence_threshold, iou_threshold)` | — | Stores engine, thresholds, loads annotation classes |
| `preprocess` | `(frames: list) -> np.ndarray` | Batched blob tensor | Normalizes, resizes, and stacks frames into NCHW blob |
| `postprocess` | `(batch_frames, batch_timestamps, output) -> list[Annotation]` | Annotations per frame | Extracts detections from raw output, applies confidence filter and NMS |
| `process` | `(video: str)` | — | End-to-end: reads video → batched inference → draws + displays results |
| `draw` | `(annotation: Annotation)` | — | Draws bounding boxes with class labels on frame, shows via cv2.imshow |
| `remove_overlapping_detections` | `(detections: list[Detection]) -> list[Detection]` | Filtered list | Custom NMS: removes overlapping detections keeping higher confidence |
## Internal Logic
- **Video processing**: Reads video via cv2.VideoCapture, processes every 4th frame (frame_count % 4), batches frames to engine batch size.
- **Preprocessing**: `cv2.dnn.blobFromImage` with 1/255 scaling, model input size, BGR→RGB swap.
- **Postprocessing**: Iterates raw output, filters by confidence threshold, normalizes coordinates from model space to [0,1], creates Detection objects, applies custom NMS.
- **Custom NMS**: Pairwise IoU comparison. When two detections overlap above threshold, keeps the one with higher confidence (ties broken by lower class ID).
- **Visualization**: Draws colored rectangles and confidence labels using annotation class colors in OpenCV window.
## Dependencies
- `inference/dto` — Detection, Annotation, AnnotationClass
- `inference/onnx_engine` — InferenceEngine ABC (type hint)
- `cv2` (external) — video I/O, image processing, display
- `numpy` (external) — tensor operations
## Consumers
start_inference
## Data Models
Uses Detection, Annotation from `inference/dto`.
## Configuration
`confidence_threshold` and `iou_threshold` set at construction.
## External Integrations
- OpenCV video capture (file or stream input)
- OpenCV GUI window for real-time display
## Security
None.
## Tests
None.
@@ -0,0 +1,50 @@
# Module: inference/onnx_engine
## Purpose
Defines the abstract `InferenceEngine` base class and the `OnnxEngine` implementation for running ONNX model inference with GPU acceleration.
## Public Interface
### InferenceEngine (ABC)
| Method | Signature | Description |
|--------|-----------|-------------|
| `__init__` | `(model_path: str, batch_size: int = 1, **kwargs)` | Abstract constructor |
| `get_input_shape` | `() -> Tuple[int, int]` | Returns (height, width) of model input |
| `get_batch_size` | `() -> int` | Returns the batch size |
| `run` | `(input_data: np.ndarray) -> List[np.ndarray]` | Runs inference, returns output tensors |
### OnnxEngine (extends InferenceEngine)
| Method | Signature | Description |
|--------|-----------|-------------|
| `__init__` | `(model_bytes, batch_size: int = 1, **kwargs)` | Loads ONNX model from bytes, creates InferenceSession with CUDA+CPU providers |
| `get_input_shape` | `() -> Tuple[int, int]` | Returns (height, width) from model input shape |
| `get_batch_size` | `() -> int` | Returns batch size (from model shape or constructor arg) |
| `run` | `(input_data: np.ndarray) -> List[np.ndarray]` | Runs ONNX inference session |
## Internal Logic
- Uses ONNX Runtime with `CUDAExecutionProvider` (primary) and `CPUExecutionProvider` (fallback).
- Reads model metadata to extract class names from custom metadata map.
- If model input shape has a fixed batch dimension (not -1), overrides the constructor batch_size.
## Dependencies
- `onnxruntime` (external) — ONNX inference runtime
- `numpy` (external)
- `abc`, `typing` (stdlib)
## Consumers
inference/inference, inference/tensorrt_engine (inherits InferenceEngine), train (imports OnnxEngine)
## Data Models
None.
## Configuration
None.
## External Integrations
- ONNX Runtime GPU execution (CUDA)
## Security
None.
## Tests
None.
@@ -0,0 +1,53 @@
# Module: inference/tensorrt_engine
## Purpose
TensorRT-based inference engine implementation. Provides GPU-accelerated inference using NVIDIA TensorRT with CUDA memory management, plus ONNX-to-TensorRT conversion.
## Public Interface
### TensorRTEngine (extends InferenceEngine)
| Method | Signature | Returns | Description |
|--------|-----------|---------|-------------|
| `__init__` | `(model_bytes: bytes, **kwargs)` | — | Deserializes TensorRT engine from bytes, allocates CUDA memory |
| `get_input_shape` | `() -> Tuple[int, int]` | (height, width) | Returns model input dimensions |
| `get_batch_size` | `() -> int` | int | Returns configured batch size |
| `run` | `(input_data: np.ndarray) -> List[np.ndarray]` | Output tensors | Runs async inference on CUDA stream |
| `get_gpu_memory_bytes` | `(device_id=0) -> int` | GPU memory in bytes | Queries total GPU VRAM via pynvml (static) |
| `get_engine_filename` | `(device_id=0) -> str \| None` | Filename string | Generates device-specific engine filename (static) |
| `convert_from_onnx` | `(onnx_model: bytes) -> bytes \| None` | Serialized TensorRT plan | Converts ONNX model to TensorRT engine (static) |
## Internal Logic
- **Initialization**: Deserializes TensorRT engine, creates execution context, allocates pinned host memory and device memory for input/output tensors.
- **Dynamic shapes**: Handles -1 (dynamic) dimensions, defaults to 1280×1280 for spatial dims, batch size from engine or constructor.
- **Output shape**: [batch_size, 300 max detections, 6 values per detection (x1, y1, x2, y2, conf, cls)].
- **Inference flow**: Host→Device async copy → execute_async_v3 → synchronize → Device→Host copy.
- **ONNX conversion**: Creates TensorRT builder, parses ONNX, configures workspace (90% of GPU memory), enables FP16 if supported, builds serialized network.
- **Engine filename**: `azaion.cc_{major}.{minor}_sm_{sm_count}.engine` — uniquely identifies engine per GPU architecture.
## Dependencies
- `inference/onnx_engine` — InferenceEngine ABC
- `tensorrt` (external) — TensorRT runtime and builder
- `pycuda.driver` (external) — CUDA memory management
- `pycuda.autoinit` (external) — CUDA context auto-initialization
- `pynvml` (external) — GPU memory query
- `numpy`, `json`, `struct`, `re`, `subprocess`, `pathlib`, `typing` (stdlib/external)
## Consumers
start_inference
## Data Models
None.
## Configuration
None.
## External Integrations
- NVIDIA TensorRT runtime (GPU inference)
- CUDA driver API (memory allocation, streams)
- NVML (GPU hardware queries)
## Security
None.
## Tests
None.
+36
View File
@@ -0,0 +1,36 @@
# Module: manual_run
## Purpose
Ad-hoc script for manual training operations. Contains commented-out alternatives and a hardcoded workflow for copying model weights and exporting.
## Public Interface
No functions or classes. Script-level code only.
## Internal Logic
- Contains commented-out calls to `Augmentator().augment_annotations()`, `train.train_dataset()`, `train.resume_training()`.
- Active code: references a specific model date (`2025-05-18`), removes intermediate epoch checkpoint files, copies `best.pt` to `CURRENT_PT_MODEL`, then calls `train.export_current_model()`.
- Serves as a developer convenience script for one-off training/export operations.
## Dependencies
- `constants` — models_dir, prefix, CURRENT_PT_MODEL
- `train` — export_current_model
- `augmentation` — Augmentator (imported, usage commented out)
- `glob`, `os`, `shutil` (stdlib)
## Consumers
None (standalone script).
## Data Models
None.
## Configuration
Hardcoded model date: `2025-05-18`.
## External Integrations
Filesystem operations on `/azaion/models/`.
## Security
None.
## Tests
None.
+45
View File
@@ -0,0 +1,45 @@
# Module: security
## Purpose
Provides AES-256-CBC encryption/decryption and key derivation functions used to protect model files and API resources in transit.
## Public Interface
| Method | Signature | Returns | Description |
|--------|-----------|---------|-------------|
| `Security.encrypt_to` | `(input_bytes: bytes, key: str) -> bytes` | IV + ciphertext | AES-256-CBC encrypt with PKCS7 padding; prepends 16-byte random IV |
| `Security.decrypt_to` | `(ciphertext_with_iv_bytes: bytes, key: str) -> bytes` | plaintext bytes | Extracts IV from first 16 bytes, decrypts, removes PKCS7 padding |
| `Security.calc_hash` | `(key: str) -> str` | base64-encoded SHA-384 hash | General-purpose hash function |
| `Security.get_hw_hash` | `(hardware: str) -> str` | base64 hash | Derives a hardware-specific hash using `Azaion_{hardware}_%$$$)0_` salt |
| `Security.get_api_encryption_key` | `(creds, hardware_hash: str) -> str` | base64 hash | Derives API encryption key from credentials + hardware hash |
| `Security.get_model_encryption_key` | `() -> str` | base64 hash | Returns a fixed encryption key derived from a hardcoded secret string |
## Internal Logic
- Encryption: SHA-256 of the key string → 32-byte AES key. Random 16-byte IV generated per encryption. PKCS7 padding applied. Output = IV ∥ ciphertext.
- Decryption: First 16 bytes = IV, remainder = ciphertext. Manual PKCS7 unpadding (checks last byte is 116).
- Key derivation uses SHA-384 + base64 encoding for all hash-based keys.
- `BUFFER_SIZE = 64 * 1024` is declared but unused.
## Dependencies
- `cryptography.hazmat` (external) — AES cipher, CBC mode, PKCS7 padding
- `hashlib`, `base64`, `os` (stdlib)
## Consumers
api_client, exports, train, start_inference, tests/security_test
## Data Models
None.
## Configuration
None consumed at runtime. Contains hardcoded key material.
## External Integrations
None.
## Security
- **Hardcoded model encryption key**: `get_model_encryption_key()` uses a static string `'-#%@AzaionKey@%#---234sdfklgvhjbnn'`. This is a significant security concern — the key should be stored in a secrets manager or environment variable.
- API encryption key is derived from user credentials + hardware fingerprint, providing per-device uniqueness.
- AES-256-CBC with random IV is cryptographically sound for symmetric encryption.
## Tests
- `tests/security_test.py` — basic round-trip encrypt/decrypt test (script-based, no test framework).
@@ -0,0 +1,52 @@
# Module: start_inference
## Purpose
Entry point for running inference on video files using a TensorRT engine. Downloads the encrypted model from the API/CDN, initializes the engine, and processes video.
## Public Interface
| Function | Signature | Returns | Description |
|----------|-----------|---------|-------------|
| `get_engine_filename` | `(device_id=0) -> str \| None` | Engine filename | Generates GPU-specific engine filename (duplicate of TensorRTEngine.get_engine_filename) |
`__main__` block: Creates ApiClient, downloads encrypted TensorRT model (split big/small), initializes TensorRTEngine, runs Inference on a test video.
## Internal Logic
- **Model download flow**: ApiClient → `load_big_small_resource` → reassembles from local big part + API-downloaded small part → decrypts with model encryption key → raw engine bytes.
- **Inference setup**: TensorRTEngine initialized from decrypted bytes, Inference configured with confidence_threshold=0.5, iou_threshold=0.3.
- **Video source**: Hardcoded to `tests/ForAI_test.mp4`.
- **get_engine_filename()**: Duplicates `TensorRTEngine.get_engine_filename()` — generates `azaion.cc_{major}.{minor}_sm_{sm_count}.engine` based on CUDA device compute capability and SM count.
## Dependencies
- `constants` — config file paths
- `api_client` — ApiClient, ApiCredentials for model download
- `cdn_manager` — CDNManager, CDNCredentials (imported but CDN managed by api_client)
- `inference/inference` — Inference pipeline
- `inference/tensorrt_engine` — TensorRTEngine
- `security` — model encryption key
- `utils` — Dotdict
- `pycuda.driver` (external) — CUDA device queries
- `yaml` (external)
## Consumers
None (entry point).
## Data Models
None.
## Configuration
- Confidence threshold: 0.5
- IoU threshold: 0.3
- Video path: `tests/ForAI_test.mp4` (hardcoded)
## External Integrations
- Azaion API + CDN for model download
- TensorRT GPU inference
- OpenCV video capture and display
## Security
- Model is downloaded encrypted (split big/small) and decrypted locally
- Uses hardware-bound and model encryption keys
## Tests
None.
+61
View File
@@ -0,0 +1,61 @@
# Module: train
## Purpose
Main training pipeline. Forms YOLO datasets from processed annotations, trains YOLOv11 models, and exports/uploads the trained model.
## Public Interface
| Function | Signature | Returns | Description |
|----------|-----------|---------|-------------|
| `form_dataset` | `()` | — | Creates train/valid/test split from processed images |
| `copy_annotations` | `(images, folder: str)` | — | Copies image+label pairs to a dataset split folder (concurrent) |
| `check_label` | `(label_path: str) -> bool` | bool | Validates YOLO label file (all coords ≤ 1.0) |
| `create_yaml` | `()` | — | Generates YOLO `data.yaml` with class names from `classes.json` |
| `resume_training` | `(last_pt_path: str)` | — | Resumes training from a checkpoint |
| `train_dataset` | `()` | — | Full pipeline: form_dataset → create_yaml → train YOLOv11 → save model |
| `export_current_model` | `()` | — | Exports current .pt to ONNX, encrypts, uploads as split resource |
## Internal Logic
- **Dataset formation**: Shuffles all processed images, splits 70/20/10 (train/valid/test). Copies in parallel via ThreadPoolExecutor. Corrupted labels (coords > 1.0) are moved to `/azaion/data-corrupted/`.
- **YAML generation**: Reads annotation classes from `classes.json`, builds `data.yaml` with 80 class names (17 actual + 63 placeholders "Class-N"), sets train/valid/test paths.
- **Training**: YOLOv11 medium (`yolo11m.yaml`), 120 epochs, batch=11 (tuned for 24GB VRAM), 1280px input, save every epoch, 24 workers.
- **Post-training**: Copies results to `/azaion/models/{date}/`, removes intermediate epoch checkpoints, copies `best.pt` to `CURRENT_PT_MODEL`.
- **Export**: Calls `export_onnx`, reads the ONNX file, encrypts with model key, uploads via `upload_big_small_resource`.
- **Dataset naming**: `azaion-{YYYY-MM-DD}` using current date.
- **`__main__`**: Runs `train_dataset()` then `export_current_model()`.
## Dependencies
- `constants` — all directory/path constants
- `api_client` — ApiClient for model upload
- `cdn_manager` — CDNCredentials, CDNManager (imported but CDN init done via api_client)
- `dto/annotationClass` — AnnotationClass for class name generation
- `inference/onnx_engine` — OnnxEngine (imported but unused in current code)
- `security` — model encryption key
- `utils` — Dotdict
- `exports` — export_tensorrt, upload_model, export_onnx
- `ultralytics` (external) — YOLO training and export
- `yaml`, `concurrent.futures`, `glob`, `os`, `random`, `shutil`, `subprocess`, `datetime`, `pathlib`, `time` (stdlib)
## Consumers
manual_run
## Data Models
Uses AnnotationClass for class definitions.
## Configuration
- Training hyperparameters hardcoded: epochs=120, batch=11, imgsz=1280, save_period=1, workers=24
- Dataset split ratios: train_set=70, valid_set=20, test_set=10
- old_images_percentage=75 (declared but unused)
- DEFAULT_CLASS_NUM=80
## External Integrations
- Ultralytics YOLOv11 training pipeline
- Azaion API + CDN for model upload
- Filesystem: `/azaion/datasets/`, `/azaion/models/`, `/azaion/data-processed/`, `/azaion/data-corrupted/`
## Security
- Trained models are encrypted before upload
- Uses `Security.get_model_encryption_key()` for encryption
## Tests
None.
+36
View File
@@ -0,0 +1,36 @@
# Module: utils
## Purpose
Provides a dictionary subclass that supports dot-notation attribute access.
## Public Interface
| Name | Type | Signature |
|------|------|-----------|
| `Dotdict` | class (extends `dict`) | `Dotdict(dict)` |
`Dotdict` overrides `__getattr__`, `__setattr__`, `__delattr__` to delegate to `dict.get`, `dict.__setitem__`, `dict.__delitem__` respectively.
## Internal Logic
Single-class module. Allows `config.url` instead of `config["url"]` for YAML-loaded dicts.
## Dependencies
None (stdlib `dict` only).
## Consumers
exports, train, start_inference
## Data Models
None.
## Configuration
None.
## External Integrations
None.
## Security
None.
## Tests
None.