Autoware.Auto
ndt_mapping_nodes.hpp
Go to the documentation of this file.
1 // Copyright 2020 Apex.AI, Inc.
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 // Co-developed by Tier IV, Inc. and Apex.AI, Inc.
16 
19 
20 #ifndef NDT_MAPPING_NODES__NDT_MAPPING_NODES_HPP_
21 #define NDT_MAPPING_NODES__NDT_MAPPING_NODES_HPP_
22 
24 #include <sensor_msgs/msg/point_cloud2.hpp>
27 #include <ndt/ndt_localizer.hpp>
31 #include <tf2_msgs/msg/tf_message.hpp>
33 
34 #include <string>
35 #include <limits>
36 #include <memory>
37 #include <utility>
38 
39 namespace autoware
40 {
41 namespace mapping
42 {
43 namespace ndt_mapping_nodes
44 {
54 
60 template<class WriteTriggerPolicyT = WritePolicy,
61  class ClearTriggerPolicyT = ClearPolicy,
62  class PrefixGeneratorT = PrefixPolicy>
63 class NDT_MAPPING_NODES_PUBLIC P2DNDTVoxelMapperNode : public rclcpp::Node
64 {
65 public:
66  using PoseWithCovarianceStamped = geometry_msgs::msg::PoseWithCovarianceStamped;
70  // Static asserts to make sure the policies are valid
71  static_assert(
73  WriteTriggerPolicyT>::value,
74  "Write trigger policy must implement the `TriggerPolicyBase` interface.");
75 
76  static_assert(
78  ClearTriggerPolicyT>::value,
79  "Clear trigger policy must implement the `TriggerPolicyBase` interface.");
80 
81  static_assert(
83  PrefixGeneratorT>::value,
84  "Prefix generator policy must implement the `PrefixGeneratorBase` interface.");
85 
86 
87  explicit P2DNDTVoxelMapperNode(const rclcpp::NodeOptions & options)
88  : Node{"ndt_mapper_node", options},
89  m_observation_sub{create_subscription<Cloud>(
90  "points_in",
91  rclcpp::QoS{rclcpp::KeepLast{
92  static_cast<size_t>(declare_parameter("observation_sub.history_depth").template
93  get<size_t>())}},
94  [this](typename Cloud::ConstSharedPtr msg) {observation_callback(msg);})},
95  m_pose_publisher(
96  create_publisher<PoseWithCovarianceStamped>(
97  "ndt_pose",
98  rclcpp::QoS{rclcpp::KeepLast{
99  static_cast<size_t>(declare_parameter(
100  "pose_pub.history_depth").template get<size_t>())}})),
101  m_base_fn_prefix{this->declare_parameter("file_name_prefix").template get<std::string>()}
102  {
103  init();
104  }
105 
107  {
108  if (m_map_ptr->size() > 0U) {
109  const auto & file_name_prefix = m_prefix_generator.get(m_base_fn_prefix);
110  RCLCPP_DEBUG(get_logger(), "The map is written to" + file_name_prefix + ".pcd");
111  m_map_ptr->write(file_name_prefix);
112  }
113  }
114 
115 private:
116  void init()
117  {
118  auto get_point_param = [this](const std::string & config_name_prefix) {
120  point.x = static_cast<float32_t>(declare_parameter(config_name_prefix + ".x").
121  template get<float32_t>());
122  point.y = static_cast<float32_t>(this->declare_parameter(config_name_prefix + ".y").
123  template get<float32_t>());
124  point.z = static_cast<float32_t>(this->declare_parameter(config_name_prefix + ".z").
125  template get<float32_t>());
126  return point;
127  };
128 
129 
130  const auto parse_grid_config = [this, get_point_param](const std::string & prefix) {
131  // Fetch map configuration
132  const auto capacity = static_cast<std::size_t>(
133  this->declare_parameter(prefix + ".capacity").template get<std::size_t>());
134 
135  return perception::filters::voxel_grid::Config{get_point_param(
136  prefix + ".min_point"), get_point_param(prefix + ".max_point"),
137  get_point_param(prefix + ".voxel_size"), capacity};
138  };
139 
140  // Fetch localizer configuration
141  P2DNDTConfig localizer_config{
142  static_cast<uint32_t>(this->declare_parameter("localizer.scan.capacity").
143  template get<uint32_t>()),
144  std::chrono::milliseconds(
145  static_cast<uint64_t>(
146  this->declare_parameter("localizer.guess_time_tolerance_ms").template get<uint64_t>()))
147  };
148 
149  const auto outlier_ratio{this->declare_parameter(
150  "localizer.optimization.outlier_ratio").template get<float64_t>()};
151 
152  const common::optimization::OptimizationOptions optimization_options{
153  static_cast<uint64_t>(
154  this->declare_parameter("localizer.optimizer.max_iterations").template get<uint64_t>()),
155  this->declare_parameter("localizer.optimizer.score_tolerance").template get<float64_t>(),
156  this->declare_parameter(
157  "localizer.optimizer.parameter_tolerance").template get<float64_t>(),
158  this->declare_parameter("localizer.optimizer.gradient_tolerance").template get<float64_t>()
159  };
160 
161  m_localizer_ptr = std::make_unique<Localizer>(
162  localizer_config,
163  Optimizer{
164  common::optimization::MoreThuenteLineSearch{
165  static_cast<float32_t>(this->declare_parameter(
166  "localizer.optimizer.line_search.step_max")
167  .template get<float32_t>()),
168  static_cast<float32_t>(this->declare_parameter(
169  "localizer.optimizer.line_search.step_min")
170  .template get<float32_t>()),
172  },
173  optimization_options},
174  outlier_ratio);
175  const auto & map_frame_id = this->declare_parameter("map.frame_id").template get<std::string>();
176  m_map_ptr = std::make_unique<VoxelMap>(
177  parse_grid_config("map"), map_frame_id,
178  NDTMap{parse_grid_config("localizer.map")});
179 
180  if (this->declare_parameter("publish_map_increment").template get<bool8_t>()) {
181  m_increment_publisher = this->template create_publisher<sensor_msgs::msg::PointCloud2>(
182  "points_registered",
183  rclcpp::QoS{rclcpp::KeepLast{
184  static_cast<size_t>(this->declare_parameter("map_increment_pub.history_depth").template
185  get<size_t>())}});
186  }
187 
188  if (declare_parameter("publish_tf").template get<bool>()) {
189  m_tf_publisher = create_publisher<tf2_msgs::msg::TFMessage>(
190  "/tf",
191  rclcpp::QoS{rclcpp::KeepLast{m_pose_publisher->get_queue_size()}});
192  }
193 
194  m_previous_transform.transform.rotation.set__w(1.0);
195  m_previous_transform.header.frame_id = m_map_ptr->frame_id();
197  CloudModifier msg_initializer{m_cached_increment,
198  map_frame_id};
199  }
200 
201  void observation_callback(Cloud::ConstSharedPtr msg_ptr)
202  {
203  try {
204  RegistrationSummary summary{};
205  m_previous_transform.header.stamp = msg_ptr->header.stamp;
206 
207  geometry_msgs::msg::PoseWithCovarianceStamped pose_out;
208  if (m_map_ptr->empty()) {
209  // If the map is empty, get the initial pose for inserting the increment into the map.
210  // If the map is empty in further iterations, then throw as we don't know where to place
211  // the scan anymore.
212  pose_out = get_initial_pose_once();
213  } else {
214  // Register the measurement only if there is a valid map.
215  pose_out = m_localizer_ptr->register_measurement(
216  *msg_ptr, m_previous_transform, m_map_ptr->localizer_map(), &summary);
217  }
218 
219  if (!validate_output(summary)) {
220  RCLCPP_WARN(get_logger(), "Invalid pose estimate. The result is ignored.");
221  return;
222  }
223  // Transform the measurement into the map frame and insert it into the map.
224  const auto & increment = get_map_increment(*msg_ptr, pose_out);
225  m_pose_publisher->publish(pose_out);
226 
227  if (m_increment_publisher) {
228  m_increment_publisher->publish(increment);
229  }
230  if (m_tf_publisher) {
231  publish_tf(pose_to_transform(pose_out, msg_ptr->header.frame_id));
232  }
233  if (m_write_trigger.ready(*m_map_ptr)) {
234  const auto & file_name_prefix = m_prefix_generator.get(m_base_fn_prefix);
235  m_map_ptr->write(file_name_prefix);
236  RCLCPP_DEBUG(get_logger(), "The map is written to" + file_name_prefix + ".pcd");
237  }
238  if (m_clear_trigger.ready(*m_map_ptr)) {
239  RCLCPP_DEBUG(get_logger(), "The map is cleared.");
240  m_map_ptr->clear();
241  }
242 
243  // Update the map after a possible clearance and not before so that the map is never fully
244  // empty.
245  m_map_ptr->update(increment);
246  m_previous_transform = pose_to_transform(pose_out, msg_ptr->header.frame_id);
247  } catch (const std::runtime_error & e) {
248  RCLCPP_ERROR(get_logger(), "Failed to register the measurement: ", e.what());
249  }
250  }
251 
252  bool8_t validate_output(
253  const RegistrationSummary & summary)
254  {
255  switch (summary.optimization_summary().termination_type()) {
257  // Numerical failure, result is unusable.
258  return false;
260  // In practice, it's hard to come up with a perfect termination criterion for ndt
261  // optimization and even non-convergence may be a decent effort in localizing the
262  // vehicle. Hence the result is not discarded on non-convergence.
263  RCLCPP_DEBUG(this->get_logger(), "NDT mapper optimizer failed to converge.");
264  return true;
265  default:
266  return true;
267  }
268  }
269 
275  const Cloud & get_map_increment(
276  const Cloud & observation,
277  const PoseWithCovarianceStamped & registered_pose)
278  {
280  CloudView obs_view{observation};
281  reset_cached_msg(obs_view.size());
282  // Convert pose to transform for `doTransform()`
284  tf.header.stamp = registered_pose.header.stamp;
285  tf.header.frame_id = registered_pose.header.frame_id;
286  tf.child_frame_id = observation.header.frame_id;
287  const auto & trans = registered_pose.pose.pose.position;
288  const auto & rot = registered_pose.pose.pose.orientation;
289  tf.transform.translation.set__x(trans.x).set__y(trans.y).set__z(trans.z);
290  tf.transform.rotation.set__x(rot.x).set__y(rot.y).set__z(rot.z).set__w(rot.w);
291 
292  tf2::doTransform(observation, m_cached_increment, tf);
293  return m_cached_increment;
294  }
295 
297  void publish_tf(const geometry_msgs::msg::TransformStamped & transform)
298  {
299  tf2_msgs::msg::TFMessage tf_message;
300  tf_message.transforms.emplace_back(transform);
301  m_tf_publisher->publish(tf_message);
302  }
303 
305  void reset_cached_msg(std::size_t size)
306  {
308  CloudModifier inc_modifier{m_cached_increment};
309  inc_modifier.clear();
310  inc_modifier.resize(size);
311  }
312 
315  const PoseWithCovarianceStamped & pose_msg,
316  const std::string & child_frame_id = "base_link")
317  {
319  tf_stamped.header = pose_msg.header;
320  tf_stamped.child_frame_id = child_frame_id;
321  const auto & tf_trans = pose_msg.pose.pose.position;
322  const auto & tf_rot = pose_msg.pose.pose.orientation;
323  tf_stamped.transform.translation.set__x(tf_trans.x).set__y(tf_trans.y).
324  set__z(tf_trans.z);
325  tf_stamped.transform.rotation.set__x(tf_rot.x).set__y(tf_rot.y).set__z(tf_rot.z).
326  set__w(tf_rot.w);
327  return tf_stamped;
328  }
329 
331  PoseWithCovarianceStamped transform_to_pose(
332  const geometry_msgs::msg::TransformStamped & transform_msg)
333  {
334  PoseWithCovarianceStamped pose;
335  pose.header = transform_msg.header;
336  const auto & pose_trans = transform_msg.transform.translation;
337  const auto & pose_rot = transform_msg.transform.rotation;
338  pose.pose.pose.position.set__x(pose_trans.x).set__y(pose_trans.y).
339  set__z(pose_trans.z);
340  pose.pose.pose.orientation.set__x(pose_rot.x).set__y(pose_rot.y).set__z(pose_rot.z).
341  set__w(pose_rot.w);
342  return pose;
343  }
344 
346  geometry_msgs::msg::PoseWithCovarianceStamped get_initial_pose_once()
347  {
348  if (!m_map_initialized) {
349  m_map_initialized = true;
350  return transform_to_pose(m_previous_transform);
351  } else {
352  throw std::runtime_error(
353  "Current map is initialized yet empty, the scan cannot be "
354  "registered.");
355  }
356  }
357 
358  std::unique_ptr<Localizer> m_localizer_ptr;
359  std::unique_ptr<VoxelMap> m_map_ptr;
360  typename rclcpp::Subscription<Cloud>::SharedPtr m_observation_sub;
361  typename rclcpp::Publisher<PoseWithCovarianceStamped>::SharedPtr m_pose_publisher;
362  typename rclcpp::Publisher<tf2_msgs::msg::TFMessage>::SharedPtr m_tf_publisher{nullptr};
363  rclcpp::Publisher<sensor_msgs::msg::PointCloud2>::SharedPtr m_increment_publisher{nullptr};
364  geometry_msgs::msg::TransformStamped m_previous_transform;
365  Cloud m_cached_increment;
366  WriteTriggerPolicyT m_write_trigger{};
367  ClearTriggerPolicyT m_clear_trigger{};
368  PrefixGeneratorT m_prefix_generator{};
369  bool8_t m_map_initialized{false};
370  std::string m_base_fn_prefix;
371 };
372 
373 } // namespace ndt_mapping_nodes
374 } // namespace mapping
375 } // namespace autoware
376 
377 #endif // NDT_MAPPING_NODES__NDT_MAPPING_NODES_HPP_
motion::motion_testing_nodes::TFMessage
tf2_msgs::msg::TFMessage TFMessage
Definition: motion_testing_publisher.hpp:34
autoware::mapping::point_cloud_mapping::CapacityTrigger
Trigger map writing when map reaches its capacity.
Definition: policies.hpp:53
autoware::localization::localization_common::OptimizedRegistrationSummary
Definition: optimized_registration_summary.hpp:32
point_cloud_utils.hpp
This class defines common functions and classes to work with pointclouds.
visibility_control.hpp
autoware::mapping::ndt_mapping_nodes::P2DNDTVoxelMapperNode::Cloud
sensor_msgs::msg::PointCloud2 Cloud
Definition: ndt_mapping_nodes.hpp:67
autoware::mapping::point_cloud_mapping::TriggerPolicyBase
Definition: policies.hpp:38
autoware::common::optimization::MoreThuenteLineSearch
This class describes a More-Thuente line search as presented in the paper "Line Search Algorithms wit...
Definition: more_thuente_line_search.hpp:94
autoware::common::types::bool8_t
bool bool8_t
Definition: types.hpp:39
autoware::mapping::point_cloud_mapping::PrefixGeneratorBase
Definition: policies.hpp:66
autoware::mapping::point_cloud_mapping::DualVoxelMap
Definition: point_cloud_map.hpp:100
autoware::mapping::ndt_mapping_nodes::NDTMap
localization::ndt::DynamicNDTMap NDTMap
Definition: ndt_mapping_nodes.hpp:47
autoware::mapping::ndt_mapping_nodes::P2DNDTConfig
localization::ndt::P2DNDTLocalizerConfig P2DNDTConfig
Definition: ndt_mapping_nodes.hpp:50
autoware
This file defines the lanelet2_map_provider_node class.
Definition: quick_sort.hpp:24
autoware::common::lidar_utils::CloudModifier
point_cloud_msg_wrapper::PointCloud2Modifier< autoware::common::types::PointXYZI > CloudModifier
Definition: point_cloud_utils.hpp:53
float_comparisons.hpp
autoware::localization::ndt::P2DNDTLocalizer
Definition: ndt_localizer.hpp:238
autoware::common::optimization::TerminationType::FAILURE
@ FAILURE
autoware::mapping::ndt_mapping_nodes::ClearPolicy
mapping::point_cloud_mapping::CapacityTrigger ClearPolicy
Definition: ndt_mapping_nodes.hpp:52
autoware::localization::ndt::DynamicNDTMap
Definition: ndt_map.hpp:42
motion::motion_testing_nodes::TransformStamped
geometry_msgs::msg::TransformStamped TransformStamped
Definition: motion_testing_publisher.hpp:37
autoware::localization::ndt::transform_adapters::transform_to_pose
void transform_to_pose(const TransformT &transform, PoseT &pose)
autoware::localization::ndt::transform_adapters::pose_to_transform
void pose_to_transform(const PoseT &pose, TransformT &transform)
autoware::common::types::float32_t
float float32_t
Definition: types.hpp:45
autoware::perception::filters::voxel_grid::Config
A configuration class for the VoxelGrid data structure, also includes some helper functionality for c...
Definition: perception/filters/voxel_grid/include/voxel_grid/config.hpp:52
autoware::mapping::ndt_mapping_nodes::P2DNDTVoxelMapperNode::P2DNDTVoxelMapperNode
P2DNDTVoxelMapperNode(const rclcpp::NodeOptions &options)
Definition: ndt_mapping_nodes.hpp:87
autoware::common::lidar_utils::CloudView
point_cloud_msg_wrapper::PointCloud2View< autoware::common::types::PointXYZI > CloudView
Definition: point_cloud_utils.hpp:54
autoware::perception::filters::voxel_grid::PointXYZ
geometry_msgs::msg::Point32 PointXYZ
Definition: perception/filters/voxel_grid/include/voxel_grid/config.hpp:47
policies.hpp
autoware::mapping::ndt_mapping_nodes::P2DNDTVoxelMapperNode
Mapper node implementation that localizes using a P2DNDTLocalizer and accumulates the registered scan...
Definition: ndt_mapping_nodes.hpp:63
autoware::common::optimization::NewtonsMethodOptimizer
Optimizer using the Newton method with line search.
Definition: newtons_method_optimizer.hpp:37
autoware::mapping::ndt_mapping_nodes::P2DNDTVoxelMapperNode::~P2DNDTVoxelMapperNode
~P2DNDTVoxelMapperNode()
Definition: ndt_mapping_nodes.hpp:106
autoware::mapping::point_cloud_mapping::TimeStampPrefixGenerator
Prefix generator that adds the current time stamp to the end of the base prefix.
Definition: policies.hpp:81
autoware::common::optimization::MoreThuenteLineSearch::OptimizationDirection::kMaximization
@ kMaximization
autoware::mapping::ndt_mapping_nodes::Optimizer
common::optimization::NewtonsMethodOptimizer< common::optimization::MoreThuenteLineSearch > Optimizer
Definition: ndt_mapping_nodes.hpp:46
ndt_localizer.hpp
autoware::common::optimization::TerminationType::NO_CONVERGENCE
@ NO_CONVERGENCE
autoware::common::types::PointXYZI
Definition: types.hpp:98
autoware::mapping::ndt_mapping_nodes::WritePolicy
mapping::point_cloud_mapping::CapacityTrigger WritePolicy
Definition: ndt_mapping_nodes.hpp:51
more_thuente_line_search.hpp
tf2::doTransform
void doTransform(const geometry_msgs::msg::Point32 &t_in, geometry_msgs::msg::Point32 &t_out, const geometry_msgs::msg::TransformStamped &transform)
Apply a geometry_msgs TransformStamped to a geometry_msgs Point32 type. This function is a specializa...
Definition: tf2_autoware_auto_msgs.hpp:58
autoware::mapping::ndt_mapping_nodes::P2DNDTVoxelMapperNode::PoseWithCovarianceStamped
geometry_msgs::msg::PoseWithCovarianceStamped PoseWithCovarianceStamped
Definition: ndt_mapping_nodes.hpp:66
autoware::perception::filters::filter_node_base::PointCloud2
sensor_msgs::msg::PointCloud2 PointCloud2
Definition: filter_node_base.cpp:32
point_cloud_map.hpp
newtons_method_optimizer.hpp
autoware::localization::ndt::P2DNDTLocalizerConfig
config class for p2d ndt localizer
Definition: ndt_config.hpp:73