feat(scenegraph): add depth-based 3D positioning via pinhole projection

- Add bbox_depth_center position strategy in SceneGraphBuilder using depth
  at bbox centre and configurable camera_hfov_degrees for pinhole projection.
- Add optional depth_sensor_uuid to HabitatSimulatorConfig; create depth
  sensor spec alongside RGB sensor.
- Add camera_position/camera_rotation fields to RoomView; capture pose from
  sensor_states when depth sensor is available.
- Update flatten_room_views for backward compatibility with legacy tuple
  format.
- Wired in depth sensor and bbox_depth_center strategy in verification
  notebook.
- Add tests for depth sensor support and new position strategies.
This commit is contained in:
2026-05-31 14:29:05 +08:00
parent a127032e18
commit 7a1e1ccf3f
7 changed files with 1729 additions and 38 deletions

View File

@@ -0,0 +1,183 @@
"""Tests for optional Habitat depth sensor support in create_habitat_simulator."""
from __future__ import annotations
import sys
from pathlib import Path
from types import SimpleNamespace
import pytest
MINI_NAV_DIR = Path(__file__).resolve().parents[1] / "mini-nav"
sys.path.insert(0, str(MINI_NAV_DIR))
from simulator.habitat import ( # noqa: E402
HabitatSimulatorConfig,
create_habitat_simulator,
)
# ---------------------------------------------------------------------------
# Fake habitat_sim module
# ---------------------------------------------------------------------------
_specs_created: list = []
class _FakeCameraSensorSpec:
"""Fake CameraSensorSpec that records itself in _specs_created."""
def __init__(self) -> None:
self.uuid = None
self.sensor_type = None
self.resolution = None
self.position = None
self.hfov = None
_specs_created.append(self)
class _FakeSensorType:
COLOR = "COLOR"
DEPTH = "DEPTH"
class _FakeSimulator:
def __init__(self, cfg: object) -> None:
self.cfg = cfg
@staticmethod
def initialize_agent(agent_id: int) -> SimpleNamespace:
return SimpleNamespace(agent_id=agent_id)
@staticmethod
def close() -> None:
return None
def _make_fake_habitat_sim_module() -> SimpleNamespace:
"""Return a fake ``habitat_sim`` module for testing."""
global _specs_created
_specs_created = []
hab_sim = SimpleNamespace(
SimulatorConfiguration=SimpleNamespace,
CameraSensorSpec=_FakeCameraSensorSpec,
SensorType=_FakeSensorType,
Configuration=lambda sim_cfg, agent_cfgs: SimpleNamespace(
sim_cfg=sim_cfg,
agent_cfgs=agent_cfgs,
),
Simulator=_FakeSimulator,
)
class _FakeActionSpec:
def __init__(self, name: str, actuation: object) -> None:
self.name = name
self.actuation = actuation
class _FakeActuationSpec:
def __init__(self, amount: float | None = None) -> None:
self.amount = amount
hab_sim.agent = SimpleNamespace(
AgentConfiguration=SimpleNamespace,
ActionSpec=_FakeActionSpec,
ActuationSpec=_FakeActuationSpec,
)
return hab_sim
# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------
@pytest.fixture
def fake_habitat_sim() -> SimpleNamespace:
"""Fixture providing a fresh fake habitat_sim module per test."""
return _make_fake_habitat_sim_module()
# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------
def test_default_config_creates_color_and_depth_sensor_specs(
fake_habitat_sim: SimpleNamespace,
) -> None:
"""Default config should produce two specs with expected UUIDs and types."""
config = HabitatSimulatorConfig(scene_path="scene.glb")
create_habitat_simulator(config, fake_habitat_sim)
assert len(_specs_created) == 2
assert [s.uuid for s in _specs_created] == ["color_sensor", "depth_sensor"]
assert [s.sensor_type for s in _specs_created] == ["COLOR", "DEPTH"]
def test_create_habitat_simulator_adds_matching_rgb_and_depth_specs(
fake_habitat_sim: SimpleNamespace,
) -> None:
"""Depth sensor spec should mirror the RGB spec when depth_sensor_uuid is set."""
config = HabitatSimulatorConfig(
scene_path="scene.glb",
image_size=320,
sensor_height=1.25,
sensor_uuid="rgb",
depth_sensor_uuid="depth",
hfov_degrees=75.0,
)
create_habitat_simulator(config, fake_habitat_sim)
assert len(_specs_created) == 2, "Expected 2 sensor specs (RGB + depth)"
rgb_spec, depth_spec = _specs_created
# UUIDs
assert rgb_spec.uuid == "rgb"
assert depth_spec.uuid == "depth"
# Sensor types
assert rgb_spec.sensor_type == "COLOR"
assert depth_spec.sensor_type == "DEPTH"
# Resolutions
assert rgb_spec.resolution == [320, 320]
assert depth_spec.resolution == [320, 320]
# Positions
assert rgb_spec.position == [0.0, 1.25, 0.0]
assert depth_spec.position == [0.0, 1.25, 0.0]
# HFOV
assert rgb_spec.hfov == 75.0
assert depth_spec.hfov == 75.0
def test_create_habitat_simulator_can_disable_depth_sensor(
fake_habitat_sim: SimpleNamespace,
) -> None:
"""``depth_sensor_uuid=None`` should produce only a single color sensor spec."""
config = HabitatSimulatorConfig(
scene_path="scene.glb",
depth_sensor_uuid=None,
)
create_habitat_simulator(config, fake_habitat_sim)
assert len(_specs_created) == 1
assert _specs_created[0].uuid == "color_sensor"
def test_habitat_config_rejects_invalid_hfov(
fake_habitat_sim: SimpleNamespace,
) -> None:
"""``hfov_degrees=0`` should raise ``ValueError`` containing ``hfov_degrees``."""
config = HabitatSimulatorConfig(
scene_path="scene.glb",
hfov_degrees=0.0,
)
with pytest.raises(ValueError, match="hfov_degrees"):
create_habitat_simulator(config, fake_habitat_sim)