Skip terminal sign reads that cannot be admitted

This commit is contained in:
firestar5683
2026-11-11 14:04:34 +00:00
committed by Dominick Thompson
parent 31c350f09a
commit 4954cf4d16
3 changed files with 201 additions and 1 deletions
@@ -946,6 +946,33 @@ class VisionModelCore:
if face_filter is not None and face_filter.reject_once((proposal_confidence, class_id, (x1, y1, x2, y2))):
self.latest_detector_proposal = None
return None
if (frame_bgr.dtype == np.uint8 and type(proposals) is list and proposal_index == len(proposals) - 1 and
expansions is DETECTOR_CLASSIFIER_EXPANSIONS and
(expand_left, expand_top, expand_right, expand_bottom, expansion_weight) == expansions[0] and
class_id == 0 and not speed_scores and not raw_is_regulatory and
getattr(self, 'classifier_net', None) is not None and
proposal_confidence < DETECTOR_CLASSIFIER_STRONG_MODEL_MIN_PROPOSAL_CONFIDENCE and
not (x1 >= frame_width * DETECTOR_CLASSIFIER_TRUSTED_MODEL_MIN_X_RATIO and
box_height <= DETECTOR_CLASSIFIER_TRUSTED_MODEL_MAX_HEIGHT and
proposal_area_ratio <= DETECTOR_CLASSIFIER_TRUSTED_MODEL_MAX_AREA_RATIO and
proposal_confidence >= DETECTOR_CLASSIFIER_TRUSTED_MODEL_MIN_PROPOSAL_CONFIDENCE)):
from openpilot.starpilot.speed_limits.vision.speed_face import SpeedFaceVerifier, SharedSpeedFaceVerifier
if (type(face_filter) in (SpeedFaceVerifier, SharedSpeedFaceVerifier) and
getattr(face_filter.reject_once, '__func__', None) is SpeedFaceVerifier.reject_once and
face_filter._checked is True and
getattr(self._is_regulatory_speed_sign, '__func__', None) is
VisionModelCore._is_regulatory_speed_sign and
getattr(self._classify_speed_limit_from_model, '__func__', None) is
VisionModelCore._classify_speed_limit_from_model):
for left, top, right, bottom, _ in expansions[1:]:
remaining_crop = frame_bgr[
max(int(y1 - box_height * top), 0):min(int(y2 + box_height * bottom), frame_height),
max(int(x1 - box_width * left), 0):min(int(x2 + box_width * right), frame_width),
]
if remaining_crop.size and self._is_regulatory_speed_sign(remaining_crop):
break
else:
break
if (type(proposals) is list and proposal_index == len(proposals) - 1 and
expansions is DETECTOR_CLASSIFIER_EXPANSIONS and
(expand_left, expand_top, expand_right, expand_bottom, expansion_weight) == expansions[-1] and
@@ -0,0 +1,173 @@
"""Synthetic pixels/default methods and fake outputs; no model loading or inference."""
from types import SimpleNamespace
import unittest
from unittest.mock import patch
import numpy as np
from openpilot.starpilot.speed_limits.vision import model, speed_face
class Reader:
def __init__(self, advisory=False, confidence=.999):
self.calls = 0
self.advisory = advisory
self.confidence = confidence
def setInput(self, value):
self.input = value
def forward(self, names=None):
self.calls += 1
values = np.zeros((1, 19), np.float32)
values[0, model.US_CLASSIFIER_SPEED_VALUES.index(40)] = self.confidence
values[0, 18] = 1 - self.confidence
if self.advisory:
rejected = np.zeros((1, 19), np.float32)
rejected[0, 18] = .95
rejected[0, model.US_CLASSIFIER_SPEED_VALUES.index(40)] = .05
return [rejected, values]
return values
class Head:
def __init__(self, logit):
self.logit = logit
self.calls = 0
def setInput(self, value):
self.input = value
def forward(self, names):
assert names == ['speed_face_logit']
self.calls += 1
return [np.array([[self.logit]], np.float32)]
@unittest.skipIf(model.cv2 is None, 'OpenCV is optional')
class TestTerminalMaterialFalse(unittest.TestCase):
def setup_case(self, *, proposals=None, control=False, face_type=speed_face.SpeedFaceVerifier):
core = object.__new__(model.VisionModelCore)
core.latest_detector_proposal = None
core.temporal_tracking_enabled = True
core.classifier_net = Reader()
core.classifier_input_size = 128
core.classifier_logits_layer = None
core.last_classifier_forward_count = 0
core.last_classifier_forward_duration_s = 0.
core.last_classifier_advisory_rejected = False
core.last_classifier_reject_dominant = False
face = object.__new__(face_type)
face.begin_frame()
face._checked = True
face.net = Head(8)
core._speed_face_filter = face
if control:
core.detector_classifier_expansions = list(model.DETECTOR_CLASSIFIER_EXPANSIONS)
proposals = proposals if proposals is not None else [(.4, 0, (1400, 100, 1490, 237))]
frame = np.zeros((1100, 2000, 3), np.uint8)
return core, face, proposals, frame
def run_case(self, case):
core, _, proposals, frame = case
with patch.object(core, '_collect_detector_classifier_proposals', return_value=proposals):
result = core._detect_sign_from_detector_classifier(frame)
return result, core.latest_detector_proposal
def test_terminal_canonical_elides_only_unadmittable_reader_work(self):
actual, control = self.setup_case(), self.setup_case(control=True)
self.assertEqual(self.run_case(actual), self.run_case(control))
self.assertEqual(actual[0].classifier_net.calls, 0)
self.assertEqual(control[0].classifier_net.calls, 3)
self.assertFalse(actual[0].last_classifier_advisory_rejected)
def test_first_original_identity_keep_or_veto_is_preserved(self):
for logit in (-8, 8):
for kind in (speed_face.SpeedFaceVerifier, speed_face.SharedSpeedFaceVerifier):
with self.subTest(logit=logit, kind=kind):
case = self.setup_case(face_type=kind)
core, face, proposals, _ = case
face._checked = False
face.net = Head(logit)
face._proposals[proposals[0]] = [(np.zeros((3, 256, 256), np.float32), np.array([80, 80, 100, 110], np.float32), 13)]
self.assertIsNone(self.run_case(case)[0])
self.assertEqual(face.net.calls, 1)
self.assertEqual(face.last_dense_id, 13)
self.assertEqual(core.classifier_net.calls, 0)
def test_later_material_true_falls_back_and_keeps_output(self):
actual, control = self.setup_case(), self.setup_case(control=True)
for case in (actual, control):
case[3][220:271, 1430:1480] = 255
self.assertEqual(self.run_case(actual), self.run_case(control))
self.assertGreater(actual[0].classifier_net.calls, 0)
def test_custom_material_and_reader_methods_fall_back(self):
for name in ('_is_regulatory_speed_sign', '_classify_speed_limit_from_model'):
case = self.setup_case()
with patch.object(case[0], name, wraps=getattr(case[0], name)):
self.run_case(case)
self.assertEqual(case[0].classifier_net.calls, 2)
def test_stateful_advisory_veto_callback_keeps_original_order(self):
case = self.setup_case()
core = case[0]
core.classifier_net = Reader(advisory=True)
core.classifier_logits_layer = 'original'
calls = []
def veto(candidate):
calls.append(candidate)
return len(calls) == 2
core._speed_face_filter = SimpleNamespace(reject_once=veto, _checked=True)
self.assertIsNone(self.run_case(case)[0])
self.assertEqual(len(calls), 1)
self.assertEqual(core.classifier_net.calls, 1)
self.assertTrue(core.last_classifier_advisory_rejected)
def test_nonlast_strong_trusted_and_custom_expansions_keep_reads(self):
variants = [
{'proposals': [(.4, 0, (1400, 100, 1490, 237)), (.3, 0, (1550, 150, 1650, 300))]},
{'proposals': [(.6, 0, (1400, 100, 1490, 237))]},
{'proposals': [(.18, 0, (1040, 100, 1120, 155))]},
{'control': True},
]
for kwargs in variants:
with self.subTest(kwargs=kwargs):
case = self.setup_case(**kwargs)
self.run_case(case)
self.assertGreaterEqual(case[0].classifier_net.calls, 3)
def test_noncanonical_face_and_unchecked_custom_method_fall_back(self):
class CustomFace(speed_face.SpeedFaceVerifier):
pass
for kind in (CustomFace, speed_face.SpeedFaceVerifier):
case = self.setup_case(face_type=kind)
if kind is speed_face.SpeedFaceVerifier:
with patch.object(case[1], 'reject_once', return_value=False):
self.run_case(case)
else:
self.run_case(case)
self.assertEqual(case[0].classifier_net.calls, 2)
case = self.setup_case()
case = (case[0], case[1], case[2], case[3].astype(np.float32))
self.run_case(case)
self.assertEqual(case[0].classifier_net.calls, 2)
def test_previous_winner_and_latest_proposal_match_control(self):
proposals = [(.4, 0, (1100, 400, 1190, 537)), (.3, 0, (1400, 100, 1490, 237))]
actual = self.setup_case(proposals=proposals)
control = self.setup_case(proposals=proposals, control=True)
for case in (actual, control):
case[0].classifier_net = Reader(confidence=.6)
case[3][400:537, 1100:1190] = 255
case[3][430:485, 1130:1160] = 0
actual_result = self.run_case(actual)
self.assertEqual(actual_result, self.run_case(control))
self.assertIsNotNone(actual_result[0])
self.assertEqual(actual_result[0].speed_limit_mph, 40)
self.assertGreater(actual[0].classifier_net.calls, 0)
self.assertLess(actual[0].classifier_net.calls, control[0].classifier_net.calls)
+1 -1
View File
@@ -1 +1 @@
{"protected_inventory_sha256": "14e194326bcf0efef228ba09eaa7695844b0917bf1af867aa5ba510889396221", "schema_version": 1}
{"protected_inventory_sha256": "5cfc0547e6a1bd3cea5de0b6a48723fa8cac005413015faaa547712c468db4ad", "schema_version": 1}