2 Commits

Author SHA1 Message Date
Oleksandr Bezdieniezhnykh 02c544746a Merge branch 'stage' into main
ci/woodpecker/push/build-arm Pipeline failed
2026-04-22 01:40:15 +03:00
Oleksandr Bezdieniezhnykh 64d3f828c8 Merge branch 'dev' into stage
ci/woodpecker/push/build-arm Pipeline failed
2026-04-22 01:37:15 +03:00
26 changed files with 58 additions and 308 deletions
-39
View File
@@ -1,39 +0,0 @@
# Manual-trigger only. Validation fixtures (sample videos + expected-detections
# baseline) are not yet authored; running the e2e on push would produce red
# builds until that data lands. Flip `event: [manual]` back to
# `event: [push, pull_request, manual]` once validation fixtures are in place,
# and add `depends_on: [01-test]` to 02-build-push.yml.
#
# Tracking: see plan "CI Auto Tests Epic", story S3b — blocked by e2e validation data.
when:
event: [manual]
branch: [dev, stage, main]
labels:
platform: arm64
steps:
- name: e2e
image: docker:24
environment:
COMPOSE_PROJECT_NAME: detections-e2e
commands:
- cd e2e
- mkdir -p results logs
- docker compose -f docker-compose.test.yml --profile cpu up --build --abort-on-container-exit --exit-code-from e2e-runner
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- name: e2e-report
image: docker:24
when:
status: [success, failure]
environment:
COMPOSE_PROJECT_NAME: detections-e2e
commands:
- cd e2e
- docker compose -f docker-compose.test.yml --profile cpu down -v || true
- test -f results/report.csv && cat results/report.csv || echo "no report"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
@@ -1,27 +1,9 @@
# No `depends_on: [01-test]` clause yet — the test workflow is manual-only
# while validation fixtures are being authored (see 01-test.yml header comment).
# When 01-test flips to push/pull_request triggers, add `depends_on: [01-test]`
# here so builds are gated on a green test run.
when: when:
event: [push, manual] event: [push, manual]
branch: [dev, stage, main] branch: [dev, stage, main]
# Multi-arch matrix. detections is the only repo today with split per-arch
# Dockerfiles (Jetson uses Dockerfile.jetson with TensorRT/CUDA on L4T;
# amd64 uses the plain Dockerfile). Adding amd64 = uncommenting the second
# entry once an amd64 agent and base image are in place.
matrix:
include:
- PLATFORM: arm64
TAG_SUFFIX: arm
DOCKERFILE: Dockerfile.jetson
# - PLATFORM: amd64
# TAG_SUFFIX: amd
# DOCKERFILE: Dockerfile
labels: labels:
platform: ${PLATFORM} platform: arm64
steps: steps:
- name: build-push - name: build-push
@@ -35,10 +17,10 @@ steps:
from_secret: registry_token from_secret: registry_token
commands: commands:
- echo "$REGISTRY_TOKEN" | docker login "$REGISTRY_HOST" -u "$REGISTRY_USER" --password-stdin - echo "$REGISTRY_TOKEN" | docker login "$REGISTRY_HOST" -u "$REGISTRY_USER" --password-stdin
- export TAG=${CI_COMMIT_BRANCH}-${TAG_SUFFIX} - export TAG=${CI_COMMIT_BRANCH}-arm
- export BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) - export BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
- | - |
docker build -f ${DOCKERFILE} \ docker build -f Dockerfile.jetson \
--build-arg CI_COMMIT_SHA=$CI_COMMIT_SHA \ --build-arg CI_COMMIT_SHA=$CI_COMMIT_SHA \
--label org.opencontainers.image.revision=$CI_COMMIT_SHA \ --label org.opencontainers.image.revision=$CI_COMMIT_SHA \
--label org.opencontainers.image.created=$BUILD_DATE \ --label org.opencontainers.image.created=$BUILD_DATE \
-15
View File
@@ -1,15 +0,0 @@
when:
event: [manual]
labels:
platform: arm64
steps:
- name: e2e-smoke-jetson
image: docker
commands:
- apk add --no-cache bash docker-cli-compose
- cd e2e
- E2E_PROFILE=jetson bash run_test.sh tests/test_health_engine.py
volumes:
- /var/run/docker.sock:/var/run/docker.sock
+2 -2
View File
@@ -5,7 +5,7 @@
- Detections with confidence below `probability_threshold` (default: 0.25) are filtered out. - Detections with confidence below `probability_threshold` (default: 0.25) are filtered out.
- Overlapping detections with containment ratio > `tracking_intersection_threshold` (default: 0.6) are deduplicated, keeping the higher-confidence detection. - Overlapping detections with containment ratio > `tracking_intersection_threshold` (default: 0.6) are deduplicated, keeping the higher-confidence detection.
- Tile duplicate detections are identified when all bounding box coordinates differ by less than 0.01 (TILE_DUPLICATE_CONFIDENCE_THRESHOLD). - Tile duplicate detections are identified when all bounding box coordinates differ by less than 0.01 (TILE_DUPLICATE_CONFIDENCE_THRESHOLD).
- Physical size filtering: detections exceeding `max_object_size_meters` for their class (defined in classes.json, range 220 meters) are removed when ground sampling distance can be computed from camera settings. - Physical size filtering: detections exceeding `max_object_size_meters` for their class (defined in classes.json, range 220 meters) are removed.
## Video Processing ## Video Processing
@@ -17,7 +17,7 @@
- Images ≤ 1.5× model dimensions (1280×1280): processed as single frame. - Images ≤ 1.5× model dimensions (1280×1280): processed as single frame.
- Larger images: tiled based on ground sampling distance. Tile physical size: 25 meters (METERS_IN_TILE). Tile overlap: `big_image_tile_overlap_percent` (default: 20%). - Larger images: tiled based on ground sampling distance. Tile physical size: 25 meters (METERS_IN_TILE). Tile overlap: `big_image_tile_overlap_percent` (default: 20%).
- GSD calculation: `sensor_width * altitude / (focal_length * image_width)` when `altitude` is provided. - GSD calculation: `sensor_width * altitude / (focal_length * image_width)`.
## API ## API
@@ -36,7 +36,7 @@ Media path is resolved from the Annotations service via `GET /api/media/{media_i
| tracking_intersection_threshold | float | 0.6 | Overlap ratio for NMS deduplication | | tracking_intersection_threshold | float | 0.6 | Overlap ratio for NMS deduplication |
| model_batch_size | int | 8 | Inference batch size | | model_batch_size | int | 8 | Inference batch size |
| big_image_tile_overlap_percent | int | 20 | Tile overlap for large images (0-100%) | | big_image_tile_overlap_percent | int | 20 | Tile overlap for large images (0-100%) |
| altitude | float | optional | Camera altitude in meters. When omitted, GSD-based size filtering and image tiling are skipped. | | altitude | float | 400 | Camera altitude in meters |
| focal_length | float | 24 | Camera focal length in mm | | focal_length | float | 24 | Camera focal length in mm |
| sensor_width | float | 23.5 | Camera sensor width in mm | | sensor_width | float | 23.5 | Camera sensor width in mm |
@@ -1,3 +1 @@
center_x,center_y,width,height,label,confidence_min center_x,center_y,width,height,label,confidence_min
0.104737,0.375955,0.154999,0.144996,Truck,0.73
0.576021,0.728599,0.1495,0.142923,Truck,0.83
1 center_x center_y width height label confidence_min
0.104737 0.375955 0.154999 0.144996 Truck 0.73
0.576021 0.728599 0.1495 0.142923 Truck 0.83
@@ -1,4 +1 @@
center_x,center_y,width,height,label,confidence_min center_x,center_y,width,height,label,confidence_min
0.214701,0.770089,0.274945,0.28434,ArmorVehicle,0.73
0.588816,0.581807,0.269273,0.209632,ArmorVehicle,0.49
0.314031,0.56274,0.039824,0.060367,MilitaryMan,0.32
1 center_x center_y width height label confidence_min
0.214701 0.770089 0.274945 0.28434 ArmorVehicle 0.73
0.588816 0.581807 0.269273 0.209632 ArmorVehicle 0.49
0.314031 0.56274 0.039824 0.060367 MilitaryMan 0.32
@@ -1,2 +1 @@
center_x,center_y,width,height,label,confidence_min center_x,center_y,width,height,label,confidence_min
0.465599,0.20807,0.137469,0.194655,ArmorVehicle,0.77
1 center_x center_y width height label confidence_min
0.465599 0.20807 0.137469 0.194655 ArmorVehicle 0.77
@@ -37,18 +37,21 @@ For videos, the additional field:
### Images ### Images
| # | Input File | Description | Expected Result File | Expected Detection Count | Notes | | # | Input File | Description | Expected Result File | Expected Detection Count | Notes |
|---|------------|-------------|---------------------|---|-------| |---|------------|-------------|---------------------|-------------------------|-------|
| 1 | `image_small.jpg`| 1280×720 aerial, contains detectable objects| `image_small_expected.csv`| 1 | Primary test image for single-frame detection| | 1 | `image_small.jpg` | 1280×720 aerial, contains detectable objects | `image_small_expected.csv` | ? | Primary test image for single-frame detection |
| 2 | `image_large.JPG`| 6252×4168 aerial, triggers GSD-based tiling| `image_large_expected.csv`| 3 | Coordinates normalized to full image (not tile)| | 2 | `image_large.JPG` | 6252×4168 aerial, triggers GSD-based tiling | `image_large_expected.csv` | ? | Coordinates normalized to full image (not tile) |
| 3 | `image_different_types.jpg`| 900×1600, varied object classes| `image_different_types_expected.csv`| 2 | Must contain multiple distinct class labels| | 3 | `image_dense01.jpg` | 1280×720 dense scene, many clustered objects | `image_dense01_expected.csv` | ? | Used for dedup and max-detection-cap tests |
| 4 | `image_empty_scene.jpg`| 1920×1080, no detectable objects| `image_empty_scene_expected.csv`| 0 | CSV has headers only — zero detections expected| | 4 | `image_dense02.jpg` | 1920×1080 dense scene variant | `image_dense02_expected.csv` | ? | Borderline tiling, dedup variant |
| 5 | `image_different_types.jpg` | 900×1600, varied object classes | `image_different_types_expected.csv` | ? | Must contain multiple distinct class labels |
| 6 | `image_empty_scene.jpg` | 1920×1080, no detectable objects | `image_empty_scene_expected.csv` | 0 | CSV has headers only — zero detections expected |
### Videos ### Videos
| # | Input File | Description | Expected Result File | Notes | | # | Input File | Description | Expected Result File | Notes |
|---|------------|-------------|---------------------|-------| |---|------------|-------------|---------------------|-------|
| 5 | `video_test01.mp4` | Standard test video | `video_test01_expected.csv` | Primary async/SSE/video test. List key-frame detections. | | 7 | `video_test01.mp4` | Standard test video | `video_test01_expected.csv` | Primary async/SSE/video test. List key-frame detections. |
| 6 | `video_1_faststart.mp4` | Faststart video variant | `video_1_faststart_expected.csv` | Streaming compatibility variant. Separate long-video overflow fixture is not currently present in local fixtures. | | 8 | `video_1.mp4` | Video variant | `video_1_expected.csv` | Secondary local fixture for resilience and concurrent-style validation. |
| 9 | `video_1_faststart.mp4` | Faststart video variant | `video_1_faststart_expected.csv` | Streaming compatibility variant. Separate long-video overflow fixture is not currently present in local fixtures. |
## How to Fill ## How to Fill
@@ -1,81 +1 @@
time_sec,center_x,center_y,width,height,label,confidence_min time_sec,center_x,center_y,width,height,label,confidence_min
0,0.289802,0.512695,0.077206,0.072666,ArmorVehicle,0.89
2,0.653329,0.376178,0.078665,0.081241,ArmorVehicle,0.89
4,0.197519,0.416036,0.067097,0.083232,ArmorVehicle,0.83
6,0.276864,0.60931,0.060284,0.066828,ArmorVehicle,0.8
8,0.379446,0.434878,0.071124,0.077504,ArmorVehicle,0.55
10,0.753483,0.198423,0.057173,0.070441,ArmorVehicle,0.72
14,0.956159,0.68494,0.075992,0.034521,Trenches,0.62
30,0.343423,0.763509,0.098184,0.079214,TyreTracks,0.82
32,0.438461,0.687812,0.09794,0.07654,TyreTracks,0.85
41,0.700711,0.590761,0.10644,0.088566,TyreTracks,0.74
47,0.830646,0.762364,0.108269,0.091804,TyreTracks,0.37
47,0.698529,0.00825,0.022697,0.01672,Vehicle,0.34
49,0.349817,0.663337,0.075551,0.061795,TyreTracks,0.77
67,0.846456,0.527966,0.03052,0.058466,Trenches,0.32
69,0.68478,0.514788,0.025761,0.053171,Trenches,0.46
71,0.368416,0.460942,0.030577,0.050027,Trenches,0.78
73,0.177122,0.455578,0.031931,0.048597,Trenches,0.36
87,0.128406,0.138269,0.040148,0.032909,Smoke,0.56
90,0.118965,0.089119,0.037023,0.033037,Smoke,0.32
95,0.570426,0.373717,0.031503,0.021361,Trenches,0.48
108,0.621403,0.45839,0.026638,0.037245,Smoke,0.58
110,0.637573,0.465766,0.027141,0.040499,Smoke,0.58
112,0.610838,0.480663,0.0283,0.041891,Smoke,0.72
114,0.60922,0.486313,0.026929,0.042859,Smoke,0.68
116,0.615395,0.492105,0.028959,0.041361,Smoke,0.67
118,0.612622,0.49208,0.027159,0.039772,Smoke,0.61
120,0.488272,0.653743,0.028969,0.045788,Smoke,0.67
122,0.276008,0.692814,0.061306,0.10955,Smoke,0.74
127,0.5029,0.24006,0.116369,0.208533,Smoke,0.74
130,0.350402,0.278989,0.122529,0.179202,Smoke,0.7
132,0.525031,0.469289,0.103297,0.193989,Smoke,0.48
134,0.475175,0.52287,0.099367,0.178105,Smoke,0.27
136,0.042595,0.458038,0.073769,0.201478,Smoke,0.72
138,0.398975,0.280201,0.132485,0.076752,Smoke,0.3
149,0.115298,0.585385,0.037456,0.030006,Trenches,0.64
149,0.149621,0.602923,0.039252,0.032263,Trenches,0.73
149,0.534584,0.082995,0.03861,0.036615,Trenches,0.51
151,0.363107,0.353294,0.036212,0.030509,Trenches,0.52
151,0.395121,0.377565,0.03571,0.034257,Trenches,0.52
153,0.540678,0.549795,0.04493,0.040794,Trenches,0.55
155,0.124097,0.466852,0.074959,0.051802,Trenches,0.32
163,0.687639,0.261656,0.116238,0.125195,TyreTracks,0.43
168,0.378062,0.769232,0.032526,0.027289,Trenches,0.46
174,0.435846,0.904734,0.03305,0.03245,Trenches,0.27
176,0.942677,0.589411,0.024974,0.052446,Trenches,0.33
181,0.612998,0.232146,0.08565,0.060314,Smoke,0.52
191,0.390291,0.036179,0.054409,0.033613,Trenches,0.39
193,0.413883,0.208317,0.053158,0.031181,Trenches,0.76
193,0.430828,0.171384,0.030674,0.029638,Trenches,0.27
193,0.484856,0.182248,0.04184,0.063353,Trenches,0.65
193,0.491091,0.316695,0.031371,0.024474,Trenches,0.37
195,0.556304,0.101112,0.046918,0.036026,Smoke,0.71
197,0.519331,0.120967,0.044832,0.039692,Smoke,0.67
199,0.176271,0.118214,0.047294,0.053538,Smoke,0.63
202,0.171963,0.138687,0.043301,0.04702,Smoke,0.46
214,0.413986,0.12009,0.050383,0.046863,Smoke,0.5
219,0.364435,0.552301,0.028486,0.0314,ArmorVehicle,0.72
240,0.502753,0.535129,0.036657,0.033246,Trenches,0.32
242,0.019098,0.607661,0.038072,0.024671,Trenches,0.27
242,0.02406,0.680742,0.044204,0.023859,Trenches,0.37
242,0.027672,0.720891,0.033973,0.04163,Trenches,0.65
242,0.041207,0.628713,0.021846,0.044712,Trenches,0.29
242,0.053195,0.682519,0.027647,0.038862,Trenches,0.65
242,0.84993,0.631653,0.046269,0.042584,Trenches,0.47
244,0.017405,0.7017,0.034866,0.023287,Trenches,0.35
244,0.018653,0.733875,0.03717,0.025044,Trenches,0.48
244,0.032765,0.621231,0.064777,0.023041,Trenches,0.45
244,0.05303,0.734179,0.034922,0.041688,Trenches,0.7
244,0.070526,0.649712,0.023196,0.061101,Trenches,0.44
244,0.076041,0.69735,0.028326,0.039375,Trenches,0.65
246,0.025853,0.893559,0.051857,0.02183,Trenches,0.33
246,0.055409,0.908165,0.020511,0.040618,Trenches,0.32
252,0.735668,0.309579,0.026179,0.044193,Trenches,0.61
252,0.760952,0.371567,0.026674,0.062061,Trenches,0.52
282,0.566367,0.010329,0.036472,0.020469,Smoke,0.49
284,0.341962,0.011659,0.03561,0.023243,Smoke,0.59
287,0.423856,0.023553,0.03954,0.044283,Smoke,0.42
290,0.397077,0.010032,0.037649,0.019987,Smoke,0.29
292,0.415613,0.011793,0.036875,0.023537,Smoke,0.37
294,0.385953,0.009416,0.037843,0.018655,Smoke,0.39
1 time_sec center_x center_y width height label confidence_min
0 0.289802 0.512695 0.077206 0.072666 ArmorVehicle 0.89
2 0.653329 0.376178 0.078665 0.081241 ArmorVehicle 0.89
4 0.197519 0.416036 0.067097 0.083232 ArmorVehicle 0.83
6 0.276864 0.60931 0.060284 0.066828 ArmorVehicle 0.8
8 0.379446 0.434878 0.071124 0.077504 ArmorVehicle 0.55
10 0.753483 0.198423 0.057173 0.070441 ArmorVehicle 0.72
14 0.956159 0.68494 0.075992 0.034521 Trenches 0.62
30 0.343423 0.763509 0.098184 0.079214 TyreTracks 0.82
32 0.438461 0.687812 0.09794 0.07654 TyreTracks 0.85
41 0.700711 0.590761 0.10644 0.088566 TyreTracks 0.74
47 0.830646 0.762364 0.108269 0.091804 TyreTracks 0.37
47 0.698529 0.00825 0.022697 0.01672 Vehicle 0.34
49 0.349817 0.663337 0.075551 0.061795 TyreTracks 0.77
67 0.846456 0.527966 0.03052 0.058466 Trenches 0.32
69 0.68478 0.514788 0.025761 0.053171 Trenches 0.46
71 0.368416 0.460942 0.030577 0.050027 Trenches 0.78
73 0.177122 0.455578 0.031931 0.048597 Trenches 0.36
87 0.128406 0.138269 0.040148 0.032909 Smoke 0.56
90 0.118965 0.089119 0.037023 0.033037 Smoke 0.32
95 0.570426 0.373717 0.031503 0.021361 Trenches 0.48
108 0.621403 0.45839 0.026638 0.037245 Smoke 0.58
110 0.637573 0.465766 0.027141 0.040499 Smoke 0.58
112 0.610838 0.480663 0.0283 0.041891 Smoke 0.72
114 0.60922 0.486313 0.026929 0.042859 Smoke 0.68
116 0.615395 0.492105 0.028959 0.041361 Smoke 0.67
118 0.612622 0.49208 0.027159 0.039772 Smoke 0.61
120 0.488272 0.653743 0.028969 0.045788 Smoke 0.67
122 0.276008 0.692814 0.061306 0.10955 Smoke 0.74
127 0.5029 0.24006 0.116369 0.208533 Smoke 0.74
130 0.350402 0.278989 0.122529 0.179202 Smoke 0.7
132 0.525031 0.469289 0.103297 0.193989 Smoke 0.48
134 0.475175 0.52287 0.099367 0.178105 Smoke 0.27
136 0.042595 0.458038 0.073769 0.201478 Smoke 0.72
138 0.398975 0.280201 0.132485 0.076752 Smoke 0.3
149 0.115298 0.585385 0.037456 0.030006 Trenches 0.64
149 0.149621 0.602923 0.039252 0.032263 Trenches 0.73
149 0.534584 0.082995 0.03861 0.036615 Trenches 0.51
151 0.363107 0.353294 0.036212 0.030509 Trenches 0.52
151 0.395121 0.377565 0.03571 0.034257 Trenches 0.52
153 0.540678 0.549795 0.04493 0.040794 Trenches 0.55
155 0.124097 0.466852 0.074959 0.051802 Trenches 0.32
163 0.687639 0.261656 0.116238 0.125195 TyreTracks 0.43
168 0.378062 0.769232 0.032526 0.027289 Trenches 0.46
174 0.435846 0.904734 0.03305 0.03245 Trenches 0.27
176 0.942677 0.589411 0.024974 0.052446 Trenches 0.33
181 0.612998 0.232146 0.08565 0.060314 Smoke 0.52
191 0.390291 0.036179 0.054409 0.033613 Trenches 0.39
193 0.413883 0.208317 0.053158 0.031181 Trenches 0.76
193 0.430828 0.171384 0.030674 0.029638 Trenches 0.27
193 0.484856 0.182248 0.04184 0.063353 Trenches 0.65
193 0.491091 0.316695 0.031371 0.024474 Trenches 0.37
195 0.556304 0.101112 0.046918 0.036026 Smoke 0.71
197 0.519331 0.120967 0.044832 0.039692 Smoke 0.67
199 0.176271 0.118214 0.047294 0.053538 Smoke 0.63
202 0.171963 0.138687 0.043301 0.04702 Smoke 0.46
214 0.413986 0.12009 0.050383 0.046863 Smoke 0.5
219 0.364435 0.552301 0.028486 0.0314 ArmorVehicle 0.72
240 0.502753 0.535129 0.036657 0.033246 Trenches 0.32
242 0.019098 0.607661 0.038072 0.024671 Trenches 0.27
242 0.02406 0.680742 0.044204 0.023859 Trenches 0.37
242 0.027672 0.720891 0.033973 0.04163 Trenches 0.65
242 0.041207 0.628713 0.021846 0.044712 Trenches 0.29
242 0.053195 0.682519 0.027647 0.038862 Trenches 0.65
242 0.84993 0.631653 0.046269 0.042584 Trenches 0.47
244 0.017405 0.7017 0.034866 0.023287 Trenches 0.35
244 0.018653 0.733875 0.03717 0.025044 Trenches 0.48
244 0.032765 0.621231 0.064777 0.023041 Trenches 0.45
244 0.05303 0.734179 0.034922 0.041688 Trenches 0.7
244 0.070526 0.649712 0.023196 0.061101 Trenches 0.44
244 0.076041 0.69735 0.028326 0.039375 Trenches 0.65
246 0.025853 0.893559 0.051857 0.02183 Trenches 0.33
246 0.055409 0.908165 0.020511 0.040618 Trenches 0.32
252 0.735668 0.309579 0.026179 0.044193 Trenches 0.61
252 0.760952 0.371567 0.026674 0.062061 Trenches 0.52
282 0.566367 0.010329 0.036472 0.020469 Smoke 0.49
284 0.341962 0.011659 0.03561 0.023243 Smoke 0.59
287 0.423856 0.023553 0.03954 0.044283 Smoke 0.42
290 0.397077 0.010032 0.037649 0.019987 Smoke 0.29
292 0.415613 0.011793 0.036875 0.023537 Smoke 0.37
294 0.385953 0.009416 0.037843 0.018655 Smoke 0.39
@@ -1,5 +1 @@
time_sec,center_x,center_y,width,height,label,confidence_min time_sec,center_x,center_y,width,height,label,confidence_min
0,0.289857,0.512138,0.07729,0.072891,ArmorVehicle,0.89
2,0.653617,0.376064,0.078636,0.08118,ArmorVehicle,0.89
4,0.197561,0.416208,0.068112,0.084079,ArmorVehicle,0.85
6,0.27662,0.609538,0.059521,0.067212,ArmorVehicle,0.8
1 time_sec center_x center_y width height label confidence_min
0 0.289857 0.512138 0.07729 0.072891 ArmorVehicle 0.89
2 0.653617 0.376064 0.078636 0.08118 ArmorVehicle 0.89
4 0.197561 0.416208 0.068112 0.084079 ArmorVehicle 0.85
6 0.27662 0.609538 0.059521 0.067212 ArmorVehicle 0.8
+2 -2
View File
@@ -20,7 +20,7 @@ Data class holding all AI recognition configuration parameters, with factory met
| `tracking_intersection_threshold` | double | 0.6 | IoU threshold for overlapping detection removal | | `tracking_intersection_threshold` | double | 0.6 | IoU threshold for overlapping detection removal |
| `model_batch_size` | int | 1 | Batch size for inference | | `model_batch_size` | int | 1 | Batch size for inference |
| `big_image_tile_overlap_percent` | int | 20 | Tile overlap percentage for large image splitting | | `big_image_tile_overlap_percent` | int | 20 | Tile overlap percentage for large image splitting |
| `altitude` | double? | optional | Camera altitude in meters. When missing, GSD-based filtering is disabled | | `altitude` | double | 400 | Camera altitude in meters |
| `focal_length` | double | 24 | Camera focal length in mm | | `focal_length` | double | 24 | Camera focal length in mm |
| `sensor_width` | double | 23.5 | Camera sensor width in mm | | `sensor_width` | double | 23.5 | Camera sensor width in mm |
@@ -51,7 +51,7 @@ Data class holding all AI recognition configuration parameters, with factory met
## Configuration ## Configuration
Camera/altitude parameters (`altitude`, `focal_length`, `sensor_width`) are used for ground sampling distance calculation in aerial image processing. If `altitude` is missing, the service skips GSD-based size filtering and does not tile large images by physical size. Camera/altitude parameters (`altitude`, `focal_length`, `sensor_width`) are used for ground sampling distance calculation in aerial image processing.
## External Integrations ## External Integrations
+6
View File
@@ -8,9 +8,12 @@
| classes-json | `classes.json` (repo root) | 19 detection classes with Id, Name, Color, MaxSizeM | All tests | Volume mount to detections `/app/classes.json` | Container restart | | classes-json | `classes.json` (repo root) | 19 detection classes with Id, Name, Color, MaxSizeM | All tests | Volume mount to detections `/app/classes.json` | Container restart |
| image-small | `input_data/image_small.jpg` | JPEG 1280×720 — below tiling threshold (1920×1920) | FT-P-01..03, 05, 07, 13..15, FT-N-03, 06, NFT-PERF-01..02, NFT-RES-01, 03, NFT-SEC-01, NFT-RES-LIM-01 | Volume mount to consumer `/media/` | N/A (read-only) | | image-small | `input_data/image_small.jpg` | JPEG 1280×720 — below tiling threshold (1920×1920) | FT-P-01..03, 05, 07, 13..15, FT-N-03, 06, NFT-PERF-01..02, NFT-RES-01, 03, NFT-SEC-01, NFT-RES-LIM-01 | Volume mount to consumer `/media/` | N/A (read-only) |
| image-large | `input_data/image_large.JPG` | JPEG 6252×4168 — above tiling threshold, triggers GSD tiling | FT-P-04, 16, NFT-PERF-03 | Volume mount to consumer `/media/` | N/A (read-only) | | image-large | `input_data/image_large.JPG` | JPEG 6252×4168 — above tiling threshold, triggers GSD tiling | FT-P-04, 16, NFT-PERF-03 | Volume mount to consumer `/media/` | N/A (read-only) |
| image-dense-01 | `input_data/image_dense01.jpg` | JPEG 1280×720 — dense scene with many clustered objects | FT-P-06, NFT-RES-LIM-03 | Volume mount to consumer `/media/` | N/A (read-only) |
| image-dense-02 | `input_data/image_dense02.jpg` | JPEG 1920×1080 — dense scene variant, borderline tiling | FT-P-06 (variant) | Volume mount to consumer `/media/` | N/A (read-only) |
| image-different-types | `input_data/image_different_types.jpg` | JPEG 900×1600 — varied object classes for class variant tests | FT-P-13 | Volume mount to consumer `/media/` | N/A (read-only) | | image-different-types | `input_data/image_different_types.jpg` | JPEG 900×1600 — varied object classes for class variant tests | FT-P-13 | Volume mount to consumer `/media/` | N/A (read-only) |
| image-empty-scene | `input_data/image_empty_scene.jpg` | JPEG 1920×1080 — clean scene with no detectable objects | Edge case (zero detections) | Volume mount to consumer `/media/` | N/A (read-only) | | image-empty-scene | `input_data/image_empty_scene.jpg` | JPEG 1920×1080 — clean scene with no detectable objects | Edge case (zero detections) | Volume mount to consumer `/media/` | N/A (read-only) |
| video-test-01 | `input_data/video_test01.mp4` | MP4 video — standard async/SSE/video detection tests | FT-P-08..12, FT-N-04, 07, NFT-PERF-04, NFT-RES-02, NFT-SEC-03 | Volume mount to consumer `/media/` | N/A (read-only) | | video-test-01 | `input_data/video_test01.mp4` | MP4 video — standard async/SSE/video detection tests | FT-P-08..12, FT-N-04, 07, NFT-PERF-04, NFT-RES-02, NFT-SEC-03 | Volume mount to consumer `/media/` | N/A (read-only) |
| video-1 | `input_data/video_1.mp4` | MP4 video — local variant for concurrent and resilience-style tests | NFT-RES-02 (variant), NFT-RES-04 | Volume mount to consumer `/media/` | N/A (read-only) |
| video-1-faststart | `input_data/video_1_faststart.mp4` | MP4 video — faststart/local streaming variant | Streaming compatibility checks | Volume mount to consumer `/media/` | N/A (read-only) | | video-1-faststart | `input_data/video_1_faststart.mp4` | MP4 video — faststart/local streaming variant | Streaming compatibility checks | Volume mount to consumer `/media/` | N/A (read-only) |
| empty-image | Generated at build time | Zero-byte file | FT-N-01 | Generated in e2e/fixtures/ | N/A | | empty-image | Generated at build time | Zero-byte file | FT-N-01 | Generated in e2e/fixtures/ | N/A |
| corrupt-image | Generated at build time | Random binary garbage (not valid image format) | FT-N-02 | Generated in e2e/fixtures/ | N/A | | corrupt-image | Generated at build time | Random binary garbage (not valid image format) | FT-N-02 | Generated in e2e/fixtures/ | N/A |
@@ -28,9 +31,12 @@ Each test run starts with fresh containers (`docker compose down -v && docker co
| azaion.onnx | `_docs/00_problem/input_data/azaion.onnx` | YOLO ONNX detection model | All detection tests | | azaion.onnx | `_docs/00_problem/input_data/azaion.onnx` | YOLO ONNX detection model | All detection tests |
| image_small.jpg | `_docs/00_problem/input_data/image_small.jpg` | 1280×720 aerial image | Single-frame detection, health, negative, perf tests | | image_small.jpg | `_docs/00_problem/input_data/image_small.jpg` | 1280×720 aerial image | Single-frame detection, health, negative, perf tests |
| image_large.JPG | `_docs/00_problem/input_data/image_large.JPG` | 6252×4168 aerial image | Tiling tests | | image_large.JPG | `_docs/00_problem/input_data/image_large.JPG` | 6252×4168 aerial image | Tiling tests |
| image_dense01.jpg | `_docs/00_problem/input_data/image_dense01.jpg` | Dense scene 1280×720 | Dedup, detection cap tests |
| image_dense02.jpg | `_docs/00_problem/input_data/image_dense02.jpg` | Dense scene 1920×1080 | Dedup variant |
| image_different_types.jpg | `_docs/00_problem/input_data/image_different_types.jpg` | Varied classes 900×1600 | Class variant tests | | image_different_types.jpg | `_docs/00_problem/input_data/image_different_types.jpg` | Varied classes 900×1600 | Class variant tests |
| image_empty_scene.jpg | `_docs/00_problem/input_data/image_empty_scene.jpg` | Empty scene 1920×1080 | Zero-detection edge case | | image_empty_scene.jpg | `_docs/00_problem/input_data/image_empty_scene.jpg` | Empty scene 1920×1080 | Zero-detection edge case |
| video_test01.mp4 | `_docs/00_problem/input_data/video_test01.mp4` | Standard video | Async, SSE, video, perf tests | | video_test01.mp4 | `_docs/00_problem/input_data/video_test01.mp4` | Standard video | Async, SSE, video, perf tests |
| video_1.mp4 | `_docs/00_problem/input_data/video_1.mp4` | Video variant | Resilience, concurrent tests |
| video_1_faststart.mp4 | `_docs/00_problem/input_data/video_1_faststart.mp4` | Faststart video variant | Streaming compatibility checks | | video_1_faststart.mp4 | `_docs/00_problem/input_data/video_1_faststart.mp4` | Faststart video variant | Streaming compatibility checks |
| classes.json | repo root `classes.json` | 19 detection classes | All tests | | classes.json | repo root `classes.json` | 19 detection classes | All tests |
@@ -27,6 +27,8 @@ e2e/
├── fixtures/ ├── fixtures/
│ ├── image_small.jpg (1280×720 JPEG, aerial, detectable objects) │ ├── image_small.jpg (1280×720 JPEG, aerial, detectable objects)
│ ├── image_large.JPG (6252×4168 JPEG, triggers tiling) │ ├── image_large.JPG (6252×4168 JPEG, triggers tiling)
│ ├── image_dense01.jpg (1280×720 JPEG, dense scene, clustered objects)
│ ├── image_dense02.jpg (1920×1080 JPEG, dense scene variant)
│ ├── image_different_types.jpg (900×1600 JPEG, varied object classes) │ ├── image_different_types.jpg (900×1600 JPEG, varied object classes)
│ ├── image_empty_scene.jpg (1920×1080 JPEG, no detectable objects) │ ├── image_empty_scene.jpg (1920×1080 JPEG, no detectable objects)
│ ├── video_short01.mp4 (short MP4 with moving objects) │ ├── video_short01.mp4 (short MP4 with moving objects)
@@ -128,6 +130,8 @@ Two Docker Compose profiles:
| `reset_mocks` | function (autouse) | Calls `POST /mock/reset` on both mocks before each test | | `reset_mocks` | function (autouse) | Calls `POST /mock/reset` on both mocks before each test |
| `image_small` | session | Reads `image_small.jpg` from `/media/` volume | | `image_small` | session | Reads `image_small.jpg` from `/media/` volume |
| `image_large` | session | Reads `image_large.JPG` from `/media/` volume | | `image_large` | session | Reads `image_large.JPG` from `/media/` volume |
| `image_dense` | session | Reads `image_dense01.jpg` from `/media/` volume |
| `image_dense_02` | session | Reads `image_dense02.jpg` from `/media/` volume |
| `image_different_types` | session | Reads `image_different_types.jpg` from `/media/` volume | | `image_different_types` | session | Reads `image_different_types.jpg` from `/media/` volume |
| `image_empty_scene` | session | Reads `image_empty_scene.jpg` from `/media/` volume | | `image_empty_scene` | session | Reads `image_empty_scene.jpg` from `/media/` volume |
| `video_short_path` | session | Path to `video_short01.mp4` on `/media/` volume | | `video_short_path` | session | Path to `video_short01.mp4` on `/media/` volume |
@@ -146,6 +150,8 @@ Two Docker Compose profiles:
| classes.json | repo root `classes.json` | JSON (19 objects with Id, Name, Color, MaxSizeM) | All tests (volume mount to detections) | | classes.json | repo root `classes.json` | JSON (19 objects with Id, Name, Color, MaxSizeM) | All tests (volume mount to detections) |
| image_small.jpg | `input_data/image_small.jpg` | JPEG 1280×720 | Health, single image, filtering, negative, performance tests | | image_small.jpg | `input_data/image_small.jpg` | JPEG 1280×720 | Health, single image, filtering, negative, performance tests |
| image_large.JPG | `input_data/image_large.JPG` | JPEG 6252×4168 | Tiling tests, performance tests | | image_large.JPG | `input_data/image_large.JPG` | JPEG 6252×4168 | Tiling tests, performance tests |
| image_dense01.jpg | `input_data/image_dense01.jpg` | JPEG 1280×720 dense scene | Dedup tests, detection cap tests |
| image_dense02.jpg | `input_data/image_dense02.jpg` | JPEG 1920×1080 dense scene | Dedup variant |
| image_different_types.jpg | `input_data/image_different_types.jpg` | JPEG 900×1600 varied classes | Weather mode class variant tests | | image_different_types.jpg | `input_data/image_different_types.jpg` | JPEG 900×1600 varied classes | Weather mode class variant tests |
| image_empty_scene.jpg | `input_data/image_empty_scene.jpg` | JPEG 1920×1080 empty | Zero-detection edge case | | image_empty_scene.jpg | `input_data/image_empty_scene.jpg` | JPEG 1920×1080 empty | Zero-detection edge case |
| video_short01.mp4 | `input_data/video_short01.mp4` | MP4 short video | Async, SSE, video processing tests | | video_short01.mp4 | `input_data/video_short01.mp4` | MP4 short video | Async, SSE, video processing tests |
-10
View File
@@ -30,13 +30,3 @@ step: 14 (Deploy) — DONE (deploy_status_report.md + deploy_scripts.md updated
## Rollback Note ## Rollback Note
2026-04-10: Rolled back from step 8 (New Task) to step 2 (Test Spec). 2026-04-10: Rolled back from step 8 (New Task) to step 2 (Test Spec).
Reason: All 9 expected result CSV files in _docs/00_problem/input_data/expected_results/ contain headers only — zero data rows. results_report.md has "?" for detection counts. Phase 1 and Phase 3 BLOCKING gates were not enforced. E2E tests cannot verify detection accuracy without ground truth data. Reason: All 9 expected result CSV files in _docs/00_problem/input_data/expected_results/ contain headers only — zero data rows. results_report.md has "?" for detection counts. Phase 1 and Phase 3 BLOCKING gates were not enforced. E2E tests cannot verify detection accuracy without ground truth data.
## Recovery Note
2026-04-23: Expected-result artifacts were populated for the active local fixture set.
- Image CSVs now exist for: `image_small`, `image_large`, `image_different_types`, `image_empty_scene`
- Video CSVs now exist for: `video_test01`, `video_1_faststart`
- `results_report.md` was updated to match the active fixture set and populated image detection counts
- Obsolete fixtures were removed from the active test-data set: `image_dense01`, `image_dense02`, `video_1`
Implication: the original expected-results blocker recorded in the rollback note no longer reflects the current repository state for the active fixture set. Resume Step 2 / Phase 3 validation from the current artifacts rather than assuming CSV ground truth is still missing.
+11
View File
@@ -228,6 +228,17 @@ def image_small():
def image_large(): def image_large():
return _read_media("image_large.JPG") return _read_media("image_large.JPG")
@pytest.fixture(scope="session")
def image_dense():
return _read_media("image_dense01.jpg")
@pytest.fixture(scope="session")
def image_dense_02():
return _read_media("image_dense02.jpg")
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def image_different_types(): def image_different_types():
return _read_media("image_different_types.jpg") return _read_media("image_different_types.jpg")
-25
View File
@@ -58,35 +58,10 @@ services:
aliases: aliases:
- detections - detections
detections-jetson:
profiles:
- jetson
build:
context: ..
dockerfile: Dockerfile.jetson
runtime: nvidia
depends_on:
- mock-loader
- mock-annotations
environment:
LOADER_URL: http://mock-loader:8080
ANNOTATIONS_URL: http://mock-annotations:8081
JWT_SECRET: test-secret-e2e-only
CLASSES_JSON_PATH: /app/classes.json
volumes:
- ./fixtures:/media:ro
- ./logs:/app/Logs
shm_size: 512m
networks:
e2e-net:
aliases:
- detections
e2e-runner: e2e-runner:
profiles: profiles:
- cpu - cpu
- gpu - gpu
- jetson
build: . build: .
depends_on: depends_on:
- mock-loader - mock-loader
-1
View File
@@ -1,7 +1,6 @@
pytest pytest
pytest-csv pytest-csv
requests==2.32.4 requests==2.32.4
PyJWT==2.12.1
sseclient-py sseclient-py
pytest-timeout pytest-timeout
flask flask
+5 -32
View File
@@ -1,24 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
PROFILE="${E2E_PROFILE:-cpu}" COMPOSE="docker compose -f docker-compose.test.yml --profile cpu"
case "$PROFILE" in
cpu)
DETECTIONS_SERVICE="detections"
;;
gpu)
DETECTIONS_SERVICE="detections-gpu"
;;
jetson)
DETECTIONS_SERVICE="detections-jetson"
;;
*)
echo "ERROR: unsupported E2E_PROFILE=$PROFILE (expected: cpu, gpu, jetson)"
exit 2
;;
esac
COMPOSE="docker compose -f docker-compose.test.yml --profile $PROFILE"
usage() { usage() {
echo "Usage: $0 <test_path> [pytest_args...]" echo "Usage: $0 <test_path> [pytest_args...]"
@@ -28,37 +11,27 @@ usage() {
echo " $0 tests/test_video.py::test_ft_p_10_frame_sampling_ac1 # run single test" echo " $0 tests/test_video.py::test_ft_p_10_frame_sampling_ac1 # run single test"
echo " $0 tests/test_video.py -k 'frame_sampling' # run by keyword" echo " $0 tests/test_video.py -k 'frame_sampling' # run by keyword"
echo "" echo ""
echo "Environment:"
echo " E2E_PROFILE=cpu|gpu|jetson compose profile to run (default: cpu)"
echo ""
echo "Flags -v -x -s are always included." echo "Flags -v -x -s are always included."
exit 1 exit 1
} }
[[ $# -lt 1 ]] && usage [[ $# -lt 1 ]] && usage
$COMPOSE up -d --build "$DETECTIONS_SERVICE" $COMPOSE up -d --build detections
echo "--- Waiting for detections service to become healthy..." echo "--- Waiting for detections service to become healthy..."
for i in $(seq 1 60); do for i in $(seq 1 60); do
if $COMPOSE exec -T "$DETECTIONS_SERVICE" python3 -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/health')" 2>/dev/null; then if $COMPOSE exec -T detections python3 -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/health')" 2>/dev/null; then
echo "--- Detections service is healthy" echo "--- Detections service is healthy"
break break
fi fi
if [[ "$i" == "60" ]]; then
echo "ERROR: detections service did not become healthy"
$COMPOSE logs "$DETECTIONS_SERVICE" --tail 100
exit 1
fi
sleep 2 sleep 2
done done
echo "--- Running: pytest $* -v -x -s --csv=/results/report.csv" echo "--- Running: pytest $* -v -x -s --csv=/results/report.csv"
set +e $COMPOSE run --rm --no-deps e2e-runner pytest "$@" -v -x -s --csv=/results/report.csv
$COMPOSE run --rm --build --no-deps e2e-runner pytest "$@" -v -x -s --csv=/results/report.csv
EXIT_CODE=$? EXIT_CODE=$?
set -e
echo "--- Test finished with exit code $EXIT_CODE" echo "--- Test finished with exit code $EXIT_CODE"
echo "--- Detections logs (last 100 lines):" echo "--- Detections logs (last 100 lines):"
$COMPOSE logs "$DETECTIONS_SERVICE" --tail 100 $COMPOSE logs detections --tail 100
exit $EXIT_CODE exit $EXIT_CODE
-1
View File
@@ -12,7 +12,6 @@ cdef class AIRecognitionConfig:
cdef public int model_batch_size cdef public int model_batch_size
cdef public bint has_altitude
cdef public double altitude cdef public double altitude
cdef public double focal_length cdef public double focal_length
cdef public double sensor_width cdef public double sensor_width
+3 -4
View File
@@ -25,8 +25,7 @@ cdef class AIRecognitionConfig:
self.big_image_tile_overlap_percent = big_image_tile_overlap_percent self.big_image_tile_overlap_percent = big_image_tile_overlap_percent
self.has_altitude = altitude is not None self.altitude = altitude
self.altitude = 0.0 if altitude is None else float(altitude)
self.focal_length = focal_length self.focal_length = focal_length
self.sensor_width = sensor_width self.sensor_width = sensor_width
@@ -37,7 +36,7 @@ cdef class AIRecognitionConfig:
f'frame_period_recognition : {self.frame_period_recognition}, ' f'frame_period_recognition : {self.frame_period_recognition}, '
f'big_image_tile_overlap_percent: {self.big_image_tile_overlap_percent}, ' f'big_image_tile_overlap_percent: {self.big_image_tile_overlap_percent}, '
f'model_batch_size: {self.model_batch_size}, ' f'model_batch_size: {self.model_batch_size}, '
f'altitude: {self.altitude if self.has_altitude else None}, ' f'altitude: {self.altitude}, '
f'focal_length: {self.focal_length}, ' f'focal_length: {self.focal_length}, '
f'sensor_width: {self.sensor_width}' f'sensor_width: {self.sensor_width}'
) )
@@ -57,7 +56,7 @@ cdef class AIRecognitionConfig:
data.get("big_image_tile_overlap_percent", 20), data.get("big_image_tile_overlap_percent", 20),
data.get("altitude", None), data.get("altitude", 400),
data.get("focal_length", 24), data.get("focal_length", 24),
data.get("sensor_width", 23.5) data.get("sensor_width", 23.5)
) )
+1 -1
View File
@@ -7,6 +7,6 @@ cdef class Detection:
cdef class Annotation: cdef class Annotation:
cdef public str name cdef public str name
cdef public str original_media_name cdef public str original_media_name
cdef public long time cdef long time
cdef public list[Detection] detections cdef public list[Detection] detections
cdef public bytes image cdef public bytes image
-15
View File
@@ -311,22 +311,13 @@ cdef class Inference:
cdef double ground_sampling_distance cdef double ground_sampling_distance
cdef int model_h, model_w cdef int model_h, model_w
cdef int img_h, img_w cdef int img_h, img_w
cdef bint has_gsd
model_h, model_w = self.engine.get_input_shape() model_h, model_w = self.engine.get_input_shape()
img_h, img_w, _ = frame.shape img_h, img_w, _ = frame.shape
has_gsd = ai_config.has_altitude and ai_config.focal_length > 0 and ai_config.sensor_width > 0 and img_w > 0
ground_sampling_distance = 0.0
if has_gsd:
ground_sampling_distance = ai_config.sensor_width * ai_config.altitude / (ai_config.focal_length * img_w) ground_sampling_distance = ai_config.sensor_width * ai_config.altitude / (ai_config.focal_length * img_w)
constants_inf.log(<str>f'ground sampling distance: {ground_sampling_distance}') constants_inf.log(<str>f'ground sampling distance: {ground_sampling_distance}')
else:
constants_inf.log(<str>'ground sampling distance: skipped (altitude unavailable)')
if img_h <= 1.5 * model_h and img_w <= 1.5 * model_w: if img_h <= 1.5 * model_h and img_w <= 1.5 * model_w:
all_frame_data.append((frame, original_media_name, f'{original_media_name}_000000', ground_sampling_distance)) all_frame_data.append((frame, original_media_name, f'{original_media_name}_000000', ground_sampling_distance))
else: else:
if not has_gsd:
all_frame_data.append((frame, original_media_name, f'{original_media_name}_000000', ground_sampling_distance))
return
tile_size = int(constants_inf.METERS_IN_TILE / ground_sampling_distance) tile_size = int(constants_inf.METERS_IN_TILE / ground_sampling_distance)
constants_inf.log(<str> f'calc tile size: {tile_size}') constants_inf.log(<str> f'calc tile size: {tile_size}')
res = self.split_to_tiles(frame, original_media_name, tile_size, ai_config.big_image_tile_overlap_percent) res = self.split_to_tiles(frame, original_media_name, tile_size, ai_config.big_image_tile_overlap_percent)
@@ -419,12 +410,6 @@ cdef class Inference:
if annotation.detections: if annotation.detections:
constants_inf.log(<str> f'Initial ann: {annotation}') constants_inf.log(<str> f'Initial ann: {annotation}')
if ground_sampling_distance <= 0:
if not annotation.detections:
return <bint>False
constants_inf.log(<str>'Skipping physical-size filtering (ground sampling distance unavailable)')
return <bint>True
cdef list[Detection] valid_detections = [] cdef list[Detection] valid_detections = []
for det in annotation.detections: for det in annotation.detections:
m_w = det.w * img_w * ground_sampling_distance m_w = det.w * img_w * ground_sampling_distance
+6 -14
View File
@@ -15,7 +15,6 @@ import cv2
import jwt as pyjwt import jwt as pyjwt
import numpy as np import numpy as np
import requests as http_requests import requests as http_requests
from loguru import logger
from fastapi import Body, Depends, FastAPI, File, Form, HTTPException, Request, UploadFile from fastapi import Body, Depends, FastAPI, File, Form, HTTPException, Request, UploadFile
from fastapi.responses import Response, StreamingResponse from fastapi.responses import Response, StreamingResponse
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
@@ -164,7 +163,7 @@ class AIConfigDto(BaseModel):
tracking_intersection_threshold: float = 0.6 tracking_intersection_threshold: float = 0.6
model_batch_size: int = 8 model_batch_size: int = 8
big_image_tile_overlap_percent: int = 20 big_image_tile_overlap_percent: int = 20
altitude: Optional[float] = None altitude: float = 400
focal_length: float = 24 focal_length: float = 24
sensor_width: float = 23.5 sensor_width: float = 23.5
@@ -271,8 +270,7 @@ def _post_media_record(payload: dict, bearer: str) -> bool:
timeout=30, timeout=30,
) )
return r.status_code in (200, 201) return r.status_code in (200, 201)
except Exception as exc: except Exception:
logger.warning(f"Failed to create media record in annotations service: {exc}")
return False return False
@@ -286,8 +284,7 @@ def _put_media_status(media_id: str, media_status: int, bearer: str) -> bool:
timeout=30, timeout=30,
) )
return r.status_code in (200, 204) return r.status_code in (200, 204)
except Exception as exc: except Exception:
logger.warning(f"Failed to update media status in annotations service for {media_id}: {exc}")
return False return False
@@ -335,13 +332,10 @@ def _post_annotation_to_service(token_mgr: TokenManager, media_id: str,
try: try:
token = token_mgr.get_valid_token() token = token_mgr.get_valid_token()
image_b64 = base64.b64encode(annotation.image).decode() if annotation.image else None image_b64 = base64.b64encode(annotation.image).decode() if annotation.image else None
total_seconds = int(annotation.time // 1000) if annotation.time else 0
hours, remainder = divmod(total_seconds, 3600)
minutes, seconds = divmod(remainder, 60)
payload = { payload = {
"mediaId": media_id, "mediaId": media_id,
"source": 0, "source": 0,
"videoTime": f"{hours:02d}:{minutes:02d}:{seconds:02d}", "videoTime": f"00:00:{annotation.time // 1000:02d}" if annotation.time else "00:00:00",
"detections": [d.model_dump() for d in dtos], "detections": [d.model_dump() for d in dtos],
} }
if image_b64: if image_b64:
@@ -352,10 +346,8 @@ def _post_annotation_to_service(token_mgr: TokenManager, media_id: str,
headers={"Authorization": f"Bearer {token}"}, headers={"Authorization": f"Bearer {token}"},
timeout=30, timeout=30,
) )
except Exception as exc: except Exception:
logger.warning( pass
f"Failed to post annotation to annotations service for media {media_id}: {exc}"
)
def _cleanup_channel(channel_id: str): def _cleanup_channel(channel_id: str):
-9
View File
@@ -5,15 +5,6 @@ def test_ai_config_from_dict_defaults():
assert cfg.model_batch_size == 8 assert cfg.model_batch_size == 8
assert cfg.frame_period_recognition == 4 assert cfg.frame_period_recognition == 4
assert cfg.frame_recognition_seconds == 2 assert cfg.frame_recognition_seconds == 2
assert cfg.has_altitude is False
def test_ai_config_from_dict_altitude_override_sets_flag():
from inference import ai_config_from_dict
cfg = ai_config_from_dict({"altitude": 400})
assert cfg.has_altitude is True
assert cfg.altitude == 400
def test_ai_config_from_dict_overrides(): def test_ai_config_from_dict_overrides():
-17
View File
@@ -118,23 +118,6 @@ def test_resolve_media_for_detect_override_wins():
assert "paths" not in cfg assert "paths" not in cfg
def test_resolve_media_for_detect_omits_altitude_when_not_provided():
# Arrange
import main
tm = main.TokenManager(_access_jwt(), "")
mock_ann = MagicMock()
mock_ann.fetch_user_ai_settings.return_value = {"probabilityThreshold": 0.2}
mock_ann.fetch_media_path.return_value = "/m/v.mp4"
with patch("main.annotations_client", mock_ann):
# Act
cfg, path = main._resolve_media_for_detect("vid-2", tm, None)
# Assert
assert "altitude" not in cfg
assert cfg["probability_threshold"] == 0.2
assert path == "/m/v.mp4"
def test_resolve_media_for_detect_raises_when_no_media_path(): def test_resolve_media_for_detect_raises_when_no_media_path():
# Arrange # Arrange
import main import main