Add layered global occupancy map server#36
Conversation
Signed-off-by: SamuelFoo <fooenzesamuel@gmail.com>
Signed-off-by: SamuelFoo <fooenzesamuel@gmail.com>
Signed-off-by: SamuelFoo <fooenzesamuel@gmail.com>
Signed-off-by: SamuelFoo <fooenzesamuel@gmail.com>
Signed-off-by: SamuelFoo <fooenzesamuel@gmail.com>
arjo129
left a comment
There was a problem hiding this comment.
We should batch all observations from a single robot into a single message instead of multiple messages this improves determinism on my end. I also have been a bit critical on the specification doc.
|
|
||
| # Quick Summary | ||
|
|
||
| * Robots and perception sources can publish sparse 2D region updates with a time-to-live (TTL) |
There was a problem hiding this comment.
We may want to think about limiting it to 2D there are many cases where 2d is insufficient. For instance, overhangs. Short robots may be able to go under such overhangs but tall robots can't.
| * Robots and perception sources can publish sparse 2D region updates with a time-to-live (TTL) | ||
| * A central layered map server composes a static map with active dynamic observations | ||
| * Observations from multiple sources are stitched into one composed global grid | ||
| * The composed output is published as `nav_msgs/OccupancyGrid` on `/map` |
There was a problem hiding this comment.
This is an implementation "detail". This document is meant to be read by someone who is building a robot and wants to integrate with it. OR someone who may have some better map representation. The "/map" topic is not something we should allow people to rely on.
| # Interface Topology Overview | ||
|
|
||
| ```mermaid | ||
| flowchart LR | ||
| static["Static map server"] -- "/map/static" --> layered["Layered map server"] | ||
| observer["Observation source"] -- "/map/region_updates" --> layered | ||
| layered -- "composed /map" --> path["Path server"] | ||
| ``` | ||
|
|
||
| The layered map server owns the composed `/map` topic. When it is present, any static map publisher should be remapped to `/map/static`. |
There was a problem hiding this comment.
The /map/static is an implementation detail.
/map/region_updates is what we should be discussing.
|
|
||
| Inputs: | ||
|
|
||
| * `/map/static`: `nav_msgs/OccupancyGrid`, transient local, reliable |
There was a problem hiding this comment.
Ignore the static map in your write up here this belongs in the read me for rmf_layered_map_server.
| # The regions should be interpreted as temporary free space. | ||
| uint8 UPDATE_CLEAR=2 | ||
|
|
||
| # The update should remove previous temporary observations from this source. |
There was a problem hiding this comment.
Reset is for when a source wants to replace or invalidate its previous temporary observations without asserting that any specific cells are free.
|
|
||
| # 2D geometric observation patches. Empty regions are valid only for | ||
| # UPDATE_RESET. | ||
| rmf_prototype_msgs/Region[] regions |
There was a problem hiding this comment.
Do we iterate in order? How do we guarantee the order by which clear and mark occur. Im trying to think of a scenario where a laser scan is involved. We would have a region that needs clearing and a region that needs updating.
I also feel region data should live together with the action that should be taken and the ttl. That way a single observation in the agent's end would make a single message improving determinism.
There was a problem hiding this comment.
Addressed this by changing MapRegionUpdate to contain MapRegionPatch[] patches.
Each patch now carries its own action, occupancy value, TTL, and regions, so a single sensor snapshot can publish clear and obstacle evidence in one ROS message. The server applies reset_source first, then sorts accepted patches so UPDATE_CLEAR is applied before UPDATE_OBSTACLE, giving deterministic behavior (clear then mark) without depending on separate message arrival order.
| # Coordinate frame for the regions. | ||
| string frame_id | ||
|
|
||
| # Time when this observation snapshot was produced. | ||
| builtin_interfaces/Time stamp |
There was a problem hiding this comment.
std_msgs/msg/Header exists for this.
| width, height, resolution, origin_x, origin_y, min_x, min_y, max_x, max_y, | ||
| ); | ||
| } | ||
|
|
There was a problem hiding this comment.
Log errors for other types of regions for now.
| @@ -0,0 +1,21 @@ | |||
| # MapObservationSource.msg | |||
There was a problem hiding this comment.
One concern I have is we would have to rely on TF like interface to look up individual odometry and sync them. It might actually be worth stamping the robot pose here just so we dont re-implment TF.
Introduce MapRegionPatch so one MapRegionUpdate can carry multiple clear and obstacle patches from the same observation snapshot. Move patch-specific action, occupancy value, TTL, and region geometry out of MapRegionUpdate, leaving the update to describe the source, optional reset_source operation, and ordered patch list. Use std_msgs/Header and robot_pose on MapObservationSource, and express TTLs as seconds so rmf_layered_map_msgs no longer depends directly on builtin_interfaces. Signed-off-by: SamuelFoo <fooenzesamuel@gmail.com>
Update the layered map server and callers for MapRegionUpdate.patches: reset_source is applied before patches from the same snapshot, clear-space patches are sorted before obstacle patches for deterministic clear-then-mark behavior, older snapshots are rejected per source/map timestamp, and the demo/test helpers now populate MapRegionPatch, Header, and TTL seconds while the server package drops its unused direct builtin_interfaces dependency. Signed-off-by: SamuelFoo <fooenzesamuel@gmail.com>
Signed-off-by: SamuelFoo <fooenzesamuel@gmail.com>
Signed-off-by: SamuelFoo <fooenzesamuel@gmail.com>
Signed-off-by: SamuelFoo <fooenzesamuel@gmail.com>
|
Addressed the review feedback:
Ready for review! |
8e575ee to
e329aa3
Compare
Signed-off-by: SamuelFoo <fooenzesamuel@gmail.com>
arjo129
left a comment
There was a problem hiding this comment.
The message is looking better. There is a performance issue that we can address in a future PR.
| MapObservationSource source | ||
|
|
||
| # UPDATE_OBSTACLE, UPDATE_CLEAR, or UPDATE_RESET. | ||
| uint8 update_type |
There was a problem hiding this comment.
| uint8 update_type | |
| uint8[] update_type |
|
|
||
| # Pose of the robot when this observation was produced. Fixed sensors or | ||
| # synthetic map layers may leave this at its default value. | ||
| geometry_msgs/PoseStamped robot_pose |
There was a problem hiding this comment.
If we use the stamp here why do we need the header? Should we just use "Pose" instead of PoseStamped since we have a header?
| width, height, resolution, origin_x, origin_y, min_x, min_y, max_x, max_y, | ||
| ); | ||
| } | ||
|
|
| let stamp_nsec = if update.source.header.stamp.sec == 0 | ||
| && update.source.header.stamp.nanosec == 0 | ||
| { | ||
| now_nsec |
There was a problem hiding this comment.
This should be an error and no update,
|
|
||
| let default_ttl_sec = update.source.default_ttl_sec; | ||
| let mut patches = update.patches; | ||
| patches.sort_by_key(|patch| match patch.update_type { |
There was a problem hiding this comment.
So we always clear before marking? Mention that in the message documentation.
| // Keep the ROS handles alive for as long as the server is running. | ||
| pub static_map_subscription: rclrs::WorkerSubscription<OccupancyGrid, LayeredMapServer>, | ||
| pub region_update_subscription: rclrs::WorkerSubscription<MapRegionUpdate, LayeredMapServer>, | ||
| pub prune_timer: Box<dyn std::any::Any + Send + Sync>, |
| #[derive(Clone, Debug)] | ||
| pub struct LayeredMap { | ||
| static_grid: Option<OccupancyGrid>, | ||
| dynamic_observations: Vec<DynamicObservation>, |
There was a problem hiding this comment.
Im wondering if a Vec is the best data structure here. Lets think of how frequently we expect these observations to come in. If we assume a 1hz rate we would see the same obstacle multiple times. Would this not add performance overhead as we would have an evergrowing list of observations for the same overlapping region.
IMO we should rasterize the moment we receive an observation and prune the list. Theres a bit of a tradeoff depending on how the protocol is implemented.
| # observations. | ||
| float64 ttl_sec | ||
|
|
||
| # 2D geometric observation patches. |
There was a problem hiding this comment.
Specify the coordinate frame of the region. Is it in the robot's frame or the global frame or configurable? I recommend robot frame and then perform a transform. The rationale is we dont need to know the server's configuration properties. The less configuration we have on the server side that needs to be shared with the robot the better.
Signed-off-by: SamuelFoo <fooenzesamuel@gmail.com>
Signed-off-by: SamuelFoo <fooenzesamuel@gmail.com>
Signed-off-by: SamuelFoo <fooenzesamuel@gmail.com>
Define robot_pose in the observation header frame, transform supported region geometry before rasterization, and reject updates whose frame does not match the static map. Signed-off-by: SamuelFoo <fooenzesamuel@gmail.com>
|
Addressed the latest review feedback:
The "clear then mark" ordering was mentioned in the The observation storage and incremental rasterization optimization will be handled in a follow-up, as discussed. Ready for review! |
|
The demo looks good. I want to hold off on a merge till we have a complete e2e showcase of replanning. I'll create an epic branch and work form there. |
|
Also partially related: #5 |
6a89c2c to
2f8c0c7
Compare
New feature implementation
Implemented feature
This PR adds the first slice of a layered global occupancy map for the next generation prototype.
It introduces a dedicated layered map message package, a Rust layered map server, a small RViz demo, and ROS integration coverage. The path server can continue consuming a normal
nav_msgs/OccupancyGridon/map, while the layered map server composes that map from a static map and temporary observation regions.Implementation description
This PR adds:
rmf_layered_map_msgsMapObservationSourceMapRegionUpdatemap_server/rmf_layered_map_serverament_cargopackage/map/static/map/region_updatesnav_msgs/OccupancyGridon/mapmap_server/rmf_layered_map_server_demo/mapmap_server/rmf_layered_map_server_testThe discourse draft in
discourse/3-layered-global-occupancy-map.mddocuments the code implemented in this PR.Next steps
MapRegionUpdatemessages.0.5 mglobal planning grid in the map/path demos.GenAI Use
We follow OSRA's policy on GenAI tools
Generated-by: GPT-5.5