Skip to content

Commit 9a0e9c7

Browse files
committed
docs: describe Nav2 observation demo
Signed-off-by: SamuelFoo <fooenzesamuel@gmail.com>
1 parent 89ed1d0 commit 9a0e9c7

4 files changed

Lines changed: 113 additions & 85 deletions

File tree

discourse/3-layered-global-occupancy-map.md

Lines changed: 56 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -2,138 +2,122 @@
22

33
# Layered Global Map Observations
44

5-
This post describes the observation interface for contributing temporary map
6-
information to the next generation Open-RMF prototype.
5+
This post describes the implemented observation interface and demos for contributing temporary map information to the next generation Open-RMF prototype.
76

8-
The goal is to let robots and perception systems publish what they currently
9-
observe without tying them to a specific central map implementation. The first
10-
prototype consumes sparse 2D occupancy regions because that is enough for the
11-
current global planning grid, but the interface is intentionally isolated in
12-
`rmf_layered_map_msgs` so richer representations, such as height-aware or voxel
13-
observations, can be added later.
7+
The goal is to let robots and perception systems publish what they currently observe without tying them to a specific central map implementation. The first prototype consumes sparse 2D occupancy regions because that is enough for the current global planning grid, but the interface is isolated in `rmf_layered_map_msgs` so richer representations, such as height-aware or voxel observations, can be added later.
148

159
# Quick Summary
1610

1711
* Observation sources publish sparse temporary map patches
18-
* One message can contain both clear-space and occupied-space patches from the
19-
same sensor snapshot
12+
* One message can contain both clear-space and occupied-space patches from the same sensor snapshot
2013
* Clear-space patches have TTLs, just like obstacle patches
2114
* A source can reset its previous observations without using a TTL
22-
* Robot-mounted sources can include the robot pose at observation time
23-
* The observation messages live in `rmf_layered_map_msgs`, leaving
24-
`rmf_prototype_msgs` unchanged
15+
* Robot-mounted sources include the observation-frame pose at observation time
16+
* The Rust map server composes a static occupancy grid and active observations into `/map`
17+
* The Nav2 demo converts scans from three robots into point regions and displays the combined map
18+
* The observation messages live in `rmf_layered_map_msgs`, leaving `rmf_prototype_msgs` unchanged
2519

26-
# Observation Topic
20+
# Observation Topics
2721

28-
Observation sources publish dynamic map observations on:
22+
The layered map server uses these topics:
2923

24+
* `/map/static` - static `nav_msgs/OccupancyGrid`
3025
* `/map/region_updates` - [`MapRegionUpdate.msg`](../rmf_layered_map_msgs/msg/MapRegionUpdate.msg)
26+
* `/map` - composed `nav_msgs/OccupancyGrid`
3127

32-
The topic is an event stream. Updates are not latched because expired
33-
observations should not be replayed to a restarted map service as if they were
34-
current.
28+
The observation topic is an event stream. Updates are not latched because expired observations should not be replayed to a restarted map service as if they were current.
3529

36-
Each observation stream should use a stable `source_id`, such as
37-
`robot_1/local_costmap`, `robot_1/front_lidar`, or `door_sensor/west_lobby`.
38-
The map service tracks each source independently so one robot can update or
39-
reset its own temporary observations without disturbing observations from other
40-
sources.
30+
Each observation stream uses a stable `source_id`, such as `robot_1/local_costmap`, `robot_1/front_lidar`, or `door_sensor/west_lobby`. The map service tracks each source independently so one robot can update or reset its temporary observations without disturbing observations from other sources.
4131

4232
# Messages
4333

4434
## `MapObservationSource`
4535

46-
[`MapObservationSource.msg`](../rmf_layered_map_msgs/msg/MapObservationSource.msg)
47-
describes where an observation came from.
36+
[`MapObservationSource.msg`](../rmf_layered_map_msgs/msg/MapObservationSource.msg) describes where an observation came from.
4837

4938
Important fields:
5039

51-
* `header`: global frame for the observation and its required, non-zero
52-
timestamp
53-
* `source_id`: stable source identifier, usually the robot namespace plus the
54-
local source name
55-
* `robot_name`: robot that produced this observation, if the source is mounted
56-
on a robot. This can be empty for fixed sensors or synthetic map layers
40+
* `header`: global frame for the observation and its required, non-zero timestamp
41+
* `source_id`: stable source identifier, usually the robot namespace plus the local source name
42+
* `robot_name`: robot that produced this observation, if the source is mounted on a robot; this can be empty for fixed sensors or synthetic map layers
5743
* `map_name`: map or level that the observations belong to
58-
* `robot_pose`: pose of the robot-local observation frame in `header.frame_id`
59-
when the observation was produced, so consumers do not need to reconstruct
60-
it from a separate TF-like lookup
44+
* `robot_pose`: pose of the robot-local observation frame in `header.frame_id` when the observation was produced, so consumers do not need to reconstruct it from a separate TF-like lookup
6145
* `default_ttl_sec`: fallback TTL in seconds for patches from this source
6246

