Repair integration CI fixtures and reviewed radar reference

This commit is contained in:
firestar5683
2026-11-10 14:49:55 -06:00
parent 736a90396d
commit 4b133c8785
23 changed files with 330 additions and 138 deletions
+1 -1
View File
@@ -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)))
@@ -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
View File
@@ -1 +1 @@
{"protected_inventory_sha256": "6d06f8093d88df04b4b2966c886c25ad3d978572ae815af3755706aa735463fd", "schema_version": 1}
{"protected_inventory_sha256": "e6d1299870cec364a7d5da259b7665b071fd7c089642009bdb44273397964f27", "schema_version": 1}