mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 13:33:42 +08:00
157 lines
7.6 KiB
Python
157 lines
7.6 KiB
Python
import copy
|
|
import unittest
|
|
from dataclasses import replace
|
|
from unittest.mock import patch
|
|
|
|
import numpy as np
|
|
import pyray as rl
|
|
|
|
from openpilot.starpilot.ui.onroad_customization import PROFILES, default_document, validate_document, widget_size
|
|
from openpilot.starpilot.ui.onroad_state import OnroadState, SpeedLimitObservation
|
|
from openpilot.starpilot.ui.onroad_torque import TorqueBarWidget
|
|
from openpilot.starpilot.ui.tests.test_onroad_torque_draw import record_gpu
|
|
|
|
|
|
def scene(document=None):
|
|
return replace(OnroadState(True, True, 20, 80, SpeedLimitObservation(), lateral_active=True),
|
|
customization=document or default_document())
|
|
|
|
|
|
def geometry(profile, state, torque=.3, alpha=1.):
|
|
data = PROFILES[profile]
|
|
bounds = data['bounds']
|
|
rect = rl.Rectangle(*(bounds[key] for key in ('x', 'y', 'width', 'height')))
|
|
widget = TorqueBarWidget()
|
|
with patch.object(widget._torque_filter, 'update', return_value=torque), \
|
|
patch.object(widget._alpha_filter, 'update', return_value=alpha), \
|
|
record_gpu() as gpu:
|
|
widget.render(rect, state, data['width'])
|
|
return gpu.frames
|
|
|
|
|
|
class TestTorqueLayout(unittest.TestCase):
|
|
def test_moved_arc_dot_and_sampling_quad_translate_without_resizing(self):
|
|
for profile in PROFILES:
|
|
with self.subTest(profile=profile):
|
|
document = default_document()
|
|
base = document['layouts'][profile]['torque_bar']
|
|
base['x'] += 10
|
|
base['y'] += 10
|
|
document = validate_document(document)
|
|
original, = geometry(profile, scene())
|
|
moved, = geometry(profile, scene(document))
|
|
np.testing.assert_allclose(moved.vertices, np.array(original.vertices) + [10, 10], atol=1e-10)
|
|
for field in ('geometry', 'dotGeometry'):
|
|
x, y, *rest = original.uniforms[field]
|
|
self.assertEqual(moved.uniforms[field], (x + 10, y + 10, *rest))
|
|
for field in ('backgroundNormals', 'indicatorNormals', 'style'):
|
|
self.assertEqual(moved.uniforms[field], original.uniforms[field])
|
|
|
|
def test_metadata_bounds_enclose_all_legal_arc_and_dot_states(self):
|
|
for profile, data in PROFILES.items():
|
|
widget = data['widgets']['torque_bar']
|
|
x, y = widget['default']['x'], widget['default']['y']
|
|
right, bottom = x + widget['width'], y + widget['height']
|
|
for torque in np.linspace(-1, 1, 41):
|
|
for alpha in (0, .01, .2, .5, 1):
|
|
for frame in geometry(profile, scene(), float(torque), alpha):
|
|
cx, cy, radius, half = frame.uniforms['geometry']
|
|
cap = frame.uniforms['style'][0]
|
|
normals = np.array(frame.uniforms['backgroundNormals']).reshape(2, 2)
|
|
# The arc and its rounded ends must fit the saved layout, independent of the padded sampling quad.
|
|
ends = np.array([cx, cy]) + radius * normals
|
|
extents = abs(normals) * (half - cap) + cap
|
|
self.assertGreaterEqual(float((ends - extents)[:, 0].min()), x)
|
|
self.assertGreaterEqual(cy - radius - half, y)
|
|
self.assertLessEqual(float((ends + extents)[:, 0].max()), right)
|
|
self.assertLessEqual(float((ends + extents)[:, 1].max()), bottom)
|
|
if frame.uniforms['style'][3] > 0:
|
|
dx, dy, dot_radius, _ = frame.uniforms['dotGeometry']
|
|
self.assertGreaterEqual(dx - dot_radius, x)
|
|
self.assertGreaterEqual(dy - dot_radius, y)
|
|
self.assertLessEqual(dx + dot_radius, right)
|
|
self.assertLessEqual(dy + dot_radius, bottom)
|
|
|
|
def test_only_torque_underlay_can_overlap_reserved_actions(self):
|
|
document = default_document()
|
|
self.assertEqual(validate_document(document), document)
|
|
document['layouts']['compact']['torque_bar'].update(x=174, y=179)
|
|
validate_document(document)
|
|
for key in ('max_speed', 'driver_monitor', 'steering_wheel'):
|
|
with self.subTest(key=key):
|
|
invalid = default_document()
|
|
actions = invalid['layouts']['compact']['speed_limit_actions']
|
|
_, height = widget_size(invalid, 'compact', key)
|
|
invalid['layouts']['compact'][key].update(x=actions['x'] - widget_size(invalid, 'compact', key)[0], y=min(actions['y'], 240 - height))
|
|
self.assertEqual(validate_document(invalid), invalid) # Touching edges do not overlap.
|
|
invalid['layouts']['compact'][key]['x'] += 1
|
|
with self.assertRaises(ValueError):
|
|
validate_document(invalid)
|
|
|
|
def test_complete_old_three_and_four_widget_shapes_migrate(self):
|
|
for count in (3, 4):
|
|
document = default_document()
|
|
for key in ('model_confidence', 'conditional_mode', 'following_distance'):
|
|
del document['layouts']['compact'][key]
|
|
for layout in document['layouts'].values():
|
|
del layout['torque_bar']
|
|
del layout['speed_limit_actions']
|
|
if count == 3:
|
|
del layout['driver_monitor']
|
|
document['palette']['text'] = '#12345678'
|
|
document['layouts']['large']['current_speed'].update(x=700, y=200, enabled=False)
|
|
old = copy.deepcopy(document)
|
|
result = validate_document(document)
|
|
self.assertEqual(document, old)
|
|
self.assertEqual(result['palette'], old['palette'])
|
|
for profile, layout in old['layouts'].items():
|
|
for key, placement in layout.items():
|
|
self.assertEqual(result['layouts'][profile][key], placement)
|
|
self.assertEqual(result['layouts'][profile]['torque_bar'], default_document()['layouts'][profile]['torque_bar'])
|
|
self.assertEqual(result['layouts'][profile]['speed_limit_actions'], default_document()['layouts'][profile]['speed_limit_actions'])
|
|
|
|
def test_mixed_legacy_shape_is_rejected(self):
|
|
document = default_document()
|
|
for layout in document['layouts'].values():
|
|
del layout['torque_bar']
|
|
del layout['speed_limit_actions']
|
|
del document['layouts']['large']['driver_monitor']
|
|
with self.assertRaises(ValueError):
|
|
validate_document(document)
|
|
|
|
def test_disabled_widget_keeps_filter_updates_without_drawing(self):
|
|
widget = TorqueBarWidget()
|
|
data = PROFILES['compact']['bounds']
|
|
rect = rl.Rectangle(*(data[key] for key in ('x', 'y', 'width', 'height')))
|
|
document = default_document()
|
|
document['layouts']['compact']['torque_bar']['enabled'] = False
|
|
with record_gpu() as gpu:
|
|
widget.render(rect, replace(scene(document), torque_utilization=.8), 536)
|
|
self.assertFalse(gpu.frames or gpu.loads)
|
|
self.assertGreater(widget._torque_filter.x, 0)
|
|
self.assertGreater(widget._alpha_filter.x, 0)
|
|
|
|
def test_unknown_source_or_inactive_lateral_resets_without_drawing(self):
|
|
widget = TorqueBarWidget()
|
|
for lateral, source in ((True, False), (False, True)):
|
|
widget._torque_filter.x = widget._alpha_filter.x = .8
|
|
state = replace(scene(), lateral_active=lateral, torque_source_available=source)
|
|
with record_gpu() as gpu:
|
|
widget.render(rl.Rectangle(0, 0, 476, 240), state, 536)
|
|
self.assertFalse(gpu.frames or gpu.loads)
|
|
self.assertEqual((widget._torque_filter.x, widget._alpha_filter.x), (0, 0))
|
|
|
|
def test_new_drive_resets_existing_filters_before_first_fresh_render(self):
|
|
widget = TorqueBarWidget()
|
|
state = replace(scene(), lateral_active=True, torque_source_available=True, torque_drive_frame=10, torque_utilization=.8)
|
|
rect = rl.Rectangle(0, 0, 476, 240)
|
|
with record_gpu():
|
|
for _ in range(30):
|
|
widget.render(rect, state, 536)
|
|
self.assertGreater(widget._torque_filter.x, .7)
|
|
self.assertGreater(widget._alpha_filter.x, .9)
|
|
state = replace(state, torque_drive_frame=100)
|
|
widget.render(rect, state, 536)
|
|
self.assertLess(widget._torque_filter.x, .2)
|
|
self.assertLess(widget._alpha_filter.x, .2)
|