6347
## `MapRegionUpdate`
6448

65-
[`MapRegionUpdate.msg`](../rmf_layered_map_msgs/msg/MapRegionUpdate.msg)
66-
describes one observation snapshot from a source.
49+
[`MapRegionUpdate.msg`](../rmf_layered_map_msgs/msg/MapRegionUpdate.msg) describes one observation snapshot from a source.
6750

6851
Important fields:
6952

7053
* `source`: metadata for the observation source
71-
* `reset_source`: remove active observations from the same `source_id` and
72-
`map_name` before applying the new patches
54+
* `reset_source`: remove active observations from the same `source_id` and `map_name` before applying the new patches
7355
* `patches`: clear-space and occupied-space patches from this snapshot
7456

75-
`reset_source` is useful when a source publishes replacement snapshots, changes
76-
maps, shuts down, or knows its previous observation state is no longer valid. It
77-
does not have a TTL because it is a bookkeeping operation, not an active map
78-
observation.
57+
`reset_source` is useful when a source publishes replacement snapshots, changes maps, shuts down, or knows its previous observation state is no longer valid. It does not have a TTL because it is a bookkeeping operation, not an active map observation.
7958

8059
## `MapRegionPatch`
8160

82-
[`MapRegionPatch.msg`](../rmf_layered_map_msgs/msg/MapRegionPatch.msg)
83-
describes one group of regions with the same action and TTL.
61+
[`MapRegionPatch.msg`](../rmf_layered_map_msgs/msg/MapRegionPatch.msg) describes one group of regions with the same action and TTL.
8462

8563
Patch types:
8664

8765
* `UPDATE_CLEAR`: regions are temporary free space
8866
* `UPDATE_OBSTACLE`: regions are temporary occupied space
8967

90-
Clear patches and obstacle patches both require a TTL because both describe
91-
temporary observations. If a patch TTL is zero or negative, the source
92-
`default_ttl_sec` is used. If that is also zero or negative, the map service's
93-
configured default TTL is used.
68+
Clear patches and obstacle patches both require a TTL because both describe temporary observations. If a patch TTL is zero or negative, the source `default_ttl_sec` is used. If that is also zero or negative, the map server's configured default TTL is used.
9469

95-
When a sensor snapshot includes both clear and occupied regions, publish them in
96-
one `MapRegionUpdate` message. The map service applies clear patches before
97-
obstacle patches, which gives deterministic "clear then mark" behavior without
98-
relying on the arrival order of separate ROS messages.
70+
When a sensor snapshot includes both clear and occupied regions, publish them in one `MapRegionUpdate` message. The map server applies clear patches before obstacle patches, which gives deterministic "clear then mark" behavior without relying on the arrival order of separate ROS messages.
9971

100-
The first prototype uses `rmf_prototype_msgs/Region` for sparse 2D geometry so
101-
robots can publish compact patches. Region coordinates are robot-local. The map
102-
service transforms them through `source.robot_pose` into
103-
`source.header.frame_id` before rasterizing them. Fixed sensors can publish
104-
sensor-local regions with their sensor pose, while synthetic sources whose
105-
regions are already global can use the identity pose. The first implementation
106-
accepts point and axis-aligned rectangle regions.
72+
The first prototype uses `rmf_prototype_msgs/Region` for sparse 2D geometry. Region coordinates are robot-local, and the map server transforms them through `source.robot_pose` into `source.header.frame_id` before rasterizing them. Fixed sensors can publish sensor-local regions with their sensor pose, while synthetic sources whose regions are already global can use the identity pose. The current implementation accepts point and axis-aligned rectangle regions.
73+
74+
# Layered Map Server
75+
76+
`rmf_layered_map_server` keeps the static occupancy grid separate from dynamic observations and publishes their composition on `/map`. It validates source timestamps and frames, ignores updates that are older than the latest accepted update from the same source, supports source resets, and removes observations after their TTL expires.
77+
78+
The server applies clear patches before obstacle patches from the same update. During composition, obstacle observations win over clear observations so occupied space is not accidentally erased by another active source.
79+
80+
# Three-Robot Nav2 Demo
81+
82+
The committed Nav2 demo can be launched with:
83+
84+
```bash
85+
ros2 launch rmf_layered_map_server_demo nav2_observations.launch.py
86+
```
87+
88+
The launch file starts three stationary robots in different free corners of the warehouse and spawns one deterministic Gazebo box near each robot. Each observation node subscribes to its robot's local `sensor_msgs/LaserScan`, filters invalid or out-of-range returns, and publishes the remaining scan endpoints as point regions.
89+
90+
Each scan is a replacement snapshot: `reset_source` removes the source's preceding points before the new points are added, and a short TTL removes the source if it stops publishing. The observation-frame pose is recorded in the shared `map` frame so the map server can transform the scan-local regions before rasterizing them.
91+
92+
The demo launches Nav2 localization but does not launch Nav2 planning or control components, RMF planning, or navigation goals. Three robot-local RViz windows are enabled by default, and another RViz window displays the combined global `/map`.
93+
94+
The scan-to-region conversion is deliberately simple so its information loss and publication cost are visible. `beam_stride` reduces the number of point regions, `publish_period_sec` throttles replacement snapshots, and `max_observation_range` limits represented returns. The current demo publishes occupied endpoints only; it does not convert raycast clearing into free-space regions or compress adjacent points into larger regions.
10795

