Skip to content

Commit 699f8df

Browse files
jgillickduburcqa
andauthored
[BUG FIX] Fix support of non-symmetrical Raycaster GridPattern size (#1815)
Co-authored-by: Alexis DUBURCQ <[email protected]>
1 parent 8e0ef05 commit 699f8df

File tree

2 files changed

+35
-23
lines changed

2 files changed

+35
-23
lines changed

genesis/sensors/raycaster/patterns.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ def compute_ray_dirs(self):
9393
self._ray_dirs[:] = self.direction.expand((*self._return_shape, 3))
9494

9595
def compute_ray_starts(self):
96-
grid_x, grid_y = torch.meshgrid(*self.coords, indexing="xy")
96+
grid_x, grid_y = torch.meshgrid(*self.coords, indexing="ij")
9797
self._ray_starts[..., 0] = grid_x
9898
self._ray_starts[..., 1] = grid_y
9999
self._ray_starts[..., 2] = 0.0

tests/test_sensors.py

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -254,14 +254,22 @@ def test_rigid_tactile_sensors_gravity_force(show_viewer, tol, n_envs):
254254
@pytest.mark.parametrize("n_envs", [0, 2])
255255
def test_raycaster_hits(show_viewer, tol, n_envs):
256256
"""Test if the Raycaster sensor with GridPattern rays pointing to ground returns the correct distance."""
257-
NUM_RAYS_XY = 3
258-
SPHERE_POS = (4.0, 0.0, 1.0)
257+
NUM_RAYS_XY = (3, 5)
258+
SPHERE_POS = (2.5, 0.5, 1.0)
259259
BOX_SIZE = 0.05
260260
RAYCAST_BOX_SIZE = 0.1
261-
RAYCAST_GRID_SIZE = 1.0
261+
RAYCAST_GRID_SIZE_X = 1.0
262262
RAYCAST_HEIGHT = 1.0
263263

264264
scene = gs.Scene(
265+
viewer_options=gs.options.ViewerOptions(
266+
camera_pos=(-3.0, RAYCAST_GRID_SIZE_X * (NUM_RAYS_XY[1] / NUM_RAYS_XY[0]), 2 * RAYCAST_HEIGHT),
267+
camera_lookat=(1.5, RAYCAST_GRID_SIZE_X * (NUM_RAYS_XY[1] / NUM_RAYS_XY[0]), RAYCAST_HEIGHT),
268+
),
269+
vis_options=gs.options.VisOptions(
270+
rendered_envs_idx=(0,),
271+
env_separate_rigid=False,
272+
),
265273
profiling_options=gs.options.ProfilingOptions(
266274
show_FPS=False,
267275
),
@@ -279,7 +287,7 @@ def test_raycaster_hits(show_viewer, tol, n_envs):
279287
spherical_raycaster = scene.add_sensor(
280288
gs.sensors.Raycaster(
281289
pattern=gs.sensors.raycaster.SphericalPattern(
282-
n_points=(NUM_RAYS_XY, NUM_RAYS_XY),
290+
n_points=NUM_RAYS_XY,
283291
),
284292
entity_idx=spherical_sensor.idx,
285293
return_world_frame=False,
@@ -297,12 +305,13 @@ def test_raycaster_hits(show_viewer, tol, n_envs):
297305
fixed=True,
298306
),
299307
)
300-
grid_res = RAYCAST_GRID_SIZE / (NUM_RAYS_XY - 1)
308+
grid_res = RAYCAST_GRID_SIZE_X / (NUM_RAYS_XY[0] - 1)
309+
grid_size_y = grid_res * (NUM_RAYS_XY[1] - 1)
301310
grid_raycaster = scene.add_sensor(
302311
gs.sensors.Raycaster(
303312
pattern=gs.sensors.raycaster.GridPattern(
304313
resolution=grid_res,
305-
size=(1.0, 1.0),
314+
size=(RAYCAST_GRID_SIZE_X, grid_size_y),
306315
direction=(0.0, 0.0, -1.0), # pointing downwards to ground
307316
),
308317
entity_idx=grid_sensor.idx,
@@ -322,7 +331,7 @@ def test_raycaster_hits(show_viewer, tol, n_envs):
322331
obstacle_2 = scene.add_entity(
323332
gs.morphs.Box(
324333
size=(BOX_SIZE, BOX_SIZE, BOX_SIZE),
325-
pos=(RAYCAST_GRID_SIZE, RAYCAST_GRID_SIZE, RAYCAST_HEIGHT + RAYCAST_BOX_SIZE + BOX_SIZE),
334+
pos=(RAYCAST_GRID_SIZE_X, grid_size_y, RAYCAST_HEIGHT + RAYCAST_BOX_SIZE + BOX_SIZE),
326335
),
327336
)
328337

@@ -331,11 +340,10 @@ def test_raycaster_hits(show_viewer, tol, n_envs):
331340
batch_shape = (n_envs,) if n_envs > 0 else ()
332341

333342
# Validate grid raycast
334-
# TODO: Check use different (global) offset positions for each environments
335343
for obstacle_pos, sensor_pos, hit_ij in (
336-
(None, None, (-1, -1)),
337-
((grid_res, grid_res, BOX_SIZE), None, (-1, -1)),
338-
(None, (RAYCAST_GRID_SIZE, RAYCAST_GRID_SIZE, RAYCAST_HEIGHT + 0.5 * RAYCAST_BOX_SIZE), (0, 0)),
344+
(None, None, (-1, -2)),
345+
((grid_res, grid_res, BOX_SIZE), None, (-1, -2)),
346+
(None, (*(grid_res * (e - 2) for e in NUM_RAYS_XY), RAYCAST_HEIGHT + 0.5 * RAYCAST_BOX_SIZE), (1, 0)),
339347
):
340348
# Update obstacle and/or sensor position if necessary
341349
if obstacle_pos is not None:
@@ -350,37 +358,41 @@ def test_raycaster_hits(show_viewer, tol, n_envs):
350358
# Fetch updated sensor data
351359
grid_hits = grid_raycaster.read().points
352360
grid_distances = grid_raycaster.read().distances
353-
assert grid_distances.shape == (*batch_shape, NUM_RAYS_XY, NUM_RAYS_XY)
361+
assert grid_distances.shape == (*batch_shape, *NUM_RAYS_XY)
354362

355363
# Check hits
356364
grid_sensor_origin = grid_sensor.get_pos()
357-
x = torch.linspace(-0.5, 0.5, NUM_RAYS_XY) + grid_sensor_origin[..., [0]]
358-
y = torch.linspace(-0.5, 0.5, NUM_RAYS_XY) + grid_sensor_origin[..., [1]]
359-
# xg, yg = torch.meshgrid(x, y, indexing="xy")
360-
xg = x.unsqueeze(-2).expand((*batch_shape, NUM_RAYS_XY, -1))
361-
yg = y.unsqueeze(-1).expand((*batch_shape, -1, NUM_RAYS_XY))
362-
zg = torch.zeros((*batch_shape, NUM_RAYS_XY, NUM_RAYS_XY))
365+
x = torch.linspace(-0.5, 0.5, NUM_RAYS_XY[0]) * RAYCAST_GRID_SIZE_X + grid_sensor_origin[..., [0]]
366+
y = torch.linspace(-0.5, 0.5, NUM_RAYS_XY[1]) * grid_size_y + grid_sensor_origin[..., [1]]
367+
# xg, yg = torch.meshgrid(x, y, indexing="ij")
368+
xg = x.unsqueeze(-1).expand((*batch_shape, -1, NUM_RAYS_XY[1]))
369+
yg = y.unsqueeze(-2).expand((*batch_shape, NUM_RAYS_XY[0], -1))
370+
zg = torch.zeros((*batch_shape, *NUM_RAYS_XY))
363371
zg[(..., *hit_ij)] = obstacle_pos[..., 2] + 0.5 * BOX_SIZE
364372
grid_hits_ref = torch.stack([xg, yg, zg], dim=-1)
365373
assert_allclose(grid_hits, grid_hits_ref, tol=gs.EPS)
366374

367375
# Check distances
368-
grid_distances_ref = torch.full((*batch_shape, NUM_RAYS_XY, NUM_RAYS_XY), RAYCAST_HEIGHT)
376+
grid_distances_ref = torch.full((*batch_shape, *NUM_RAYS_XY), RAYCAST_HEIGHT)
369377
grid_distances_ref[(..., *hit_ij)] = RAYCAST_HEIGHT - obstacle_pos[..., 2] - 0.5 * BOX_SIZE
370378
assert_allclose(grid_distances, grid_distances_ref, tol=gs.EPS)
371379

372380
# Validate spherical raycast
373381
spherical_distances = spherical_raycaster.read().distances
374-
assert spherical_distances.shape == (*batch_shape, NUM_RAYS_XY, NUM_RAYS_XY)
382+
assert spherical_distances.shape == (*batch_shape, *NUM_RAYS_XY)
375383
# Note that the tolerance must be large bevcause the sphere geometry is discretized
376384
assert_allclose(spherical_distances, RAYCAST_HEIGHT, tol=5e-3)
377385

378386
# Simulate for a while and check again that the ray is casted properly
387+
offset = torch.from_numpy(np.random.rand(*batch_shape, 3)).to(dtype=gs.tc_float, device=gs.device)
388+
for entity in (grid_sensor, obstacle_1, obstacle_2):
389+
entity.set_pos(entity.get_pos() + offset)
379390
for _ in range(100):
380391
scene.step()
381392

382393
grid_distances = grid_raycaster.read().distances
383-
grid_distances_ref = torch.full((*batch_shape, NUM_RAYS_XY, NUM_RAYS_XY), RAYCAST_HEIGHT)
384-
grid_distances_ref[..., (NUM_RAYS_XY - 1) // 2, (NUM_RAYS_XY - 1) // 2] = RAYCAST_HEIGHT - BOX_SIZE
394+
grid_distances_ref = torch.full((*batch_shape, *NUM_RAYS_XY), RAYCAST_HEIGHT)
395+
grid_distances_ref[(..., -1, -2)] = RAYCAST_HEIGHT - BOX_SIZE
385396
grid_distances_ref[(..., *hit_ij)] = RAYCAST_HEIGHT - BOX_SIZE
397+
grid_distances_ref += offset[..., 2].reshape((*(-1 for e in batch_shape), 1, 1))
386398
assert_allclose(grid_distances, grid_distances_ref, tol=1e-3)

0 commit comments

Comments
 (0)