Skip to content

Commit 227f2ae

Browse files
committed
Publish an occupancy grid
1 parent c277b97 commit 227f2ae

File tree

5 files changed

+88
-25
lines changed

5 files changed

+88
-25
lines changed

kiss_slam/ros/Dockerfile

Lines changed: 0 additions & 7 deletions
This file was deleted.

kiss_slam/ros/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# KISS-SLAM ROS 2 Wrapper
2+
This directory provides a ROS 2 wrapper for some of KISS-SLAM's functionality.
3+
The kiss_slam_node receives point cloud messages and publishes odometry and a transform between the map frame and laser frame.
4+
An occupancy grid is also published, but this is currently incomplete and does not incorporate loop closures.
5+
6+
Currently tested on Humble.
7+
8+
### How to build
9+
Copy this directory into your workspace, then build, source, and run as normal provided that `kiss-slam` is already installed in your environment.
10+
11+
### How to run
12+
By default, the node will listen for the topic `/points`.
13+
This can be changed at runtime with a parameter:
14+
```
15+
ros2 run kiss_slam_ros kiss_slam_node --ros-args -p points_topic:=your_pointcloud_topic
16+
```

kiss_slam/ros/docker-compose.yaml

Lines changed: 0 additions & 9 deletions
This file was deleted.

kiss_slam/ros/src/kiss_slam_ros/kiss_slam_ros/conversions.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
from geometry_msgs.msg import Pose, TransformStamped
99
from sensor_msgs_py import point_cloud2
1010
from sensor_msgs.msg import PointCloud2
11-
from nav_msgs.msg import Odometry
11+
from nav_msgs.msg import Odometry, OccupancyGrid
12+
13+
from kiss_slam.voxel_map import VoxelMap
1214

1315

