forked from IQ.Lvbs/IQ.Pilot
IQ.Pilot Release Commit @ b6534c0
This commit is contained in:
123
iqpilot/selfdrive/tests/test_constructiond.py
Normal file
123
iqpilot/selfdrive/tests/test_constructiond.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
from types import SimpleNamespace
|
||||
|
||||
import numpy as np
|
||||
|
||||
from iqpilot.selfdrive.constructiond import (
|
||||
ANALYSIS_PERIOD,
|
||||
ENTER_HITS,
|
||||
HIT_FRAC,
|
||||
HOLD_SEC,
|
||||
LUMA_MIN,
|
||||
WASH_FRAC,
|
||||
ConstructionZoneDetector,
|
||||
State,
|
||||
orange_fraction,
|
||||
)
|
||||
|
||||
# barrel orange / yellow paint chroma as measured on-device (see module docstring)
|
||||
BARREL_U, BARREL_V = 88, 178
|
||||
PAINT_U, PAINT_V = 84, 159
|
||||
|
||||
|
||||
def _nv12_buf(width=1928, height=1208, patches=()):
|
||||
stride = width
|
||||
uv_offset = stride * height
|
||||
data = np.full(uv_offset + stride * (height // 2), 128, dtype=np.uint8)
|
||||
data[:uv_offset] = 120 # mid luma
|
||||
uv = data[uv_offset:].reshape(height // 2, stride)
|
||||
for r0, r1, c0, c1, u, v in patches:
|
||||
uv[r0:r1, 2 * c0:2 * c1:2] = u
|
||||
uv[r0:r1, 2 * c0 + 1:2 * c1:2] = v
|
||||
return SimpleNamespace(data=data, width=width, height=height, stride=stride, uv_offset=uv_offset)
|
||||
|
||||
|
||||
def test_orange_fraction_fires_on_barrel_chroma():
|
||||
ch, cw = 1208 // 2, 1928 // 2
|
||||
buf = _nv12_buf(patches=[(int(0.6 * ch), int(0.6 * ch) + 20, int(0.5 * cw), int(0.5 * cw) + 20, BARREL_U, BARREL_V)])
|
||||
assert orange_fraction(buf)[0] > HIT_FRAC
|
||||
|
||||
|
||||
def test_orange_fraction_rejects_yellow_paint_chroma():
|
||||
ch, cw = 1208 // 2, 1928 // 2
|
||||
buf = _nv12_buf(patches=[(int(0.6 * ch), int(0.9 * ch), int(0.2 * cw), int(0.4 * cw), PAINT_U, PAINT_V)])
|
||||
assert orange_fraction(buf)[0] == 0.0
|
||||
|
||||
|
||||
def test_orange_fraction_ignores_sky_region():
|
||||
buf = _nv12_buf(patches=[(0, 100, 100, 300, BARREL_U, BARREL_V)])
|
||||
assert orange_fraction(buf)[0] == 0.0
|
||||
|
||||
|
||||
def test_detector_enters_after_persistent_hits_and_holds():
|
||||
det = ConstructionZoneDetector()
|
||||
t = 0.0
|
||||
assert not det.update(0.0, t)
|
||||
for _ in range(ENTER_HITS - 1):
|
||||
t += ANALYSIS_PERIOD
|
||||
assert not det.update(2 * HIT_FRAC, t)
|
||||
assert det.state == State.pending
|
||||
|
||||
t += ANALYSIS_PERIOD
|
||||
assert det.update(2 * HIT_FRAC, t)
|
||||
assert det.state == State.active
|
||||
|
||||
# barrel-free stretch inside the zone: stays active through the hold window
|
||||
t += HOLD_SEC - 1.0
|
||||
assert det.update(0.0, t)
|
||||
|
||||
# past the hold: releases
|
||||
t += HOLD_SEC + 1.0
|
||||
assert not det.update(0.0, t)
|
||||
assert det.state == State.inactive
|
||||
|
||||
|
||||
def test_detector_single_hit_does_not_enter():
|
||||
det = ConstructionZoneDetector()
|
||||
det.update(2 * HIT_FRAC, 0.0)
|
||||
for i in range(20):
|
||||
assert not det.update(0.0, 1.0 + i * ANALYSIS_PERIOD)
|
||||
|
||||
|
||||
def test_detector_ignores_global_orange_wash():
|
||||
det = ConstructionZoneDetector()
|
||||
for i in range(10):
|
||||
assert not det.update(2 * WASH_FRAC, i * ANALYSIS_PERIOD)
|
||||
assert det.state == State.inactive
|
||||
|
||||
|
||||
def test_orange_fraction_reports_roi_luma():
|
||||
buf = _nv12_buf()
|
||||
frac, luma = orange_fraction(buf)
|
||||
assert abs(luma - 120.0) < 1.0 # _nv12_buf fills Y with 120
|
||||
|
||||
|
||||
def test_detector_night_luma_gates_hits():
|
||||
det = ConstructionZoneDetector()
|
||||
# night: retroreflective amber markers pass the chroma test but must not enter
|
||||
for i in range(20):
|
||||
assert not det.update(2 * HIT_FRAC, i * ANALYSIS_PERIOD, luma=LUMA_MIN - 30.0)
|
||||
assert det.state == State.inactive
|
||||
# same signal in daylight enters
|
||||
for i in range(ENTER_HITS):
|
||||
det.update(2 * HIT_FRAC, 100.0 + i * ANALYSIS_PERIOD, luma=LUMA_MIN + 30.0)
|
||||
assert det.state == State.active
|
||||
|
||||
|
||||
def test_detector_low_speed_gates_entry_but_not_hold():
|
||||
from iqpilot.selfdrive.constructiond import MIN_ENTER_SPEED
|
||||
det = ConstructionZoneDetector()
|
||||
# parked/lot speeds: orange never enters
|
||||
for i in range(20):
|
||||
assert not det.update(2 * HIT_FRAC, i * ANALYSIS_PERIOD, luma=120.0, v_ego=3.0)
|
||||
assert det.state == State.inactive
|
||||
# at speed: enters
|
||||
t = 100.0
|
||||
for i in range(ENTER_HITS):
|
||||
det.update(2 * HIT_FRAC, t + i * ANALYSIS_PERIOD, luma=120.0, v_ego=MIN_ENTER_SPEED + 5.0)
|
||||
assert det.state == State.active
|
||||
# slowing inside the zone: still held
|
||||
assert det.update(0.0, t + 30.0, luma=120.0, v_ego=2.0)
|
||||
Reference in New Issue
Block a user