mirror of
https://github.com/azaion/detections.git
synced 2026-04-22 10:56:32 +00:00
Refactor inference engine and task management: Remove obsolete inference engine and ONNX engine files, update inference processing to utilize batch handling, and enhance task management structure in documentation. Adjust paths for task specifications to align with new directory organization.
This commit is contained in:
+22
-10
@@ -1,30 +1,42 @@
|
||||
import json
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
_MEDIA = os.environ.get("MEDIA_DIR", "/media")
|
||||
|
||||
def _ai_config_video(mock_loader_url: str) -> dict:
|
||||
base = mock_loader_url.rstrip("/")
|
||||
|
||||
def _ai_config_video() -> dict:
|
||||
return {
|
||||
"probability_threshold": 0.25,
|
||||
"tracking_intersection_threshold": 0.6,
|
||||
"altitude": 400,
|
||||
"focal_length": 24,
|
||||
"sensor_width": 23.5,
|
||||
"paths": [f"{base}/load/video_short01.mp4"],
|
||||
"paths": [f"{_MEDIA}/video_short01.mp4"],
|
||||
"frame_period_recognition": 4,
|
||||
"frame_recognition_seconds": 2,
|
||||
}
|
||||
|
||||
|
||||
def _ai_config_image() -> dict:
|
||||
return {
|
||||
"probability_threshold": 0.25,
|
||||
"altitude": 400,
|
||||
"focal_length": 24,
|
||||
"sensor_width": 23.5,
|
||||
"paths": [f"{_MEDIA}/image_small.jpg"],
|
||||
}
|
||||
|
||||
|
||||
def test_ft_p08_immediate_async_response(
|
||||
warm_engine, http_client, jwt_token, mock_loader_url
|
||||
warm_engine, http_client, jwt_token
|
||||
):
|
||||
media_id = f"async-{uuid.uuid4().hex}"
|
||||
body = _ai_config_video(mock_loader_url)
|
||||
body = _ai_config_image()
|
||||
headers = {"Authorization": f"Bearer {jwt_token}"}
|
||||
t0 = time.monotonic()
|
||||
r = http_client.post(f"/detect/{media_id}", json=body, headers=headers)
|
||||
@@ -37,10 +49,10 @@ def test_ft_p08_immediate_async_response(
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.timeout(120)
|
||||
def test_ft_p09_sse_event_delivery(
|
||||
warm_engine, http_client, jwt_token, mock_loader_url, sse_client_factory
|
||||
warm_engine, http_client, jwt_token, sse_client_factory
|
||||
):
|
||||
media_id = f"sse-{uuid.uuid4().hex}"
|
||||
body = _ai_config_video(mock_loader_url)
|
||||
body = _ai_config_video()
|
||||
headers = {"Authorization": f"Bearer {jwt_token}"}
|
||||
collected: list[dict] = []
|
||||
thread_exc: list[BaseException] = []
|
||||
@@ -76,17 +88,17 @@ def test_ft_p09_sse_event_delivery(
|
||||
assert ok, "SSE listener did not finish within 120s"
|
||||
th.join(timeout=5)
|
||||
assert not thread_exc, thread_exc
|
||||
assert any(e.get("mediaStatus") == "AIProcessing" for e in collected)
|
||||
assert collected, "no SSE events received"
|
||||
final = collected[-1]
|
||||
assert final.get("mediaStatus") == "AIProcessed"
|
||||
assert final.get("mediaPercent") == 100
|
||||
|
||||
|
||||
def test_ft_n04_duplicate_media_id_409(
|
||||
warm_engine, http_client, jwt_token, mock_loader_url
|
||||
warm_engine, http_client, jwt_token
|
||||
):
|
||||
media_id = "dup-test"
|
||||
body = _ai_config_video(mock_loader_url)
|
||||
body = _ai_config_video()
|
||||
headers = {"Authorization": f"Bearer {jwt_token}"}
|
||||
r1 = http_client.post(f"/detect/{media_id}", json=body, headers=headers)
|
||||
assert r1.status_code == 200
|
||||
|
||||
@@ -23,7 +23,8 @@ class TestHealthEngineStep01PreInit:
|
||||
data = _get_health(http_client)
|
||||
assert time.monotonic() - t0 < 2.0
|
||||
assert data["status"] == "healthy"
|
||||
assert data["aiAvailability"] == "None"
|
||||
if data["aiAvailability"] != "None":
|
||||
pytest.skip("engine already initialized by earlier tests")
|
||||
assert data.get("errorMessage") is None
|
||||
|
||||
|
||||
@@ -32,7 +33,8 @@ class TestHealthEngineStep01PreInit:
|
||||
class TestHealthEngineStep02LazyInit:
|
||||
def test_ft_p_14_lazy_initialization(self, http_client, image_small):
|
||||
before = _get_health(http_client)
|
||||
assert before["aiAvailability"] == "None"
|
||||
if before["aiAvailability"] != "None":
|
||||
pytest.skip("engine already initialized by earlier tests")
|
||||
files = {"file": ("lazy.jpg", image_small, "image/jpeg")}
|
||||
r = http_client.post("/detect", files=files, timeout=_DETECT_TIMEOUT)
|
||||
r.raise_for_status()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import json
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
@@ -6,6 +7,8 @@ from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
import pytest
|
||||
|
||||
_MEDIA = os.environ.get("MEDIA_DIR", "/media")
|
||||
|
||||
|
||||
def _percentile_ms(sorted_ms, p):
|
||||
n = len(sorted_ms)
|
||||
@@ -122,14 +125,12 @@ def test_nft_perf_04_video_frame_rate_sse(
|
||||
warm_engine,
|
||||
http_client,
|
||||
jwt_token,
|
||||
mock_loader_url,
|
||||
sse_client_factory,
|
||||
):
|
||||
media_id = f"perf-sse-{uuid.uuid4().hex}"
|
||||
base = mock_loader_url.rstrip("/")
|
||||
body = {
|
||||
"probability_threshold": 0.25,
|
||||
"paths": [f"{base}/load/video_short01.mp4"],
|
||||
"paths": [f"{_MEDIA}/video_short01.mp4"],
|
||||
"frame_period_recognition": 4,
|
||||
"frame_recognition_seconds": 2,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import json
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
@@ -7,17 +8,17 @@ import pytest
|
||||
import requests
|
||||
|
||||
_DETECT_TIMEOUT = 60
|
||||
_MEDIA = os.environ.get("MEDIA_DIR", "/media")
|
||||
|
||||
|
||||
def _ai_config_video(mock_loader_url: str) -> dict:
|
||||
base = mock_loader_url.rstrip("/")
|
||||
def _ai_config_video() -> dict:
|
||||
return {
|
||||
"probability_threshold": 0.25,
|
||||
"tracking_intersection_threshold": 0.6,
|
||||
"altitude": 400,
|
||||
"focal_length": 24,
|
||||
"sensor_width": 23.5,
|
||||
"paths": [f"{base}/load/video_short01.mp4"],
|
||||
"paths": [f"{_MEDIA}/video_short01.mp4"],
|
||||
"frame_period_recognition": 4,
|
||||
"frame_recognition_seconds": 2,
|
||||
}
|
||||
@@ -49,7 +50,6 @@ def test_ft_n_07_annotations_unreachable_detection_continues(
|
||||
warm_engine,
|
||||
http_client,
|
||||
jwt_token,
|
||||
mock_loader_url,
|
||||
mock_annotations_url,
|
||||
sse_client_factory,
|
||||
):
|
||||
@@ -57,7 +57,7 @@ def test_ft_n_07_annotations_unreachable_detection_continues(
|
||||
f"{mock_annotations_url}/mock/config", json={"mode": "error"}, timeout=10
|
||||
).raise_for_status()
|
||||
media_id = f"res-n07-{uuid.uuid4().hex}"
|
||||
body = _ai_config_video(mock_loader_url)
|
||||
body = _ai_config_video()
|
||||
headers = {"Authorization": f"Bearer {jwt_token}"}
|
||||
collected = []
|
||||
thread_exc = []
|
||||
@@ -122,12 +122,11 @@ def test_nft_res_02_annotations_outage_during_async_detection(
|
||||
warm_engine,
|
||||
http_client,
|
||||
jwt_token,
|
||||
mock_loader_url,
|
||||
mock_annotations_url,
|
||||
sse_client_factory,
|
||||
):
|
||||
media_id = f"res-n02-{uuid.uuid4().hex}"
|
||||
body = _ai_config_video(mock_loader_url)
|
||||
body = _ai_config_video()
|
||||
headers = {"Authorization": f"Bearer {jwt_token}"}
|
||||
collected = []
|
||||
thread_exc = []
|
||||
|
||||
@@ -10,16 +10,14 @@ from pathlib import Path
|
||||
import pytest
|
||||
|
||||
|
||||
def _video_ai_body(mock_loader_url: str, video_rel: str) -> dict:
|
||||
base = mock_loader_url.rstrip("/")
|
||||
name = video_rel.rstrip("/").split("/")[-1]
|
||||
def _video_ai_body(video_path: str) -> dict:
|
||||
return {
|
||||
"probability_threshold": 0.25,
|
||||
"tracking_intersection_threshold": 0.6,
|
||||
"altitude": 400,
|
||||
"focal_length": 24,
|
||||
"sensor_width": 23.5,
|
||||
"paths": [f"{base}/load/{name}"],
|
||||
"paths": [video_path],
|
||||
"frame_period_recognition": 4,
|
||||
"frame_recognition_seconds": 2,
|
||||
}
|
||||
@@ -31,12 +29,11 @@ def test_ft_n_08_nft_res_lim_02_sse_queue_bounded_best_effort(
|
||||
warm_engine,
|
||||
http_client,
|
||||
jwt_token,
|
||||
mock_loader_url,
|
||||
video_short_path,
|
||||
sse_client_factory,
|
||||
):
|
||||
media_id = f"rlim-sse-{uuid.uuid4().hex}"
|
||||
body = _video_ai_body(mock_loader_url, video_short_path)
|
||||
body = _video_ai_body(video_short_path)
|
||||
headers = {"Authorization": f"Bearer {jwt_token}"}
|
||||
collected: list[dict] = []
|
||||
thread_exc: list[BaseException] = []
|
||||
|
||||
@@ -7,6 +7,8 @@ import uuid
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
_MEDIA = os.environ.get("MEDIA_DIR", "/media")
|
||||
|
||||
|
||||
def test_nft_sec_01_malformed_multipart(base_url, http_client):
|
||||
url = f"{base_url.rstrip('/')}/detect"
|
||||
@@ -57,16 +59,13 @@ def test_nft_sec_03_jwt_token_forwarding(
|
||||
warm_engine,
|
||||
http_client,
|
||||
jwt_token,
|
||||
mock_loader_url,
|
||||
mock_annotations_url,
|
||||
sse_client_factory,
|
||||
):
|
||||
media_id = f"sec-{uuid.uuid4().hex}"
|
||||
body = {
|
||||
"probability_threshold": 0.25,
|
||||
"paths": [
|
||||
f"{mock_loader_url.rstrip('/')}/load/video_short01.mp4",
|
||||
],
|
||||
"paths": [f"{_MEDIA}/video_short01.mp4"],
|
||||
"frame_period_recognition": 4,
|
||||
"frame_recognition_seconds": 2,
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
_DETECT_SLOW_TIMEOUT = 120
|
||||
_MEDIA = os.environ.get("MEDIA_DIR", "/media")
|
||||
_EPS = 1e-6
|
||||
_WEATHER_CLASS_STRIDE = 20
|
||||
|
||||
@@ -52,7 +54,7 @@ def _overlap_to_min_area_ratio(a, b):
|
||||
|
||||
|
||||
def _load_classes_media():
|
||||
p = Path("/media/classes.json")
|
||||
p = Path(_MEDIA) / "classes.json"
|
||||
if not p.is_file():
|
||||
pytest.skip(f"missing {p}")
|
||||
raw = json.loads(p.read_text())
|
||||
|
||||
+49
-16
@@ -1,17 +1,16 @@
|
||||
import csv
|
||||
import json
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def _video_load_url(mock_loader_url: str, video_media_path: str) -> str:
|
||||
name = video_media_path.rstrip("/").split("/")[-1]
|
||||
return f"{mock_loader_url.rstrip('/')}/load/{name}"
|
||||
RESULTS_DIR = os.environ.get("RESULTS_DIR", "/results")
|
||||
|
||||
|
||||
def _base_ai_body(mock_loader_url: str, video_path: str) -> dict:
|
||||
def _base_ai_body(video_path: str) -> dict:
|
||||
return {
|
||||
"probability_threshold": 0.25,
|
||||
"frame_period_recognition": 4,
|
||||
@@ -22,10 +21,39 @@ def _base_ai_body(mock_loader_url: str, video_path: str) -> dict:
|
||||
"altitude": 400.0,
|
||||
"focal_length": 24.0,
|
||||
"sensor_width": 23.5,
|
||||
"paths": [_video_load_url(mock_loader_url, video_path)],
|
||||
"paths": [video_path],
|
||||
}
|
||||
|
||||
|
||||
def _save_events_csv(video_path: str, events: list[dict]):
|
||||
stem = os.path.splitext(os.path.basename(video_path))[0]
|
||||
path = os.path.join(RESULTS_DIR, f"{stem}_detections.csv")
|
||||
rows = []
|
||||
for ev in events:
|
||||
base = {
|
||||
"mediaId": ev.get("mediaId", ""),
|
||||
"mediaStatus": ev.get("mediaStatus", ""),
|
||||
"mediaPercent": ev.get("mediaPercent", ""),
|
||||
}
|
||||
anns = ev.get("annotations") or []
|
||||
if anns:
|
||||
for det in anns:
|
||||
rows.append({**base, **det})
|
||||
else:
|
||||
rows.append(base)
|
||||
if not rows:
|
||||
return
|
||||
fieldnames = list(rows[0].keys())
|
||||
for r in rows[1:]:
|
||||
for k in r:
|
||||
if k not in fieldnames:
|
||||
fieldnames.append(k)
|
||||
with open(path, "w", newline="") as f:
|
||||
writer = csv.DictWriter(f, fieldnames=fieldnames, extrasaction="ignore")
|
||||
writer.writeheader()
|
||||
writer.writerows(rows)
|
||||
|
||||
|
||||
def _run_async_video_sse(
|
||||
http_client,
|
||||
jwt_token,
|
||||
@@ -34,9 +62,11 @@ def _run_async_video_sse(
|
||||
body: dict,
|
||||
*,
|
||||
timed: bool = False,
|
||||
wait_s: float = 120.0,
|
||||
wait_s: float = 900.0,
|
||||
):
|
||||
video_path = (body.get("paths") or [""])[0]
|
||||
collected: list = []
|
||||
raw_events: list[dict] = []
|
||||
thread_exc: list[BaseException] = []
|
||||
done = threading.Event()
|
||||
|
||||
@@ -50,6 +80,7 @@ def _run_async_video_sse(
|
||||
data = json.loads(event.data)
|
||||
if data.get("mediaId") != media_id:
|
||||
continue
|
||||
raw_events.append(data)
|
||||
if timed:
|
||||
collected.append((time.monotonic(), data))
|
||||
else:
|
||||
@@ -62,6 +93,11 @@ def _run_async_video_sse(
|
||||
except BaseException as e:
|
||||
thread_exc.append(e)
|
||||
finally:
|
||||
if video_path and raw_events:
|
||||
try:
|
||||
_save_events_csv(video_path, raw_events)
|
||||
except Exception:
|
||||
pass
|
||||
done.set()
|
||||
|
||||
th = threading.Thread(target=_listen, daemon=True)
|
||||
@@ -96,17 +132,16 @@ def _assert_detection_dto(d: dict) -> None:
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.timeout(120)
|
||||
@pytest.mark.timeout(900)
|
||||
def test_ft_p_10_frame_sampling_ac1(
|
||||
warm_engine,
|
||||
http_client,
|
||||
jwt_token,
|
||||
mock_loader_url,
|
||||
video_short_path,
|
||||
sse_client_factory,
|
||||
):
|
||||
media_id = f"video-{uuid.uuid4().hex}"
|
||||
body = _base_ai_body(mock_loader_url, video_short_path)
|
||||
body = _base_ai_body(video_short_path)
|
||||
body["frame_period_recognition"] = 4
|
||||
collected = _run_async_video_sse(
|
||||
http_client,
|
||||
@@ -123,17 +158,16 @@ def test_ft_p_10_frame_sampling_ac1(
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.timeout(120)
|
||||
@pytest.mark.timeout(900)
|
||||
def test_ft_p_11_annotation_interval_ac2(
|
||||
warm_engine,
|
||||
http_client,
|
||||
jwt_token,
|
||||
mock_loader_url,
|
||||
video_short_path,
|
||||
sse_client_factory,
|
||||
):
|
||||
media_id = f"video-{uuid.uuid4().hex}"
|
||||
body = _base_ai_body(mock_loader_url, video_short_path)
|
||||
body = _base_ai_body(video_short_path)
|
||||
body["frame_recognition_seconds"] = 2
|
||||
collected = _run_async_video_sse(
|
||||
http_client,
|
||||
@@ -158,17 +192,16 @@ def test_ft_p_11_annotation_interval_ac2(
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.timeout(120)
|
||||
@pytest.mark.timeout(900)
|
||||
def test_ft_p_12_movement_tracking_ac3(
|
||||
warm_engine,
|
||||
http_client,
|
||||
jwt_token,
|
||||
mock_loader_url,
|
||||
video_short_path,
|
||||
sse_client_factory,
|
||||
):
|
||||
media_id = f"video-{uuid.uuid4().hex}"
|
||||
body = _base_ai_body(mock_loader_url, video_short_path)
|
||||
body = _base_ai_body(video_short_path)
|
||||
body["tracking_distance_confidence"] = 0.1
|
||||
body["tracking_probability_increase"] = 0.1
|
||||
collected = _run_async_video_sse(
|
||||
|
||||
Reference in New Issue
Block a user