Files

82 lines
3.3 KiB
Python

from __future__ import annotations
import pytest
from yovision_brain.rules import (
AreaDefinition,
DirectionalLineDefinition,
NormalizedPoint,
RuleConfigError,
RuleEngine,
RuleSet,
)
from yovision_brain.vision import BoundingBox, TrackedObject
def point(x: float, y: float) -> NormalizedPoint:
return NormalizedPoint(x, y)
def rules() -> RuleSet:
return RuleSet(
version="rules-v7",
profile_id="main",
width=100,
height=100,
areas=(AreaDefinition("yard", (point(0.2, 0.2), point(0.8, 0.2), point(0.8, 0.8), point(0.2, 0.8))),),
directional_lines=(DirectionalLineDefinition("gate", point(0.5, 0.1), point(0.5, 0.9), "left_to_right", 0.01),),
)
def track(track_id: str, anchor_x: int, anchor_y: int, sequence: int = 0) -> TrackedObject:
return TrackedObject(track_id, BoundingBox(anchor_x - 1, anchor_y - 2, anchor_x + 1, anchor_y), "anonymous_target", 1.0, sequence, sequence)
def decisions(engine: RuleEngine, item: TrackedObject):
return engine.evaluate((item,), profile_id="main", width=100, height=100)
def test_area_outside_entered_inside_and_boundary() -> None:
engine = RuleEngine(rules())
assert decisions(engine, track("one", 10, 50))[0].state == "outside"
entered = decisions(engine, track("one", 20, 50, 1))[0]
assert (entered.state, entered.triggered) == ("entered", True)
inside = decisions(engine, track("one", 50, 50, 2))[0]
assert (inside.state, inside.triggered) == ("inside", False)
assert inside.config_version == "rules-v7"
def test_direction_and_reverse_crossing_are_distinct() -> None:
engine = RuleEngine(rules())
decisions(engine, track("one", 40, 50))
forward = decisions(engine, track("one", 60, 50, 1))[1]
assert (forward.state, forward.triggered) == ("triggered", True)
reverse_engine = RuleEngine(rules())
decisions(reverse_engine, track("two", 60, 50))
reverse = decisions(reverse_engine, track("two", 40, 50, 1))[1]
assert (reverse.state, reverse.triggered) == ("reverse_crossing", False)
def test_line_deadband_prevents_jitter_trigger() -> None:
engine = RuleEngine(rules())
decisions(engine, track("one", 40, 50))
on_line = decisions(engine, track("one", 50, 50, 1))[1]
assert (on_line.state, on_line.triggered) == ("on_line", False)
triggered = decisions(engine, track("one", 60, 50, 2))[1]
assert triggered.triggered is True
def test_profile_resolution_mismatch_is_rejected() -> None:
with pytest.raises(RuleConfigError, match="Profile/resolution"):
RuleEngine(rules()).evaluate((track("one", 20, 20),), profile_id="sub", width=100, height=100)
def test_invalid_polygon_line_and_duplicate_ids_are_rejected() -> None:
with pytest.raises(RuleConfigError, match="non-degenerate"):
RuleSet("v", "main", 10, 10, areas=(AreaDefinition("bad", (point(0, 0), point(0.5, 0.5), point(1, 1))),))
with pytest.raises(RuleConfigError, match="distinct endpoints"):
RuleSet("v", "main", 10, 10, directional_lines=(DirectionalLineDefinition("bad", point(0, 0), point(0, 0), "left_to_right"),))
with pytest.raises(RuleConfigError, match="unique"):
RuleSet("v", "main", 10, 10, areas=(AreaDefinition("same", (point(0, 0), point(1, 0), point(0, 1))),), directional_lines=(DirectionalLineDefinition("same", point(0, 0), point(1, 1), "left_to_right"),))