HM-EQA NodeSet
The HM-EQA NodeSet (EnvHMEQANodeSet) wraps Habitat-Sim + HM3D semantic meshes for House-Mesh Embodied Question Answering (Ren et al. 2024, explore-eqa). Each episode is a question about a specific scene+floor; the agent explores via free-pose teleports and emits a letter answer (A/B/C/D).
Sibling of Habitat β not an extension. The two cannot share an environment: Habitat NodeSet pins habitat-sim 0.1.7 + Python 3.8 (VLN-CE); HM-EQA uses a newer habitat-sim + Python 3.9 + torch 2.2.1. Different conda env (hmeqa), different simulator instance.
1. Overview
Purpose
House-Mesh Embodied Question Answering asks multi-choice questions over HM3D semantic scenes. Each episode = one question + one scene + one floor + one initial pose. The 500-question val split ships in the HM-EQA repo's CSVs (HM3D train-split license gates the training Qs).
Architecture
Three-layer pattern mirroring Habitat:
HMEQAEnvManager(singleton) β per-episode simulator rebuild (one.glbper episode), pinned single-thread executor for GL affinity, loadsquestions.csv+scene_init_poses.csvonce.- Canvas tool nodes β 5 tools (reset / step / episode_info / cam_intrinsics / evaluate).
EnvHMEQANodeSetβserver_pythonpoints to$HMEQA_PYTHON, so the framework auto-routes the nodeset into aac-hmeqasubprocess when loaded via?mode=server.
Server-Mode Only
The nodeset always runs server-mode: habitat-sim isn't installed in the default agentcanvas env. Set HMEQA_PYTHON=/home/$(whoami)/miniforge3/envs/ac-hmeqa/bin/python before loading, or rely on the default.
reset (metadata only), step_* (control signals, no observation), observe_* (pull perception), evaluate. The node list below predates the migration and may be stale; the template's migration map (Β§6) and capability matrix (Β§3) are authoritative.2. Canvas Nodes
| Node Type | Display Name | Input Ports | Output Ports | Purpose |
|---|---|---|---|---|
env_hmeqa__reset |
HM-EQA: Reset | trigger (ANY, optional) |
rgb, depth, pose, cam_pose_matrix, pose_normal, angle, floor_height, question, episode_id, num_step |
Start/restart the episode selected on the env panel; emits initial obs + question |
env_hmeqa__step |
HM-EQA: Step (teleport) | action (TEXT JSON) |
rgb, depth, pose, cam_pose_matrix, pose_normal, angle, floor_height, question, episode_id, step_index, done |
Free-pose teleport; emits post-step obs; fires done=True when step_index >= num_step |
env_hmeqa__episode_info |
HM-EQA: Episode Info | (none) | question, raw_question, choices, answer, scene, floor, episode_id, num_step, tsdf_bnds, floor_height |
Episode metadata incl. 3Γ2 TSDF voxel-volume bounds for the planner |
env_hmeqa__cam_intrinsics |
HM-EQA: Cam Intrinsics | (none) | cam_intr (3Γ3) |
Pinhole intrinsics β episode-constant, derived from hfov + img size |
env_hmeqa__evaluate |
HM-EQA: Evaluate | pred_letter (TEXT) |
success (BOOL), gt (TEXT), metrics (METRICS) |
Post-hoc success check β compare predicted letter to GT |
Output-port highlights
cam_pose_matrix(ANY): 4Γ4 extrinsic in the TSDF frame (already composed viapose_habitat_to_normalβpose_normal_to_tsdf). Feeds directly intoTSDFPlanner.integrate.pose_normal(ANY): 3-vector agent position in normal frame. FeedsTSDFPlanner.find_next_pose.tsdf_bnds(ANY):[[x_lo, x_hi], [y_lo, y_hi], [z_lo, z_hi]]derived from the HM3D mesh bounds + floor height; constructs the planner on first step.num_step(ANY): scene-size-driven budget βint(sqrt(scene_size) * max_step_room_size_ratio), typically 10β20 steps.
3. Action Contract
Free-pose teleport, sent as JSON TEXT:
{"position_normal": [x, y], "angle": 1.5708}
position_normal:[x, y]in the normal frame. The env appends the episode'sfloor_heightand converts to habitat coordinates internally.angle: yaw in radians.
There is no mid-episode "answer" action. Answer emission is post-hoc on the method side β see ExploreEQA NodeSet aggregate step. This deliberately sidesteps the action-manifest unification tracked as TODO #46 β once a second multi-action env shows up we'll bundle them.
Source reference: workspace/nodesets/_upstream/explore-eqa/upstream/run_vlm_exp.py:313-321.
4. Observation Bundle
Every reset and step returns the same 10/11-port bundle. The bundle is designed so a downstream method nodeset (e.g. ExploreEQA) can run TSDFPlanner.integrate, find_prompt_points_within_view, and find_next_pose without needing any env-side knowledge.
| Port | Wire Type | Meaning |
|---|---|---|
rgb |
IMAGE | HΓWΓ3 uint8 (alpha dropped) |
depth |
DEPTH | HΓW float32 in meters |
pose |
POSE | habitat-frame agent pose (canvas UI only) |
cam_pose_matrix |
ANY | 4Γ4 TSDF-frame camera extrinsic |
pose_normal |
ANY | 3-vector normal-frame position |
angle |
ANY | yaw (radians) |
floor_height |
ANY | episode-constant z (normal frame) |
question |
TEXT | formatted multi-choice question (tail: \nA. <choice>\nB. ...) |
episode_id |
TEXT | episode index as string |
step_index |
ANY | step counter (step only) |
done |
BOOL | step_index >= num_step (step only) |
Black-image guard
The env tracks is_black in the bundle when the agent falls through the floor / outside the mesh. The HM-EQA method nodeset skips TSDF integration on black frames (matching upstream behaviour).
5. Configuration
Controlled via module-level _DEFAULTS in workspace/nodesets/env/env_hmeqa/__init__.py. Override via env vars if needed:
| Key | Default | Notes |
|---|---|---|
img_width |
640 | pixels |
img_height |
480 | pixels |
hfov |
120 | degrees |
camera_height |
1.5 | m (above floor) β paper cfg/vlm_exp.yaml line 18 |
camera_tilt_deg |
-30.0 | negative = tilted downward |
tsdf_grid_size |
0.1 | m β consumed by the method-side TSDFPlanner |
init_clearance |
0.5 | m β initial pose clearance radius |
max_step_room_size_ratio |
3.0 | num_step = int(sqrt(scene_size) * ratio) |
black_pixel_ratio |
0.5 | frame is "black" if #black-pixels > ratio * H * W β paper cfg/vlm_exp.yaml line 30 |
seed |
42 | pathfinder seed |
Paper-baseline values are mirrored from workspace/nodesets/_upstream/explore-eqa/upstream/cfg/vlm_exp.yaml.
Data layout (ADR-platform-005):
data/hm3d/hmeqa/questions.csv HM-EQA Q&A
data/hm3d/hmeqa/scene_init_poses.csv per-(scene, floor) init
data/hm3d/hmeqa/Open_Sans/ annotation font (method-side)
data/hm3d/hm3dsem/{scene}/{scene[6:]}.basis.glb meshes (HuggingFace-gated)
data/hm3d/hm3dsem/{scene}/{scene[6:]}.basis.navmesh navmesh
Environment variable overrides:
HMEQA_PYTHONβ hmeqa-env Python interpreter path (default/home/$(whoami)/miniforge3/envs/ac-hmeqa/bin/python)HMEQA_DATA_ROOTβ override the CSV/font dirHMEQA_SCENE_ROOTβ override the HM3D mesh dir
6. Env panel Integration
HMEQAEnvPanel is a two-field cascade:
| Field | Type | Options |
|---|---|---|
split |
select | val (only split shipped in the CSV) |
episode_index |
select | 0 .. ~500 |
Actions: play, pause, stop, reset. The play button triggers run_start; Reset re-applies the currently-selected episode without touching graph state.
The split field is retained as a single-option select to give future train/test splits a zero-migration path.
7. Environment Setup
One-time install:
bash scripts/install/install_ac_hmeqa.sh # creates the hmeqa conda env
bash scripts/data/fetch_episodes_vln.sh --hmeqa # stages CSVs + fonts
HM3D mesh download is manual β gated by HuggingFace auth:
- Request access at https://aihabitat.org/datasets/hm3d-semantics
huggingface-cli loginwith a read token- Download
hm3d-train-habitat-v0.2.tarand extract todata/hm3d/hm3dsem/
See also:
- ExploreEQA NodeSet β the method side that consumes this env
- Habitat NodeSet β the VLN-CE sibling