Avoid numeric reads that cannot change sign results

This commit is contained in:
firestar5683
2026-11-11 00:53:31 +00:00
committed by Dominick Thompson
parent d574b959c1
commit 7f2273aabf
3 changed files with 132 additions and 2 deletions
@@ -805,7 +805,8 @@ class VisionModelCore:
cached_bytes += crop.nbytes
return result
for proposal_confidence, class_id, (x1, y1, x2, y2) in self._collect_detector_classifier_proposals(frame_bgr):
proposals = self._collect_detector_classifier_proposals(frame_bgr)
for proposal_index, (proposal_confidence, class_id, (x1, y1, x2, y2)) in enumerate(proposals):
if class_id == 1:
continue
if class_id == 0:
@@ -945,6 +946,16 @@ 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 (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
getattr(self, 'classifier_net', None) is not None and class_id == 0 and not speed_scores and not raw_is_regulatory 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)):
break
model_read = classify_crop(sign_crop)
if not any((expand_left, expand_top, expand_right, expand_bottom)):
primary_reject_dominant = bool(
@@ -0,0 +1,119 @@
import unittest
from types import SimpleNamespace
import numpy as np
from openpilot.starpilot.speed_limits.vision import model
class TestFinalUnusableCrop(unittest.TestCase):
def run_case(self, *, proposals=None, regulatory=(False, False, False), reads=None,
control=False, custom=False, net=True, primary_reject=False, veto_at=None):
core = object.__new__(model.VisionModelCore)
core.latest_detector_proposal = None
core.temporal_tracking_enabled = True
core.last_classifier_advisory_rejected = False
core.last_classifier_reject_dominant = False
if net:
core.classifier_net = object()
proposals = proposals if proposals is not None else [(.4, 0, (1400, 100, 1490, 237))]
core._collect_detector_classifier_proposals = lambda frame: proposals
if control or custom:
core.detector_classifier_expansions = list(model.DETECTOR_CLASSIFIER_EXPANSIONS)
core._iter_school_zone_read_crops = lambda crop: [(crop, 1.)]
regions = iter(regulatory)
region_count = [0]
def color(crop):
region_count[0] += 1
return next(regions)
core._is_regulatory_speed_sign = color
calls, identity_calls = [], []
values = iter(reads) if reads is not None else None
def classify(crop):
index = len(calls)
calls.append(region_count[0])
result = next(values) if values is not None else (40, .8)
core.last_classifier_advisory_rejected = result is None and not primary_reject
core.last_classifier_reject_dominant = primary_reject and index == 0
return result
def reject(candidate):
identity_calls.append(candidate)
return len(identity_calls) == veto_at
core._classify_speed_limit_from_model = classify
core._speed_face_filter = SimpleNamespace(reject_once=reject)
result = core._detect_sign_from_detector_classifier(np.zeros((1100, 2000, 3), dtype=np.uint8))
return result, core.latest_detector_proposal, calls, identity_calls
def test_only_final_unusable_read_is_skipped(self):
actual = self.run_case()
control = self.run_case(control=True)
self.assertEqual(actual[:2], control[:2])
self.assertEqual(actual[2], [1, 2])
self.assertEqual(control[2], [1, 2, 3])
self.assertEqual(actual[3], control[3])
def test_eligibility_counterexamples_keep_all_reads(self):
cases = (
('strong_boundary', {'proposals': [(.60, 0, (1400, 100, 1490, 237))]}),
('trusted_shape_boundary', {'proposals': [(.18, 0, (1040, 100, 1120, 155))]}),
('tiny_trusted', {'proposals': [(.18, 0, (1040, 100, 1060, 130))]}),
('accumulated_scores', {'regulatory': (True, False, False)}),
('regulatory', {'regulatory': (True, True, True)}),
('custom_expansions', {'custom': True}),
('missing_net', {'net': False}),
)
for name, kwargs in cases:
with self.subTest(name=name):
actual = self.run_case(**kwargs)
control = self.run_case(control=True, **kwargs)
self.assertEqual(actual[:2], control[:2])
self.assertEqual(actual[2], [1, 2, 3])
self.assertEqual(actual[3], control[3])
def test_nonfinal_proposal_keeps_its_final_read(self):
proposals = [(.4, 0, (1400, 100, 1490, 237)), (.3, 0, (1550, 150, 1650, 300))]
actual = self.run_case(proposals=proposals, regulatory=(False,) * 6)
control = self.run_case(proposals=proposals, regulatory=(False,) * 6, control=True)
self.assertEqual(actual[:2], control[:2])
self.assertEqual(actual[2], [1, 2, 3, 4, 5])
self.assertEqual(control[2], [1, 2, 3, 4, 5, 6])
def test_prior_best_and_retry_conflict_are_preserved(self):
proposals = [(.4, 0, (1400, 100, 1490, 237)), (.3, 0, (1550, 150, 1650, 300))]
cases = (
((True, True, True, False, False, False), ((40, .5),) * 3 + ((50, .8),) * 3, False, 40),
((False, True, True, False, False, False), (None, (40, .5), None) + ((50, .8),) * 3, True, None),
)
for regulatory, reads, primary_reject, expected in cases:
with self.subTest(retry_conflict=primary_reject):
kwargs = {'proposals': proposals, 'regulatory': regulatory, 'reads': reads, 'primary_reject': primary_reject}
actual = self.run_case(**kwargs)
control = self.run_case(control=True, **kwargs)
self.assertEqual(actual[:2], control[:2])
self.assertEqual(None if actual[0] is None else actual[0].speed_limit_mph, expected)
self.assertEqual(len(actual[2]), 5)
self.assertEqual(len(control[2]), 6)
def test_school_class_is_unchanged(self):
reads = (None,) * len(model.SCHOOL_ZONE_DIRECT_EXPANSIONS) + ((40, .8),) * 3
kwargs = {'proposals': [(.4, 2, (1400, 100, 1490, 237))], 'reads': reads}
actual = self.run_case(**kwargs)
control = self.run_case(control=True, **kwargs)
self.assertEqual(actual, control)
self.assertEqual(len(actual[2]), 4)
def test_identity_veto_runs_before_skip(self):
actual = self.run_case(veto_at=3)
self.assertIsNone(actual[0])
self.assertIsNone(actual[1])
self.assertEqual(actual[2], [1, 2])
self.assertEqual(len(actual[3]), 3)
if __name__ == '__main__':
unittest.main()
+1 -1
View File
@@ -1 +1 @@
{"protected_inventory_sha256": "7e0769655012151a24d010b6fe2498b7f002d08a1821c5285825fcea7037fbe5", "schema_version": 1}
{"protected_inventory_sha256": "2d361bacbecdd746f4a72467a899523697cc45c88d70a3ccb8500c8ef6e15f63", "schema_version": 1}