17
17
18
18
import pytest
19
19
from isaaclab .scene import InteractiveScene , InteractiveSceneCfg
20
+ from isaaclab .sensors import RayCasterCfg , patterns
20
21
from isaaclab .sim import build_simulation_context
21
22
from isaaclab .utils import configclass
23
+ from isaaclab_assets .robots .anymal import ANYMAL_C_CFG
22
24
23
25
from nav_suite import NAVSUITE_TEST_ASSETS_DIR
24
26
from nav_suite .terrain_analysis import TerrainAnalysis , TerrainAnalysisCfg
@@ -30,14 +32,29 @@ class BasicSceneCfg(InteractiveSceneCfg):
30
32
"""Configuration for a basic test scene with terrain."""
31
33
32
34
terrain = NavTerrainImporterCfg (
33
- prim_path = "/World/ground " ,
35
+ prim_path = "/World/Ground " ,
34
36
terrain_type = "usd" ,
35
37
usd_path = os .path .join (NAVSUITE_TEST_ASSETS_DIR , "terrains" , "ground_plane.usda" ),
36
38
num_envs = 1 ,
37
39
env_spacing = 2.0 ,
38
40
add_colliders = False ,
39
41
)
40
42
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
+
41
58
42
59
@pytest .fixture (params = ["cuda" , "cpu" ])
43
60
def device (request ):
@@ -63,7 +80,7 @@ def scene(simulation_context):
63
80
64
81
65
82
@pytest .fixture
66
- def terrain_analysis (scene ):
83
+ def terrain_analysis_test (scene ):
67
84
"""Fixture providing terrain analysis with standard 3x3 height grid."""
68
85
69
86
# Create terrain analysis configuration (following matterport_viewpoint_sampling.py pattern)
@@ -93,63 +110,98 @@ def terrain_analysis(scene):
93
110
return terrain_analysis
94
111
95
112
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 ):
97
133
"""Test get_height with a single position."""
98
134
# Test a position that should map to grid index [0, 0]
99
135
positions = torch .tensor ([
100
136
[0.05 , 0.05 ], # Grid index [0, 0] -> height 1.0
101
137
])
102
- heights = terrain_analysis .get_height (positions )
138
+ heights = terrain_analysis_test .get_height (positions )
103
139
104
140
expected_height = torch .tensor ([1.0 ])
105
141
assert torch .equal (heights .cpu (), expected_height ), f"Expected { expected_height } , got { heights .cpu ()} "
106
142
107
143
108
- def test_get_height_multiple_positions (terrain_analysis ):
144
+ def test_get_height_multiple_positions (terrain_analysis_test ):
109
145
"""Test get_height with multiple positions."""
110
146
# Test multiple positions
111
147
positions = torch .tensor ([
112
148
[0.05 , 0.05 ], # Grid index [0, 0] -> height 1.0
113
149
[0.15 , 0.25 ], # Grid index [1, 2] -> height 6.0
114
150
[0.25 , 0.15 ], # Grid index [2, 1] -> height 8.0
115
151
])
116
- heights = terrain_analysis .get_height (positions )
152
+ heights = terrain_analysis_test .get_height (positions )
117
153
118
154
expected_heights = torch .tensor ([1.0 , 6.0 , 8.0 ]) # Heights at [0,0], [1,2] and [2,1]
119
155
assert torch .equal (heights .cpu (), expected_heights ), f"Expected { expected_heights } , got { heights .cpu ()} "
120
156
121
157
122
- def test_get_height_boundary_clamping (terrain_analysis ):
158
+ def test_get_height_boundary_clamping (terrain_analysis_test ):
123
159
"""Test that positions outside grid bounds are clamped correctly."""
124
160
# Test positions outside bounds
125
161
positions = torch .tensor ([
126
162
[- 0.1 , - 0.1 ], # Outside bounds, should clamp to [0, 0]
127
163
[0.5 , 0.5 ], # Outside bounds, should clamp to [2, 2]
128
164
])
129
- heights = terrain_analysis .get_height (positions )
165
+ heights = terrain_analysis_test .get_height (positions )
130
166
131
167
expected_heights = torch .tensor ([1.0 , 9.0 ]) # Heights at [0,0] and [2,2]
132
168
assert torch .equal (heights .cpu (), expected_heights ), f"Expected { expected_heights } , got { heights .cpu ()} "
133
169
134
170
135
- def test_get_height_exact_grid_boundaries (terrain_analysis ):
171
+ def test_get_height_exact_grid_boundaries (terrain_analysis_test ):
136
172
"""Test positions that fall exactly on grid boundaries."""
137
173
# Test positions on exact boundaries
138
174
positions = torch .tensor ([
139
175
[0.0 , 0.0 ], # Exact corner -> [0, 0]
140
176
[0.2 , 0.2 ], # Grid boundary -> [2, 2]
141
177
[0.1 , 0.0 ], # Edge position -> [1, 0]
142
178
])
143
- heights = terrain_analysis .get_height (positions )
179
+ heights = terrain_analysis_test .get_height (positions )
144
180
145
181
expected_heights = torch .tensor ([1.0 , 9.0 , 4.0 ]) # Heights at [0,0], [2,2] and [1,0]
146
182
assert torch .equal (heights .cpu (), expected_heights ), f"Expected { expected_heights } , got { heights .cpu ()} "
147
183
148
184
149
- def test_get_height_empty_input (terrain_analysis ):
185
+ def test_get_height_empty_input (terrain_analysis_test ):
150
186
"""Test get_height with empty input tensor."""
151
187
# Test empty input
152
188
positions = torch .empty ((0 , 2 ))
153
- heights = terrain_analysis .get_height (positions )
189
+ heights = terrain_analysis_test .get_height (positions )
154
190
155
191
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