In [1]:
import radarsimpy
print("`RadarSimPy` used in this example is version: " + str(radarsimpy.__version__))
`RadarSimPy` used in this example is version: 15.2.0
Lidar Point Cloud Simulation with RadarSimPy¶
RadarSimPy's ray tracing engine traces rays through 3D mesh models to generate lidar point clouds — the same infrastructure used for radar RCS simulation.
This Example¶
- Scene: 5 objects (ground plane + 4 vehicles), mix of static and moving targets
- Sensor: 360° azimuth × 40° elevation, 1° steps → 14,400 rays/scan at 1.5 m height
- Motion: Cars 2 & 4 auto-positioned via
speed; Car 3 follows a manual turning path - Output: Time-sequence of 6 point clouds over 2.5 s
Define Scene with 3D Models¶
Import Required Modules¶
In [2]:
import numpy as np
import plotly.graph_objs as go
from plotly.subplots import make_subplots
from IPython.display import Image, display
# Set to True for interactive plots; False renders a static JPEG (e.g. for HTML export)
INTERACTIVE = False
def show(fig):
if INTERACTIVE:
fig.show()
else:
display(Image(fig.to_image(format="jpg", scale=2)))
In [3]:
car3_speed = 2 # m/s
car3_yaw = 30 # deg/s
ground = {
"model": "../models/surface_60x60.stl",
}
car_1 = {
"model": "../models/vehicles/ford_raptor.stl",
"location": [-8, 0, 0],
"rotation": [0, 0, 0],
}
car_2 = {
"model": "../models/vehicles/lamborgini_aventador.stl",
"location": [-12, -4, 0],
"rotation": [0, 0, 0],
"rotation_rate": [0, 0, 0],
"speed": [5, 0, 0],
}
car_3 = {
"model": "../models/vehicles/tesla_model_s.stl",
"location": [5, 4, 0],
"rotation": [0, 0, 0],
}
car_4 = {
"model": "../models/vehicles/scania_truck.stl",
"location": [-15, 4, 0],
"rotation": [0, 0, 0],
"rotation_rate": [0, 0, 0],
"speed": [2, 0, 0],
}
targets = [ground, car_1, car_2, car_3, car_4]
Target Object Configuration¶
| Object | Motion type | Method |
|---|---|---|
| Ground | Static | model only |
| Car 1 | Static | location |
| Car 2 | Linear | speed auto-motion |
| Car 3 | Turning | Manual location/rotation update |
| Car 4 | Linear | speed auto-motion |
Configure Lidar Sensor¶
The lidar dictionary requires three keys: sensor position, azimuth angles phi, and elevation angles theta. Total rays = len(phi) × len(theta).
| Parameter | Value | Description |
|---|---|---|
position |
[0, 0, 1.5] | Sensor at origin, 1.5 m above ground |
phi |
0°–359°, 1° step | Full 360° azimuth scan |
theta |
70°–109°, 1° step | 40° vertical FOV (90° = horizontal) |
In [4]:
lidar = {
"position": [0, 0, 1.5],
"phi": np.arange(0, 360, 1),
"theta": np.arange(70, 110, 1),
}
Simulate Point Clouds¶
sim_lidar(lidar, targets, frame_time) traces rays and returns all hit positions as a (N, 3) array. Each call advances the scene to frame_time: targets with speed are auto-positioned; Car 3's turning path is updated manually between calls.
Output: points[i]["positions"] → NumPy array of shape (N, 3) for frame i.
In [5]:
import time
from radarsimpy.simulator import sim_lidar
delta_t = 0.5
time_seq = np.arange(0, 3, delta_t)
points = []
tic = time.time()
for t in time_seq:
points.append(sim_lidar(lidar, targets, frame_time=t))
targets[3]["rotation"][0] += car3_yaw * delta_t
targets[3]["location"][0] += (
car3_speed * np.cos(targets[3]["rotation"][0] / 180 * np.pi) * delta_t
)
targets[3]["location"][1] += (
car3_speed * np.sin(targets[3]["rotation"][0] / 180 * np.pi) * delta_t
)
toc = time.time()
print("Exection time:", toc - tic, "s")
Exection time: 8.244309902191162 s
Visualize Point Cloud Results¶
Display 6 frames as a 3×2 subplot grid. Points are colored by height (z-coordinate).
In [6]:
fig = go.Figure()
fig = make_subplots(
rows=3,
cols=2,
specs=[
[{"type": "scatter3d"}, {"type": "scatter3d"}],
[{"type": "scatter3d"}, {"type": "scatter3d"}],
[{"type": "scatter3d"}, {"type": "scatter3d"}],
],
subplot_titles=("Frame 0", "Frame 1", "Frame 2", "Frame 3", "Frame 4", "Frame 5"),
horizontal_spacing=0.01,
vertical_spacing=0.03,
)
for t_idx in range(0, len(time_seq)):
fig.add_trace(
go.Scatter3d(
x=points[t_idx]["positions"][:, 0],
y=points[t_idx]["positions"][:, 1],
z=points[t_idx]["positions"][:, 2],
mode="markers",
marker=dict(
size=1,
color=points[t_idx]["positions"][:, 2],
colorscale="Rainbow",
opacity=1,
),
showlegend=False,
),
row=np.floor(t_idx / 2).astype(int) + 1,
col=np.mod(t_idx, 2) + 1,
)
fig.update_scenes(
aspectmode="data",
row=np.floor(t_idx / 2).astype(int) + 1,
col=np.mod(t_idx, 2) + 1,
)
fig.update_layout(height=1600, template="plotly_dark", margin=dict(l=5, r=0, b=0, t=30))
show(fig)
Summary¶
sim_lidar()uses ray tracing on 3D meshes to return a point cloud per scan frame.- Scan resolution is set by
phi/thetastep sizes; finer steps increase point density and compute time. - Two motion modes: auto (
speed+frame_time) for linear paths, manual (updatelocation/rotationbetween calls) for complex trajectories.
Things to Try¶
| Experiment | Parameter to change | Observable effect |
|---|---|---|
| Higher resolution | Decrease phi/theta step to 0.5° |
4× more points, longer runtime |
| Wider vertical FOV | Extend theta range |
More coverage above/below horizon |
| Different viewpoint | Change position |
Different occlusion patterns |
| Add a target | Append dict to targets |
New object appears in point cloud |