mirror of
https://github.com/SikongJueluo/Mini-Nav.git
synced 2026-07-13 04:25:32 +08:00
refactor(simulator): extract habitat simulator and visualization modules
This commit is contained in:
124
mini-nav/tests/test_habitat_simulator.py
Normal file
124
mini-nav/tests/test_habitat_simulator.py
Normal file
@@ -0,0 +1,124 @@
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from simulator import HabitatSimulatorConfig, create_habitat_simulator
|
||||
|
||||
|
||||
class _FakeSimulatorConfiguration:
|
||||
def __init__(self):
|
||||
self.scene_id = ""
|
||||
self.enable_physics = True
|
||||
|
||||
|
||||
class _FakeAgentConfiguration:
|
||||
def __init__(self):
|
||||
self.sensor_specifications = []
|
||||
self.action_space = {}
|
||||
|
||||
|
||||
class _FakeCameraSensorSpec:
|
||||
def __init__(self):
|
||||
self.uuid = ""
|
||||
self.sensor_type = None
|
||||
self.resolution = []
|
||||
self.position = []
|
||||
|
||||
|
||||
class _FakeActuationSpec:
|
||||
def __init__(self, amount):
|
||||
self.amount = amount
|
||||
|
||||
|
||||
class _FakeActionSpec:
|
||||
def __init__(self, name, actuation):
|
||||
self.name = name
|
||||
self.actuation = actuation
|
||||
|
||||
|
||||
class _FakeConfiguration:
|
||||
def __init__(self, sim_cfg, agent_cfgs):
|
||||
self.sim_cfg = sim_cfg
|
||||
self.agent_cfgs = agent_cfgs
|
||||
|
||||
|
||||
class _FakeSimulator:
|
||||
def __init__(self, cfg):
|
||||
self.cfg = cfg
|
||||
self.initialized_agent_id = None
|
||||
|
||||
def initialize_agent(self, agent_id):
|
||||
self.initialized_agent_id = agent_id
|
||||
return {"agent_id": agent_id}
|
||||
|
||||
|
||||
def _create_fake_habitat_module():
|
||||
return SimpleNamespace(
|
||||
SimulatorConfiguration=_FakeSimulatorConfiguration,
|
||||
CameraSensorSpec=_FakeCameraSensorSpec,
|
||||
SensorType=SimpleNamespace(COLOR="color"),
|
||||
Configuration=_FakeConfiguration,
|
||||
Simulator=_FakeSimulator,
|
||||
agent=SimpleNamespace(
|
||||
AgentConfiguration=_FakeAgentConfiguration,
|
||||
ActionSpec=_FakeActionSpec,
|
||||
ActuationSpec=_FakeActuationSpec,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def test_create_habitat_simulator_builds_expected_configuration():
|
||||
fake_habitat = _create_fake_habitat_module()
|
||||
config = HabitatSimulatorConfig(
|
||||
scene_path="scene.glb",
|
||||
views_per_room=8,
|
||||
image_size=128,
|
||||
sensor_height=1.25,
|
||||
move_forward_step=0.5,
|
||||
enable_physics=False,
|
||||
sensor_uuid="rgb",
|
||||
agent_id=2,
|
||||
)
|
||||
|
||||
simulator, agent = create_habitat_simulator(config, habitat_sim_module=fake_habitat)
|
||||
|
||||
assert simulator.cfg.sim_cfg.scene_id == "scene.glb"
|
||||
assert simulator.cfg.sim_cfg.enable_physics is False
|
||||
|
||||
created_agent_cfg = simulator.cfg.agent_cfgs[0]
|
||||
sensor = created_agent_cfg.sensor_specifications[0]
|
||||
assert sensor.uuid == "rgb"
|
||||
assert sensor.sensor_type == "color"
|
||||
assert sensor.resolution == [128, 128]
|
||||
assert sensor.position == [0.0, 1.25, 0.0]
|
||||
|
||||
assert created_agent_cfg.action_space["move_forward"].actuation.amount == 0.5
|
||||
assert created_agent_cfg.action_space["turn_left"].actuation.amount == 45.0
|
||||
assert created_agent_cfg.action_space["turn_right"].actuation.amount == 45.0
|
||||
|
||||
assert simulator.initialized_agent_id == 2
|
||||
assert agent == {"agent_id": 2}
|
||||
|
||||
|
||||
def test_create_habitat_simulator_validates_views_per_room():
|
||||
fake_habitat = _create_fake_habitat_module()
|
||||
config = HabitatSimulatorConfig(scene_path="scene.glb", views_per_room=0)
|
||||
|
||||
with pytest.raises(ValueError, match="views_per_room"):
|
||||
create_habitat_simulator(config, habitat_sim_module=fake_habitat)
|
||||
|
||||
|
||||
def test_create_habitat_simulator_validates_image_size():
|
||||
fake_habitat = _create_fake_habitat_module()
|
||||
config = HabitatSimulatorConfig(scene_path="scene.glb", image_size=0)
|
||||
|
||||
with pytest.raises(ValueError, match="image_size"):
|
||||
create_habitat_simulator(config, habitat_sim_module=fake_habitat)
|
||||
|
||||
|
||||
def test_create_habitat_simulator_validates_move_forward_step():
|
||||
fake_habitat = _create_fake_habitat_module()
|
||||
config = HabitatSimulatorConfig(scene_path="scene.glb", move_forward_step=0)
|
||||
|
||||
with pytest.raises(ValueError, match="move_forward_step"):
|
||||
create_habitat_simulator(config, habitat_sim_module=fake_habitat)
|
||||
108
mini-nav/tests/test_room_views.py
Normal file
108
mini-nav/tests/test_room_views.py
Normal file
@@ -0,0 +1,108 @@
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from simulator import collect_room_views_by_room
|
||||
|
||||
|
||||
class _FakeAgent:
|
||||
def __init__(self):
|
||||
self.positions = []
|
||||
|
||||
def set_state(self, state):
|
||||
self.positions.append(state.position)
|
||||
|
||||
|
||||
class _FakeSimulator:
|
||||
def __init__(self):
|
||||
self._frame_index = 0
|
||||
self.actions = []
|
||||
|
||||
def get_sensor_observations(self):
|
||||
observations = {
|
||||
"color_sensor": f"frame_{self._frame_index}",
|
||||
"depth_sensor": f"depth_{self._frame_index}",
|
||||
}
|
||||
self._frame_index += 1
|
||||
return observations
|
||||
|
||||
def step(self, action_name):
|
||||
self.actions.append(action_name)
|
||||
|
||||
|
||||
class _FakeAgentState:
|
||||
def __init__(self):
|
||||
self.position = None
|
||||
|
||||
|
||||
def test_collect_room_views_by_room_collects_grouped_frames_with_single_outer_progress():
|
||||
track_calls = []
|
||||
|
||||
def fake_track(iterable, description):
|
||||
track_calls.append(description)
|
||||
return iterable
|
||||
|
||||
agent = _FakeAgent()
|
||||
sim = _FakeSimulator()
|
||||
room_nodes = [
|
||||
SimpleNamespace(room_id="room_00", center=[1.0, 2.0, 3.0]),
|
||||
SimpleNamespace(room_id="room_01", center=[4.0, 5.0, 6.0]),
|
||||
]
|
||||
fake_habitat = SimpleNamespace(AgentState=_FakeAgentState)
|
||||
|
||||
room_views = collect_room_views_by_room(
|
||||
agent=agent,
|
||||
sim=sim,
|
||||
room_nodes=room_nodes,
|
||||
views_per_room=3,
|
||||
habitat_sim_module=fake_habitat,
|
||||
progress_track=fake_track,
|
||||
)
|
||||
|
||||
assert track_calls == ["Collecting room views"]
|
||||
assert room_views == {
|
||||
"room_00": ["frame_0", "frame_1", "frame_2"],
|
||||
"room_01": ["frame_3", "frame_4", "frame_5"],
|
||||
}
|
||||
assert sim.actions == [
|
||||
"turn_left",
|
||||
"turn_left",
|
||||
"turn_left",
|
||||
"turn_left",
|
||||
"turn_left",
|
||||
"turn_left",
|
||||
]
|
||||
assert agent.positions == [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]
|
||||
|
||||
|
||||
def test_collect_room_views_by_room_uses_custom_sensor_and_turn_action():
|
||||
agent = _FakeAgent()
|
||||
sim = _FakeSimulator()
|
||||
room_nodes = [SimpleNamespace(room_id="room_00", center=[0.0, 1.0, 0.0])]
|
||||
fake_habitat = SimpleNamespace(AgentState=_FakeAgentState)
|
||||
|
||||
room_views = collect_room_views_by_room(
|
||||
agent=agent,
|
||||
sim=sim,
|
||||
room_nodes=room_nodes,
|
||||
views_per_room=2,
|
||||
habitat_sim_module=fake_habitat,
|
||||
sensor_uuid="depth_sensor",
|
||||
turn_action="turn_right",
|
||||
progress_track=lambda iterable, description: iterable,
|
||||
)
|
||||
|
||||
assert room_views == {"room_00": ["depth_0", "depth_1"]}
|
||||
assert sim.actions == ["turn_right", "turn_right"]
|
||||
|
||||
|
||||
def test_collect_room_views_by_room_validates_views_per_room():
|
||||
with pytest.raises(ValueError, match="views_per_room"):
|
||||
collect_room_views_by_room(
|
||||
agent=_FakeAgent(),
|
||||
sim=_FakeSimulator(),
|
||||
room_nodes=[SimpleNamespace(room_id="room_00", center=[0.0, 1.0, 0.0])],
|
||||
views_per_room=0,
|
||||
habitat_sim_module=SimpleNamespace(AgentState=_FakeAgentState),
|
||||
progress_track=lambda iterable, description: iterable,
|
||||
)
|
||||
126
mini-nav/tests/test_topdown_map.py
Normal file
126
mini-nav/tests/test_topdown_map.py
Normal file
@@ -0,0 +1,126 @@
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from simulator import (
|
||||
TopDownRenderStyle,
|
||||
TopDownSceneElements,
|
||||
render_topdown_scene_map,
|
||||
)
|
||||
|
||||
|
||||
class _FakeMaps:
|
||||
def __init__(self):
|
||||
self.to_grid_calls: list[tuple[float, float]] = []
|
||||
|
||||
def get_topdown_map(self, pathfinder, height, meters_per_pixel):
|
||||
return [[0, 0], [0, 0]]
|
||||
|
||||
def to_grid(self, z, x, shape, pathfinder):
|
||||
self.to_grid_calls.append((z, x))
|
||||
return (int(z), int(x))
|
||||
|
||||
|
||||
class _FakePlt:
|
||||
def __init__(self):
|
||||
self.scatter_calls: list[tuple[int, int]] = []
|
||||
self.text_calls: list[str] = []
|
||||
self.shown = False
|
||||
|
||||
def figure(self, figsize):
|
||||
return None
|
||||
|
||||
def imshow(self, image, cmap):
|
||||
return None
|
||||
|
||||
def scatter(self, x, y, c, s):
|
||||
self.scatter_calls.append((x, y))
|
||||
|
||||
def text(self, x, y, text, color, fontsize):
|
||||
self.text_calls.append(text)
|
||||
|
||||
def title(self, title):
|
||||
return None
|
||||
|
||||
def axis(self, mode):
|
||||
return None
|
||||
|
||||
def show(self):
|
||||
self.shown = True
|
||||
|
||||
|
||||
def test_render_topdown_scene_map_renders_room_nodes_only():
|
||||
fake_maps = _FakeMaps()
|
||||
fake_plt = _FakePlt()
|
||||
room_nodes = [
|
||||
SimpleNamespace(room_id="room_00", center=[1.0, 2.0, 3.0]),
|
||||
SimpleNamespace(room_id="room_01", center=[4.0, 2.0, 5.0]),
|
||||
]
|
||||
elements = TopDownSceneElements(room_nodes=room_nodes)
|
||||
|
||||
top_down_map = render_topdown_scene_map(
|
||||
pathfinder=SimpleNamespace(),
|
||||
elements=elements,
|
||||
meters_per_pixel=0.05,
|
||||
style=TopDownRenderStyle(),
|
||||
maps_module=fake_maps,
|
||||
plt_module=fake_plt,
|
||||
)
|
||||
|
||||
assert top_down_map == [[0, 0], [0, 0]]
|
||||
assert fake_maps.to_grid_calls == [(3.0, 1.0), (5.0, 4.0)]
|
||||
assert fake_plt.scatter_calls == [(1, 3), (4, 5)]
|
||||
assert fake_plt.text_calls == ["room_00", "room_01"]
|
||||
assert fake_plt.shown is True
|
||||
|
||||
|
||||
def test_render_topdown_scene_map_validates_room_nodes():
|
||||
with pytest.raises(ValueError, match="room_nodes"):
|
||||
render_topdown_scene_map(
|
||||
pathfinder=SimpleNamespace(),
|
||||
elements=TopDownSceneElements(room_nodes=[]),
|
||||
meters_per_pixel=0.05,
|
||||
maps_module=_FakeMaps(),
|
||||
plt_module=_FakePlt(),
|
||||
)
|
||||
|
||||
|
||||
def test_render_topdown_scene_map_validates_meters_per_pixel():
|
||||
with pytest.raises(ValueError, match="meters_per_pixel"):
|
||||
render_topdown_scene_map(
|
||||
pathfinder=SimpleNamespace(),
|
||||
elements=TopDownSceneElements(
|
||||
room_nodes=[SimpleNamespace(room_id="room_00", center=[0.0, 1.0, 0.0])]
|
||||
),
|
||||
meters_per_pixel=0,
|
||||
maps_module=_FakeMaps(),
|
||||
plt_module=_FakePlt(),
|
||||
)
|
||||
|
||||
|
||||
def test_render_topdown_scene_map_rejects_object_nodes_before_implementation():
|
||||
with pytest.raises(NotImplementedError, match="object_nodes"):
|
||||
render_topdown_scene_map(
|
||||
pathfinder=SimpleNamespace(),
|
||||
elements=TopDownSceneElements(
|
||||
room_nodes=[SimpleNamespace(room_id="room_00", center=[0.0, 1.0, 0.0])],
|
||||
object_nodes=[SimpleNamespace(obj_id="obj_00")],
|
||||
),
|
||||
meters_per_pixel=0.05,
|
||||
maps_module=_FakeMaps(),
|
||||
plt_module=_FakePlt(),
|
||||
)
|
||||
|
||||
|
||||
def test_render_topdown_scene_map_rejects_edges_before_implementation():
|
||||
with pytest.raises(NotImplementedError, match="edge"):
|
||||
render_topdown_scene_map(
|
||||
pathfinder=SimpleNamespace(),
|
||||
elements=TopDownSceneElements(
|
||||
room_nodes=[SimpleNamespace(room_id="room_00", center=[0.0, 1.0, 0.0])],
|
||||
edges=[("room_00", "obj_00")],
|
||||
),
|
||||
meters_per_pixel=0.05,
|
||||
maps_module=_FakeMaps(),
|
||||
plt_module=_FakePlt(),
|
||||
)
|
||||
Reference in New Issue
Block a user