Skip to content

Commit 5b3bb5e

Browse files
committed
Test basic analyse() functionality
Also change terrain_analysis warp array conversion to first convert to numpy array and then to torch tensor. Also add raycaster sensor to the test scene and a robot for the raycaster to sit on
1 parent fceb2c8 commit 5b3bb5e

File tree

2 files changed

+69
-14
lines changed

2 files changed

+69
-14
lines changed

exts/nav_suite/nav_suite/terrain_analysis/terrain_analysis.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -445,8 +445,11 @@ def _get_mesh_dimensions(self) -> tuple[float, float, float, float, float, float
445445
bounds = []
446446
for mesh in self._raycaster.meshes.values():
447447
curr_bounds = torch.zeros((2, 3))
448-
curr_bounds[0] = torch.tensor(mesh.points).max(dim=0)[0]
449-
curr_bounds[1] = torch.tensor(mesh.points).min(dim=0)[0]
448+
# Cannot convert warp array -> pytorch tensor
449+
# We have to do conversion warp array -> numpy array -> pytorch tensor
450+
mesh_points = torch.from_numpy(mesh.points.numpy())
451+
curr_bounds[0] = mesh_points.max(dim=0)[0]
452+
curr_bounds[1] = mesh_points.min(dim=0)[0]
450453
bounds.append(curr_bounds)
451454
bounds = torch.vstack(bounds)
452455
x_min, y_min, z_min = bounds[:, 0].min().item(), bounds[:, 1].min().item(), bounds[:, 2].min().item()

exts/nav_suite/tests/test_terrain_analysis.py

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717

1818
import pytest
1919
from isaaclab.scene import InteractiveScene, InteractiveSceneCfg
20+
from isaaclab.sensors import RayCasterCfg, patterns
2021
from isaaclab.sim import build_simulation_context
2122
from isaaclab.utils import configclass
23+
from isaaclab_assets.robots.anymal import ANYMAL_C_CFG
2224

2325
from nav_suite import NAVSUITE_TEST_ASSETS_DIR
2426
from nav_suite.terrain_analysis import TerrainAnalysis, TerrainAnalysisCfg
@@ -30,14 +32,29 @@ class BasicSceneCfg(InteractiveSceneCfg):
3032
"""Configuration for a basic test scene with terrain."""
3133

3234
terrain = NavTerrainImporterCfg(
33-
prim_path="/World/ground",
35+
prim_path="/World/Ground",
3436
terrain_type="usd",
3537
usd_path=os.path.join(NAVSUITE_TEST_ASSETS_DIR, "terrains", "ground_plane.usda"),
3638
num_envs=1,
3739
env_spacing=2.0,
3840
add_colliders=False,
3941
)
4042

43+
robot = ANYMAL_C_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot")
44+
45+
# raycaster for terrain analysis
46+
raycaster = RayCasterCfg(
47+
prim_path="{ENV_REGEX_NS}/Robot/base/lidar_cage",
48+
update_period=0,
49+
debug_vis=False,
50+
pattern_cfg=patterns.GridPatternCfg(
51+
resolution=0.1,
52+
size=(1.0, 1.0),
53+
),
54+
mesh_prim_paths=["/World/Ground"],
55+
attach_yaw_only=True,
56+
)
57+
4158

4259
@pytest.fixture(params=["cuda", "cpu"])
4360
def device(request):
@@ -63,7 +80,7 @@ def scene(simulation_context):
6380

6481

6582
@pytest.fixture
66-
def terrain_analysis(scene):
83+
def terrain_analysis_test(scene):
6784
"""Fixture providing terrain analysis with standard 3x3 height grid."""
6885

6986
# Create terrain analysis configuration (following matterport_viewpoint_sampling.py pattern)
@@ -93,63 +110,98 @@ def terrain_analysis(scene):
93110
return terrain_analysis
94111

95112

96-
def test_get_height_single_position(terrain_analysis):
113+
@pytest.fixture
114+
def terrain_analysis_real(scene):
115+
"""Fixture providing terrain analysis that will construct its own height map from the scene."""
116+
117+
# Create terrain analysis configuration with raycaster sensor for mesh-based raycasting
118+
terrain_analysis_cfg = TerrainAnalysisCfg(
119+
grid_resolution=0.1,
120+
sample_points=10, # Use small number for faster tests
121+
viz_graph=False, # Disable visualization for tests
122+
viz_height_map=False,
123+
semantic_cost_mapping=None,
124+
raycaster_sensor="raycaster", # Use the raycaster sensor defined in scene
125+
)
126+
127+
terrain_analysis = TerrainAnalysis(terrain_analysis_cfg, scene=scene)
128+
129+
return terrain_analysis
130+
131+
132+
def test_get_height_single_position(terrain_analysis_test):
97133
"""Test get_height with a single position."""
98134
# Test a position that should map to grid index [0, 0]
99135
positions = torch.tensor([
100136
[0.05, 0.05], # Grid index [0, 0] -> height 1.0
101137
])
102-
heights = terrain_analysis.get_height(positions)
138+
heights = terrain_analysis_test.get_height(positions)
103139

104140
expected_height = torch.tensor([1.0])
105141
assert torch.equal(heights.cpu(), expected_height), f"Expected {expected_height}, got {heights.cpu()}"
106142

107143

108-
def test_get_height_multiple_positions(terrain_analysis):
144+
def test_get_height_multiple_positions(terrain_analysis_test):
109145
"""Test get_height with multiple positions."""
110146
# Test multiple positions
111147
positions = torch.tensor([
112148
[0.05, 0.05], # Grid index [0, 0] -> height 1.0
113149
[0.15, 0.25], # Grid index [1, 2] -> height 6.0
114150
[0.25, 0.15], # Grid index [2, 1] -> height 8.0
115151
])
116-
heights = terrain_analysis.get_height(positions)
152+
heights = terrain_analysis_test.get_height(positions)
117153

118154
expected_heights = torch.tensor([1.0, 6.0, 8.0]) # Heights at [0,0], [1,2] and [2,1]
119155
assert torch.equal(heights.cpu(), expected_heights), f"Expected {expected_heights}, got {heights.cpu()}"
120156

121157

122-
def test_get_height_boundary_clamping(terrain_analysis):
158+
def test_get_height_boundary_clamping(terrain_analysis_test):
123159
"""Test that positions outside grid bounds are clamped correctly."""
124160
# Test positions outside bounds
125161
positions = torch.tensor([
126162
[-0.1, -0.1], # Outside bounds, should clamp to [0, 0]
127163
[0.5, 0.5], # Outside bounds, should clamp to [2, 2]
128164
])
129-
heights = terrain_analysis.get_height(positions)
165+
heights = terrain_analysis_test.get_height(positions)
130166

131167
expected_heights = torch.tensor([1.0, 9.0]) # Heights at [0,0] and [2,2]
132168
assert torch.equal(heights.cpu(), expected_heights), f"Expected {expected_heights}, got {heights.cpu()}"
133169

134170

135-
def test_get_height_exact_grid_boundaries(terrain_analysis):
171+
def test_get_height_exact_grid_boundaries(terrain_analysis_test):
136172
"""Test positions that fall exactly on grid boundaries."""
137173
# Test positions on exact boundaries
138174
positions = torch.tensor([
139175
[0.0, 0.0], # Exact corner -> [0, 0]
140176
[0.2, 0.2], # Grid boundary -> [2, 2]
141177
[0.1, 0.0], # Edge position -> [1, 0]
142178
])
143-
heights = terrain_analysis.get_height(positions)
179+
heights = terrain_analysis_test.get_height(positions)
144180

145181
expected_heights = torch.tensor([1.0, 9.0, 4.0]) # Heights at [0,0], [2,2] and [1,0]
146182
assert torch.equal(heights.cpu(), expected_heights), f"Expected {expected_heights}, got {heights.cpu()}"
147183

148184

149-
def test_get_height_empty_input(terrain_analysis):
185+
def test_get_height_empty_input(terrain_analysis_test):
150186
"""Test get_height with empty input tensor."""
151187
# Test empty input
152188
positions = torch.empty((0, 2))
153-
heights = terrain_analysis.get_height(positions)
189+
heights = terrain_analysis_test.get_height(positions)
154190

155191
assert heights.shape == (0,), f"Expected empty tensor, got shape {heights.shape}"
192+
193+
194+
def test_analyse_basic_functionality(terrain_analysis_real):
195+
"""Test that analyse() completes without errors and sets expected attributes."""
196+
197+
# Run analysis - this will automatically setup the raycaster and construct height map
198+
terrain_analysis_real.analyse()
199+
200+
# Verify analysis completed and required attributes are set
201+
assert terrain_analysis_real.complete, "TerrainAnalysis should be complete after analyse()"
202+
assert hasattr(terrain_analysis_real, "graph"), "graph attribute should be set after analyse()"
203+
assert hasattr(terrain_analysis_real, "samples"), "samples attribute should be set after analyse()"
204+
assert hasattr(terrain_analysis_real, "points"), "points attribute should be set after analyse()"
205+
assert terrain_analysis_real.graph is not None, "graph should not be None after analyse()"
206+
assert terrain_analysis_real.samples is not None, "samples should not be None after analyse()"
207+
assert terrain_analysis_real.points is not None, "points should not be None after analyse()"

0 commit comments

Comments
 (0)