AgentCanvas / Pages / Developer Guide / Nodesets / Env / HM-EQA
2026-08-13 16:30

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:

  1. HMEQAEnvManager (singleton) β€” per-episode simulator rebuild (one .glb per episode), pinned single-thread executor for GL affinity, loads questions.csv + scene_init_poses.csv once.
  2. Canvas tool nodes β€” 5 tools (reset / step / episode_info / cam_intrinsics / evaluate).
  3. EnvHMEQANodeSet β€” server_python points to $HMEQA_PYTHON, so the framework auto-routes the nodeset into a ac-hmeqa subprocess 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.


Interface migrated (2026-06-09). This nodeset now implements the gym-like env interface β€” 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


3. Action Contract

Free-pose teleport, sent as JSON TEXT:

{"position_normal": [x, y], "angle": 1.5708}

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:


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.

Superseded 2026-07-29: the cascade is now three-deep β€” dataset (hmeqa / mt_hm3d) β†’ split β†’ episode_index β€” since the dataset-spec extension in Β§8. Eval submissions select MT-HM3D with dataset=mt_hm3d, like ObjectNav's mp3d_v1.


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:

  1. Request access at https://aihabitat.org/datasets/hm3d-semantics
  2. huggingface-cli login with a read token
  3. Download hm3d-train-habitat-v0.2.tar and extract to data/hm3d/hm3dsem/

See also:


8. Datasets: hmeqa / mt_hm3d

Since 2026-07-29 the nodeset carries a _DATASET_SPECS table and serves two benchmarks through one env. Selecting a dataset is an episode-corpus choice, not a task change β€” the verified explore_eqa_hmeqa graph runs MT-HM3D unmodified via the panel's dataset field (or dataset=mt_hm3d on an eval submission).

SpecBenchmarkCorpusScoring
hmeqaHM-EQA (24.03, Explore-until-Confident)500 multi-choice questions / 266 scenes, val onlyletter-match SR
mt_hm3dMT-HM3D (25.05, MemoryEQA, BIT; preprint)1,587 multi-choice questions / 828 scenes (737 train + 91 val; both eval-only β€” "train" is the HM3D scene split)letter-match SR + norm_step; steps_taken added to the metrics dict (num_steps stays = budget for the verified graph)

Each spec names its own questions/init-pose CSVs, its own choices parser, and a force_floor0_init flag. Three upstream behaviours are deliberate benchmark-native mirrors β€” do not "fix":

Answer-distribution skew. 67.4% of MT-HM3D answers are "A" β€” a constant-A agent beats every published baseline. Always report per-category numbers alongside SR, and keep a blind baseline column for calibration.

MT-HM3D verification evidence

CheckEvidenceResult
Loader + sim smokes2026-07-29, agentcanvas env (loader) + ac-hmeqa (sim)both specs load; scene pool audited 828/828 MT scenes with non-empty glb + navmesh
Graph run on MT-HM3Drun 20260813_155648 β€” explore_eqa_hmeqa, dataset=mt_hm3d, 1 episode, slot-bcompleted: 47 steps to budget, Prismatic in the loop, metrics harvested β€” success=0, norm_step=1.0, steps_taken=47. Single episode; not comparable to the paper's full-set rows (ExploreEQA/Prismatic 30.22%, MemoryEQA 54.5%)
30-episode MT-HM3D runrun 20260814_183047 β€” explore_eqa_hmeqa, dataset=mt_hm3d, 30 episodes, 6 workerscompleted 30/30: SR 40.0% (12/30), norm_step 1.0 (the graph always answers at budget, by design). First-30 head subset, easy-skewed β€” not comparable to the paper's full-set 30.22%

Not yet: any multi-episode MT-HM3D number; the MemoryEQA method itself (YOLOv11 + CLIP + RAG memory β€” reference clone at tmp/thirdparty/MemoryEQA). The sibling benchmark from the same lineage, EXPRESS-Bench, is a separate nodeset β€” see EXPRESS-Bench.

AgentCanvas docs