mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-12 07:23:45 +08:00
Repair integration CI fixtures and reviewed radar reference
This commit is contained in:
@@ -350,7 +350,7 @@ jobs:
|
||||
- name: Build native host dependencies
|
||||
run: scons
|
||||
- name: Gate every assigned lifecycle module serially
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 15
|
||||
env:
|
||||
RAYLIB_BACKEND: headless
|
||||
run: python3 tools/ci/run_vehicle_tests.py --suite aol-lifecycle --shard-index "${{ matrix.shard }}" --shard-count 16 --output "ci-results/aol-lifecycle-${{ matrix.shard }}"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import os
|
||||
from tempfile import TemporaryDirectory
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import Mock, patch
|
||||
@@ -16,7 +17,8 @@ from opendbc.safety.tests.libsafety import libsafety_py
|
||||
from opendbc.safety.tests import test_hyundai_gv60_hda1_long_aol as native_fixture
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.starpilot.car.hyundai.aol import policy_for, native_accepts_cp
|
||||
from openpilot.selfdrive.ui.layouts.settings import toggles
|
||||
with patch.dict(os.environ, {"SCALE": "1"}):
|
||||
from openpilot.selfdrive.ui.layouts.settings import toggles
|
||||
|
||||
|
||||
def camera_params(offset=0, alpha=False):
|
||||
|
||||
@@ -84,6 +84,7 @@ class CardManualEventTests(unittest.TestCase):
|
||||
|
||||
def test_traffic_media_uses_only_new_packet_durations_across_empty_card_polls(self):
|
||||
self.cp.carFingerprint = HYUNDAI_CAR.HYUNDAI_IONIQ_6
|
||||
self.cp.safetyConfigs = [{'safetyModel': 'hyundaiCanfd'}]
|
||||
self.cp.flags = int(HyundaiFlags.CANFD_LKA_STEER_MSG)
|
||||
self.params.put('ModeButtonControl', 6, block=True)
|
||||
self.params.put('LongModeButtonControl', 6, block=True)
|
||||
@@ -124,6 +125,7 @@ class CardManualEventTests(unittest.TestCase):
|
||||
def test_exact_ioniq_media_assignment_requires_source_and_host_authority(self):
|
||||
self.params.put('ModeButtonControl', 5, block=True)
|
||||
self.cp.carFingerprint = HYUNDAI_CAR.HYUNDAI_IONIQ_6
|
||||
self.cp.safetyConfigs = [{'safetyModel': 'hyundaiCanfd'}]
|
||||
self.assertIsNone(self.candidate(media=self.media((False, False, 700_000_000),
|
||||
(True, False, 900_000_000),
|
||||
(False, False, 1_000_000_000)))) # ECAN is 0 in this CP.
|
||||
@@ -157,6 +159,7 @@ class CardManualEventTests(unittest.TestCase):
|
||||
|
||||
def test_ordinary_buttons_do_not_depend_on_optional_media_cache(self):
|
||||
self.cp.carFingerprint = HYUNDAI_CAR.HYUNDAI_IONIQ_6
|
||||
self.cp.safetyConfigs = [{'safetyModel': 'hyundaiCanfd'}]
|
||||
self.cp.flags = int(HyundaiFlags.CANFD_LKA_STEER_MSG)
|
||||
self.params.put('LKASButtonControl', 5, block=True)
|
||||
self.params.put('DistanceButtonControl', 5, block=True)
|
||||
@@ -177,6 +180,7 @@ class CardManualEventTests(unittest.TestCase):
|
||||
|
||||
def test_ioniq_media_hold_cannot_cross_map_or_drive_change(self):
|
||||
self.cp.carFingerprint = HYUNDAI_CAR.HYUNDAI_IONIQ_6
|
||||
self.cp.safetyConfigs = [{'safetyModel': 'hyundaiCanfd'}]
|
||||
self.cp.flags = int(HyundaiFlags.CANFD_LKA_STEER_MSG)
|
||||
self.params.put('ModeButtonControl', 5, block=True)
|
||||
self.candidate(media=self.media((False, False, 1_000_000_000)))
|
||||
|
||||
BIN
Binary file not shown.
@@ -83,6 +83,7 @@ class ManualSavedJoinTests(unittest.TestCase):
|
||||
self.assertEqual(self.owner.manual_state.intent, ManualIntent.NONE) # Honda cannot consume Ioniq media.
|
||||
self.assertEqual(self.owner.card_last_sequence, 0)
|
||||
self.cp.carFingerprint = HYUNDAI_CAR.HYUNDAI_IONIQ_6
|
||||
self.cp.safetyConfigs = [{'safetyModel': 'hyundaiCanfd'}]
|
||||
self.cp.flags = int(HyundaiFlags.CANFD_LKA_STEER_MSG)
|
||||
self.sample(stamp + 50_000_000, self.receipt(stamp + 50_000_000, 2, button='mode'))
|
||||
self.assertEqual(self.owner.card_last_sequence, 2)
|
||||
|
||||
@@ -261,6 +261,7 @@ def test_actual_card_alpha_off_short_wheel_gesture_has_lateral_only_authority():
|
||||
f.setUp()
|
||||
try:
|
||||
f.cp.carFingerprint = CAR.HYUNDAI_IONIQ_6
|
||||
f.cp.safetyConfigs = [{"safetyModel": "hyundaiCanfd"}]
|
||||
f.cp.flags = int(HyundaiFlags.CANFD_LKA_STEER_MSG)
|
||||
f.cp.openpilotLongitudinalControl = False
|
||||
f.sm['carControl'].enabled = f.sm['carControl'].longActive = False
|
||||
|
||||
@@ -7,6 +7,7 @@ from openpilot.starpilot.ui.long_profile_feature import CATEGORY_HELP, VALUE_HEL
|
||||
from openpilot.starpilot.ui.feature_settings_state import SLC_OFFSET_KEYS
|
||||
|
||||
DESCRIPTIONS = {
|
||||
'FordRearFogIndicator': 'Shows a movable amber telltale while the Ford rear fog light is on, using read-only vehicle status.',
|
||||
'DisengageOnAccelerator': 'When enabled, pressing the accelerator pedal will disengage openpilot.',
|
||||
'ReduceMotion': 'Reduces interface animations for a steadier display.',
|
||||
'IsMetric': 'On displays speed and distance in kilometers; Off uses miles. Saved speed-limit offsets keep their physical values.',
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"FordLowSpeedFactor_ang",
|
||||
"FordPrefLateralControl",
|
||||
"FordPrefSteerAngleCurvature",
|
||||
"FordRearFogIndicator",
|
||||
"GMAutoHold",
|
||||
"GMLongitudinalTune",
|
||||
"GMPedalLongitudinal",
|
||||
|
||||
@@ -171,7 +171,7 @@ assert.deepEqual(cardsFor(""), [["", [0]], ["StarPort", [2, 3]]], "a heading row
|
||||
assert.deepEqual(cardsFor("h.265"), [["", []], ["StarPort", [3]]], "search keeps a row in its card when the heading is filtered out")
|
||||
assert.deepEqual(cardsFor("voacc"), [["", [0]]], "an emptied card is not shown")
|
||||
|
||||
const experimentRows = [{ label: "Stay on Wi-Fi", value: "On", reason: "" }, { label: "Specific car experiments", value: "", reason: "", heading: true, collapsed: true },
|
||||
const experimentRows = [{ label: "Stay on Wi-Fi", value: "On", reason: "" }, { key: "heading:experiments", label: "Specific car experiments", value: "", reason: "", heading: true, collapsed: true },
|
||||
{ label: "Try experimental fixes", value: "Off", reason: "" }]
|
||||
const experimentCards = (state) => {
|
||||
const view = { state: { query: "", data: { rows: experimentRows }, ...state }, initialPage: "developer", activeSection: {} }
|
||||
@@ -179,12 +179,12 @@ const experimentCards = (state) => {
|
||||
return SettingsPage.computed.rowCards.call(view).map(({ title, open }) => [title, open])
|
||||
}
|
||||
assert.deepEqual(experimentCards({ openCards: [] }), [["", true], ["Specific car experiments", false]], "a collapsible group starts closed")
|
||||
assert.deepEqual(experimentCards({ openCards: ["Specific car experiments"] }), [["", true], ["Specific car experiments", true]])
|
||||
assert.deepEqual(experimentCards({ openCards: ["heading:experiments"] }), [["", true], ["Specific car experiments", true]])
|
||||
assert.deepEqual(experimentCards({ openCards: [], query: "experimental" }).at(-1), ["Specific car experiments", true], "searching opens a matching group")
|
||||
const toggled = { state: { openCards: [] } }
|
||||
SettingsPage.methods.toggleCard.call(toggled, "Specific car experiments")
|
||||
assert.deepEqual(toggled.state.openCards, ["Specific car experiments"])
|
||||
SettingsPage.methods.toggleCard.call(toggled, "Specific car experiments")
|
||||
SettingsPage.methods.toggleCard.call(toggled, "heading:experiments")
|
||||
assert.deepEqual(toggled.state.openCards, ["heading:experiments"])
|
||||
SettingsPage.methods.toggleCard.call(toggled, "heading:experiments")
|
||||
assert.deepEqual(toggled.state.openCards, [])
|
||||
|
||||
const conditional = fixture()
|
||||
|
||||
@@ -840,7 +840,7 @@ class SettingsGatewayTest(unittest.TestCase):
|
||||
self.assertTrue(self.gateway.confirm(intent["intent"], self.session, self.generation))
|
||||
self.assertEqual(read_lane_change(self.params).policy.close_gap_seconds, 0.8)
|
||||
self.assertEqual(self.page("lane_change")["rows"][gap]["value"], "0.8")
|
||||
self.assertEqual(self.params.get("LaneChangePreferences")["version"], 5)
|
||||
self.assertEqual(self.params.get("LaneChangePreferences")["version"], 6)
|
||||
|
||||
saved = self.page("lane_change")
|
||||
revoke = self.gateway.preview(saved["view"], close, -1, self.session, self.generation)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import unittest
|
||||
from unittest.mock import Mock
|
||||
|
||||
import numpy as np
|
||||
|
||||
@@ -24,9 +25,9 @@ class TestReaderSaturation(unittest.TestCase):
|
||||
core.temporal_tracking_enabled = True
|
||||
core.last_classifier_advisory_rejected = False
|
||||
core.last_classifier_reject_dominant = False
|
||||
core._collect_detector_classifier_proposals = lambda frame, proposal=proposal: [(proposal, 0, (110, 10, 170, 70))]
|
||||
core._collect_detector_classifier_proposals = Mock(side_effect=lambda frame, proposal=proposal: [(proposal, 0, (110, 10, 170, 70))])
|
||||
regions = iter(regulatory)
|
||||
core._is_regulatory_speed_sign = lambda crop, regions=regions: next(regions)
|
||||
core._is_regulatory_speed_sign = Mock(side_effect=lambda crop, regions=regions: next(regions))
|
||||
calls = []
|
||||
|
||||
def classify(crop, calls=calls, core=core, reads=reads):
|
||||
@@ -36,7 +37,7 @@ class TestReaderSaturation(unittest.TestCase):
|
||||
core.last_classifier_reject_dominant = core.last_classifier_advisory_rejected
|
||||
return reads[index]
|
||||
|
||||
core._classify_speed_limit_from_model = classify
|
||||
core._classify_speed_limit_from_model = Mock(side_effect=classify)
|
||||
result = core._detect_sign_from_detector_classifier(np.zeros((100, 200, 3), dtype=np.uint8))
|
||||
self.assertEqual(len(calls), expected_calls)
|
||||
if speed is None:
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import partial
|
||||
|
||||
import math
|
||||
import os
|
||||
import time
|
||||
@@ -155,8 +157,8 @@ def _send(pm, *, session: str, status: str, stream: str, observed_ns: int = 0,
|
||||
pm.send('slcVisionObservation', msg)
|
||||
|
||||
|
||||
def _publish_ready_stop(pm, sm, params, *, session, stream, is_metric, offset_ns,
|
||||
source_observed_ns, eof_boot_ns, frame_id, width, height, stops,
|
||||
def _publish_ready_stop(pm, sm, params, stops, *, session, stream, is_metric, offset_ns,
|
||||
source_observed_ns, eof_boot_ns, frame_id, width, height,
|
||||
last_result, last_result_session, last_result_observed_ns, last_result_valid_until_ns):
|
||||
sm.update(0)
|
||||
pair = clock_pair_ns()
|
||||
@@ -389,12 +391,12 @@ def main() -> None:
|
||||
# The observation belongs to the original camera frame, not the end of
|
||||
# CPU inference. Completion never restamps an older camera observation.
|
||||
source_observed_ns = eof_boot_ns - offset_ns
|
||||
core._stop_ready_callback = (lambda stops: _publish_ready_stop(
|
||||
core._stop_ready_callback = partial(_publish_ready_stop,
|
||||
pm, sm, params, session=session, stream=stream, is_metric=is_metric, offset_ns=offset_ns,
|
||||
source_observed_ns=source_observed_ns, eof_boot_ns=eof_boot_ns, frame_id=frame_id,
|
||||
width=frame.shape[1], height=frame.shape[0], stops=stops, last_result=last_result,
|
||||
width=frame.shape[1], height=frame.shape[0], last_result=last_result,
|
||||
last_result_session=last_result_session, last_result_observed_ns=last_result_observed_ns,
|
||||
last_result_valid_until_ns=last_result_valid_until_ns)) if tasks[1] else None
|
||||
last_result_valid_until_ns=last_result_valid_until_ns) if tasks[1] else None
|
||||
try:
|
||||
result = core.observe(frame, now=source_observed_ns / 1e9)
|
||||
finally:
|
||||
|
||||
@@ -56,6 +56,7 @@ class TestAdvisoryRejection(unittest.TestCase):
|
||||
core = VisionModelCore.__new__(VisionModelCore)
|
||||
core._collect_detector_classifier_proposals = Mock(return_value=proposals)
|
||||
core._remember_detector_proposal = Mock()
|
||||
core.last_classifier_reject_dominant = False
|
||||
return core
|
||||
|
||||
def test_confident_nonregulatory_rejection_stops_only_that_proposal(self):
|
||||
@@ -67,14 +68,15 @@ class TestAdvisoryRejection(unittest.TestCase):
|
||||
nonlocal calls
|
||||
calls += 1
|
||||
core.last_classifier_advisory_rejected = calls == 2
|
||||
core.last_classifier_reject_dominant = core.last_classifier_advisory_rejected
|
||||
return None if calls == 2 else (35 if calls == 1 else 55, .99)
|
||||
|
||||
with patch.object(core, '_classify_speed_limit_from_model', side_effect=classify), \
|
||||
patch.object(core, '_is_regulatory_speed_sign', side_effect=lambda _crop: calls >= 2):
|
||||
result = core._detect_sign_from_detector_classifier(np.zeros((200, 400, 3), np.uint8))
|
||||
result = core._detect_sign_from_detector_classifier(np.random.default_rng(0).integers(0, 256, (200, 400, 3), dtype=np.uint8))
|
||||
assert result is not None
|
||||
self.assertEqual(result.speed_limit_mph, 55)
|
||||
self.assertEqual(calls, 2 + len(model.DETECTOR_CLASSIFIER_EXPANSIONS))
|
||||
self.assertEqual(calls, 4) # Two advisory reads, then the existing two-read strong consensus.
|
||||
|
||||
def test_white_regulatory_crop_keeps_its_expansion_fallback(self):
|
||||
core = self.detector([(.99, 0, (240, 30, 280, 90))])
|
||||
@@ -84,11 +86,12 @@ class TestAdvisoryRejection(unittest.TestCase):
|
||||
nonlocal calls
|
||||
calls += 1
|
||||
core.last_classifier_advisory_rejected = calls == 1
|
||||
core.last_classifier_reject_dominant = core.last_classifier_advisory_rejected
|
||||
return None if calls == 1 else (55, .99)
|
||||
|
||||
with patch.object(core, '_classify_speed_limit_from_model', side_effect=classify), \
|
||||
patch.object(core, '_is_regulatory_speed_sign', return_value=True):
|
||||
result = core._detect_sign_from_detector_classifier(np.zeros((200, 400, 3), np.uint8))
|
||||
result = core._detect_sign_from_detector_classifier(np.random.default_rng(0).integers(0, 256, (200, 400, 3), dtype=np.uint8))
|
||||
assert result is not None
|
||||
self.assertEqual(result.speed_limit_mph, 55)
|
||||
self.assertEqual(calls, len(model.DETECTOR_CLASSIFIER_EXPANSIONS))
|
||||
|
||||
@@ -5,6 +5,7 @@ import inspect
|
||||
import textwrap
|
||||
import types
|
||||
import unittest
|
||||
from unittest.mock import Mock
|
||||
|
||||
import numpy as np
|
||||
|
||||
@@ -42,8 +43,8 @@ def fixture(frame, proposals, mode='counted_reject', uncached=False, after_read=
|
||||
core.speed_enabled = True
|
||||
core.last_classifier_advisory_rejected = False
|
||||
core.last_classifier_reject_dominant = False
|
||||
core._collect_detector_classifier_proposals = lambda _: proposals
|
||||
core._is_regulatory_speed_sign = lambda crop: mode == 'counted_reject' or bool(int(crop.sum()) % 3)
|
||||
core._collect_detector_classifier_proposals = Mock(side_effect=lambda _: proposals)
|
||||
core._is_regulatory_speed_sign = Mock(side_effect=lambda crop: mode == 'counted_reject' or bool(int(crop.sum()) % 3))
|
||||
def reader(crop):
|
||||
import hashlib
|
||||
digest = hashlib.sha256(crop.tobytes()).hexdigest()
|
||||
@@ -65,9 +66,9 @@ def fixture(frame, proposals, mode='counted_reject', uncached=False, after_read=
|
||||
if value % 4 == 0:
|
||||
return None
|
||||
return [20, 25, 35, 45][value % 4], .6 + (value % 39) / 100
|
||||
core._classify_speed_limit_from_model = reader
|
||||
core._classify_speed_limit_from_model = Mock(side_effect=reader)
|
||||
if uncached:
|
||||
core._detect_sign_from_detector_classifier = types.MethodType(uncached_reference(), core)
|
||||
core._detect_sign_from_detector_classifier = Mock(side_effect=types.MethodType(uncached_reference(), core))
|
||||
return core
|
||||
|
||||
|
||||
@@ -120,7 +121,7 @@ class TestClassifierReuse(unittest.TestCase):
|
||||
'CLASSIFIER_SHA256': '0' * 64}
|
||||
for key, value in changes.items():
|
||||
with self.subTest(key=key):
|
||||
def change(core, count):
|
||||
def change(core, count, key=key, value=value):
|
||||
if count == 3:
|
||||
setattr(core, key, value)
|
||||
core = fixture(frame, proposals, after_read=change)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import ast
|
||||
import dataclasses
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
import unittest
|
||||
@@ -7,17 +8,20 @@ import unittest
|
||||
P = Path(__file__).resolve().parent
|
||||
SOURCE = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def function(path, name, namespace):
|
||||
tree = ast.parse(path.read_text())
|
||||
node = next(n for n in tree.body if isinstance(n, ast.FunctionDef) and n.name == name)
|
||||
exec(compile(ast.Module(body=[node], type_ignores=[]), str(path), 'exec'), namespace)
|
||||
return namespace[name]
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class Box:
|
||||
confidence: float
|
||||
bbox: tuple
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class Evidence:
|
||||
session: str
|
||||
@@ -29,74 +33,142 @@ class Evidence:
|
||||
height: int
|
||||
boxes: tuple
|
||||
|
||||
|
||||
class Tests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.sent = []
|
||||
self.clock = [200, 1200]
|
||||
self.health = True
|
||||
self.ns = dict(clock_pair_ns=lambda: tuple(self.clock), MAX_PAIR_SKEW_NS=10,
|
||||
MAX_RESULT_AGE_NS=1000, STOP_MAX_AGE_NS=500, _driving_model_ready=lambda sm, now: self.health,
|
||||
StopEvidence=Evidence, StopBox=Box,
|
||||
_send=lambda pm, **kw: self.sent.append(kw))
|
||||
self.publish = function(SOURCE/'producer.py', '_publish_ready_stop', self.ns)
|
||||
self.args = dict(session='s', stream='road', is_metric=False, offset_ns=1000,
|
||||
source_observed_ns=100, eof_boot_ns=1100, frame_id=7, width=100, height=50,
|
||||
stops=(Box(.8, (1,2,3,4)),), last_result=(40,.99,3,1),
|
||||
last_result_session='s', last_result_observed_ns=80, last_result_valid_until_ns=800)
|
||||
self.ns = {
|
||||
'clock_pair_ns': lambda: tuple(self.clock),
|
||||
'MAX_PAIR_SKEW_NS': 10,
|
||||
'MAX_RESULT_AGE_NS': 1000,
|
||||
'STOP_MAX_AGE_NS': 500,
|
||||
'_driving_model_ready': lambda sm, now: self.health,
|
||||
'StopEvidence': Evidence,
|
||||
'StopBox': Box,
|
||||
'_send': lambda pm, **kw: self.sent.append(kw),
|
||||
}
|
||||
self.publish = function(SOURCE / 'producer.py', '_publish_ready_stop', self.ns)
|
||||
self.args = {
|
||||
'session': 's',
|
||||
'stream': 'road',
|
||||
'is_metric': False,
|
||||
'offset_ns': 1000,
|
||||
'source_observed_ns': 100,
|
||||
'eof_boot_ns': 1100,
|
||||
'frame_id': 7,
|
||||
'width': 100,
|
||||
'height': 50,
|
||||
'stops': (Box(0.8, (1, 2, 3, 4)),),
|
||||
'last_result': (40, 0.99, 3, 1),
|
||||
'last_result_session': 's',
|
||||
'last_result_observed_ns': 80,
|
||||
'last_result_valid_until_ns': 800,
|
||||
}
|
||||
self.sm = SimpleNamespace(update=lambda timeout: None)
|
||||
self.params = SimpleNamespace(get_bool=lambda key: False)
|
||||
|
||||
def test_callback_after_all_regions_before_reader_with_readonly_tuple(self):
|
||||
events=[]
|
||||
events = []
|
||||
|
||||
class Base:
|
||||
def _collect_detector_classifier_proposals(self, frame):
|
||||
events.extend(['region1', 'region2'])
|
||||
self.stop_detections=(Box(.8,(1,2,3,4)),)
|
||||
self.stop_detections = (Box(0.8, (1, 2, 3, 4)),)
|
||||
return [('unchanged',)]
|
||||
tree=ast.parse((SOURCE/'traffic_signs.py').read_text())
|
||||
cls=next(n for n in tree.body if isinstance(n,ast.ClassDef) and n.name=='TrafficSignModelCore')
|
||||
method=next(n for n in cls.body if isinstance(n,ast.FunctionDef) and n.name=='_collect_detector_classifier_proposals')
|
||||
trial=ast.ClassDef(name='Trial',bases=[ast.Name(id='Base',ctx=ast.Load())],keywords=[],body=[method],decorator_list=[])
|
||||
ns={'Base':Base};exec(compile(ast.fix_missing_locations(ast.Module(body=[trial],type_ignores=[])),'hook','exec'),ns)
|
||||
core=ns['Trial']();core.net=object();received=[]
|
||||
core._stop_ready_callback=lambda stops:(events.append('publish'),received.append(stops))
|
||||
result=core._collect_detector_classifier_proposals(None);events.append('reader')
|
||||
self.assertEqual(events,['region1','region2','publish','reader'])
|
||||
self.assertEqual(result,[('unchanged',)])
|
||||
self.assertIsInstance(received[0],tuple)
|
||||
with self.assertRaises(dataclasses.FrozenInstanceError):received[0][0].confidence=1
|
||||
|
||||
tree = ast.parse((SOURCE / 'traffic_signs.py').read_text())
|
||||
cls = next(n for n in tree.body if isinstance(n, ast.ClassDef) and n.name == 'TrafficSignModelCore')
|
||||
method = next(n for n in cls.body if isinstance(n, ast.FunctionDef) and n.name == '_collect_detector_classifier_proposals')
|
||||
trial = ast.ClassDef(name='Trial', bases=[ast.Name(id='Base', ctx=ast.Load())], keywords=[], body=[method], decorator_list=[], type_params=[])
|
||||
ns = {'Base': Base}
|
||||
exec(compile(ast.fix_missing_locations(ast.Module(body=[trial], type_ignores=[])), 'hook', 'exec'), ns)
|
||||
core = ns['Trial']()
|
||||
core.net = object()
|
||||
received = []
|
||||
core._stop_ready_callback = lambda stops: (events.append('publish'), received.append(stops))
|
||||
result = core._collect_detector_classifier_proposals(None)
|
||||
events.append('reader')
|
||||
self.assertEqual(events, ['region1', 'region2', 'publish', 'reader'])
|
||||
self.assertEqual(result, [('unchanged',)])
|
||||
self.assertIsInstance(received[0], tuple)
|
||||
with self.assertRaises(dataclasses.FrozenInstanceError):
|
||||
received[0][0].confidence = 1
|
||||
|
||||
def test_speed_support_unchanged_and_stop_source_not_restamped(self):
|
||||
self.publish(None,self.sm,self.params,**self.args)
|
||||
row=self.sent.pop();self.assertEqual(row['result'],self.args['last_result'])
|
||||
self.assertEqual(row['observed_ns'],80);self.assertEqual(row['valid_until_ns'],800)
|
||||
self.assertEqual(row['stop_sample'].observed_ns,100);self.assertEqual(row['stop_sample'].frame_id,7)
|
||||
self.assertEqual(row['stop_sample'].boxes,self.args['stops'])
|
||||
self.args['last_result_valid_until_ns']=150
|
||||
self.publish(None,self.sm,self.params,**self.args)
|
||||
self.assertIsNone(self.sent[-1]['result']);self.assertEqual(self.sent[-1]['observed_ns'],0)
|
||||
self.publish(None, self.sm, self.params, **self.args)
|
||||
row = self.sent.pop()
|
||||
self.assertEqual(row['result'], self.args['last_result'])
|
||||
self.assertEqual(row['observed_ns'], 80)
|
||||
self.assertEqual(row['valid_until_ns'], 800)
|
||||
self.assertEqual(row['stop_sample'].observed_ns, 100)
|
||||
self.assertEqual(row['stop_sample'].frame_id, 7)
|
||||
self.assertEqual(row['stop_sample'].boxes, self.args['stops'])
|
||||
self.args['last_result_valid_until_ns'] = 150
|
||||
self.publish(None, self.sm, self.params, **self.args)
|
||||
self.assertIsNone(self.sent[-1]['result'])
|
||||
self.assertEqual(self.sent[-1]['observed_ns'], 0)
|
||||
|
||||
def test_late_clock_and_health_reject_without_send(self):
|
||||
self.clock[:]=[200,1300];self.publish(None,self.sm,self.params,**self.args)
|
||||
self.assertEqual(self.sent,[])
|
||||
self.clock[:]=[2000,3000];self.publish(None,self.sm,self.params,**self.args)
|
||||
self.assertEqual(self.sent,[])
|
||||
for pair in ([600,1600], [700,1700]):
|
||||
self.clock[:]=pair;self.publish(None,self.sm,self.params,**self.args)
|
||||
self.assertEqual(self.sent,[])
|
||||
self.clock[:]=[200,1200];self.health=False;self.publish(None,self.sm,self.params,**self.args)
|
||||
self.assertEqual(self.sent,[])
|
||||
self.clock[:] = [200, 1300]
|
||||
self.publish(None, self.sm, self.params, **self.args)
|
||||
self.assertEqual(self.sent, [])
|
||||
self.clock[:] = [2000, 3000]
|
||||
self.publish(None, self.sm, self.params, **self.args)
|
||||
self.assertEqual(self.sent, [])
|
||||
for pair in ([600, 1600], [700, 1700]):
|
||||
self.clock[:] = pair
|
||||
self.publish(None, self.sm, self.params, **self.args)
|
||||
self.assertEqual(self.sent, [])
|
||||
self.clock[:] = [200, 1200]
|
||||
self.health = False
|
||||
self.publish(None, self.sm, self.params, **self.args)
|
||||
self.assertEqual(self.sent, [])
|
||||
|
||||
def test_actual_callback_binds_frame_inputs_and_receives_stop_tuple(self):
|
||||
tree = ast.parse((SOURCE / 'producer.py').read_text())
|
||||
node = next(n for n in ast.walk(tree) if isinstance(n, ast.Assign) and
|
||||
any(isinstance(v, ast.Attribute) and v.attr == '_stop_ready_callback' for v in n.targets) and
|
||||
isinstance(n.value, ast.IfExp))
|
||||
core = SimpleNamespace()
|
||||
namespace = dict(self.args, core=core, partial=partial, _publish_ready_stop=self.publish,
|
||||
pm=None, sm=self.sm, params=self.params, tasks=(True, True), frame=SimpleNamespace(shape=(50, 100)))
|
||||
exec(compile(ast.Module(body=[node], type_ignores=[]), 'actual_callback', 'exec'), namespace)
|
||||
namespace.update(session='later-frame', frame_id=99, frame=SimpleNamespace(shape=(1, 1)), last_result=None)
|
||||
core._stop_ready_callback(self.args['stops'])
|
||||
row = self.sent.pop()
|
||||
self.assertEqual(row['stop_sample'].session, 's')
|
||||
self.assertEqual(row['stop_sample'].frame_id, 7)
|
||||
self.assertEqual((row['stop_sample'].width, row['stop_sample'].height), (100, 50))
|
||||
self.assertEqual(row['stop_sample'].boxes, self.args['stops'])
|
||||
self.assertEqual(row['result'], self.args['last_result'])
|
||||
|
||||
def test_reader_failure_clears_actual_producer_callback_finally(self):
|
||||
class ReaderFailure(Exception):pass
|
||||
class ReaderFailure(Exception):
|
||||
pass
|
||||
|
||||
class Core:
|
||||
_stop_ready_callback=lambda self,stops: None
|
||||
def observe(self,*a,**kw):raise ReaderFailure()
|
||||
tree=ast.parse((SOURCE/'producer.py').read_text())
|
||||
node=next(n for n in ast.walk(tree)if isinstance(n,ast.Try) and n.finalbody and
|
||||
any(isinstance(x,ast.Assign) and any(isinstance(v,ast.Attribute) and v.attr=='_stop_ready_callback' for v in x.targets)for x in n.finalbody))
|
||||
core=Core();ns={'core':core,'frame':None,'source_observed_ns':100}
|
||||
with self.assertRaises(ReaderFailure):exec(compile(ast.Module(body=[node],type_ignores=[]),'actual_finally','exec'),ns)
|
||||
def _stop_ready_callback(self, stops):
|
||||
pass
|
||||
|
||||
def observe(self, *a, **kw):
|
||||
raise ReaderFailure()
|
||||
|
||||
tree = ast.parse((SOURCE / 'producer.py').read_text())
|
||||
node = next(
|
||||
n
|
||||
for n in ast.walk(tree)
|
||||
if isinstance(n, ast.Try)
|
||||
and n.finalbody
|
||||
and any(isinstance(x, ast.Assign) and any(isinstance(v, ast.Attribute) and v.attr == '_stop_ready_callback' for v in x.targets) for x in n.finalbody)
|
||||
)
|
||||
core = Core()
|
||||
ns = {'core': core, 'frame': None, 'source_observed_ns': 100}
|
||||
with self.assertRaises(ReaderFailure):
|
||||
exec(compile(ast.Module(body=[node], type_ignores=[]), 'actual_finally', 'exec'), ns)
|
||||
self.assertIsNone(core._stop_ready_callback)
|
||||
|
||||
if __name__ == '__main__': unittest.main()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -692,6 +692,7 @@ class TestVisionProducer(unittest.TestCase):
|
||||
messages.append(messaging.log_from_bytes(event.to_bytes()).slcVisionObservation.vision)
|
||||
|
||||
clocks = [(1_000_000_000, 5_000_000_000), (1_000_000_000, 5_000_000_000),
|
||||
(1_010_000_000, 5_010_000_000), (1_010_000_000, 5_010_000_000),
|
||||
(1_010_000_000, 5_010_000_000)]
|
||||
from openpilot.starpilot.speed_limits.vision.stop_signs import CapturedStopDetectorOutputs
|
||||
with patch.object(core, '_detector_for_mode', return_value=CapturedStopDetectorOutputs(projection_fixture.Net('stop'))), \
|
||||
|
||||
@@ -11,7 +11,7 @@ from openpilot.starpilot.speed_limits.vision.tests import _identity_fixture as i
|
||||
from openpilot.starpilot.speed_limits.vision.traffic_signs import TrafficSignModelCore
|
||||
|
||||
|
||||
def probabilities(value=None, confidence=.8):
|
||||
def probabilities(value=None, confidence=0.8):
|
||||
scores = np.full(19, (1 - confidence) / 18, dtype=np.float32)
|
||||
scores[18 if value is None else model.US_CLASSIFIER_SPEED_VALUES.index(value)] = confidence
|
||||
return scores
|
||||
@@ -44,7 +44,7 @@ def core_fixture(reads, regulatory, proposals=None, cls=model.VisionModelCore):
|
||||
core.classifier_logits_layer = 'original_logits'
|
||||
core.classifier_input_size = 128
|
||||
core.last_classifier_forward_count = 0
|
||||
core.last_classifier_forward_duration_s = 0.
|
||||
core.last_classifier_forward_duration_s = 0.0
|
||||
core.last_classifier_advisory_rejected = False
|
||||
core.last_classifier_reject_dominant = False
|
||||
core.temporal_tracking_enabled = False
|
||||
@@ -53,7 +53,7 @@ def core_fixture(reads, regulatory, proposals=None, cls=model.VisionModelCore):
|
||||
core.stop_detections = ()
|
||||
core._stop_face_filter = speed_face.StopFaceVerifier(identity_fixture.Net("aux"))
|
||||
core._is_regulatory_speed_sign = lambda crop: regulatory[core.classifier_net.calls]
|
||||
core._collect_detector_classifier_proposals = lambda frame: proposals or [(.9, 0, (40, 20, 100, 100))]
|
||||
core._collect_detector_classifier_proposals = lambda frame: proposals or [(0.9, 0, (40, 20, 100, 100))]
|
||||
core._iter_school_zone_read_crops = lambda crop: []
|
||||
return core
|
||||
|
||||
@@ -65,14 +65,14 @@ class RetryConsistencyTests(unittest.TestCase):
|
||||
self.addCleanup(self.no_model_loads.stop)
|
||||
self.frame = np.zeros((200, 200, 3), dtype=np.uint8)
|
||||
self.reject = probabilities()
|
||||
self.numeric = probabilities(25, .75)
|
||||
self.numeric = probabilities(25, 0.75)
|
||||
|
||||
def pair(self, scores):
|
||||
return scores, scores
|
||||
|
||||
def test_conflicting_single_context_read_stops_at_original_short_circuit(self):
|
||||
reads = [self.pair(self.reject), self.pair(self.reject), self.pair(self.numeric)]
|
||||
proposals = [(.9, 0, (40, 20, 100, 100)), (.8, 0, (105, 20, 165, 100))]
|
||||
proposals = [(0.9, 0, (40, 20, 100, 100)), (0.8, 0, (105, 20, 165, 100))]
|
||||
core = core_fixture(reads, [False, True, True], proposals)
|
||||
self.assertIsNone(core.infer(self.frame))
|
||||
self.assertEqual(core.classifier_net.calls, 3)
|
||||
@@ -98,18 +98,18 @@ class RetryConsistencyTests(unittest.TestCase):
|
||||
self.assertEqual(core.infer(self.frame).speed_limit_mph, 25)
|
||||
|
||||
def test_school_fallback_is_unchanged(self):
|
||||
core = core_fixture([self.pair(self.reject), self.pair(self.reject), self.pair(self.numeric)],
|
||||
[False, True, True], [(.9, 2, (40, 20, 100, 100))])
|
||||
core = core_fixture([self.pair(self.reject), self.pair(self.reject), self.pair(self.numeric)], [False, True, True], [(0.9, 2, (40, 20, 100, 100))])
|
||||
self.assertEqual(core.infer(self.frame).speed_limit_mph, 25)
|
||||
self.assertEqual(core.classifier_net.calls, 3)
|
||||
|
||||
def test_new_winner_does_not_inherit_previous_proposal_conflict(self):
|
||||
low = probabilities(25, .6001)
|
||||
high = probabilities(25, .9)
|
||||
low = probabilities(25, 0.6001)
|
||||
high = probabilities(25, 0.9)
|
||||
reads = [self.pair(self.reject), self.pair(self.reject), self.pair(low)] + [self.pair(high)] * 3
|
||||
proposals = [(.9, 0, (40, 20, 100, 100)), (.8, 0, (105, 20, 165, 100))]
|
||||
proposals = [(0.9, 0, (40, 20, 100, 100)), (0.8, 0, (105, 20, 165, 100))]
|
||||
core = core_fixture(reads, [False, True, True, True, True, True], proposals)
|
||||
self.assertEqual(core.infer(self.frame).speed_limit_mph, 25)
|
||||
frame = np.random.default_rng(0).integers(0, 256, self.frame.shape, dtype=np.uint8)
|
||||
self.assertEqual(core.infer(frame).speed_limit_mph, 25)
|
||||
self.assertEqual(core.classifier_net.calls, 6)
|
||||
|
||||
def test_missing_original_logits_does_not_invent_primary_reject_evidence(self):
|
||||
@@ -153,7 +153,11 @@ class RetryConsistencyTests(unittest.TestCase):
|
||||
self.assertFalse(core.last_classifier_reject_dominant)
|
||||
core.last_classifier_reject_dominant = True
|
||||
from openpilot.starpilot.speed_limits.vision.stop_signs import CapturedStopDetectorOutputs
|
||||
with patch.object(core, '_detector_for_mode', return_value=CapturedStopDetectorOutputs(identity_fixture.Net('stop'))), patch.object(core, '_verified_model_path', return_value=Path('unused')):
|
||||
|
||||
with (
|
||||
patch.object(core, '_detector_for_mode', return_value=CapturedStopDetectorOutputs(identity_fixture.Net('stop'))),
|
||||
patch.object(core, '_verified_model_path', return_value=Path('unused')),
|
||||
):
|
||||
core.set_speed_enabled(False)
|
||||
self.assertFalse(core.last_classifier_reject_dominant)
|
||||
self.assertIsNone(core.classifier_net)
|
||||
@@ -161,10 +165,12 @@ class RetryConsistencyTests(unittest.TestCase):
|
||||
self.assertEqual(core.last_classifier_forward_count, 0)
|
||||
|
||||
def test_stop_only_constructor_has_no_numeric_evidence_or_numeric_load(self):
|
||||
with patch.object(model.VisionModelCore, '_load_net', return_value=identity_fixture.Net('stop')) as load, \
|
||||
patch.object(TrafficSignModelCore, '_verified_model_path', return_value=Path('unused')), \
|
||||
patch('openpilot.starpilot.speed_limits.vision.traffic_signs.SpeedFaceVerifier', side_effect=lambda _: identity_fixture.fake_identity()), \
|
||||
patch.object(TrafficSignModelCore, '_validate_model_abi'):
|
||||
with (
|
||||
patch.object(model.VisionModelCore, '_load_net', return_value=identity_fixture.Net('stop')) as load,
|
||||
patch.object(TrafficSignModelCore, '_verified_model_path', return_value=Path('unused')),
|
||||
patch('openpilot.starpilot.speed_limits.vision.traffic_signs.SpeedFaceVerifier', side_effect=lambda _: identity_fixture.fake_identity()),
|
||||
patch.object(TrafficSignModelCore, '_validate_model_abi'),
|
||||
):
|
||||
core = TrafficSignModelCore(is_metric=False, speed_enabled=False)
|
||||
self.assertFalse(core.last_classifier_reject_dominant)
|
||||
self.assertIsNone(core.classifier_net)
|
||||
|
||||
@@ -1,31 +1,94 @@
|
||||
"""Fresh typed synthetic envelopes: new full asset identity and independent STOP evidence."""
|
||||
|
||||
from types import SimpleNamespace as NS
|
||||
import unittest
|
||||
import hashlib
|
||||
from openpilot.cereal import messaging
|
||||
from openpilot.starpilot.speed_limits.vision import observation,producer,stop_observation
|
||||
from openpilot.starpilot.speed_limits.vision import observation, producer, stop_observation
|
||||
from openpilot.starpilot.speed_limits.vision.traffic_signs import TrafficSignModelCore
|
||||
from openpilot.starpilot.speed_limits.vision.speed_face import bundle_model_id,MODEL_SHA256
|
||||
BASE=100_000_000_000
|
||||
OFFSET=2_000_000_000
|
||||
OLD='52bb30f73bf0d166342f33d1b32b29b726bf036004e0359fef10ef7bf1f5556f:5d861067ab8f50986488f983cbc24f78527775d37af0b9740c5545d5f3173902'
|
||||
class Input(dict):
|
||||
def __init__(self,event,now):
|
||||
super().__init__(slcVisionObservation=event.slcVisionObservation);self.seen={'slcVisionObservation':True};self.valid=self.seen.copy();self.alive=self.seen.copy();self.logMonoTime={'slcVisionObservation':int(event.logMonoTime)};self.recv_time={'slcVisionObservation':now/1e9};self.recv_frame={'slcVisionObservation':2}
|
||||
def envelope(model_id=None):
|
||||
now=BASE+400_000_000;sample=stop_observation.StopEvidence('fresh_synthetic_camera','road',BASE,BASE+OFFSET,7,1344,760,(stop_observation.StopBox(.875,(780,200,840,260)),));captured=[]
|
||||
def send(service,event):
|
||||
assert service=='slcVisionObservation';event.logMonoTime=now
|
||||
if model_id is not None:event.slcVisionObservation.vision.modelId=model_id
|
||||
captured.append(messaging.log_from_bytes(event.to_bytes()))
|
||||
producer._send(NS(send=send),session=sample.session,status='unknown',stream=sample.stream,observed_ns=BASE,eof_boot_ns=BASE+OFFSET,frame_id=7,result=None,stop_sample=sample)
|
||||
return captured[0],sample,now
|
||||
class TestSpeedFaceNativeBinding(unittest.TestCase):
|
||||
def test_full_three_asset_identity_wire_and_None_speed_valid_STOP(self):
|
||||
assets=(TrafficSignModelCore.DETECTOR_SHA256,TrafficSignModelCore.CLASSIFIER_SHA256,MODEL_SHA256,TrafficSignModelCore.STOP_PROJECTION_SHA256);expected=hashlib.sha256(('vision_joint_stop_projection_identity_v1\n'+'\n'.join(assets)+'\n').encode()).hexdigest()+':'+TrafficSignModelCore.CLASSIFIER_SHA256;self.assertEqual(tuple(TrafficSignModelCore.ASSET_MANIFEST),assets);self.assertEqual(observation.MODEL_ID,TrafficSignModelCore.MODEL_ID);self.assertEqual(expected,observation.MODEL_ID);self.assertNotEqual(expected,OLD)
|
||||
event,sample,now=envelope();self.assertEqual(str(event.slcVisionObservation.vision.modelId),expected);self.assertEqual(len(expected),129);self.assertEqual(event.slcVisionObservation.vision.supportCount,0);decoded=stop_observation.read_stop_evidence(Input(event,now),now_ns=now,now_boot_ns=now+OFFSET,drive_id=BASE-1_000_000_000);self.assertEqual(decoded,sample);speed=observation.vision_observation(event.slcVisionObservation.vision,receipt_mono_ns=now,now_mono_ns=now,now_boot_ns=now+OFFSET,car_fingerprint='synthetic',drive_session='synthetic');self.assertEqual(speed.kind,observation.acc.ObservationKind.UNKNOWN)
|
||||
def test_old_two_asset_identity_rejected_by_actual_typed_consumers(self):
|
||||
event,_,now=envelope(OLD);self.assertIsNone(stop_observation.read_stop_evidence(Input(event,now),now_ns=now,now_boot_ns=now+OFFSET,drive_id=BASE-1_000_000_000));speed=observation.vision_observation(event.slcVisionObservation.vision,receipt_mono_ns=now,now_mono_ns=now,now_boot_ns=now+OFFSET,car_fingerprint='synthetic',drive_session='synthetic');self.assertEqual(speed.kind,observation.acc.ObservationKind.UNKNOWN)
|
||||
from openpilot.starpilot.speed_limits.vision.speed_face import bundle_model_id, MODEL_SHA256
|
||||
|
||||
def test_previous_three_asset_identity_rejected_by_actual_typed_consumers(self):
|
||||
previous=bundle_model_id(TrafficSignModelCore.DETECTOR_SHA256,TrafficSignModelCore.CLASSIFIER_SHA256);self.assertNotEqual(previous,observation.MODEL_ID);event,_,now=envelope(previous);self.assertIsNone(stop_observation.read_stop_evidence(Input(event,now),now_ns=now,now_boot_ns=now+OFFSET,drive_id=BASE-1_000_000_000));speed=observation.vision_observation(event.slcVisionObservation.vision,receipt_mono_ns=now,now_mono_ns=now,now_boot_ns=now+OFFSET,car_fingerprint='synthetic',drive_session='synthetic');self.assertEqual(speed.kind,observation.acc.ObservationKind.UNKNOWN)
|
||||
BASE = 100_000_000_000
|
||||
OFFSET = 2_000_000_000
|
||||
OLD = '52bb30f73bf0d166342f33d1b32b29b726bf036004e0359fef10ef7bf1f5556f:5d861067ab8f50986488f983cbc24f78527775d37af0b9740c5545d5f3173902'
|
||||
|
||||
|
||||
class Input(dict):
|
||||
def __init__(self, event, now):
|
||||
super().__init__(slcVisionObservation=event.slcVisionObservation)
|
||||
self.seen = {'slcVisionObservation': True}
|
||||
self.valid = self.seen.copy()
|
||||
self.alive = self.seen.copy()
|
||||
self.logMonoTime = {'slcVisionObservation': int(event.logMonoTime)}
|
||||
self.recv_time = {'slcVisionObservation': now / 1e9}
|
||||
self.recv_frame = {'slcVisionObservation': 2}
|
||||
|
||||
|
||||
def envelope(model_id=None):
|
||||
now = BASE + 400_000_000
|
||||
sample = stop_observation.StopEvidence(
|
||||
'fresh_synthetic_camera', 'road', BASE, BASE + OFFSET, 7, 1344, 760, (stop_observation.StopBox(0.875, (780, 200, 840, 260)),)
|
||||
)
|
||||
captured = []
|
||||
|
||||
def send(service, event):
|
||||
assert service == 'slcVisionObservation'
|
||||
event.logMonoTime = now
|
||||
if model_id is not None:
|
||||
event.slcVisionObservation.vision.modelId = model_id
|
||||
captured.append(messaging.log_from_bytes(event.to_bytes()))
|
||||
|
||||
producer._send(
|
||||
NS(send=send),
|
||||
session=sample.session,
|
||||
status='unknown',
|
||||
stream=sample.stream,
|
||||
observed_ns=BASE,
|
||||
eof_boot_ns=BASE + OFFSET,
|
||||
frame_id=7,
|
||||
result=None,
|
||||
stop_sample=sample,
|
||||
)
|
||||
return captured[0], sample, now
|
||||
|
||||
|
||||
class TestSpeedFaceNativeBinding(unittest.TestCase):
|
||||
def test_full_three_asset_identity_wire_and_None_speed_valid_STOP(self):
|
||||
assets = (TrafficSignModelCore.DETECTOR_SHA256, TrafficSignModelCore.CLASSIFIER_SHA256, MODEL_SHA256, TrafficSignModelCore.STOP_PROJECTION_SHA256)
|
||||
expected = (
|
||||
hashlib.sha256(('vision_joint_stop_projection_identity_v1\n' + '\n'.join(assets) + '\n').encode()).hexdigest()
|
||||
+ ':'
|
||||
+ TrafficSignModelCore.CLASSIFIER_SHA256
|
||||
)
|
||||
self.assertEqual(tuple(TrafficSignModelCore.ASSET_MANIFEST), assets)
|
||||
self.assertEqual(observation.MODEL_ID, TrafficSignModelCore.MODEL_ID)
|
||||
self.assertEqual(expected, observation.MODEL_ID)
|
||||
self.assertNotEqual(expected, OLD)
|
||||
event, sample, now = envelope()
|
||||
self.assertEqual(str(event.slcVisionObservation.vision.modelId), expected)
|
||||
self.assertEqual(len(expected), 129)
|
||||
self.assertEqual(event.slcVisionObservation.vision.supportCount, 0)
|
||||
decoded = stop_observation.read_stop_evidence(Input(event, now), now_ns=now, now_boot_ns=now + OFFSET, drive_id=BASE - 1_000_000_000)
|
||||
self.assertEqual(decoded, sample)
|
||||
speed = observation.vision_observation(
|
||||
event.slcVisionObservation.vision, receipt_mono_ns=now, now_mono_ns=now, now_boot_ns=now + OFFSET, car_fingerprint='synthetic', drive_session='synthetic'
|
||||
)
|
||||
self.assertEqual(speed.kind, observation.acc.ObservationKind.UNKNOWN)
|
||||
|
||||
def test_old_two_asset_identity_rejected_by_actual_typed_consumers(self):
|
||||
event, _, now = envelope(OLD)
|
||||
self.assertIsNone(stop_observation.read_stop_evidence(Input(event, now), now_ns=now, now_boot_ns=now + OFFSET, drive_id=BASE - 1_000_000_000))
|
||||
speed = observation.vision_observation(
|
||||
event.slcVisionObservation.vision, receipt_mono_ns=now, now_mono_ns=now, now_boot_ns=now + OFFSET, car_fingerprint='synthetic', drive_session='synthetic'
|
||||
)
|
||||
self.assertEqual(speed.kind, observation.acc.ObservationKind.UNKNOWN)
|
||||
|
||||
def test_previous_three_asset_identity_rejected_by_actual_typed_consumers(self):
|
||||
previous = bundle_model_id(TrafficSignModelCore.DETECTOR_SHA256, TrafficSignModelCore.CLASSIFIER_SHA256)
|
||||
self.assertNotEqual(previous, observation.MODEL_ID)
|
||||
event, _, now = envelope(previous)
|
||||
self.assertIsNone(stop_observation.read_stop_evidence(Input(event, now), now_ns=now, now_boot_ns=now + OFFSET, drive_id=BASE - 1_000_000_000))
|
||||
speed = observation.vision_observation(
|
||||
event.slcVisionObservation.vision, receipt_mono_ns=now, now_mono_ns=now, now_boot_ns=now + OFFSET, car_fingerprint='synthetic', drive_session='synthetic'
|
||||
)
|
||||
self.assertEqual(speed.kind, observation.acc.ObservationKind.UNKNOWN)
|
||||
|
||||
@@ -27,10 +27,17 @@ class TestStopProjection(unittest.TestCase):
|
||||
owner = self
|
||||
|
||||
class Identity:
|
||||
def __init__(self, _): self.net = object()
|
||||
def begin_frame(self): pass
|
||||
def end_frame(self): pass
|
||||
def close(self): self.net = None
|
||||
def __init__(self, _):
|
||||
self.net = object()
|
||||
|
||||
def begin_frame(self):
|
||||
pass
|
||||
|
||||
def end_frame(self):
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
self.net = None
|
||||
|
||||
def load(cls, name, expected):
|
||||
owner.loads.append(name)
|
||||
@@ -48,22 +55,24 @@ class TestStopProjection(unittest.TestCase):
|
||||
wrapped.setInput(np.zeros((1, 3, 256, 256), np.float32))
|
||||
self.assertEqual(wrapped.forward_stops().shape, (1, 5, 4096))
|
||||
self.assertEqual(raw.calls, [['stop/output0']])
|
||||
with self.assertRaises(ValueError): wrapped.forward()
|
||||
with self.assertRaises(ValueError):
|
||||
wrapped.forward()
|
||||
|
||||
def test_stop_output_rejects_invalid_shape_dtype_or_nonfinite_values(self):
|
||||
values = [np.zeros((1, 5, 1344), np.float32), np.zeros((1, 5, 4096), np.float64),
|
||||
np.full((1, 5, 4096), np.nan, np.float32)]
|
||||
values = [np.zeros((1, 5, 1344), np.float32), np.zeros((1, 5, 4096), np.float64), np.full((1, 5, 4096), np.nan, np.float32)]
|
||||
for value in values:
|
||||
with self.subTest(shape=value.shape, dtype=value.dtype):
|
||||
wrapped = CapturedStopDetectorOutputs(SimpleNamespace(forward=lambda _: [value]))
|
||||
wrapped = CapturedStopDetectorOutputs(SimpleNamespace(forward=lambda _, value=value: [value]))
|
||||
wrapped.stops = 'previous'
|
||||
with self.assertRaises(ValueError): wrapped.forward_stops()
|
||||
with self.assertRaises(ValueError):
|
||||
wrapped.forward_stops()
|
||||
self.assertIsNone(wrapped.stops)
|
||||
|
||||
def test_stop_output_requires_exactly_one_result(self):
|
||||
for outputs in ([], [None, None], None):
|
||||
wrapped = CapturedStopDetectorOutputs(SimpleNamespace(forward=lambda _, value=outputs: value))
|
||||
with self.assertRaises(ValueError): wrapped.forward_stops()
|
||||
with self.assertRaises(ValueError):
|
||||
wrapped.forward_stops()
|
||||
|
||||
def test_stop_capture_is_owned_and_cleared_by_new_input(self):
|
||||
wrapped = CapturedStopDetectorOutputs(self.raw_net('traffic_signs_us_stop_detector.onnx'))
|
||||
@@ -108,7 +117,8 @@ class TestStopProjection(unittest.TestCase):
|
||||
original = core.net
|
||||
core.stop_detections = ('previous',)
|
||||
with patch.object(core, '_validate_candidate', side_effect=ValueError('Invalid candidate')):
|
||||
with self.assertRaises(ValueError): core.set_speed_enabled(False)
|
||||
with self.assertRaises(ValueError):
|
||||
core.set_speed_enabled(False)
|
||||
self.assertIs(core.net, original)
|
||||
self.assertTrue(core.speed_enabled)
|
||||
self.assertEqual(core.stop_detections, ())
|
||||
@@ -119,6 +129,7 @@ class TestStopProjection(unittest.TestCase):
|
||||
self.assertEqual(core.MODEL_ID, observation.MODEL_ID)
|
||||
with patch.object(core, '_verified_model_path') as verify, patch.object(core, '_detector_for_mode'):
|
||||
core._load_detector_net()
|
||||
self.assertEqual([call.args[0] for call in verify.call_args_list],
|
||||
['traffic_signs_us_detector.onnx', 'traffic_signs_us_stop_detector.onnx', 'traffic_sign_identity.onnx'])
|
||||
self.assertEqual(
|
||||
[call.args[0] for call in verify.call_args_list], ['traffic_signs_us_detector.onnx', 'traffic_signs_us_stop_detector.onnx', 'traffic_sign_identity.onnx']
|
||||
)
|
||||
self.assertEqual(verify.call_args_list[1].args[1], core.STOP_PROJECTION_SHA256)
|
||||
|
||||
@@ -13,14 +13,25 @@ from openpilot.starpilot.speed_limits.vision.tests import _identity_fixture as i
|
||||
|
||||
class TestStopSigns(unittest.TestCase):
|
||||
def decode(self, raw):
|
||||
return decode_stop_signs(raw, ratio=1, pad_width=0, pad_height=0, region_width=256, region_height=256,
|
||||
origin_x=100, origin_y=20, full_frame_width=600, full_frame_height=400, threshold=.5)
|
||||
return decode_stop_signs(
|
||||
raw,
|
||||
ratio=1,
|
||||
pad_width=0,
|
||||
pad_height=0,
|
||||
region_width=256,
|
||||
region_height=256,
|
||||
origin_x=100,
|
||||
origin_y=20,
|
||||
full_frame_width=600,
|
||||
full_frame_height=400,
|
||||
threshold=0.5,
|
||||
)
|
||||
|
||||
def test_stop_output_is_independent_of_speed_and_uses_one_forward(self):
|
||||
speed = np.zeros((1, 5, 1344), np.float32)
|
||||
speed[0, 4, 0] = .3
|
||||
speed[0, 4, 0] = 0.3
|
||||
stops = np.zeros((1, 5, 4096), np.float32)
|
||||
stops[0, :, 0] = [128, 128, 40, 40, .99]
|
||||
stops[0, :, 0] = [128, 128, 40, 40, 0.99]
|
||||
calls = []
|
||||
net = SignDetectorOutputs(SimpleNamespace(forward=lambda names: calls.append(names) or (speed, stops), setInput=lambda _: None))
|
||||
self.assertIs(net.forward(), speed)
|
||||
@@ -41,7 +52,7 @@ class TestStopSigns(unittest.TestCase):
|
||||
def test_stop_work_is_bounded_before_nms(self):
|
||||
raw = np.zeros((1, 5, 4096), np.float32)
|
||||
raw[0, :4] = np.array([128, 128, 20, 20])[:, None]
|
||||
raw[0, 4] = np.linspace(.6, .99, 4096)
|
||||
raw[0, 4] = np.linspace(0.6, 0.99, 4096)
|
||||
with patch('openpilot.starpilot.speed_limits.vision.stop_signs.cv2.dnn.NMSBoxes', return_value=[]) as nms:
|
||||
self.decode(raw)
|
||||
self.assertEqual(len(nms.call_args.args[0]), 32)
|
||||
@@ -52,17 +63,17 @@ class TestStopSigns(unittest.TestCase):
|
||||
core.history = deque()
|
||||
core.net = SimpleNamespace(rgb256=None)
|
||||
core._stop_face_filter = StopFaceVerifier(identity_fixture.Net('aux'))
|
||||
core.stop_detections = (StopDetection(.9, (10, 10, 30, 30)),)
|
||||
core.stop_detections = (StopDetection(0.9, (10, 10, 30, 30)),)
|
||||
core.reset()
|
||||
self.assertEqual(core.stop_detections, ())
|
||||
core.stop_detections = (StopDetection(.9, (10, 10, 30, 30)),)
|
||||
core.stop_detections = (StopDetection(0.9, (10, 10, 30, 30)),)
|
||||
with self.assertRaises(ValueError):
|
||||
core.infer(np.zeros((0, 0, 3), np.uint8))
|
||||
self.assertEqual(core.stop_detections, ())
|
||||
|
||||
def test_sign_api_returns_both_outputs_without_an_extra_inference(self):
|
||||
core = VisionModelCore.__new__(VisionModelCore)
|
||||
stop = StopDetection(.9, (100, 20, 140, 60))
|
||||
stop = StopDetection(0.9, (100, 20, 140, 60))
|
||||
core.stop_detections = (stop,)
|
||||
with patch.object(core, 'infer', return_value=None) as infer:
|
||||
result = core.infer_signs(np.zeros((256, 256, 3), np.uint8))
|
||||
@@ -72,6 +83,7 @@ class TestStopSigns(unittest.TestCase):
|
||||
|
||||
def test_shipped_joint_model_runs_and_preserves_speed_output(self):
|
||||
from openpilot.starpilot.speed_limits.vision.model import VisionModelCore as SpeedCore
|
||||
|
||||
frame = np.zeros((760, 1344, 3), np.uint8)
|
||||
joint = VisionModelCore(is_metric=False)
|
||||
speed = SpeedCore(is_metric=False)
|
||||
@@ -83,6 +95,7 @@ class TestStopSigns(unittest.TestCase):
|
||||
|
||||
def test_stop_only_does_not_load_or_run_the_numeric_classifier(self):
|
||||
from openpilot.starpilot.speed_limits.vision import model as vision_model
|
||||
|
||||
calls = []
|
||||
|
||||
class StopNet(identity_fixture.Net):
|
||||
@@ -97,10 +110,17 @@ class TestStopSigns(unittest.TestCase):
|
||||
self.assertEqual(expected, VisionModelCore.STOP_PROJECTION_SHA256)
|
||||
return StopNet('joint')
|
||||
|
||||
with patch.object(vision_model.VisionModelCore, '_load_net', classmethod(checked)), patch.object(VisionModelCore, '_verified_model_path'), patch('openpilot.starpilot.speed_limits.vision.traffic_signs.SpeedFaceVerifier', side_effect=lambda _: identity_fixture.fake_identity()):
|
||||
with (
|
||||
patch.object(vision_model.VisionModelCore, '_load_net', classmethod(checked)),
|
||||
patch.object(VisionModelCore, '_verified_model_path'),
|
||||
patch('openpilot.starpilot.speed_limits.vision.traffic_signs.SpeedFaceVerifier', side_effect=lambda _: identity_fixture.fake_identity()),
|
||||
):
|
||||
core = VisionModelCore(is_metric=False, speed_enabled=False)
|
||||
frame = np.zeros((760, 1344, 3), np.uint8)
|
||||
with patch.object(core, '_classify_speed_limit_from_model', side_effect=AssertionError('Unused speed classifier')), patch.object(core._stop_face_filter.net, 'forward', side_effect=AssertionError('Unexpected identity work')):
|
||||
with (
|
||||
patch.object(core, '_classify_speed_limit_from_model', side_effect=AssertionError('Unused speed classifier')),
|
||||
patch.object(core._stop_face_filter.net, 'forward', side_effect=AssertionError('Unexpected identity work')),
|
||||
):
|
||||
self.assertIsNone(core.observe(frame, now=1.0))
|
||||
self.assertIsNone(core.classifier_net)
|
||||
self.assertIsNone(core._speed_face_filter)
|
||||
|
||||
@@ -21,7 +21,8 @@ class TrafficSignModelCore(model.VisionModelCore):
|
||||
DETECTOR_SHA256 = '52bb30f73bf0d166342f33d1b32b29b726bf036004e0359fef10ef7bf1f5556f'
|
||||
STOP_PROJECTION_SHA256 = "a744df1b585bfd80a2a983229509f52bf2f09714d9df011109ad1b8b875aaecc"
|
||||
ASSET_MANIFEST = (DETECTOR_SHA256, model.VisionModelCore.CLASSIFIER_SHA256, MODEL_SHA256, STOP_PROJECTION_SHA256)
|
||||
MODEL_ID = hashlib.sha256(("vision_joint_stop_projection_identity_v1\n" + "\n".join(ASSET_MANIFEST) + "\n").encode()).hexdigest() + ":" + model.VisionModelCore.CLASSIFIER_SHA256
|
||||
MODEL_ID = (hashlib.sha256(("vision_joint_stop_projection_identity_v1\n" + "\n".join(ASSET_MANIFEST) + "\n").encode()).hexdigest() +
|
||||
":" + model.VisionModelCore.CLASSIFIER_SHA256)
|
||||
STOP_THRESHOLD = .6
|
||||
_speed_face_filter: SpeedFaceVerifier | None = None
|
||||
_stop_face_filter: StopFaceVerifier | None = None
|
||||
|
||||
@@ -112,6 +112,7 @@ def test_renderer_dispatches_each_widget_in_saved_order(profile, viewport):
|
||||
method = next(node for node in ast.walk(tree) if isinstance(node, ast.FunctionDef) and node.name == '_ordered_widgets')
|
||||
namespace: dict[str, Any] = {'CLOCK_WIDGET': CLOCK_WIDGET, 'MODE_WIDGET': MODE_WIDGET, 'placement': placement, 'widget_order': widget_order,
|
||||
'RAIL_WIDGETS': RAIL_WIDGETS, 'STOPPED_TIMER_WIDGET': STOPPED_TIMER_WIDGET,
|
||||
'render_rear_fog': lambda *args: seen.append('rear_fog'),
|
||||
'manual_slc_confirmation': lambda profile, state: False, 'AlertSize': NS(NONE='none', FULL='full'), 'CameraViewChoice': NS(NONE='none'),
|
||||
'rl': NS(Rectangle=lambda x, y, width, height: NS(x=x, y=y, width=width, height=height))}
|
||||
exec(compile(ast.Module(body=[method], type_ignores=[]), str(source), 'exec'), namespace)
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"protected_inventory_sha256": "6d06f8093d88df04b4b2966c886c25ad3d978572ae815af3755706aa735463fd", "schema_version": 1}
|
||||
{"protected_inventory_sha256": "e6d1299870cec364a7d5da259b7665b071fd7c089642009bdb44273397964f27", "schema_version": 1}
|
||||
|
||||
Reference in New Issue
Block a user