Skip to content

Commit 4fcd4d1

Browse files
committed
feat(trajectory-rendering): added more test cases for each component of trajectory rendering function
Signed-off-by: Arseni10Lk <arseniy230606@gmail.com>
1 parent 62940d4 commit 4fcd4d1

1 file changed

Lines changed: 131 additions & 2 deletions

File tree

Model/tests/test_trajectory_render.py

Lines changed: 131 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import sys
22
sys.path.append('..')
3-
from visualization.trajectory_rendering import Visualization
3+
from visualization.trajectory_rendering import Visualization, _DT, _FUTURE_TIMESTEPS
44
import torch
55
import pytest
66
from PIL import Image
@@ -44,4 +44,133 @@ def test_visualization_with_dummy_data(tmp_path: Path):
4444
assert result_img.mode == mock_map.mode, "Mode does not match"
4545
assert list(map_copy.getdata()) == list(mock_map.getdata()), "Original image mutated"
4646
assert list(result_img.getdata()) != list(mock_map.getdata()), "Image file was not created in the target directory"
47-
assert os.path.isfile(output_path), "Image file was not created in the target directory"
47+
assert os.path.isfile(output_path), "Image file was not created in the target directory"
48+
49+
def test_accel_and_curv_to_meters_trajectory_straight_no_accel():
50+
# 1. Create a dummy action sequence for going straight with no acceleration
51+
action_sequence = torch.zeros(_FUTURE_TIMESTEPS * 2)
52+
current_speed = 10.0 # 10 m/s
53+
54+
# 2. Run the function
55+
trajectory_m = Visualization.accel_and_curv_to_meters_trajectory(action_sequence, current_speed, _FUTURE_TIMESTEPS)
56+
57+
# 3. Assertions
58+
assert trajectory_m.shape == (_FUTURE_TIMESTEPS + 1, 2), "Shape of trajectory tensor is incorrect"
59+
# The car should move straight along the y-axis (forward)
60+
# X should be 0, Y should increase based on speed
61+
v = current_speed
62+
for i in range(1, _FUTURE_TIMESTEPS + 1):
63+
# Note: In the function, positive Y is up, positive X is right.
64+
assert trajectory_m[i, 0].item() == pytest.approx(0.0), "X should be 0"
65+
assert trajectory_m[i, 1].item() > trajectory_m[i-1, 1].item(), "Y should be increasing"
66+
assert trajectory_m[i, 1].item() == pytest.approx(trajectory_m[i-1, 1].item() + v * _DT), "Integration is incorrect"
67+
68+
def test_accel_and_curv_to_meters_trajectory_stationary():
69+
# Edge case: 0 speed, 0 acceleration -> Car should remain at origin (0, 0)
70+
action_sequence = torch.zeros(_FUTURE_TIMESTEPS * 2)
71+
current_speed = 0.0
72+
73+
trajectory_m = Visualization.accel_and_curv_to_meters_trajectory(action_sequence, current_speed, _FUTURE_TIMESTEPS)
74+
75+
for i in range(_FUTURE_TIMESTEPS + 1):
76+
assert trajectory_m[i, 0].item() == pytest.approx(0.0)
77+
assert trajectory_m[i, 1].item() == pytest.approx(0.0)
78+
79+
def test_accel_and_curv_to_meters_trajectory_constant_acceleration_from_standstill():
80+
# Edge case: starting from 0 speed, but applying constant acceleration
81+
action_sequence = torch.zeros(_FUTURE_TIMESTEPS * 2)
82+
action_sequence[0::2] = 2.0 # Constant 2.0 m/s^2 acceleration (every even index is accel)
83+
current_speed = 0.0
84+
85+
trajectory_m = Visualization.accel_and_curv_to_meters_trajectory(action_sequence, current_speed, _FUTURE_TIMESTEPS)
86+
87+
assert trajectory_m[0, 0].item() == pytest.approx(0.0)
88+
assert trajectory_m[0, 1].item() == pytest.approx(0.0)
89+
90+
# Check that distance covered in each timestep is strictly increasing
91+
for i in range(2, _FUTURE_TIMESTEPS + 1):
92+
dist_prev = trajectory_m[i-1, 1].item() - trajectory_m[i-2, 1].item()
93+
dist_curr = trajectory_m[i, 1].item() - trajectory_m[i-1, 1].item()
94+
95+
assert trajectory_m[i, 0].item() == pytest.approx(0.0), "X should be 0, no curvature applied"
96+
assert dist_curr > dist_prev, "Distance per timestep should increase under constant acceleration"
97+
98+
def test_accel_and_curv_to_meters_trajectory_turning():
99+
# Edge case: turning left with constant speed
100+
action_sequence = torch.zeros(_FUTURE_TIMESTEPS * 2)
101+
action_sequence[1::2] = 0.1 # Constant positive curvature (left turn)
102+
current_speed = 10.0
103+
104+
trajectory_m = Visualization.accel_and_curv_to_meters_trajectory(action_sequence, current_speed, _FUTURE_TIMESTEPS)
105+
106+
# After 64 timesteps, X should be negative (left of the starting Y-axis) and Y should be positive
107+
assert trajectory_m[-1, 0].item() < -0.1, "Car should have turned left (negative X)"
108+
assert trajectory_m[-1, 1].item() > 0.1, "Car should have moved forward (positive Y)"
109+
110+
def test_accel_and_curv_to_meters_trajectory_extreme_spiral():
111+
# Edge case: extreme spiral
112+
# Constant acceleration and linearly increasing curvature.
113+
action_sequence = torch.zeros(_FUTURE_TIMESTEPS * 2)
114+
action_sequence[0::2] = 0.5 # Constant acceleration
115+
action_sequence[1::2] = torch.linspace(0.5, 1.0, _FUTURE_TIMESTEPS) # Increasing curvature
116+
current_speed = 5.0
117+
118+
trajectory_m = Visualization.accel_and_curv_to_meters_trajectory(action_sequence, current_speed, _FUTURE_TIMESTEPS)
119+
120+
assert not torch.isnan(trajectory_m).any(), "Trajectory contains NaNs"
121+
assert not torch.isinf(trajectory_m).any(), "Trajectory contains Infs"
122+
123+
# A tight spiral with these parameters will complete multiple full 360-degree rotations.
124+
# This means the vehicle must travel "backwards" relative to its start at some point.
125+
assert trajectory_m[:, 1].min().item() < -0.5, "Car did not loop backwards significantly"
126+
127+
def test_meters_to_pixels_trajectory():
128+
trajectory_m = torch.tensor([
129+
[0.0, 0.0],
130+
[10.0, 0.0],
131+
[10.0, 10.0],
132+
[0.0, 10.0],
133+
])
134+
radius_m = 20.0
135+
map_image = Image.new("RGB", (400, 400))
136+
137+
trajectory_px = Visualization.meters_to_pixels_trajectory(trajectory_m, radius_m, map_image)
138+
139+
assert trajectory_px.shape == trajectory_m.shape
140+
# Check pixel coordinates
141+
# Origin (0,0) in meters is at the top-center of the image. Y is increasing down.
142+
# Image dimensions: 400x400. Center X is 200.
143+
# Meter to pixel scale: 400 pixels / (2 * 20m) = 10 pixels/meter
144+
assert trajectory_px[0, 0] == 200 and trajectory_px[0, 1] == 200 # Origin
145+
assert trajectory_px[1, 0] == 300 and trajectory_px[1, 1] == 200 # 10m right
146+
assert trajectory_px[2, 0] == 300 and trajectory_px[2, 1] == 100 # 10m right, 10m up
147+
assert trajectory_px[3, 0] == 200 and trajectory_px[3, 1] == 100 # 10m up
148+
149+
def test_overlay_the_trajectory_with_map():
150+
map_image = Image.new("RGB", (400, 400), color="black")
151+
trajectory_px = torch.tensor([
152+
[200, 399], # Start at bottom center, slightly off edge
153+
[300, 399],
154+
[300, 300],
155+
])
156+
157+
overlaid_image = Visualization.overlay_the_trajectory_with_map(trajectory_px, map_image)
158+
159+
assert overlaid_image is not None
160+
assert isinstance(overlaid_image, Image.Image)
161+
assert overlaid_image.size == map_image.size
162+
163+
# Check if pixels are colored correctly
164+
# The trajectory should be a non-black color
165+
# We check points along the drawn line segments
166+
p1 = (trajectory_px[0,0].item(), trajectory_px[0,1].item())
167+
p2 = (trajectory_px[1,0].item(), trajectory_px[1,1].item())
168+
p3 = (trajectory_px[2,0].item(), trajectory_px[2,1].item())
169+
170+
assert overlaid_image.getpixel(p1) != (0, 0, 0)
171+
assert overlaid_image.getpixel(p2) != (0, 0, 0)
172+
assert overlaid_image.getpixel(p3) != (0, 0, 0)
173+
174+
# Check a point on the line between p1 and p2
175+
mid_p1_p2 = (int((p1[0]+p2[0])/2), int((p1[1]+p2[1])/2))
176+
assert overlaid_image.getpixel(mid_p1_p2) != (0,0,0)

0 commit comments

Comments
 (0)