1416
def pc2_to_numpy(msg: PointCloud2):
@@ -96,4 +98,34 @@ def build_odometry(header: Header, pose: Pose, position_cov: float, orientation_
9698
odom.pose.covariance[21] = orientation_cov
9799
odom.pose.covariance[28] = orientation_cov
98100
odom.pose.covariance[35] = orientation_cov
99-
return odom
101+
return odom
102+
103+
def build_map(header: Header, occupancy_2d: np.ndarray, min_voxel_idx: tuple, resolution: float):
104+
"""Convenience function for packing a map message.
105+
106+
:param header: A header to provide timestamp and frame_id
107+
:type header: Header
108+
:param occupancy_2d: An occupancy grid supplied by an OccupancyGridMapper
109+
:type occupancy_2d: np.ndarray
110+
:param min_voxel_idx: The lowest active voxel index, used to calculate map origin
111+
:type min_voxel_idx: tuple
112+
:param resolution: Size of a grid cell in m
113+
:type resolution: float
114+
:return: An occupancy grid message containing input information
115+
:rtype: OccupancyGrid
116+
"""
117+
map_msg = OccupancyGrid()
118+
map_msg.header = header
119+
map_msg.info.resolution = resolution
120+
map_msg.info.width = occupancy_2d.shape[0]
121+
map_msg.info.height = occupancy_2d.shape[1]
122+
map_msg.info.origin.position.x = min_voxel_idx[0] * resolution
123+
map_msg.info.origin.position.y = min_voxel_idx[1] * resolution
124+
125+
# Rotate, invert probabilities, and scale to [0, 100]
126+
occupancy_image = np.rint(100 * (1 - occupancy_2d)).astype(int)
127+
occupancy_image = np.rot90(occupancy_image)
128+
occupancy_image = np.flip(occupancy_image, axis=0)
129+
map_msg.data = occupancy_image.ravel().tolist()
130+
131+
return map_msg

kiss_slam/ros/src/kiss_slam_ros/kiss_slam_ros/kiss_slam_node.py

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@
99

1010
from kiss_slam.slam import KissSLAM
1111
from kiss_slam.config import load_config
12+
from kiss_slam.occupancy_mapper import OccupancyGridMapper
13+
from map_closures import map_closures
1214

13-
from kiss_slam_ros.conversions import pc2_to_numpy, build_transform, build_odometry, matrix_to_pose
15+
from kiss_slam_ros.conversions import pc2_to_numpy, build_transform, build_odometry, build_map, matrix_to_pose
1416
from rcl_interfaces.msg import ParameterDescriptor, SetParametersResult
1517
from rclpy.parameter import Parameter
1618
from std_msgs.msg import Header
1719
from tf2_ros import TransformBroadcaster
1820
from sensor_msgs.msg import PointCloud2
19-
from nav_msgs.msg import Odometry
21+
from nav_msgs.msg import Odometry, OccupancyGrid
2022

2123
class KissSLAMNode(Node):
2224

@@ -52,9 +54,15 @@ def __init__(self):
5254

5355
# Publishers
5456
self.odom_publisher = self.create_publisher(Odometry, 'odom', 10)
57+
self.map_publisher = self.create_publisher(OccupancyGrid, 'map', 10)
5558
self.tf_broadcaster = TransformBroadcaster(self)
5659

57-
self.slam = KissSLAM(load_config(None))
60+
self.slam_config = load_config(None)
61+
self.slam = KissSLAM(self.slam_config)
62+
self.mapper = OccupancyGridMapper(self.slam_config.occupancy_mapper)
63+
self.ref_ground_alignment = None
64+
self.min_voxel_idx = (0, 0)
65+
5866
self.add_on_set_parameters_callback(self.parameter_callback)
5967

6068
def parameter_callback(self, params: list):
@@ -81,7 +89,8 @@ def cloud_callback(self, in_msg: PointCloud2):
8189
:param in_msg: New points to injest
8290
:type in_msg: PointCloud2
8391
"""
84-
self.slam.process_scan(pc2_to_numpy(in_msg), np.empty((0,)))
92+
pcd = pc2_to_numpy(in_msg)
93+
self.slam.process_scan(pcd, np.empty((0,)))
8594

8695
pose = matrix_to_pose(self.slam.poses[-1])
8796
header = Header()
@@ -93,6 +102,31 @@ def cloud_callback(self, in_msg: PointCloud2):
93102

94103
odom = build_odometry(header, pose, self.position_covariance, self.orientation_covariance)
95104
self.odom_publisher.publish(odom)
105+
106+
self.update_mapper(pcd)
107+
map_msg = build_map(header, self.occupancy_2d, self.min_voxel_idx, self.slam_config.occupancy_mapper.resolution)
108+
self.map_publisher.publish(map_msg)
109+
110+
def update_mapper(self, pcd: np.ndarray):
111+
"""Use a point cloud to update the node's OccupancyGridMapper and store the new 2d grid.
112+
Also update the minimum active voxel xy idx so the map origin can be placed.
113+
114+
:param pcd: Latest point cloud received
115+
:type pcd: np.ndarray
116+
"""
117+
if self.ref_ground_alignment is None:
118+
self.ref_ground_alignment = map_closures.align_map_to_local_ground(
119+
self.slam.voxel_grid.open3d_pcd_with_normals().point.positions.cpu().numpy(),
120+
self.slam_config.odometry.mapping.voxel_size,
121+
)
122+
123+
self.mapper.integrate_frame(
124+
pcd, self.ref_ground_alignment @ self.slam.poses[-1]
125+
)
126+
self.mapper.compute_3d_occupancy_information()
127+
self.mapper.compute_2d_occupancy_information()
128+
self.occupancy_2d = self.mapper.occupancy_grid
129+
self.min_voxel_idx = (self.mapper.lower_bound[0], self.mapper.lower_bound[1])
96130

97131

98132
def main(args=None):
@@ -102,9 +136,6 @@ def main(args=None):
102136

103137
rclpy.spin(kiss)
104138

105-
# Destroy the node explicitly
106-
# (optional - otherwise it will be done automatically
107-
# when the garbage collector destroys the node object)
108139
kiss.destroy_node()
109140
rclpy.shutdown()
110141

0 commit comments

Comments
 (0)