Skip to content

arena_hearing

Robot hearing for Arena: the srp and seld front-ends on any robot audio source, the directional belief grid, the speed-filter policy and the Nav2 overlay that applies its mask. It is the consumer side of the audio a robot receives. The audio itself comes from an acoustics simulator, acoustics:=arena (the arena_auditory backend) today. The bus front-end, which republishes the simulator's propagated receptions, lives in arena_auditory.

This repository is the optional hearing feature, a submodule of Arena. Install it with arena feature hearing install, which checks it out, installs its system dependencies through rosdep, syncs its Python dependencies (torch, librosa, scipy, huggingface-hub) and rebuilds. robot.hearing other than none also needs acoustics:=arena, installed by arena feature auditory install.

Layout

arena_hearing/                  this repository
`-- arena_hearing/              ament_python package
    |-- arena_hearing/          python package
    |   |-- constants.py        topic names of the hearing stack
    |   |-- params.py           every hearing node parameter and its default
    |   |-- fleet.py            per-robot lifecycle and lockstep channels
    |   |-- belief_grid.py belief_node.py      belief grid and its node
    |   |-- policy.py corners.py policy_node.py speed-filter policy and its node
    |   |-- doa.py onset.py timeline.py srp_frontend_node.py   srp front-end
    |   |-- seld.py dcase.py weights.py seld_frontend_node.py  seld front-end
    |   `-- audio_replay.py     wav replay as AudioFrame
    |-- config/                 nav2_overlay.yaml, weights.yaml
    |-- launch/                 hearing.launch.py
    `-- tools/                  source-only scripts, not installed: belief replay

Front-ends

Every detection source publishes arena_robots_msgs/SoundDetection on <r>/hearing/<frontend>/detections, so the belief node is source-agnostic:

Frontend Source
bus the arena_auditory bus node, the propagated receptions of the robot, map-frame bearings
srp hearing_srp_frontend, energy onsets plus GCC-PHAT over raw_array, array-frame bearings, kind onset
seld hearing_seld_frontend, the SELDnet model over raw_array, array-frame bearings

Every hearing node covers every fleet robot of its env: it follows state/robots, keeps its state per robot and registers one hard lockstep channel per robot. hearing_belief turns each robot's detections into a decaying pedestrian-likelihood grid (hearing/belief_grid, hearing/belief_wedges). hearing_policy reads it and is the single writer of the robot's Nav2 SpeedFilter mask (hearing/speed_filter_mask) and its CostmapFilterInfo (hearing/costmap_filter_info).

The front-ends read the array geometry off each AudioFrame: srp fits GCC-PHAT over every pair of the microphone positions the frame declares, any array of two or more microphones apart in the plane, and rebuilds when the layout changes. seld reads only the array its weights were trained on (array in config/weights.yaml): a stream whose sample rate, channel names or microphone positions differ is refused with an error. Both convert levels to dB SPL with the MEMS sensitivity the frame carries.

Running in an Arena env

robot.hearing:=bus|srp|seld makes the task generator's hearing axis include launch/hearing.launch.py per env and merge config/nav2_overlay.yaml (a SpeedFilter on the local costmap and the controller's speed_limit_topic, both below the robot namespace) into every robot's Nav2 parameters:

export ARENA_WORLD_PATH=$ARENA_WS_DIR/src/Arena/_assets/arena-benchmarks-prod-public/suites/acoustics/worlds
arena launch sim:=gazebo robot:=jackal world:=acoustics_bend_narrow_O \
    task.robots:=scenario task.obstacles:=scenario \
    task.scenario.file:=hearing__world-acoustics_bend_narrow_O__robot-moving__pedestrians-1__ends-a-to-b \
    acoustics:=arena robot.hearing:=bus

srp and seld default auditory.array.spec to four_mic.

Launch arguments

hearing.launch.py declares:

Argument Default Effect
env.ns /arena/env_0 Env namespace, the hearing nodes live in it
tg_node task_generator_node Task generator node, the robot topics live below it
frontend bus bus, srp or seld, srp and seld also start their front-end node
policy full Mask layers: belief only, listen adds the corner listen cap, full adds yield
robot.hearing.seld.device empty (cuda) Torch device of the SELDnet front-end
robot.hearing.seld.lookahead_frames empty (5) SELDnet label frames of future context, 100 ms each
robot.hearing.seld.bearing_source empty (gcc) gcc fits GCC-PHAT over the array, seld takes the model azimuth
robot.hearing.srp.hop_s empty (0.1) srp hop length in seconds
robot.hearing.srp.floor_window_s empty (5.0) srp noise-floor median window in seconds
robot.hearing.srp.onset_db empty (6.0) srp onset threshold above the floor in dB

The robot.hearing.* arguments are declared from the parameters in params.py that carry a description. Every robot.hearing.<param>:=<value> reaches the hearing nodes as parameter <param>, coerced to the type of its default, for example robot.hearing.belief.tau_s:=3.0 or robot.hearing.belief.emission_db.footstep:=55.0. An unknown key or an emission level of a kind that is not detected is an error, an empty value keeps the node default, and a list cannot be empty: robot.hearing.belief.kinds:=[all] accepts every kind. These keys never reach the robot adapters. The launch also derives hearing.frontend, hearing.tg_node, belief.event_rate_hz, policy.listen.enabled, policy.yield.enabled and, for srp, belief.kinds.

Topics

Below the task generator node /arena/env_0/task_generator_node, <r> is the robot namespace:

Direction Topic Note
in <r>/audio/raw_array arena_robots_msgs/AudioFrame (ArrayStream.RAW of arena_robots.audio), srp and seld
in <r>/hearing/<frontend>/detections arena_robots_msgs/SoundDetection
in map, state/resetting, state/robots grid geometry, reset, fleet
in <r>/plan Nav2 global plan, for blind-bend detection
out <r>/hearing/srp/detections, <r>/hearing/seld/detections the front-ends
out <r>/lockstep/hearing front-end heartbeat per robot
out <r>/hearing/belief_grid, <r>/hearing/belief_wedges RViz, the wedges only while subscribed
out <r>/hearing/speed_filter_mask OccupancyGrid, latched, read by the robot's SpeedFilter
out <r>/hearing/costmap_filter_info CostmapFilterInfo, latched, points the SpeedFilter at the mask
out <r>/hearing/policy_state JSON: state, distances, masses, level slope, limit, binding layer, yield count and time
out <r>/hearing/policy_markers approach lane and hold band, only while subscribed
Nav2 <r>/hearing/speed_limit SpeedFilter to controller

Belief and policy

The belief layer of the mask is the belief max-filtered over a disc of policy.reaction_radius_m (2.0 m) before thresholding. The robot slows from policy.speed_free_pct (100) to policy.speed_min_pct (40) as the belief goes from policy.belief_threshold (0.6) to 1. A wedge is never narrower than belief.min_half_width_m (0.3 m). belief.level_range.enabled (default false) keeps the wedge flat out to belief.max_range_m, since walls and doors attenuate an occluded pedestrian and the level would place it too far away. The emission level per kind is the level of the kind's default asset in the sound catalog, overridable by belief.emission_db.<kind>.

hearing_policy composes the mask from three layers by lowest nonzero percentage: belief, listen (policy.listen_mps on the approach to a blind bend) and hold (policy.hold_mps before the bend while yielding). The robot's own drivetrain masks the pedestrian it listens for, and its level grows with speed.

A plan point is blind when at least 25 % of the free cells within 2 m of the point policy.lookahead_m further along the plan, on its far side, are hidden from it by occupied cells. The bend is the first point from which that clears, or the plan end when the plan ends blind. Only blind stretches that start within policy.approach_m plus policy.lookahead_m are searched. States: cruise, listen inside policy.approach_m, yield once the belief mass within policy.corner_radius_m of the bend exceeds policy.yield_fraction, and pass once the mass moved behind the robot, faded below policy.release_fraction, or the level fell for policy.recede_s (each after policy.min_yield_s), or at policy.yield_timeout_s. Pass takes the bend at listen speed. Nav2 reads 0 % as no limit, so the hold is a 0.03 m/s creep. robot.hearing.policy:=belief|listen|full picks the active layers.

Evaluation

The arms are contestants of contests/hearing.yaml in arena_evaluation, run against the acoustics suite:

arena evaluation benchmark --suite acoustics --contest hearing

yield_count and time_yielding_s on <r>/hearing/policy_state are cumulative per robot. python3 arena_hearing/tools/replay_belief.py EPISODE_DIR (from a source checkout) replays an exported episode through the belief grid offline, from SELDnet detections or the ground-truth labels.

Front-end checks

ros2 run arena_hearing hearing_audio_replay <4ch.wav> <robot> --array four_mic --ros-args -r __ns:=/arena/env_0

hearing_audio_replay publishes every channel of the wav on <tg>/<robot>/audio/raw_array, --array declares the geometry the front-ends need. A front-end picks the robot up from state/robots.

The streaming SELDnet path re-runs the model on a 5 s sliding window once per 100 ms label frame and emits the frame seld.lookahead_frames (default 5) before the window end. The bearing comes from seld.bearing_source: gcc (default) fits the array geometry to GCC-PHAT delays over every microphone pair, seld takes the model azimuth, which is front-biased on the shipped checkpoint.

Weights

The SELDnet checkpoint and feature scaler are pinned with sha256 in config/weights.yaml, hosted on Hugging Face and stored in $ARENA_DATA_DIR/auditory/seld/:

ros2 run arena_hearing hearing_setup

The front-end fetches them on first use when they are missing. The model, SALSA-Lite features and multi-ACCDOA decode are in dcase.py, adapted from the DCASE 2023 SELD baseline (MIT).

Tests

python3 -m pytest arena_hearing/tests/unit -q
python3 -m pytest arena_hearing/tests/ros -q

The ROS tests run the belief and policy nodes as subprocesses against a synthetic fleet, map and TF, and need a sourced ROS 2 environment.

Configuration

File Content
config/weights.yaml SELDnet weights and pins
config/nav2_overlay.yaml Nav2 SpeedFilter overlay
arena_hearing/params.py every hearing node parameter and default
task_generator/launch/hearing/ (Arena) hearing axis dispatch and the include of launch/hearing.launch.py
task_generator/task_generator/simulators/hearing/ (Arena) node-side hearing axis, the single gateway into this package