Skip to content

Commit 47d1212

Browse files
authored
feat(probabilistic_occupancy_grid_map): add synchronized ogm fusion node (#5485)
* add synchronized ogm fusion node Signed-off-by: yoshiri <yoshiyoshidetteiu@gmail.com> * add launch test for grid map fusion node Signed-off-by: yoshiri <yoshiyoshidetteiu@gmail.com> * fix test cases input msg error Signed-off-by: yoshiri <yoshiyoshidetteiu@gmail.com> * change default fusion parameter Signed-off-by: yoshiri <yoshiyoshidetteiu@gmail.com> * rename parameter for ogm fusion Signed-off-by: yoshiri <yoshiyoshidetteiu@gmail.com> * feat: add multi_lidar_ogm generation method Signed-off-by: yoshiri <yoshiyoshidetteiu@gmail.com> * enable ogm creation launcher in tier4_perception_launch to call multi_lidar ogm creation Signed-off-by: yoshiri <yoshiyoshidetteiu@gmail.com> * fix: change ogm fusion node pub policy to reliable Signed-off-by: yoshiri <yoshiyoshidetteiu@gmail.com> * fix: fix to use lidar frame as scan frame Signed-off-by: Yoshi, Ri <yoshiyoshidetteiu@gmail.com> * fix: launcher node Signed-off-by: Yoshi, Ri <yoshiyoshidetteiu@gmail.com> * feat: update param name Signed-off-by: yoshiri <yoshiyoshidetteiu@gmail.com> * chore: fix ogm pointcloud subscription Signed-off-by: yoshiri <yoshiyoshidetteiu@gmail.com> * feat: enable to publish pipeline latency Signed-off-by: yoshiri <yoshiyoshidetteiu@gmail.com> --------- Signed-off-by: yoshiri <yoshiyoshidetteiu@gmail.com> Signed-off-by: Yoshi, Ri <yoshiyoshidetteiu@gmail.com>
1 parent 0eac12e commit 47d1212

File tree

5 files changed

+277
-1
lines changed

5 files changed

+277
-1
lines changed

launch/tier4_perception_launch/launch/occupancy_grid_map/probabilistic_occupancy_grid_map.launch.xml

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
<!--multi lidar pointclouds based method-->
5252
<group if="$(eval &quot;'$(var occupancy_grid_map_method)'=='multi_lidar_pointcloud_based_occupancy_grid_map'&quot;)">
5353
<include file="$(find-pkg-share probabilistic_occupancy_grid_map)/launch/multi_lidar_pointcloud_based_occupancy_grid_map.launch.py">
54+
<arg name="input/obstacle_pointcloud" value="$(var input/obstacle_pointcloud)"/>
5455
<arg name="output" value="/perception/occupancy_grid_map/map"/>
5556
<arg name="use_intra_process" value="true"/>
5657
<arg name="use_multithread" value="true"/>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# sample grid map fusion parameters for sample sensor kit
2+
/**:
3+
ros__parameters:
4+
# shared parameters
5+
shared_config:
6+
map_frame: "map"
7+
base_link_frame: "base_link"
8+
# center of the grid map
9+
gridmap_origin_frame: "base_link"
10+
11+
map_resolution: 0.5 # [m]
12+
map_length_x: 150.0 # [m]
13+
map_length_y: 150.0 # [m]
14+
15+
# each grid map parameters
16+
ogm_creation_config:
17+
height_filter:
18+
use_height_filter: true
19+
min_height: -1.0
20+
max_height: 2.0
21+
enable_single_frame_mode: true
22+
# use sensor pointcloud to filter obstacle pointcloud
23+
filter_obstacle_pointcloud_by_raw_pointcloud: true
24+
25+
grid_map_type: "OccupancyGridMapFixedBlindSpot"
26+
OccupancyGridMapFixedBlindSpot:
27+
distance_margin: 1.0
28+
OccupancyGridMapProjectiveBlindSpot:
29+
projection_dz_threshold: 0.01 # [m] for avoiding null division
30+
obstacle_separation_threshold: 1.0 # [m] fill the interval between obstacles with unknown for this length
31+
pub_debug_grid: false
32+
33+
# parameter settings for ogm fusion
34+
fusion_config:
35+
# following parameters are shared: map_frame, base_link_frame, gridmap_origin_frame, map_resolution, map_length
36+
# Setting1: tune ogm creation parameters
37+
raw_pointcloud_topics: # put each sensor's pointcloud topic
38+
- "/sensing/lidar/top/pointcloud"
39+
- "/sensing/lidar/left/pointcloud"
40+
- "/sensing/lidar/right/pointcloud"
41+
fusion_input_ogm_topics:
42+
- "/perception/occupancy_grid_map/top_lidar/map"
43+
- "/perception/occupancy_grid_map/left_lidar/map"
44+
- "/perception/occupancy_grid_map/right_lidar/map"
45+
# reliability of each sensor (0.0 ~ 1.0) only work with "log-odds" and "dempster-shafer"
46+
input_ogm_reliabilities:
47+
- 1.0
48+
- 0.6
49+
- 0.6
50+
51+
# Setting2: tune ogm fusion parameters
52+
## choose fusion method from ["overwrite", "log-odds", "dempster-shafer"]
53+
fusion_method: "overwrite"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
# Copyright 2021 Tier IV, Inc. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
16+
from ament_index_python.packages import get_package_share_directory
17+
from launch import LaunchDescription
18+
from launch.actions import DeclareLaunchArgument
19+
from launch.actions import OpaqueFunction
20+
from launch.actions import SetLaunchConfiguration
21+
from launch.conditions import IfCondition
22+
from launch.conditions import UnlessCondition
23+
from launch.substitutions import LaunchConfiguration
24+
from launch_ros.actions import ComposableNodeContainer
25+
from launch_ros.actions import LoadComposableNodes
26+
from launch_ros.descriptions import ComposableNode
27+
import yaml
28+
29+
# In this file, we use "ogm" as a meaning of occupancy grid map
30+
31+
32+
# overwrite parameter
33+
def overwrite_config(param_dict, launch_config_name, node_params_name, context):
34+
if LaunchConfiguration(launch_config_name).perform(context) != "":
35+
param_dict[node_params_name] = LaunchConfiguration(launch_config_name).perform(context)
36+
37+
38+
# load parameter files
39+
def load_config_file(context, configuration_name: str):
40+
config_file = LaunchConfiguration(configuration_name).perform(context)
41+
with open(config_file, "r") as f:
42+
config_param_dict = yaml.safe_load(f)["/**"]["ros__parameters"]
43+
return config_param_dict
44+
45+
46+
# check if the length of the parameters are the same
47+
def fusion_config_sanity_check(fusion_config: dict):
48+
listed_param_names = [
49+
"raw_pointcloud_topics",
50+
"fusion_input_ogm_topics",
51+
# "each_ogm_sensor_frames",
52+
"input_ogm_reliabilities",
53+
]
54+
param_length_list = []
55+
56+
for param_name in listed_param_names:
57+
# parameters is not in the config file dict
58+
if param_name not in fusion_config:
59+
raise Exception("Error: " + param_name + " is not in the config file")
60+
# get len of the parameter
61+
param_length_list.append(len(fusion_config[param_name]))
62+
63+
# check if the length of the parameters are the same
64+
if not all(x == param_length_list[0] for x in param_length_list):
65+
raise Exception("Error: the length of the parameters are not the same")
66+
67+
68+
def get_fusion_config(total_config: dict) -> dict:
69+
fusion_config = total_config["fusion_config"]
70+
shared_config = total_config["shared_config"]
71+
# merge shared config and fusion config
72+
fusion_config.update(shared_config)
73+
# overwrite ogm size settings
74+
fusion_config["fusion_map_length_x"] = shared_config["map_length_x"]
75+
fusion_config["fusion_map_length_y"] = shared_config["map_length_y"]
76+
fusion_config["fusion_map_resolution"] = shared_config["map_resolution"]
77+
return fusion_config
78+
79+
80+
# extract ogm creation config from fusion config
81+
def get_ogm_creation_config(total_config: dict, list_iter: int) -> dict:
82+
ogm_creation_config = total_config["ogm_creation_config"]
83+
shared_config = total_config["shared_config"]
84+
# fusion_config_ = total_config["fusion_config"]
85+
# merge shared config and ogm creation config
86+
ogm_creation_config.update(shared_config)
87+
# overwrite scan_origin_frame with sensor frame settings
88+
# ogm_creation_config["scan_origin_frame"] = fusion_config_["each_ogm_sensor_frames"][list_iter]
89+
# use fusion_map_length to set map_length
90+
ogm_creation_config["map_length"] = max(
91+
shared_config["map_length_x"], shared_config["map_length_y"]
92+
)
93+
return ogm_creation_config
94+
95+
96+
def launch_setup(context, *args, **kwargs):
97+
"""Launch fusion based occupancy grid map creation nodes.
98+
99+
1. describe occupancy grid map generation nodes for each sensor input
100+
2. describe occupancy grid map fusion node
101+
3. launch setting
102+
"""
103+
# 1. launch occupancy grid map generation nodes for each sensor input
104+
105+
# load fusion config parameter
106+
total_config = load_config_file(context, "multi_lidar_fusion_config_file")
107+
fusion_config = get_fusion_config(total_config)
108+
fusion_config_sanity_check(fusion_config)
109+
updater_config = load_config_file(context, "updater_param_file")
110+
111+
# pointcloud based occupancy grid map nodes
112+
gridmap_generation_composable_nodes = []
113+
114+
number_of_nodes = len(fusion_config["raw_pointcloud_topics"])
115+
print(number_of_nodes)
116+
117+
for i in range(number_of_nodes):
118+
# load parameter file
119+
ogm_creation_config = get_ogm_creation_config(total_config, i)
120+
ogm_creation_config["updater_type"] = LaunchConfiguration("updater_type").perform(context)
121+
ogm_creation_config.update(updater_config)
122+
123+
# generate composable node
124+
node = ComposableNode(
125+
package="probabilistic_occupancy_grid_map",
126+
plugin="occupancy_grid_map::PointcloudBasedOccupancyGridMapNode",
127+
name="occupancy_grid_map_node_in_" + str(i),
128+
remappings=[
129+
("~/input/obstacle_pointcloud", LaunchConfiguration("input/obstacle_pointcloud")),
130+
("~/input/raw_pointcloud", fusion_config["raw_pointcloud_topics"][i]),
131+
("~/output/occupancy_grid_map", fusion_config["fusion_input_ogm_topics"][i]),
132+
],
133+
parameters=[ogm_creation_config],
134+
extra_arguments=[{"use_intra_process_comms": LaunchConfiguration("use_intra_process")}],
135+
)
136+
gridmap_generation_composable_nodes.append(node)
137+
138+
# 2. launch occupancy grid map fusion node
139+
gridmap_fusion_node = [
140+
ComposableNode(
141+
package="probabilistic_occupancy_grid_map",
142+
plugin="synchronized_grid_map_fusion::GridMapFusionNode",
143+
name="occupancy_grid_map_fusion_node",
144+
remappings=[
145+
("~/output/occupancy_grid_map", LaunchConfiguration("output")),
146+
],
147+
parameters=[fusion_config],
148+
extra_arguments=[{"use_intra_process_comms": LaunchConfiguration("use_intra_process")}],
149+
),
150+
]
151+
152+
# 3. launch setting
153+
occupancy_grid_map_container = ComposableNodeContainer(
154+
name=LaunchConfiguration("container_name"),
155+
namespace="",
156+
package="rclcpp_components",
157+
executable=LaunchConfiguration("container_executable"),
158+
composable_node_descriptions=gridmap_generation_composable_nodes + gridmap_fusion_node,
159+
condition=UnlessCondition(LaunchConfiguration("use_pointcloud_container")),
160+
output="screen",
161+
)
162+
163+
load_composable_nodes = LoadComposableNodes(
164+
composable_node_descriptions=gridmap_generation_composable_nodes + gridmap_fusion_node,
165+
target_container=LaunchConfiguration("container_name"),
166+
condition=IfCondition(LaunchConfiguration("use_pointcloud_container")),
167+
)
168+
169+
return [occupancy_grid_map_container, load_composable_nodes]
170+
171+
172+
def generate_launch_description():
173+
def add_launch_arg(name: str, default_value=None):
174+
return DeclareLaunchArgument(name, default_value=default_value)
175+
176+
set_container_executable = SetLaunchConfiguration(
177+
"container_executable",
178+
"component_container",
179+
condition=UnlessCondition(LaunchConfiguration("use_multithread")),
180+
)
181+
182+
set_container_mt_executable = SetLaunchConfiguration(
183+
"container_executable",
184+
"component_container_mt",
185+
condition=IfCondition(LaunchConfiguration("use_multithread")),
186+
)
187+
188+
return LaunchDescription(
189+
[
190+
add_launch_arg("use_multithread", "false"),
191+
add_launch_arg("use_intra_process", "true"),
192+
add_launch_arg("use_pointcloud_container", "false"),
193+
add_launch_arg("container_name", "occupancy_grid_map_container"),
194+
add_launch_arg("input/obstacle_pointcloud", "no_ground/oneshot/pointcloud"),
195+
add_launch_arg("output", "occupancy_grid"),
196+
add_launch_arg(
197+
"multi_lidar_fusion_config_file",
198+
get_package_share_directory("probabilistic_occupancy_grid_map")
199+
+ "/config/multi_lidar_pointcloud_based_occupancy_grid_map.param.yaml",
200+
),
201+
add_launch_arg("updater_type", "binary_bayes_filter"),
202+
add_launch_arg(
203+
"updater_param_file",
204+
get_package_share_directory("probabilistic_occupancy_grid_map")
205+
+ "/config/binary_bayes_filter_updater.param.yaml",
206+
),
207+
set_container_executable,
208+
set_container_mt_executable,
209+
]
210+
+ [OpaqueFunction(function=launch_setup)]
211+
)

perception/probabilistic_occupancy_grid_map/src/fusion/synchronized_grid_map_fusion_node.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -311,10 +311,16 @@ void GridMapFusionNode::publish()
311311
if (debug_publisher_ptr_ && stop_watch_ptr_) {
312312
const double cyclic_time_ms = stop_watch_ptr_->toc("cyclic_time", true);
313313
const double processing_time_ms = stop_watch_ptr_->toc("processing_time", true);
314+
const double pipeline_latency_ms =
315+
std::chrono::duration<double, std::milli>(
316+
std::chrono::nanoseconds((this->get_clock()->now() - latest_stamp).nanoseconds()))
317+
.count();
314318
debug_publisher_ptr_->publish<tier4_debug_msgs::msg::Float64Stamped>(
315319
"debug/cyclic_time_ms", cyclic_time_ms);
316320
debug_publisher_ptr_->publish<tier4_debug_msgs::msg::Float64Stamped>(
317321
"debug/processing_time_ms", processing_time_ms);
322+
debug_publisher_ptr_->publish<tier4_debug_msgs::msg::Float64Stamped>(
323+
"debug/pipeline_latency_ms", pipeline_latency_ms);
318324
}
319325
}
320326

perception/probabilistic_occupancy_grid_map/src/pointcloud_based_occupancy_grid_map/pointcloud_based_occupancy_grid_map_node.cpp

+6-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ PointcloudBasedOccupancyGridMapNode::PointcloudBasedOccupancyGridMapNode(
5858
map_frame_ = this->declare_parameter<std::string>("map_frame");
5959
base_link_frame_ = this->declare_parameter<std::string>("base_link_frame");
6060
gridmap_origin_frame_ = this->declare_parameter<std::string>("gridmap_origin_frame");
61-
scan_origin_frame_ = this->declare_parameter<std::string>("scan_origin_frame");
61+
scan_origin_frame_ = this->declare_parameter<std::string>("scan_origin_frame", "");
6262
use_height_filter_ = this->declare_parameter<bool>("height_filter.use_height_filter");
6363
min_height_ = this->declare_parameter<double>("height_filter.min_height");
6464
max_height_ = this->declare_parameter<double>("height_filter.max_height");
@@ -136,6 +136,11 @@ void PointcloudBasedOccupancyGridMapNode::onPointcloudWithObstacleAndRaw(
136136
if (stop_watch_ptr_) {
137137
stop_watch_ptr_->toc("processing_time", true);
138138
}
139+
// if scan_origin_frame_ is "", replace it with input_raw_msg->header.frame_id
140+
if (scan_origin_frame_.empty()) {
141+
scan_origin_frame_ = input_raw_msg->header.frame_id;
142+
}
143+
139144
// Apply height filter
140145
PointCloud2 cropped_obstacle_pc{};
141146
PointCloud2 cropped_raw_pc{};

0 commit comments

Comments
 (0)