10896
# Example Flow
10997

110-
A local costmap or LiDAR observation node can publish a replacement snapshot by:
98+
A local costmap, LiDAR, or other observation node can publish a replacement snapshot by:
11199

112-
1. Stamping the observation source with the global frame and observation time,
113-
and including the pose of the robot-local observation frame.
114-
2. Setting `reset_source` if the new snapshot replaces the source's previous
115-
temporary observations.
100+
1. Stamping the observation source with the global frame and observation time, and including the pose of the robot-local observation frame.
101+
2. Setting `reset_source` if the new snapshot replaces the source's previous temporary observations.
116102
3. Adding one or more clear patches for free space seen by the sensor.
117103
4. Adding one or more obstacle patches for occupied space seen by the sensor.
118-
5. Giving each patch a TTL long enough to survive normal publication jitter but
119-
short enough to decay when the observation is no longer refreshed.
104+
5. Giving each patch a TTL long enough to survive normal publication jitter but short enough to decay when the observation is no longer refreshed.
120105

121-
The map service should ignore snapshots from a source if their timestamp is
122-
older than a newer snapshot that has already been accepted. This prevents a late
123-
clear/reset message from removing obstacle information that came from a newer
124-
observation.
106+
The map server ignores snapshots from a source if their timestamp is older than a newer snapshot that has already been accepted. This prevents a late clear or reset message from removing obstacle information that came from a newer observation.
125107

126108
# Implemented Test Coverage
127109

128-
The first server tests cover:
110+
The committed server and demo tests cover:
129111

130112
* composing obstacle regions over a static planning grid
131113
* point regions with non-zero map origins and non-1.0 resolutions
132114
* transforming robot-local regions into the global map frame
133115
* out-of-bounds regions, malformed point arrays, and unsupported region types
134-
* rejecting updates without a timestamp
116+
* rejecting updates without a timestamp or in a different global frame
135117
* pruning expired observations by TTL
136118
* clear and obstacle patches in the same update
137119
* late older snapshots being ignored
138120
* reset updates removing observations from the same source and map
139121
* multiple robot sources being stitched into one composed grid
122+
* filtering invalid and out-of-range laser returns
123+
* preserving original beam angles when scan points are sampled

