feat: zone config loader and static ROI builder
This commit is contained in:
1 parent
5bf44d6457
commit
4ad4284d9a
3 files changed
+134
No files matched your search
@@ -0,0 +1,73 @@
|
||||
"""Zone configuration — load/save counting zone presets from zones.json."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
from src.truck_roi import TruckROI
|
||||
|
||||
|
||||
def load_zone_config(path: str) -> dict[str, Any]:
|
||||
"""Load zones.json and return full config dict."""
|
||||
with open(path) as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def save_zone_config(config: dict[str, Any], path: str) -> None:
|
||||
"""Save zone config dict to zones.json."""
|
||||
with open(path, "w") as f:
|
||||
json.dump(config, f, indent=2)
|
||||
|
||||
|
||||
def build_static_roi(zone: dict[str, Any], video_w: int, video_h: int) -> TruckROI:
|
||||
"""Build a TruckROI from a zone config dict.
|
||||
|
||||
The zone dict should have:
|
||||
- truck_area: [[x1,y1], [x2,y2], [x3,y3], [x4,y4]] (4-point polygon)
|
||||
- count_line: {"x_start": int, "x_end": int, "y": int}
|
||||
Coordinates are in the video's native resolution (no scaling needed
|
||||
if the zone was calibrated for this resolution).
|
||||
"""
|
||||
truck_pts = zone.get("truck_area", [])
|
||||
count_line = zone.get("count_line", {})
|
||||
|
||||
# Compute bounding box of truck_area polygon
|
||||
if truck_pts and len(truck_pts) >= 4:
|
||||
xs = [p[0] for p in truck_pts]
|
||||
ys = [p[1] for p in truck_pts]
|
||||
x1 = max(0, min(xs))
|
||||
y1 = max(0, min(ys))
|
||||
x2 = min(video_w, max(xs))
|
||||
y2 = min(video_h, max(ys))
|
||||
else:
|
||||
# Fallback: use 50% of frame
|
||||
x1, y1 = int(video_w * 0.3), int(video_h * 0.1)
|
||||
x2, y2 = int(video_w * 0.75), int(video_h * 0.95)
|
||||
|
||||
line_y = count_line.get("y", int((y1 + y2) / 2))
|
||||
|
||||
return TruckROI(
|
||||
x1=x1, y1=y1, x2=x2, y2=y2,
|
||||
line_y=line_y,
|
||||
confidence=1.0,
|
||||
)
|
||||
|
||||
|
||||
def list_zone_presets(path: str) -> list[dict[str, Any]]:
|
||||
"""Return list of preset summaries: [{"name": str, "description": str, ...}]."""
|
||||
config = load_zone_config(path)
|
||||
presets = config.get("presets", {})
|
||||
return [
|
||||
{"name": name, "description": vals.get("description", ""), **vals}
|
||||
for name, vals in presets.items()
|
||||
]
|
||||
|
||||
|
||||
def get_active_zone(path: str, preset_name: str | None = None) -> dict[str, Any] | None:
|
||||
"""Get the active preset dict. Falls back to active_preset field if name not given."""
|
||||
config = load_zone_config(path)
|
||||
presets = config.get("presets", {})
|
||||
name = preset_name or config.get("active_preset")
|
||||
return presets.get(name)
|
||||
@@ -0,0 +1,47 @@
|
||||
import json
|
||||
import os
|
||||
import pytest
|
||||
from src.zone_config import load_zone_config, build_static_roi, list_zone_presets
|
||||
|
||||
|
||||
def test_load_zone_config_reads_json(tmp_path):
|
||||
cfg = {"presets": {"test": {"count_line": {"x_start": 100, "x_end": 200, "y": 150}}}, "active_preset": "test"}
|
||||
p = tmp_path / "zones.json"
|
||||
p.write_text(json.dumps(cfg))
|
||||
result = load_zone_config(str(p))
|
||||
assert result["active_preset"] == "test"
|
||||
assert result["presets"]["test"]["count_line"]["y"] == 150
|
||||
|
||||
|
||||
def test_build_static_roi_from_zone_config():
|
||||
zone = {
|
||||
"truck_area": [[100, 50], [110, 300], [400, 310], [390, 40]],
|
||||
"count_line": {"x_start": 110, "x_end": 400, "y": 150},
|
||||
}
|
||||
roi = build_static_roi(zone, video_w=640, video_h=480)
|
||||
assert roi.line_y == 150
|
||||
assert roi.confidence == 1.0
|
||||
assert roi.x1 < roi.x2
|
||||
assert roi.y1 < roi.y2
|
||||
|
||||
|
||||
def test_build_static_roi_scales_coords():
|
||||
zone = {
|
||||
"truck_area": [[600, 385], [609, 1076], [1404, 1078], [1381, 343]],
|
||||
"count_line": {"x_start": 577, "x_end": 1401, "y": 330},
|
||||
}
|
||||
roi = build_static_roi(zone, video_w=1920, video_h=1080)
|
||||
assert roi.line_y == 330
|
||||
assert roi.x1 == 600
|
||||
assert roi.x2 == 1404
|
||||
|
||||
|
||||
def test_list_zone_presets(tmp_path):
|
||||
cfg = {"presets": {"p1": {"description": "Zone 1"}, "p2": {"description": "Zone 2"}}}
|
||||
p = tmp_path / "zones.json"
|
||||
p.write_text(json.dumps(cfg))
|
||||
presets = list_zone_presets(str(p))
|
||||
assert len(presets) == 2
|
||||
names = [p["name"] for p in presets]
|
||||
assert "p1" in names
|
||||
assert "p2" in names
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"presets": {
|
||||
"default": {
|
||||
"truck_area": [[600, 385], [609, 1076], [1404, 1078], [1381, 343]],
|
||||
"counting_area": [[574, 50], [586, 1077], [1418, 1076], [1397, 50]],
|
||||
"count_line": {"x_start": 577, "x_end": 1401, "y": 330},
|
||||
"left_limit": 0.27578,
|
||||
"right_limit": 0.72578,
|
||||
"duplicate_circle_radius": 30,
|
||||
"description": "Default zone for 1920x1080 camera"
|
||||
}
|
||||
},
|
||||
"active_preset": "default"
|
||||
}
|
||||
Reference in new issue
Block a user