map_server/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,13 @@ ros2 launch rmf_layered_map_server_demo demo.launch.py
1111
```
1212

1313
Use `use_rviz:=False` for a headless run.
14+
15+
## Nav2 Observations
16+
17+
The three-robot demo converts local laser observations into point regions and combines them into the global `/map`. It launches three robots in warehouse corners, deterministic clutter for each robot to observe, the layered map server, and a separate RViz window for the combined map. It does not launch RMF or Nav2 planning components.
18+
19+
```bash
20+
ros2 launch rmf_layered_map_server_demo nav2_observations.launch.py
21+
```
22+
23+
See [`rmf_layered_map_server_demo/README.md`](rmf_layered_map_server_demo/README.md) for launch arguments and the current scan-to-region limitations.

map_server/rmf_layered_map_server/README.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,9 @@ Default topics:
1212

1313
Dynamic observations expire according to their TTL, so transient local obstacles do not remain in the global planning map forever. A LiDAR or local costmap observer can keep refreshing an obstacle while it is still seen, then let the server decay it after the TTL.
1414

15-
`MapRegionUpdate` messages contain a list of `MapRegionPatch` entries. A sensor
16-
snapshot that clears freespace and marks obstacles can publish clear and obstacle
17-
patches in the same message. The server applies clear patches before obstacle
18-
patches. If the snapshot replaces the source's previous observation state, set
19-
`reset_source` so the server removes old observations from that `source_id` and
20-
`map_name` before adding the new patches. Resetting has no TTL. Clear and
21-
obstacle patches both use TTLs in seconds.
22-
23-
Patch regions are expressed in the robot-local observation frame. The server
24-
uses `source.robot_pose` to transform them into `source.header.frame_id`, which
25-
must match the static occupancy grid frame. Updates need a non-zero source
26-
timestamp. The current implementation accepts point and axis-aligned rectangle
27-
regions. It logs and ignores other region types.
15+
`MapRegionUpdate` messages contain a list of `MapRegionPatch` entries. A sensor snapshot that clears free space and marks obstacles can publish clear and obstacle patches in the same message. The server applies clear patches before obstacle patches. If the snapshot replaces the source's previous observation state, set `reset_source` so the server removes old observations from that `source_id` and `map_name` before adding the new patches. Resetting has no TTL. Clear and obstacle patches both use TTLs in seconds.
16+
17+
Patch regions are expressed in the robot-local observation frame. The server uses `source.robot_pose` to transform them into `source.header.frame_id`, which must match the static occupancy grid frame. Updates need a non-zero source timestamp. The current implementation accepts point and axis-aligned rectangle regions. It logs and ignores other region types.
2818

2919
## Visualization Smoke Test
3020

@@ -43,3 +33,13 @@ ros2 launch rmf_layered_map_server_demo demo.launch.py
4333
```
4434

4535
The launch file starts RViz with a `Map` display on `/map` and the fixed frame set to `map`. The demo publisher sends a bordered static map and a temporary obstacle region with a 5 second TTL. The obstacle should appear in the composed map and then disappear when the TTL expires.
36+
37+
## Nav2 Observation Demo
38+
39+
To combine observations from three Nav2 robots without starting RMF or Nav2 planning, launch:
40+
41+
```bash
42+
ros2 launch rmf_layered_map_server_demo nav2_observations.launch.py
43+
```
44+
45+
Each robot converts valid laser endpoints into point regions. A separate RViz window displays the composed `/map`; see the demo package README for the fidelity and publication-rate tuning arguments.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Layered Map Server Demos
2+
3+
This package contains two demonstrations of the layered map server.
4+
5+
## TTL Smoke Test
6+
7+
The small smoke test publishes a synthetic static grid and a temporary rectangle with a five-second TTL:
8+
9+
```bash
10+
ros2 launch rmf_layered_map_server_demo demo.launch.py
11+
```
12+
13+
## Three-Robot Nav2 Observation Demo
14+
15+
The Nav2 demo starts three stationary robots in free corners of the warehouse map and spawns one deterministic box near each robot. Each robot's observation node subscribes to its `sensor_msgs/LaserScan`, represents valid scan endpoints as point `Region` messages, and publishes a replacing `MapRegionUpdate` snapshot. The layered map server transforms and combines all three sources over the static map.
16+
17+
No RMF planning components or navigation goals are required. By default the Nav2 launch opens three robot-local RViz windows, and the demo opens another RViz window showing the combined `/map`:
18+
19+
```bash
20+
ros2 launch rmf_layered_map_server_demo nav2_observations.launch.py
21+
```
22+
23+
Use `use_nav2_rviz:=False` to open only the combined-map view. Use `spawn_clutter:=False` to run against the unmodified warehouse world.
24+
25+
The initial implementation deliberately keeps the scan-to-region conversion simple so its cost and information loss are visible. `beam_stride` samples fewer endpoints, `publish_period_sec` throttles snapshots, `max_observation_range` limits represented returns, and `ttl_sec` controls how quickly stale snapshots expire. For example:
26+
27+
```bash
28+
ros2 launch rmf_layered_map_server_demo nav2_observations.launch.py \
29+
beam_stride:=4 publish_period_sec:=1.0
30+
```
31+
32+
Each publisher logs the number of input beams and output regions. This makes it possible to compare message density and global-map update rate against the visual fidelity in RViz.
33+
34+
The prototype publishes occupied endpoints only. It does not yet turn clearing rays into free-space regions or compress adjacent endpoints into larger region primitives. `reset_source` removes the previous snapshot before each new scan, and TTL expiry removes observations if a robot stops publishing.

0 commit comments

Comments
 (0)