ros_msgpack_publisher.cpp
Go to the documentation of this file.
1 /*
2  * @brief RosMsgpackPublisher publishes PointCloud2 messages with msgpack data
3  * received from multiScan136.
4  *
5  * Copyright (C) 2020 Ing.-Buero Dr. Michael Lehning, Hildesheim
6  * Copyright (C) 2020 SICK AG, Waldkirch
7  *
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  *
20  * All rights reserved.
21  *
22  * Redistribution and use in source and binary forms, with or without
23  * modification, are permitted provided that the following conditions are met:
24  *
25  * * Redistributions of source code must retain the above copyright
26  * notice, this list of conditions and the following disclaimer.
27  * * Redistributions in binary form must reproduce the above copyright
28  * notice, this list of conditions and the following disclaimer in the
29  * documentation and/or other materials provided with the distribution.
30  * * Neither the name of SICK AG nor the names of its
31  * contributors may be used to endorse or promote products derived from
32  * this software without specific prior written permission
33  * * Neither the name of Ing.-Buero Dr. Michael Lehning nor the names of its
34  * contributors may be used to endorse or promote products derived from
35  * this software without specific prior written permission
36  *
37  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
38  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
39  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
40  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
41  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
43  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
44  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
45  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
46  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
47  * POSSIBILITY OF SUCH DAMAGE.
48  *
49  * Authors:
50  * Michael Lehning <michael.lehning@lehning.de>
51  *
52  *
53  */
54 #include <climits>
55 
59 #if defined ROSSIMU
61 #endif
62 
65 sick_scansegment_xd::CustomPointCloudConfiguration::CustomPointCloudConfiguration(const std::string& cfg_name, const std::string& cfg_str)
66 {
67  // Split into list of parameters, each parameter is a key-value-pair
68  std::vector<std::string> parameters;
69  sick_scansegment_xd::util::parseVector(cfg_str, parameters, ' ');
70  std::map<std::string, std::string> key_value_pairs;
71  for(int n = 0; n < parameters.size(); n++)
72  {
73  // Split into key-value-pairs
74  std::vector<std::string> key_value_pair;
75  sick_scansegment_xd::util::parseVector(parameters[n], key_value_pair, '=');
76  if (key_value_pair.size() != 2)
77  {
78  ROS_ERROR_STREAM("## ERROR CustomPointCloudConfiguration(name=" << cfg_name << ", value=" << cfg_str << "): can't parse " << parameters[n] << ", expected key=value format, check configuration");
79  continue;
80  }
81  key_value_pairs[key_value_pair[0]] = key_value_pair[1];
82  ROS_DEBUG_STREAM("CustomPointCloudConfiguration(" << cfg_name << "): " << key_value_pair[0] << " = " << key_value_pair[1]);
83  }
84  m_cfg_name = cfg_name;
85  m_publish = (key_value_pairs["publish"].empty() ? false : std::stoi(key_value_pairs["publish"]) > 0);
86  m_topic = key_value_pairs["topic"];
87  m_frameid = key_value_pairs["frameid"];
88  m_coordinate_notation = (key_value_pairs["coordinateNotation"].empty() ? 0 : std::stoi(key_value_pairs["coordinateNotation"]));
89  m_update_method = (key_value_pairs["updateMethod"].empty() ? 0 : std::stoi(key_value_pairs["updateMethod"]));
90  if (m_coordinate_notation == 0) // coordinateNotation=0: cartesian (default, pointcloud has fields x,y,z,i)
91  key_value_pairs["fields"] = "x,y,z,i";
92  else if (m_coordinate_notation == 1) // coordinateNotation=1: polar (pointcloud has fields azimuth,elevation,r,i)
93  key_value_pairs["fields"] = "azimuth,elevation,range,i";
94  else if (m_coordinate_notation == 2) // both cartesian and polar (pointcloud has fields x,y,z,azimuth,elevation,r,i)
95  key_value_pairs["fields"] = "x,y,z,azimuth,elevation,range,i";
96  else if (m_coordinate_notation != 3) // coordinateNotation=3: customized pointcloud fields from configuration
97  ROS_ERROR_STREAM("## ERROR CustomPointCloudConfiguration(name=" << cfg_name << ", value=" << cfg_str << "): coordinateNotation has invalid value " << m_coordinate_notation << ", check configuration");
98  if (!key_value_pairs["rangeFilter"].empty()) // Configuration of optional range filter
99  {
100  const std::string& range_filter_str = key_value_pairs["rangeFilter"];
101  std::vector<std::string> range_filter_args;
102  sick_scansegment_xd::util::parseVector(range_filter_str, range_filter_args, ',');
103  if(range_filter_args.size() == 3)
104  {
105  float range_min = std::stof(range_filter_args[0]);
106  float range_max = std::stof(range_filter_args[1]);
107  int range_filter_handling = std::stoi(range_filter_args[2]);
109  }
110  else if(!range_filter_args.empty())
111  {
112  ROS_ERROR_STREAM("## ERROR CustomPointCloudConfiguration(name=" << cfg_name << ", value=" << cfg_str << "): rangeFilter has invalid value " << range_filter_str << ", check configuration");
113  }
114  }
115  std::vector<std::string> fields;
116  std::vector<int> echos, layers, reflectors, infringed;
117  sick_scansegment_xd::util::parseVector(key_value_pairs["fields"], fields, ',');
118  sick_scansegment_xd::util::parseVector(key_value_pairs["echos"], echos, ',');
119  sick_scansegment_xd::util::parseVector(key_value_pairs["layers"], layers, ',');
120  sick_scansegment_xd::util::parseVector(key_value_pairs["reflectors"], reflectors, ',');
121  sick_scansegment_xd::util::parseVector(key_value_pairs["infringed"], infringed, ',');
122  for(int n = 0; n < layers.size(); n++)
123  layers[n] -= 1; // layer_ids in the launchfile enumerate from 1 up to 16, layer_idx starts from 0, i.e. layer_idx = layer_id - 1
124  // default if not configured in launchfile: all fields, echos, layers, reflectors and infringements enabled
125  if (fields.empty())
126  fields = { "x", "y", "z", "i", "range", "azimuth", "elevation", "layer", "echo", "reflector" };
127  if (echos.empty())
128  echos = { 0, 1, 2 };
129  if (layers.empty())
130  layers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
131  if (reflectors.empty())
132  reflectors = { 0, 1 };
133  if (infringed.empty())
134  infringed = { 0, 1 };
135  // convert properties to enabled-map for faster access
136  for(int n = 0; n < fields.size(); n++)
137  m_field_enabled[fields[n]] = true;
138  for(int n = 0; n < echos.size(); n++)
139  m_echo_enabled[echos[n]] = true;
140  for(int n = 0; n < layers.size(); n++)
141  m_layer_enabled[layers[n]] = true;
142  for(int n = 0; n < reflectors.size(); n++)
143  m_reflector_enabled[reflectors[n]] = true;
144  for(int n = 0; n < infringed.size(); n++)
145  m_infringed_enabled[infringed[n]] = true;
146  // Topic and frame id must always be set, topics must be different for each pointcloud
147  if (m_topic.empty() || m_frameid.empty())
148  {
149  ROS_ERROR_STREAM("## ERROR CustomPointCloudConfiguration(name=" << cfg_name << ", value=" << cfg_str << "): topic and frameid required, pointcloud will not be published, check configuration");
150  m_publish = false;
151  }
152 }
153 
155 {
156  ROS_INFO_STREAM("CustomPointCloudConfiguration(" << m_cfg_name << "): publish = " << m_publish);
157  ROS_INFO_STREAM("CustomPointCloudConfiguration(" << m_cfg_name << "): topic = " << m_topic);
158  ROS_INFO_STREAM("CustomPointCloudConfiguration(" << m_cfg_name << "): frameid = " << m_frameid);
159  ROS_INFO_STREAM("CustomPointCloudConfiguration(" << m_cfg_name << "): coordinate_notation = " << m_coordinate_notation);
160  ROS_INFO_STREAM("CustomPointCloudConfiguration(" << m_cfg_name << "): update_method = " << m_update_method);
161  ROS_INFO_STREAM("CustomPointCloudConfiguration(" << m_cfg_name << "): fields_enabled = " << printValuesEnabled(m_field_enabled));
162  ROS_INFO_STREAM("CustomPointCloudConfiguration(" << m_cfg_name << "): echos_enabled = " << printValuesEnabled(m_echo_enabled));
163  ROS_INFO_STREAM("CustomPointCloudConfiguration(" << m_cfg_name << "): layers_enabled = " << printValuesEnabled(m_layer_enabled));
164  ROS_INFO_STREAM("CustomPointCloudConfiguration(" << m_cfg_name << "): reflector_enabled = " << printValuesEnabled(m_reflector_enabled));
165  ROS_INFO_STREAM("CustomPointCloudConfiguration(" << m_cfg_name << "): infringed_enabled = " << printValuesEnabled(m_infringed_enabled));
166  ROS_INFO_STREAM("CustomPointCloudConfiguration(" << m_cfg_name << "): range_filter = " << m_range_filter.print());
167 }
168 
169 std::string sick_scansegment_xd::CustomPointCloudConfiguration::printValuesEnabled(const std::map<std::string,bool>& mapped_values, const std::string& delim)
170 {
171  std::stringstream s;
172  for(auto iter = mapped_values.cbegin(); iter != mapped_values.cend(); iter++)
173  {
174  if(iter->second)
175  s << (s.str().empty() ? "" : delim) << iter->first;
176  }
177  return s.str();
178 }
179 
180 std::string sick_scansegment_xd::CustomPointCloudConfiguration::printValuesEnabled(const std::map<int8_t,bool>& mapped_values, const std::string& delim)
181 {
182  std::stringstream s;
183  for(auto iter = mapped_values.cbegin(); iter != mapped_values.cend(); iter++)
184  {
185  if(iter->second)
186  s << (s.str().empty() ? "" : delim) << (int)(iter->first);
187  }
188  return s.str();
189 }
190 
191 /* @brief Determine and initialize all_segments_min/max_deg by LFPangleRangeFilter
192 ** host_set_LFPangleRangeFilter = "<enabled> <azimuth_start> <azimuth_stop> <elevation_start> <elevation_stop> <beam_increment>" with azimuth and elevation given in degree
193 ** Returns true, if angleRangeFilterSettings are enabled, otherwise false.
194 */
195 bool sick_scansegment_xd::RosMsgpackPublisher::initLFPangleRangeFilterSettings(const std::string& host_LFPangleRangeFilter)
196 {
197  std::vector<std::string> parameter_token;
198  sick_scansegment_xd::util::parseVector(host_LFPangleRangeFilter, parameter_token, ' ');
199  try
200  {
201  if(parameter_token.size() >= 3 && std::stoi(parameter_token[0]) > 0) // LFPangleRangeFilter enabled, i.e. all_segments_min/max_deg given by LFPangleRangeFilter settings
202  {
203  float all_segments_azimuth_min_deg = std::stof(parameter_token[1]);
204  float all_segments_azimuth_max_deg = std::stof(parameter_token[2]);
205  m_all_segments_azimuth_min_deg = std::max<float>(m_all_segments_azimuth_min_deg, all_segments_azimuth_min_deg);
206  m_all_segments_azimuth_max_deg = std::min<float>(m_all_segments_azimuth_max_deg, all_segments_azimuth_max_deg);
207  return true;
208  }
209  }
210  catch(const std::exception& e)
211  {
212  ROS_ERROR_STREAM("## ERROR in RosMsgpackPublisher: can't parse LFPangleRangeFilter settings, exception " << e.what() << ", check LFPangleRangeFilter configuration in the launchfile");
213  throw e; // fatal error: rethrow exception, which will be caught in sick_generic_caller and handled by exiting with error
214  }
215  return false;
216 }
217 
218 /* @brief Determine and initialize all_segments_elevation_min/max_deg by host_LFPlayerFilter"
219 ** host_LFPlayerFilter = "<enabled> <layer0enabled> <layer1enabled> ... <layer15enabled>", default: "0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1"
220 ** Returns true, if layerFilterSettings are enabled, otherwise false.
221 */
223 {
224  std::vector<std::string> parameter_token;
225  sick_scansegment_xd::util::parseVector(host_LFPlayerFilter, parameter_token, ' ');
226  try
227  {
228  if(parameter_token.size() >= 17 && std::stoi(parameter_token[0]) > 0) // LFPlayerFilter enabled
229  {
230  float all_segments_elevation_min_deg = 999;
231  float all_segments_elevation_max_deg = -999;
232  for(int n = 1; n < parameter_token.size(); n++)
233  {
234  if(std::stoi(parameter_token[n]) > 0)
235  {
237  all_segments_elevation_min_deg = std::min<float>(all_segments_elevation_min_deg, layer_elevation_deg);
238  all_segments_elevation_max_deg = std::max<float>(all_segments_elevation_max_deg, layer_elevation_deg);
239  }
240  }
241  if (all_segments_elevation_max_deg > all_segments_elevation_min_deg)
242  {
243  m_all_segments_elevation_min_deg = all_segments_elevation_min_deg;
244  m_all_segments_elevation_max_deg = all_segments_elevation_max_deg;
245  return true;
246  }
247  }
248  }
249  catch(const std::exception& e)
250  {
251  ROS_ERROR_STREAM("## ERROR in RosMsgpackPublisher: can't parse initLFPlayerFilterSettings settings, exception " << e.what() << ", check initLFPlayerFilterSettings configuration in the launchfile");
252  throw e; // fatal error: rethrow exception, which will be caught in sick_generic_caller and handled by exiting with error
253  }
254  return false;
255 }
256 
257 /*
258  * @brief RosMsgpackPublisher constructor
259  * @param[in] node_name name of the ros node
260  * @param[in] config sick_scansegment_xd configuration, RosMsgpackPublisher uses
261  * config.publish_frame_id: frame id of ros Laserscan messages, default: "world"
262  * @param[in] qos quality of service profile for the ros publisher, default: 1
263  */
265 #if defined __ROS_VERSION && __ROS_VERSION > 1
266  : Node(node_name)
267 #endif
268 {
269  m_active = false;
270  m_frame_id = config.publish_frame_id;
271  m_node = config.node;
272  m_laserscan_layer_filter = config.laserscan_layer_filter;
273  // m_segment_count = config.segment_count;
274  m_all_segments_azimuth_min_deg = (float)config.all_segments_min_deg;
275  m_all_segments_azimuth_max_deg = (float)config.all_segments_max_deg;
276  m_host_FREchoFilter = config.host_FREchoFilter;
277  m_host_set_FREchoFilter = config.host_set_FREchoFilter;
278  if (config.host_set_LFPangleRangeFilter)
279  {
280  initLFPangleRangeFilterSettings(config.host_LFPangleRangeFilter);
281  }
282  if (config.host_set_LFPlayerFilter)
283  {
284  initLFPlayerFilterSettings(config.host_LFPlayerFilter);
285  }
286  std::string imu_topic = config.imu_topic;
287 #if defined __ROS_VERSION && __ROS_VERSION > 1 // ROS-2 publisher
288  rosQoS qos = rclcpp::SystemDefaultsQoS();
289  QoSConverter qos_converter;
290  int qos_val = -1;
291  rosDeclareParam(m_node, "ros_qos", qos_val);
292  rosGetParam(m_node, "ros_qos", qos_val);
293  if (qos_val >= 0)
294  qos = qos_converter.convert(qos_val);
295  m_publisher_laserscan_segment = create_publisher<ros_sensor_msgs::LaserScan>(config.publish_laserscan_segment_topic, qos);
296  ROS_INFO_STREAM("RosMsgpackPublisher: publishing LaserScan segment messages on topic \"" << m_publisher_laserscan_segment->get_topic_name() << "\"");
297  m_publisher_laserscan_360 = create_publisher<ros_sensor_msgs::LaserScan>(config.publish_laserscan_fullframe_topic, qos);
298  ROS_INFO_STREAM("RosMsgpackPublisher: publishing LaserScan fullframe messages on topic \"" << m_publisher_laserscan_360->get_topic_name() << "\"");
299  if (config.imu_enable)
300  {
301  if (imu_topic[0] != '/')
302  imu_topic = "~/" + imu_topic;
303  m_publisher_imu = create_publisher<ros_sensor_msgs::Imu>(imu_topic, qos);
304  m_publisher_imu_initialized = true;
305  ROS_INFO_STREAM("RosMsgpackPublisher: publishing Imu messages on topic \"" << m_publisher_imu->get_topic_name() << "\"");
306  }
307 #elif defined __ROS_VERSION && __ROS_VERSION > 0 // ROS-1 publisher
308  int qos = 16 * 12 * 3; // 16 layers, 12 segments, 3 echos
309  int qos_val = -1;
310  rosDeclareParam(m_node, "ros_qos", qos_val);
311  rosGetParam(m_node, "ros_qos", qos_val);
312  if (qos_val >= 0)
313  qos = qos_val;
314  m_publisher_laserscan_segment = m_node->advertise<ros_sensor_msgs::LaserScan>(config.publish_laserscan_segment_topic, qos);
315  ROS_INFO_STREAM("RosMsgpackPublisher: publishing LaserScan segment messages on topic \"" << config.publish_laserscan_segment_topic << "\"");
316  m_publisher_laserscan_360 = m_node->advertise<ros_sensor_msgs::LaserScan>(config.publish_laserscan_fullframe_topic, qos);
317  ROS_INFO_STREAM("RosMsgpackPublisher: publishing LaserScan fullframe messages on topic \"" << config.publish_laserscan_fullframe_topic << "\"");
318 
319  if (config.imu_enable)
320  {
321  m_publisher_imu = m_node->advertise<ros_sensor_msgs::Imu>(config.imu_topic, qos);
322  m_publisher_imu_initialized = true;
323  ROS_INFO_STREAM("RosMsgpackPublisher: publishing Imu messages on topic \"" << config.imu_topic << "\"");
324  }
325 #endif
326 
327  /* Configuration of customized pointclouds:
328 
329  Parameter "custom_pointclouds" lists all customized pointclouds to be published. Each pointcloud is given by its name and configured by the following parameters:
330  "<name_of_custom_pointcloud>" type="string" value="list of key-value-pairs"
331 
332  The list of key-value-pairs defines the pointcloud properties. List of supported key-value-pairs for customized pointclouds:
333 
334  Parameter "coordinateNotation" is an enum to configure pointcloud coordinates:
335  coordinateNotation=0: cartesian (default, pointcloud has fields x,y,z,i), identical to customized with fields=x,y,z,i
336  coordinateNotation=1: polar (pointcloud has fields azimuth,elevation,r,i), identical to customized with fields=azimuth,elevation,range,i
337  coordinateNotation=2: both cartesian and polar (pointcloud has fields x,y,z,azimuth,elevation,r,i), identical to customized with fields=x,y,z,azimuth,elevation,range,i
338  coordinateNotation=3: customized pointcloud fields, i.e. the pointcloud has fields configured by parameter "fields"
339 
340  Parameter "updateMethod" is an enum to configure fullframe pointclouds versus segmented pointcloud:
341  updateMethod=0: fullframe pointcloud (default)
342  updateMethod=1: segmented pointcloud
343 
344  Parameter "fields" defines the fields of the pointcloud for coordinateNotation == 3 (customized pointcloud fields), e.g.
345  fields=x,y,z,i: cartesian pointcloud
346  fields=range,azimuth,elevation: polar pointcloud
347  or any other combination of x,y,z,i,range,azimuth,elevation,layer,echo,reflector
348 
349  Parameter "echos" defines which echos are included in the pointcloud, e.g.
350  echos=0,1,2: all echos
351  echos=2: last echo
352  or any other combination of 0,1,2
353 
354  Parameter "layers" defines which echos are included in the pointcloud, e.g
355  layers=0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 for all layers
356  layers=0 for the 0 degree layer
357  or any other combination of 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
358 
359  Parameter "reflectors" filters the points by the reflector bit, i.e.
360  reflectors=0,1 for points with reflector bit set or not set
361  reflectors=0 for points with reflector bit not set
362  reflectors=1 for points with reflector bit set
363 
364  Parameter "infringed" defines filters the points by infringement, i.e.
365  infringed=0,1 for points with infringement bit set or not set
366  infringed=0 for points with infringement bit not set
367  infringed=1 for points with infringement bit set
368  Parameter "infringed" is currently not supported (reserved for future use)
369 
370  Parameter "topic" defines the ros topic, e.g. topic=/cloud_fullframe for cartesian fullframe pointclouds
371 
372  Parameter "frameid" defines the ros frame of the pointcloud, e.g. frameid=world, frameid=map or frameid=base_link
373 
374  Parameter "publish" activates or deactivates the pointcloud, e.g. publish=1 to generate and publish, or publish=0 to deactivate that pointcloud
375 
376  Examples:
377  <!-- List of customized pointclouds -->
378  <param name="custom_pointclouds" type="string" value="custom_pointcloud_cartesian_segmented custom_pointcloud_polar_segmented custom_pointcloud_cartesian_fullframe custom_pointcloud_all_fields_fullframe"/>
379 
380  <!-- custom_pointcloud_cartesian_segmented: cartesian coordinates, segmented, all echos, all layer, topic "cloud_unstructured_segments" -->
381  <param name="custom_pointcloud_cartesian_segmented" type="string" value="coordinateNotation=0 updateMethod=1 echos=0,1,2 layers=0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 reflectors=0,1 infringed=0,1 topic=/cloud_unstructured_segments frameid=world publish=1"/>
382 
383  <!-- custom_pointcloud_polar_segmented: polar coordinates, segmented, all echos, all layer, topic "cloud_polar_unstructured_segments" -->
384  <param name="custom_pointcloud_polar_segmented" type="string" value="coordinateNotation=1 updateMethod=1 echos=0,1,2 layers=0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 reflectors=0,1 infringed=0,1 topic=/cloud_polar_unstructured_segments frameid=world publish=1"/>
385 
386  <!-- custom_pointcloud_cartesian_fullframe: cartesian coordinates, fullframe, all echos, all layer, topic "cloud_unstructured_fullframe" -->
387  <param name="custom_pointcloud_cartesian_fullframe" type="string" value="coordinateNotation=0 updateMethod=0 echos=0,1,2 layers=0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 reflectors=0,1 infringed=0,1 topic=/cloud_unstructured_fullframe frameid=world publish=1"/>
388 
389  <!-- cloud_all_fields_fullframe: all fields (x,y,z,i,range,azimuth,elevation,layer,echo,reflector), fullframe, all echos, all layer, topic "cloud_unstructured_fullframe" -->
390  <param name="custom_pointcloud_all_fields_fullframe" type="string" value="coordinateNotation=3 updateMethod=0 fields=x,y,z,i,range,azimuth,elevation,layer,echo,reflector echos=0,1,2 layers=0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 reflectors=0,1 infringed=0,1 topic=/cloud_all_fields_fullframe frameid=world publish=1"/>
391 
392  */
393  std::string custom_pointclouds = "";
394  rosDeclareParam(m_node, "custom_pointclouds", custom_pointclouds);
395  rosGetParam(m_node, "custom_pointclouds", custom_pointclouds);
396  std::vector<std::string> custom_pointclouds_tokens;
397  sick_scansegment_xd::util::parseVector(custom_pointclouds, custom_pointclouds_tokens, ' ');
398  for(int n = 0; n < custom_pointclouds_tokens.size(); n++)
399  {
400  std::string custom_pointcloud_cfg_str = "";
401  rosDeclareParam(m_node, custom_pointclouds_tokens[n], custom_pointcloud_cfg_str);
402  rosGetParam(m_node, custom_pointclouds_tokens[n], custom_pointcloud_cfg_str);
403  CustomPointCloudConfiguration custom_pointcloud_cfg(custom_pointclouds_tokens[n], custom_pointcloud_cfg_str);
404  if (custom_pointcloud_cfg.publish())
405  {
406  custom_pointcloud_cfg.print();
407 #if defined __ROS_VERSION && __ROS_VERSION > 1 // ROS-2 publisher
408  custom_pointcloud_cfg.publisher() = create_publisher<PointCloud2Msg>(custom_pointcloud_cfg.topic(), qos);
409 #elif defined __ROS_VERSION && __ROS_VERSION > 0 // ROS-1 publisher
410  custom_pointcloud_cfg.publisher() = m_node->advertise<PointCloud2Msg>(custom_pointcloud_cfg.topic(), qos);
411 #endif
412  m_custom_pointclouds_cfg.push_back(custom_pointcloud_cfg);
413  }
414  }
415 }
416 
417 /*
418  * @brief RosMsgpackPublisher destructor
419  */
421 {
422 }
423 
425 std::string sick_scansegment_xd::RosMsgpackPublisher::printElevationAzimuthTable(const std::vector<std::vector<sick_scansegment_xd::PointXYZRAEI32f>>& lidar_points)
426 {
427  std::stringstream s;
428  for (int echoIdx = 0; echoIdx < lidar_points.size(); echoIdx++)
429  {
430  s << (echoIdx > 0 ? ", " : "") << "echo" << echoIdx << ":[";
431  int last_elevation_mdeg = -99999, last_azimuth_ideg = -99999;
432  for (int n = 0; n < lidar_points[echoIdx].size(); n++)
433  {
434  const sick_scansegment_xd::PointXYZRAEI32f& point = lidar_points[echoIdx][n];
435  float elevation_deg = point.elevation * 180.0f / (float)M_PI;
436  float azimuth_fdeg = point.azimuth * 180.0f / (float)M_PI;
437  int elevation_mdeg = (int)(1000.0f * elevation_deg);
438  int azimuth_ideg = (int)(azimuth_fdeg);
439  if (elevation_mdeg != last_elevation_mdeg || azimuth_ideg != last_azimuth_ideg)
440  s << (n > 0 ? "," : "") << " (" << (elevation_mdeg/1000) << "," << azimuth_ideg << ")";
441  last_elevation_mdeg = elevation_mdeg;
442  last_azimuth_ideg = azimuth_ideg;
443  }
444  s << " ]";
445  }
446 
447  return s.str();
448 }
449 
450 
452 std::string sick_scansegment_xd::RosMsgpackPublisher::printCoverageTable(const std::map<int, std::map<int, int>>& elevation_azimuth_histograms)
453 {
454  std::stringstream s;
455  s << "[";
456  for (std::map<int, std::map<int, int>>::const_iterator coverage_elevation_iter = elevation_azimuth_histograms.cbegin(); coverage_elevation_iter != elevation_azimuth_histograms.cend(); coverage_elevation_iter++)
457  {
458  int elevation_mdeg = coverage_elevation_iter->first;
459  const std::map<int, int>& azimuth_histogram = coverage_elevation_iter->second;
460  for (std::map<int, int>::const_iterator coverage_azimuth_iter = azimuth_histogram.cbegin(); coverage_azimuth_iter != azimuth_histogram.cend(); coverage_azimuth_iter++)
461  {
462  const int& azimuth_ideg = coverage_azimuth_iter->first;
463  const int& azimuth_cnt = coverage_azimuth_iter->second;
464  if (azimuth_cnt > 0)
465  s << " (" << (elevation_mdeg/1000) << "," << azimuth_ideg << "),";
466  }
467  }
468  s << " ]";
469  return s.str();
470 }
471 
473 void sick_scansegment_xd::RosMsgpackPublisher::publishPointCloud2Msg(rosNodePtr node, PointCloud2MsgPublisher& publisher, PointCloud2Msg& pointcloud_msg, int32_t num_echos, int32_t segment_idx, int coordinate_notation, const std::string& topic)
474 {
475  if (coordinate_notation == 0) // coordinateNotation=0: cartesian (default, pointcloud has fields x,y,z,i) => notify cartesian pointcloud listener
476  {
477  sick_scan_xd::PointCloud2withEcho cloud_msg_with_echo(&pointcloud_msg, num_echos, segment_idx, topic);
478  notifyCartesianPointcloudListener(node, &cloud_msg_with_echo);
479  }
480 #if defined RASPBERRY && RASPBERRY > 0 // polar pointcloud deactivated on Raspberry for performance reasons
481 #else
482  if (coordinate_notation == 1) // coordinateNotation=1: polar (pointcloud has fields azimuth,elevation,r,i) => notify polar pointcloud listener
483  {
484  sick_scan_xd::PointCloud2withEcho cloud_msg_with_echo(&pointcloud_msg, num_echos, segment_idx, topic);
485  notifyPolarPointcloudListener(node, &cloud_msg_with_echo);
486  }
487 #endif
488 #if defined __ROS_VERSION && __ROS_VERSION > 1
489  publisher->publish(pointcloud_msg);
490 #elif defined __ROS_VERSION && __ROS_VERSION > 0
491  publisher.publish(pointcloud_msg);
492 #elif defined ROSSIMU
493  // plotPointCloud(pointcloud_msg);
494 #endif
495 }
496 
498 void sick_scansegment_xd::RosMsgpackPublisher::publishLaserScanMsg(rosNodePtr node, LaserscanMsgPublisher& laserscan_publisher, LaserScanMsgMap& laser_scan_msg_map, int32_t num_echos, int32_t segment_idx)
499 {
500 #if defined RASPBERRY && RASPBERRY > 0 // laserscan messages deactivated on Raspberry for performance reasons
501 #else
502  for(LaserScanMsgMap::iterator laser_scan_echo_iter = laser_scan_msg_map.begin(); laser_scan_echo_iter != laser_scan_msg_map.end(); laser_scan_echo_iter++)
503  {
504  int echo_idx = laser_scan_echo_iter->first;
505  std::map<int,ros_sensor_msgs::LaserScan>& laser_scan_layer_map = laser_scan_echo_iter->second;
506  for(std::map<int,ros_sensor_msgs::LaserScan>::iterator laser_scan_msg_iter = laser_scan_layer_map.begin(); laser_scan_msg_iter != laser_scan_layer_map.end(); laser_scan_msg_iter++)
507  {
508  int layer_idx = laser_scan_msg_iter->first;
509  ros_sensor_msgs::LaserScan& laser_scan_msg = laser_scan_msg_iter->second;
510  if (laser_scan_msg.ranges.size() > 0)
511  {
512 #if defined __ROS_VERSION && __ROS_VERSION > 1
513  laserscan_publisher->publish(laser_scan_msg);
514 #elif defined __ROS_VERSION && __ROS_VERSION > 0
515  laserscan_publisher.publish(laser_scan_msg);
516 #endif
517  // ROS_INFO_STREAM("publish LaserScan: segment_idx:" << segment_idx << ", frame_id:" << laser_scan_msg.header.frame_id << ", num_points:" << laser_scan_msg.ranges.size() << ", angle_min:" << (laser_scan_msg.angle_min * 180.0 / M_PI) << ", angle_max:" << (laser_scan_msg.angle_max * 180.0 / M_PI));
518  // usleep(1000);
519  }
520  }
521  }
522 #endif
523 }
524 
526 {
527 public:
529  CustomizedPointXYZRAEI32f(uint32_t timestamp_sec, uint32_t timestamp_nsec, uint64_t lidar_timestamp_start_microsec, const sick_scansegment_xd::PointXYZRAEI32f& point) : sick_scansegment_xd::PointXYZRAEI32f(point)
530  {
531  if (point.lidar_timestamp_microsec > lidar_timestamp_start_microsec)
532  {
533  time_offset_nanosec = (uint32_t)(1000 * (point.lidar_timestamp_microsec - lidar_timestamp_start_microsec));
534  time_offset_sec = (float)(1.0e-6 * (point.lidar_timestamp_microsec - lidar_timestamp_start_microsec));
535  lidar_sec = (uint32_t)(point.lidar_timestamp_microsec / 1000000);
536  lidar_nsec = (uint32_t)(1000 * (point.lidar_timestamp_microsec % 1000000));
537  // assert((uint64_t)lidar_sec * 1000000 + (uint64_t)lidar_nsec / 1000 == point.lidar_timestamp_microsec);
538  }
539  ring = point.layer;
540  }
541  // Additional fields for customized point clouds:
542  uint32_t time_offset_nanosec = 0; // field "t": 4 byte time offset of a scan point in nano seconds relative to the timestamp in the point cloud header, used by rtabmap for deskewing
543  float time_offset_sec = 0; // field "ts": time offset in seconds (otherwise identical to field "t")
544  uint32_t lidar_sec = 0; // field "lidar_sec": 4 byte seconds part of the lidar timestamp in microseconds (lidar time), lidar_sec = (uint32_t)(lidar_timestamp_microsec / 1000000)
545  uint32_t lidar_nsec = 0; // field "lidar_nsec": 4 byte nano seconds part of the lidar timestamp in microseconds (lidar time), lidar_nsec = (uint32_t)(1000 * (lidar_timestamp_microsec % 1000000))
546  int8_t ring = 0; // field "ring": 1 byte layer id, identical to field "layer"
547 };
548 
549 /*
550 * Converts the lidarpoints to a customized PointCloud2Msg containing configured fields (e.g. x, y, z, i, range, azimuth, elevation, layer, echo, reflector).
551 * @param[in] timestamp_sec seconds part of timestamp (system time)
552 * @param[in] timestamp_nsec nanoseconds part of timestamp (system time)
553 * @param[in] lidar_timestamp_start_microsec lidar start timestamp in microseconds (lidar ticks)
554 * @param[in] lidar_points list of PointXYZRAEI32f: lidar_points[echoIdx] are the points of one echo
555 * @param[in] pointcloud_cfg configuration of customized pointcloud
556 * @param[out] pointcloud_msg customized pointcloud message
557 */
558 void sick_scansegment_xd::RosMsgpackPublisher::convertPointsToCustomizedFieldsCloud(uint32_t timestamp_sec, uint32_t timestamp_nsec, uint64_t lidar_timestamp_start_microsec,
559  const std::vector<std::vector<sick_scansegment_xd::PointXYZRAEI32f>>& lidar_points, CustomPointCloudConfiguration& pointcloud_cfg, PointCloud2Msg& pointcloud_msg)
560 {
561  // set pointcloud header
562  pointcloud_msg.header.stamp.sec = timestamp_sec;
563 #if defined __ROS_VERSION && __ROS_VERSION > 1
564  pointcloud_msg.header.stamp.nanosec = timestamp_nsec;
565 #else
566  pointcloud_msg.header.stamp.nsec = timestamp_nsec;
567 #endif
568  pointcloud_msg.header.frame_id = pointcloud_cfg.frameid();
569  // set pointcloud field properties
570  CustomizedPointXYZRAEI32f dummy_lidar_point;
571  std::vector<PointCloudFieldProperty> field_properties;
572  field_properties.reserve(12);
573  if (pointcloud_cfg.fieldEnabled("x"))
574  field_properties.push_back(PointCloudFieldProperty("x", PointField::FLOAT32, sizeof(float), (uint8_t*)&dummy_lidar_point.x - (uint8_t*)&dummy_lidar_point));
575  if (pointcloud_cfg.fieldEnabled("y"))
576  field_properties.push_back(PointCloudFieldProperty("y", PointField::FLOAT32, sizeof(float), (uint8_t*)&dummy_lidar_point.y - (uint8_t*)&dummy_lidar_point));
577  if (pointcloud_cfg.fieldEnabled("z"))
578  field_properties.push_back(PointCloudFieldProperty("z", PointField::FLOAT32, sizeof(float), (uint8_t*)&dummy_lidar_point.z - (uint8_t*)&dummy_lidar_point));
579  if (pointcloud_cfg.fieldEnabled("i"))
580  field_properties.push_back(PointCloudFieldProperty("i", PointField::FLOAT32, sizeof(float), (uint8_t*)&dummy_lidar_point.i - (uint8_t*)&dummy_lidar_point));
581  if (pointcloud_cfg.fieldEnabled("range"))
582  field_properties.push_back(PointCloudFieldProperty("range", PointField::FLOAT32, sizeof(float), (uint8_t*)&dummy_lidar_point.range - (uint8_t*)&dummy_lidar_point));
583  if (pointcloud_cfg.fieldEnabled("azimuth"))
584  field_properties.push_back(PointCloudFieldProperty("azimuth", PointField::FLOAT32, sizeof(float), (uint8_t*)&dummy_lidar_point.azimuth - (uint8_t*)&dummy_lidar_point));
585  if (pointcloud_cfg.fieldEnabled("elevation"))
586  field_properties.push_back(PointCloudFieldProperty("elevation", PointField::FLOAT32, sizeof(float), (uint8_t*)&dummy_lidar_point.elevation - (uint8_t*)&dummy_lidar_point));
587  if (pointcloud_cfg.fieldEnabled("t"))
588  field_properties.push_back(PointCloudFieldProperty("t", PointField::UINT32, sizeof(uint32_t), (uint8_t*)&dummy_lidar_point.time_offset_nanosec - (uint8_t*)&dummy_lidar_point));
589  if (pointcloud_cfg.fieldEnabled("ts"))
590  field_properties.push_back(PointCloudFieldProperty("ts", PointField::FLOAT32, sizeof(float), (uint8_t*)&dummy_lidar_point.time_offset_sec - (uint8_t*)&dummy_lidar_point));
591  if (pointcloud_cfg.fieldEnabled("lidar_sec"))
592  field_properties.push_back(PointCloudFieldProperty("lidar_sec", PointField::UINT32, sizeof(uint32_t), (uint8_t*)&dummy_lidar_point.lidar_sec - (uint8_t*)&dummy_lidar_point));
593  if (pointcloud_cfg.fieldEnabled("lidar_nsec"))
594  field_properties.push_back(PointCloudFieldProperty("lidar_nsec", PointField::UINT32, sizeof(uint32_t), (uint8_t*)&dummy_lidar_point.lidar_nsec - (uint8_t*)&dummy_lidar_point));
595  if (pointcloud_cfg.fieldEnabled("ring"))
596  field_properties.push_back(PointCloudFieldProperty("ring", PointField::INT8, sizeof(int8_t), (uint8_t*)&dummy_lidar_point.ring - (uint8_t*)&dummy_lidar_point));
597  if (pointcloud_cfg.fieldEnabled("layer"))
598  field_properties.push_back(PointCloudFieldProperty("layer", PointField::INT8, sizeof(int8_t), (uint8_t*)&dummy_lidar_point.layer - (uint8_t*)&dummy_lidar_point));
599  if (pointcloud_cfg.fieldEnabled("echo"))
600  field_properties.push_back(PointCloudFieldProperty("echo", PointField::INT8, sizeof(int8_t), (uint8_t*)&dummy_lidar_point.echo - (uint8_t*)&dummy_lidar_point));
601  if (pointcloud_cfg.fieldEnabled("reflector"))
602  field_properties.push_back(PointCloudFieldProperty("reflector", PointField::INT8, sizeof(int8_t), (uint8_t*)&dummy_lidar_point.reflectorbit - (uint8_t*)&dummy_lidar_point));
603  if (pointcloud_cfg.fieldEnabled("infringed"))
604  field_properties.push_back(PointCloudFieldProperty("infringed", PointField::INT8, sizeof(int8_t), (uint8_t*)&dummy_lidar_point.infringed - (uint8_t*)&dummy_lidar_point));
605  int num_fields = field_properties.size();
606  size_t max_number_of_points = 0;
607  for (int echo_idx = 0; echo_idx < lidar_points.size(); echo_idx++)
608  {
609  max_number_of_points += lidar_points[echo_idx].size();
610  }
611  pointcloud_msg.height = 1;
612  pointcloud_msg.width = max_number_of_points;
613  pointcloud_msg.is_bigendian = false;
614  pointcloud_msg.is_dense = true;
615  pointcloud_msg.point_step = 0;
616  pointcloud_msg.fields.resize(num_fields);
617  for (int i = 0; i < num_fields; i++)
618  {
619  pointcloud_msg.fields[i].count = 1;
620  pointcloud_msg.fields[i].name = field_properties[i].name;
621  pointcloud_msg.fields[i].datatype = field_properties[i].datatype;
622  pointcloud_msg.fields[i].offset = pointcloud_msg.point_step;
623  pointcloud_msg.point_step += field_properties[i].datasize;
624  }
625  pointcloud_msg.row_step = pointcloud_msg.point_step * max_number_of_points;
626  pointcloud_msg.data.clear();
627  pointcloud_msg.data.resize(pointcloud_msg.row_step * pointcloud_msg.height, 0);
628  // fill pointcloud data
629  int point_cnt = 0;
630  for (int echo_idx = 0; echo_idx < lidar_points.size(); echo_idx++)
631  {
632  for (int point_idx = 0; point_cnt < max_number_of_points && point_idx < lidar_points[echo_idx].size(); point_idx++)
633  {
634  CustomizedPointXYZRAEI32f cur_lidar_point(timestamp_sec, timestamp_nsec, lidar_timestamp_start_microsec, lidar_points[echo_idx][point_idx]);
635  if (pointcloud_cfg.pointEnabled(cur_lidar_point))
636  {
637  size_t pointcloud_offset = point_cnt * pointcloud_msg.point_step; // offset in bytes in pointcloud_msg.data (destination)
638  const uint8_t* src_lidar_point = (const uint8_t*)(&cur_lidar_point); // pointer to source lidar point (type CustomizedPointXYZRAEI32f)
639  for(int field_idx = 0; field_idx < field_properties.size(); field_idx++)
640  {
641  size_t field_offset = field_properties[field_idx].fieldoffset;
642  for (int n = 0; n < field_properties[field_idx].datasize; n++, pointcloud_offset++, field_offset++)
643  {
644  pointcloud_msg.data[pointcloud_offset] = src_lidar_point[field_offset];
645  }
646  }
647  point_cnt++;
648  }
649  }
650  }
651  // resize pointcloud to actual number of points
652  pointcloud_msg.width = point_cnt;
653  pointcloud_msg.row_step = pointcloud_msg.point_step * point_cnt;
654  pointcloud_msg.data.resize(pointcloud_msg.row_step * pointcloud_msg.height);
655  ROS_DEBUG_STREAM("CustomPointCloudConfiguration " << pointcloud_cfg.cfgName() << ": " << point_cnt << " points per cloud, " << num_fields << " fields per point");
656 }
657 
658 /*
659  * Converts the lidarpoints from a msgpack to a LaserScan messages for each layer.
660  * @param[in] timestamp_sec seconds part of timestamp
661  * @param[in] timestamp_nsec nanoseconds part of timestamp
662  * @param[in] lidar_points list of PointXYZRAEI32f: lidar_points[echoIdx] are the points of one echo
663  * @param[in] total_point_count total number of points in all echos
664  * @param[out] laser_scan_msg_map laserscan message: ros_sensor_msgs::LaserScan for each echo and layer is laser_scan_msg_map[echo][layer]
665  * @param[in] frame_id frame id of laserscan message, will be expanded to "<frame_id>_<layer_idx>"
666  */
667 void sick_scansegment_xd::RosMsgpackPublisher::convertPointsToLaserscanMsg(uint32_t timestamp_sec, uint32_t timestamp_nsec, const std::vector<std::vector<sick_scansegment_xd::PointXYZRAEI32f>>& lidar_points, size_t total_point_count,
668  LaserScanMsgMap& laser_scan_msg_map, const std::string& frame_id, bool is_fullframe)
669 {
670 #if defined RASPBERRY && RASPBERRY > 0 // laserscan messages deactivated on Raspberry for performance reasons
671 #else
672  // Split lidar points into echos, layers and segments
673  struct LaserScanMsgPoint
674  {
675  LaserScanMsgPoint(float _range = 0, float _azimuth = 0, float _i = 0) : range(_range), azimuth(_azimuth), i(_i) {}
676  LaserScanMsgPoint(const sick_scansegment_xd::PointXYZRAEI32f& lidar_point) : range(lidar_point.range), azimuth(lidar_point.azimuth), i(lidar_point.i) {}
677  float range; // polar coordinate range in meter
678  float azimuth; // polar coordinate azimuth in radians
679  float i; // intensity
680  };
681  typedef std::vector<LaserScanMsgPoint> LaserScanMsgPoints; // list of laserscan points in one segment
682  typedef std::vector<LaserScanMsgPoints> LaserScanMsgSegments; // list of laserscan segments
683  typedef std::map<int,std::map<int,LaserScanMsgSegments>> LaserScanMsgEchoLayerSegments; // LaserScanMsgMap[echo][layer][segment] := laserscan points in one segment
684  typedef std::map<int,std::map<int,LaserScanMsgPoints>> LaserScanMsgEchoLayerSortedPoints; // LaserScanMsgEchoLayerSortedPoints[echo][layer] := sorted list of concatenated laserscan points
685  struct SortSegmentsAscendingAzimuth
686  {
687  bool operator()(const LaserScanMsgPoints& segment1, const LaserScanMsgPoints& segment2) { return !segment1.empty() && !segment2.empty() && segment1[0].azimuth < segment2[0].azimuth; }
688  };
689  struct SortSegmentsDescendingAzimuth
690  {
691  bool operator()(const LaserScanMsgPoints& segment1, const LaserScanMsgPoints& segment2) { return !segment1.empty() && !segment2.empty() && segment1[0].azimuth > segment2[0].azimuth; }
692  };
693  struct ScanPointPrinter
694  {
695  static std::string printAzimuthTable(const std::vector<sick_scansegment_xd::PointXYZRAEI32f>& points)
696  {
697  std::stringstream s;
698  for (int pointIdx = 0; pointIdx < points.size(); pointIdx++)
699  s << (pointIdx>0?",":"") << std::fixed << std::setprecision(1) << points[pointIdx].azimuth * 180.0 / M_PI;
700  return s.str();
701  }
702  static std::string printAzimuthTable(const LaserScanMsgPoints& points)
703  {
704  std::stringstream s;
705  for (int pointIdx = 0; pointIdx < points.size(); pointIdx++)
706  s << (pointIdx>0?",":"") << std::fixed << std::setprecision(1) << points[pointIdx].azimuth * 180.0 / M_PI;
707  return s.str();
708  }
709  };
710 
711  LaserScanMsgEchoLayerSegments points_echo_layer_segment_map;
712  for (int echoIdx = 0; echoIdx < lidar_points.size(); echoIdx++)
713  {
714  int last_layer = INT_MAX;
715  float last_azimuth = FLT_MAX;
716  // if (is_fullframe)
717  // ROS_INFO_STREAM("convertPointsToLaserscanMsg(" << (is_fullframe ? "fullframe": "segment") << "): echo" << echoIdx << ", unsorted azimuth=[" << ScanPointPrinter::printAzimuthTable(lidar_points[echoIdx]) << "]");
718  for (int pointIdx = 0; pointIdx < lidar_points[echoIdx].size(); pointIdx++)
719  {
720  const sick_scansegment_xd::PointXYZRAEI32f& lidar_point = lidar_points[echoIdx][pointIdx];
721  bool layer_enabled = (m_laserscan_layer_filter.empty() ? 1 : (m_laserscan_layer_filter[lidar_point.layer]));
722  if (layer_enabled)
723  {
724  LaserScanMsgSegments& point_segment = points_echo_layer_segment_map[lidar_point.echo][lidar_point.layer];
725  if (point_segment.empty() || last_layer != lidar_point.layer || std::abs(last_azimuth - lidar_point.azimuth) > (2.0*M_PI/180.0)) // start of a new segment
726  point_segment.push_back(LaserScanMsgPoints());
727  point_segment.back().push_back(LaserScanMsgPoint(lidar_point)); // append lidar point to last segment
728  last_layer = lidar_point.layer;
729  last_azimuth = lidar_point.azimuth;
730  }
731  }
732  }
733 
734  // Sort segments and concatenate points sorted by azimuth
735  LaserScanMsgEchoLayerSortedPoints sorted_points_echo_layer_map;
736  for(std::map<int,std::map<int,LaserScanMsgSegments>>::iterator iter_echos = points_echo_layer_segment_map.begin(); iter_echos != points_echo_layer_segment_map.end(); iter_echos++)
737  {
738  const int& echo = iter_echos->first;
739  for(std::map<int,LaserScanMsgSegments>::iterator iter_layer = iter_echos->second.begin(); iter_layer != iter_echos->second.end(); iter_layer++)
740  {
741  const int& layer = iter_layer->first;
742  LaserScanMsgSegments& segments = iter_layer->second;
743  // All points within a segment should be ordered by ascending azimuth values. Now we have to sort the segments by ascending start azimuth
744  int segment_cnt_azimuth_ascending = 0, segment_cnt_azimuth_descending = 0;
745  for(int segment_cnt = 0; segment_cnt < segments.size(); segment_cnt++)
746  {
747  const LaserScanMsgPoints& segment_points = segments[segment_cnt];
748  segment_cnt_azimuth_ascending += ((segment_points.size() > 1 && segment_points[0].azimuth < segment_points[1].azimuth) ? 1 : 0);
749  segment_cnt_azimuth_descending += ((segment_points.size() > 1 && segment_points[0].azimuth > segment_points[1].azimuth) ? 1 : 0);
750  // if (is_fullframe)
751  // ROS_INFO_STREAM("convertPointsToLaserscanMsg(" << (is_fullframe ? "fullframe": "segment") << "): echo" << echo << ", layer" << layer << ", segment" << segment_cnt << ", unsorted azimuth=[" << ScanPointPrinter::printAzimuthTable(segment_points) << "]");
752  }
753  if(segments.size() > 1)
754  {
755  if(segment_cnt_azimuth_ascending > 0 && segment_cnt_azimuth_descending > 0)
756  {
757  ROS_ERROR_STREAM("## ERROR convertPointsToLaserscanMsg(): " << segment_cnt_azimuth_ascending << " segments ordered by ascending azimuth, " << segment_cnt_azimuth_descending << " segments ordered by descending azimuth");
758  return; // scan points within a segment must be ordered by either ascending or descending azimuth values
759  }
760  if (segment_cnt_azimuth_ascending > 0)
761  std::sort(segments.begin(), segments.end(), SortSegmentsAscendingAzimuth());
762  else if(segment_cnt_azimuth_descending > 0)
763  std::sort(segments.begin(), segments.end(), SortSegmentsDescendingAzimuth());
764  else
765  {
766  ROS_ERROR_STREAM("## ERROR convertPointsToLaserscanMsg(): " << segment_cnt_azimuth_ascending << " segments ordered by ascending azimuth, " << segment_cnt_azimuth_descending << " segments ordered by descending azimuth");
767  return; // scan points within a segment must be ordered by either ascending or descending azimuth values
768  }
769  }
770  for(int segment_cnt = 0; segment_cnt < segments.size(); segment_cnt++)
771  {
772  const LaserScanMsgPoints& segment_points = segments[segment_cnt];
773  sorted_points_echo_layer_map[echo][layer].insert(sorted_points_echo_layer_map[echo][layer].end(), segment_points.begin(), segment_points.end());
774  // if (is_fullframe)
775  // ROS_INFO_STREAM("convertPointsToLaserscanMsg(" << (is_fullframe ? "fullframe": "segment") << "): echo" << echo << ", layer" << layer << ", segment" << segment_cnt << ", sorted azimuth=[" << ScanPointPrinter::printAzimuthTable(segment_points) << "]");
776  }
777  // Verify all points within the current segment have ascending or descending order (debug and verification only)
778  // for(int segment_cnt = 0; segment_cnt < iter_layer->second.size(); segment_cnt++)
779  // {
780  // const LaserScanMsgPoints& segment_points = segments[segment_cnt];
781  // bool azimuth_ascending = (segment_points.size() > 1 && segment_points[0].azimuth < segment_points[1].azimuth);
782  // ROS_INFO_STREAM("convertPointsToLaserscanMsg(" << (is_fullframe ? "fullframe": "segment") << "): echo" << echo << ", layer" << layer << ", segment" << segment_cnt << ": " << segment_points.size() << " points, azimuth in " << (azimuth_ascending ? "ascending":"descending") << " order");
783  // for(int point_cnt = 1; point_cnt < segment_points.size(); point_cnt++)
784  // {
785  // bool ascending = segment_points[point_cnt - 1].azimuth < segment_points[point_cnt].azimuth;
786  // if (azimuth_ascending != ascending)
787  // ROS_ERROR_STREAM("## ERROR convertPointsToLaserscanMsg(): echo" << echo << ", layer" << layer << ", segment" << segment_cnt << ": points in segment not ordered by azimuth");
788  // }
789  // }
790  // Convert to laserscan message, laser_scan_msg = laser_scan_msg_map[layer]
791  LaserScanMsgPoints sorted_points = sorted_points_echo_layer_map[echo][layer];
792  if (!sorted_points.empty())
793  {
794  ros_sensor_msgs::LaserScan& laser_scan_msg = laser_scan_msg_map[echo][layer];
795  laser_scan_msg.ranges.clear();
796  laser_scan_msg.intensities.clear();
797  laser_scan_msg.ranges.reserve(sorted_points.size());
798  laser_scan_msg.intensities.reserve(sorted_points.size());
799  laser_scan_msg.angle_min = sorted_points.front().azimuth;
800  laser_scan_msg.angle_max = sorted_points.back().azimuth;
801  laser_scan_msg.range_min = sorted_points.front().range;
802  laser_scan_msg.range_max = sorted_points.front().range;
803  float delta_azimuth_expected = (laser_scan_msg.angle_max - laser_scan_msg.angle_min) / std::max(1.0f, (float)sorted_points.size() - 1.0f);
804  for(int point_cnt = 0; point_cnt < sorted_points.size(); point_cnt++)
805  {
806  laser_scan_msg.ranges.push_back(sorted_points[point_cnt].range);
807  laser_scan_msg.intensities.push_back(sorted_points[point_cnt].i);
808  laser_scan_msg.range_min = std::min(sorted_points[point_cnt].range, laser_scan_msg.range_min);
809  laser_scan_msg.range_max = std::max(sorted_points[point_cnt].range, laser_scan_msg.range_max);
810  // Verify all azimuth values monotonously ordered (debug and verification only)
811  // if (point_cnt > 0)
812  // {
813  // float delta_azimuth = sorted_points[point_cnt].azimuth - sorted_points[point_cnt - 1].azimuth;
814  // if (std::abs(delta_azimuth - delta_azimuth_expected) > 0.1 * M_PI / 180.0)
815  // ROS_ERROR_STREAM("## ERROR convertPointsToLaserscanMsg(): delta_azimuth: " << (delta_azimuth * 180.0 / M_PI) << ", expected: " << (delta_azimuth_expected * 180.0 / M_PI) << ", " << point_cnt << ". point of " << sorted_points.size() << " points");
816  // }
817  }
818  }
819  }
820  }
821 
822  // Create laserscan messages for all echos and layers
823  int num_echos = (int)lidar_points.size();
824  int num_echos_publish = num_echos;
825  for(LaserScanMsgMap::iterator laser_scan_echo_iter = laser_scan_msg_map.begin(); laser_scan_echo_iter != laser_scan_msg_map.end(); laser_scan_echo_iter++)
826  {
827  int echo_idx = laser_scan_echo_iter->first;
828  bool echo_enabled = true;
829  // If only one echo is activated by FREchoFilter, but 3 echos are received, we apply the FREchoFilter for laserscan messages:
830  if (m_host_set_FREchoFilter && num_echos > 1 && (m_host_FREchoFilter == 0 || m_host_FREchoFilter == 2)) // m_host_FREchoFilter == 0: FIRST_ECHO only (EchoCount=1), m_host_FREchoFilter == 1: ALL_ECHOS, m_host_FREchoFilter == 2: LAST_ECHO (EchoCount=1)
831  {
832  num_echos_publish = 1;
833  if (m_host_FREchoFilter == 0 && echo_idx > 0)
834  echo_enabled = false; // m_host_FREchoFilter == 0: FIRST_ECHO only (EchoCount=1)
835  else if (m_host_FREchoFilter == 2 && echo_idx < num_echos - 1)
836  echo_enabled = false; // m_host_FREchoFilter == 2: LAST_ECHO only (EchoCount=1)
837  }
838  if (!echo_enabled)
839  continue;
840  std::map<int,ros_sensor_msgs::LaserScan>& laser_scan_layer_map = laser_scan_echo_iter->second;
841  for(std::map<int,ros_sensor_msgs::LaserScan>::iterator laser_scan_msg_iter = laser_scan_layer_map.begin(); laser_scan_msg_iter != laser_scan_layer_map.end(); laser_scan_msg_iter++)
842  {
843  int layer_idx = laser_scan_msg_iter->first;
844  ros_sensor_msgs::LaserScan& laser_scan_msg = laser_scan_msg_iter->second;
845  if (laser_scan_msg.ranges.size() > 1 && laser_scan_msg.angle_max > laser_scan_msg.angle_min)
846  {
847  float angle_diff = laser_scan_msg.angle_max - laser_scan_msg.angle_min;
848  while (angle_diff > (float)(2.0 * M_PI))
849  angle_diff -= (float)(2.0 * M_PI);
850  while (angle_diff < 0)
851  angle_diff += (float)(2.0 * M_PI);
852  laser_scan_msg.angle_increment = angle_diff / (float)(laser_scan_msg.ranges.size() - 1);
853  laser_scan_msg.range_min -= 1.0e-03f;
854  laser_scan_msg.range_max += 1.0e-03f;
855  laser_scan_msg.range_min = std::max(0.05f, laser_scan_msg.range_min); // min range of multiScan and picoScan: 0.05 [m]
856 
857 
858  laser_scan_msg.header.stamp.sec = timestamp_sec;
859 #if defined __ROS_VERSION && __ROS_VERSION > 1
860  laser_scan_msg.header.stamp.nanosec = timestamp_nsec;
861 #elif defined __ROS_VERSION && __ROS_VERSION > 0
862  laser_scan_msg.header.stamp.nsec = timestamp_nsec;
863 #endif
864  laser_scan_msg.header.frame_id = frame_id + "_" + std::to_string(layer_idx + 1);
865  if (num_echos_publish > 1)
866  laser_scan_msg.header.frame_id = laser_scan_msg.header.frame_id + "_" + std::to_string(echo_idx);
867  // scan_time = 1 / scan_frequency = time for a full 360-degree rotation of the sensor
868  laser_scan_msg.scan_time = m_scan_time;
869  // time_increment = 1 / measurement_frequency = scan_time / (number of scan points in a full 360-degree rotation of the sensor)
870  laser_scan_msg.time_increment = laser_scan_msg.scan_time / (float)(laser_scan_msg.ranges.size() * 2.0 * M_PI / angle_diff);
871  // ROS_INFO_STREAM("convert to LaserScan: frame_id=" << laser_scan_msg.header.frame_id << ", num_points=" << laser_scan_msg.ranges.size() << ", angle_min=" << (laser_scan_msg.angle_min * 180.0 / M_PI) << ", angle_max=" << (laser_scan_msg.angle_max * 180.0 / M_PI));
872  }
873  else
874  {
875  laser_scan_msg.ranges.clear();
876  laser_scan_msg.intensities.clear();
877  }
878  }
879  }
880  // Remove filtered echos
881  for(LaserScanMsgMap::iterator laser_scan_echo_iter = laser_scan_msg_map.begin(); laser_scan_echo_iter != laser_scan_msg_map.end(); )
882  {
883  int echo_idx = laser_scan_echo_iter->first;
884  bool echo_found = false;
885  std::map<int,ros_sensor_msgs::LaserScan>& laser_scan_layer_map = laser_scan_echo_iter->second;
886  for(std::map<int,ros_sensor_msgs::LaserScan>::iterator laser_scan_msg_iter = laser_scan_layer_map.begin(); !echo_found && laser_scan_msg_iter != laser_scan_layer_map.end(); laser_scan_msg_iter++)
887  {
888  int layer_idx = laser_scan_msg_iter->first;
889  ros_sensor_msgs::LaserScan& laser_scan_msg = laser_scan_msg_iter->second;
890  if (!laser_scan_msg.header.frame_id.empty() && !laser_scan_msg.ranges.empty())
891  echo_found = true;
892  }
893  if(!echo_found)
894  laser_scan_echo_iter = laser_scan_msg_map.erase(laser_scan_echo_iter);
895  else
896  laser_scan_echo_iter++;
897  }
898 #endif // !RASPBERRY
899 }
900 
901 /*
902  * Callback function of MsgPackExportListenerIF. HandleMsgPackData() will be called in MsgPackExporter
903  * for each registered listener after msgpack data have been received and converted.
904  * This function converts and publishes msgpack data to PointCloud2 messages.
905  */
907 {
908  if (!m_active)
909  return; // publishing deactivated
910  // Publish optional IMU data
911  if (msgpack_data.scandata.empty() && msgpack_data.imudata.valid)
912  {
913  ROS_DEBUG_STREAM("Publishing IMU data: { " << msgpack_data.imudata.to_string() << " }");
914  // Convert to ros_sensor_msgs::Imu
915  ros_sensor_msgs::Imu imu_msg;
916  imu_msg.header.stamp.sec = msgpack_data.timestamp_sec;
917 #if defined __ROS_VERSION && __ROS_VERSION > 1
918  imu_msg.header.stamp.nanosec = msgpack_data.timestamp_nsec;
919 #else
920  imu_msg.header.stamp.nsec = msgpack_data.timestamp_nsec;
921 #endif
922  imu_msg.header.frame_id = m_frame_id;
923  imu_msg.orientation.w = msgpack_data.imudata.orientation_w;
924  imu_msg.orientation.x = msgpack_data.imudata.orientation_x;
925  imu_msg.orientation.y = msgpack_data.imudata.orientation_y;
926  imu_msg.orientation.z = msgpack_data.imudata.orientation_z;
927  imu_msg.angular_velocity.x = msgpack_data.imudata.angular_velocity_x;
928  imu_msg.angular_velocity.y = msgpack_data.imudata.angular_velocity_y;
929  imu_msg.angular_velocity.z = msgpack_data.imudata.angular_velocity_z;
930  imu_msg.linear_acceleration.x = msgpack_data.imudata.acceleration_x;
931  imu_msg.linear_acceleration.y = msgpack_data.imudata.acceleration_y;
932  imu_msg.linear_acceleration.z = msgpack_data.imudata.acceleration_z;
933  // ros imu message definition: A covariance matrix of all zeros will be interpreted as "covariance unknown"
934  for(int n = 0; n < 9; n++)
935  {
936  imu_msg.orientation_covariance[n] = 0;
937  imu_msg.angular_velocity_covariance[n] = 0;
938  imu_msg.linear_acceleration_covariance[n] = 0;
939  }
940  // Publish imu message
941  sick_scan_xd::notifyImuListener(m_node, &imu_msg);
942  if (m_publisher_imu_initialized)
943  {
944 #if defined __ROS_VERSION && __ROS_VERSION > 1
945  m_publisher_imu->publish(imu_msg);
946 #else
947  m_publisher_imu.publish(imu_msg);
948 #endif
949  }
950  return;
951  }
952  // Reorder points in consecutive lidarpoints for echo 0, echo 1 and echo 2 as described in https://github.com/michael1309/sick_lidar3d_pretest/issues/5
953  size_t echo_count = 0; // number of echos (multiScan136: 1 or 3 echos)
954  size_t point_count_per_echo = 0; // number of points per echo
955  size_t total_point_count = 0; // total number of points in all echos
956  int32_t segment_idx = msgpack_data.segmentIndex;
957  int32_t telegram_cnt = msgpack_data.telegramCnt;
958  for (int groupIdx = 0; groupIdx < msgpack_data.scandata.size(); groupIdx++)
959  {
960  echo_count = std::max(msgpack_data.scandata[groupIdx].scanlines.size(), echo_count);
961  for (int echoIdx = 0; echoIdx < msgpack_data.scandata[groupIdx].scanlines.size(); echoIdx++)
962  {
963  total_point_count += msgpack_data.scandata[groupIdx].scanlines[echoIdx].points.size();
964  point_count_per_echo = std::max(msgpack_data.scandata[groupIdx].scanlines[echoIdx].points.size(), point_count_per_echo);
965  }
966  }
967  float lidar_points_min_azimuth = +2.0f * (float)M_PI, lidar_points_max_azimuth = -2.0f * (float)M_PI;
968  std::vector<std::vector<sick_scansegment_xd::PointXYZRAEI32f>> lidar_points(echo_count);
969  for (int echoIdx = 0; echoIdx < echo_count; echoIdx++)
970  {
971  lidar_points[echoIdx].reserve(point_count_per_echo);
972  }
973  uint64_t lidar_timestamp_start_microsec = std::numeric_limits<uint64_t>::max();
974  for (int groupIdx = 0; groupIdx < msgpack_data.scandata.size(); groupIdx++)
975  {
976  for (int echoIdx = 0; echoIdx < msgpack_data.scandata[groupIdx].scanlines.size(); echoIdx++)
977  {
978  const std::vector<sick_scansegment_xd::ScanSegmentParserOutput::LidarPoint>& scanline = msgpack_data.scandata[groupIdx].scanlines[echoIdx].points;
979  for (int pointIdx = 0; pointIdx < scanline.size(); pointIdx++)
980  {
981  const sick_scansegment_xd::ScanSegmentParserOutput::LidarPoint& point = scanline[pointIdx];
982  lidar_points[echoIdx].push_back(sick_scansegment_xd::PointXYZRAEI32f(point.x, point.y, point.z, point.range,
983  point.azimuth, point.elevation, point.i, point.groupIdx, point.echoIdx, point.lidar_timestamp_microsec, point.reflectorbit));
984  lidar_points_min_azimuth = std::min(lidar_points_min_azimuth, point.azimuth);
985  lidar_points_max_azimuth = std::max(lidar_points_max_azimuth, point.azimuth);
986  lidar_timestamp_start_microsec = std::min(lidar_timestamp_start_microsec, point.lidar_timestamp_microsec);
987  }
988  }
989  }
990 
991  // Versendung von Vollumläufen als ROS-Nachricht:
992  // a. Prozess läuft an
993  // b. Segmente werden verworfen, bis ein Segment mit Startwinkel 0° eintrifft.
994  // c. Es werden dann 12 Segmente aufgesammelt, bis 360° erreicht sind.
995  // d. Die 12 Segmente werden nur ausgegeben, wenn keines von den Segmenten korrupt ist.
996  // e. Die 12 Segmente werden als eine Pointcloud aufgesammelt und dann als eine Pointcloud2-Nachricht versendet.
997  // f. Es werden intern zwei Topics verwendet:
998  // i. Topic für 30° (Segmente)
999  // ii. Topic für 360° (Vollumlauf)
1000  // iii. Ist ein Topic leer, dann wird auf diesem Kanal nichts publiziert.
1001  // iv. Konfiguration erfolgt über YAML-Datei.
1002  // if(m_publish_topic_all_segments != "")
1003  {
1004  // ROS_INFO_STREAM("RosMsgpackPublisher::HandleMsgPackData(): check allSegmentsCovered, segment_idx=" << segment_idx);
1005  // ROS_DEBUG_STREAM(" RosMsgpackPublisher::HandleMsgPackData(): collected azimuth table = " << printElevationAzimuthTable(m_points_collector.lidar_points));
1006  float precheck_min_azimuth_deg = m_points_collector.min_azimuth * 180.0f / (float)M_PI;
1007  float precheck_max_azimuth_deg = m_points_collector.max_azimuth * 180.0f / (float)M_PI;
1008  bool publish_cloud_360 = (precheck_max_azimuth_deg - precheck_min_azimuth_deg + 1 >= m_all_segments_azimuth_max_deg - m_all_segments_azimuth_min_deg - 1) // fast pre-check
1009  && m_points_collector.allSegmentsCovered(m_all_segments_azimuth_min_deg, m_all_segments_azimuth_max_deg, m_all_segments_elevation_min_deg, m_all_segments_elevation_max_deg); // all segments collected in m_points_collector
1010  // ROS_INFO_STREAM(" RosMsgpackPublisher::HandleMsgPackData(): segment_idx=" << segment_idx << ", m_points_collector.lastSegmentIdx=" << m_points_collector.lastSegmentIdx()
1011  // << ", m_points_collector.total_point_count=" << m_points_collector.total_point_count
1012  // << ", azimuth_range_collected=(" << precheck_min_azimuth_deg << "," << precheck_max_azimuth_deg << ")=" << (precheck_max_azimuth_deg - precheck_min_azimuth_deg)
1013  // << ", azimuth_range_configured=(" << m_all_segments_azimuth_min_deg << "," << m_all_segments_azimuth_max_deg << ")=" << (m_all_segments_azimuth_max_deg - m_all_segments_azimuth_min_deg)
1014  // << ", m_points_collector.allSegmentsCovered=" << publish_cloud_360);
1015  if (m_points_collector.total_point_count <= 0 || m_points_collector.telegram_cnt <= 0 || publish_cloud_360 || m_points_collector.lastSegmentIdx() > segment_idx)
1016  {
1017  // 1. publish 360 degree point cloud if all segments collected
1018  // 2. start a new collection of all points (first time call, all segments covered, or segment index wrap around)
1019  if (m_points_collector.total_point_count > 0 && m_points_collector.telegram_cnt > 0 && publish_cloud_360)
1020  {
1021  // publish 360 degree point cloud
1022  // scan_time = 1 / scan_frequency = time for a full 360-degree rotation of the sensor
1023  m_scan_time = (msgpack_data.timestamp_sec + 1.0e-9 * msgpack_data.timestamp_nsec) - (m_points_collector.timestamp_sec + 1.0e-9 * m_points_collector.timestamp_nsec);
1024  for (int cloud_cnt = 0; cloud_cnt < m_custom_pointclouds_cfg.size(); cloud_cnt++)
1025  {
1026  CustomPointCloudConfiguration& custom_pointcloud_cfg = m_custom_pointclouds_cfg[cloud_cnt];
1027  if (custom_pointcloud_cfg.publish() && custom_pointcloud_cfg.fullframe())
1028  {
1029  PointCloud2Msg pointcloud_msg_custom_fields;
1030  convertPointsToCustomizedFieldsCloud(m_points_collector.timestamp_sec, m_points_collector.timestamp_nsec, m_points_collector.lidar_timestamp_start_microsec,
1031  m_points_collector.lidar_points, custom_pointcloud_cfg, pointcloud_msg_custom_fields);
1032  publishPointCloud2Msg(m_node, custom_pointcloud_cfg.publisher(), pointcloud_msg_custom_fields, std::max(1, (int)echo_count), -1, custom_pointcloud_cfg.coordinateNotation(), custom_pointcloud_cfg.topic());
1033  // ROS_INFO_STREAM("RosMsgpackPublisher::HandleMsgPackData(): published " << pointcloud_msg_custom_fields.width << "x" << pointcloud_msg_custom_fields.height << " pointcloud, " << pointcloud_msg_custom_fields.fields.size() << " fields/point, " << pointcloud_msg_custom_fields.data.size() << " bytes");
1034  }
1035  }
1036  // publish 360 degree Laserscan message
1037  LaserScanMsgMap laser_scan_msg_map; // laser_scan_msg_map[echo][layer] := LaserScan message given echo (Multiscan136: max 3 echos) and layer index (Multiscan136: 16 layer)
1038  convertPointsToLaserscanMsg(m_points_collector.timestamp_sec, m_points_collector.timestamp_nsec, m_points_collector.lidar_points, m_points_collector.total_point_count, laser_scan_msg_map, m_frame_id, true);
1039  publishLaserScanMsg(m_node, m_publisher_laserscan_360, laser_scan_msg_map, std::max(1, (int)echo_count), -1);
1040  }
1041  // Start a new 360 degree collection
1042  m_points_collector = SegmentPointsCollector(telegram_cnt);
1043  m_points_collector.timestamp_sec = msgpack_data.timestamp_sec;
1044  m_points_collector.timestamp_nsec = msgpack_data.timestamp_nsec;
1045  m_points_collector.total_point_count = total_point_count;
1046  m_points_collector.lidar_points = std::vector<std::vector<sick_scansegment_xd::PointXYZRAEI32f>>(lidar_points.size());
1047  for (int echoIdx = 0; echoIdx < lidar_points.size(); echoIdx++)
1048  m_points_collector.lidar_points[echoIdx].reserve(12 * lidar_points[echoIdx].size());
1049  m_points_collector.appendLidarPoints(lidar_points, segment_idx, telegram_cnt);
1050  m_points_collector.min_azimuth = lidar_points_min_azimuth;
1051  m_points_collector.max_azimuth = lidar_points_max_azimuth;
1052  // ROS_INFO_STREAM("RosMsgpackPublisher::HandleMsgPackData(): started new point collection with segment_idx=" << segment_idx);
1053  // ROS_INFO_STREAM(" RosMsgpackPublisher::HandleMsgPackData(): appendLidarPoints, lidar_points_min_azimuth=" << (lidar_points_min_azimuth * 180.0f / M_PI) << ", lidar_points_max_azimuth=" << (lidar_points_max_azimuth* 180.0f / M_PI));
1054  // ROS_DEBUG_STREAM(" RosMsgpackPublisher::HandleMsgPackData(): appendLidarPoints, azimuth table = " << printElevationAzimuthTable(lidar_points));
1055  // ROS_DEBUG_STREAM(" RosMsgpackPublisher::HandleMsgPackData(): appendLidarPoints, collected azimuth table = " << printElevationAzimuthTable(m_points_collector.lidar_points));
1056  // ROS_DEBUG_STREAM(" RosMsgpackPublisher::HandleMsgPackData(): appendLidarPoints, collected coverage table = " << printCoverageTable(m_points_collector.segment_coverage));
1057  }
1058  else if (telegram_cnt > m_points_collector.telegram_cnt) // append lidar points to m_points_collector
1059  {
1060  if (m_points_collector.lidar_points.size() < lidar_points.size())
1061  m_points_collector.lidar_points.resize(lidar_points.size());
1062  // m_points_collector.segment_count = segment_idx;
1063  m_points_collector.telegram_cnt = telegram_cnt;
1064  m_points_collector.total_point_count += total_point_count;
1065  m_points_collector.appendLidarPoints(lidar_points, segment_idx, telegram_cnt);
1066  m_points_collector.min_azimuth = std::min(m_points_collector.min_azimuth, lidar_points_min_azimuth);
1067  m_points_collector.max_azimuth = std::max(m_points_collector.max_azimuth, lidar_points_max_azimuth);
1068  // ROS_INFO_STREAM(" RosMsgpackPublisher::HandleMsgPackData(): appendLidarPoints, lidar_points_min_azimuth=" << (lidar_points_min_azimuth * 180.0f / M_PI) << ", lidar_points_max_azimuth=" << (lidar_points_max_azimuth* 180.0f / M_PI));
1069  // ROS_DEBUG_STREAM(" RosMsgpackPublisher::HandleMsgPackData(): appendLidarPoints, azimuth table = " << printElevationAzimuthTable(lidar_points));
1070  // ROS_DEBUG_STREAM(" RosMsgpackPublisher::HandleMsgPackData(): appendLidarPoints, collected azimuth table = " << printElevationAzimuthTable(m_points_collector.lidar_points));
1071  // ROS_DEBUG_STREAM(" RosMsgpackPublisher::HandleMsgPackData(): appendLidarPoints, collected coverage table = " << printCoverageTable(m_points_collector.segment_coverage));
1072  }
1073  else
1074  {
1075  static fifo_timestamp last_print_timestamp = fifo_clock::now();
1076  if (sick_scansegment_xd::Fifo<ScanSegmentParserOutput>::Seconds(last_print_timestamp, fifo_clock::now()) > 1.0) // avoid printing with more than 1 Hz
1077  {
1078  if (m_points_collector.telegram_cnt > telegram_cnt) // probably test enviroment with recorded and repeated telegrams from pcapng- or upd-player
1079  {
1080  ROS_INFO_STREAM("RosMsgpackPublisher::HandleMsgPackData(): current telegram cnt: " << telegram_cnt << ", last telegram cnt in collector: " << m_points_collector.telegram_cnt
1081  << ", 360-degree-pointcloud not published (ok if sick_scan_xd is running in a test enviroment with recorded and repeated telegrams from pcapng- or upd-player, otherwise not ok)");
1082  }
1083  else
1084  {
1085  ROS_WARN_STREAM("## WARNING RosMsgpackPublisher::HandleMsgPackData(): current segment: " << segment_idx << ", last segment in collector: " << m_points_collector.lastSegmentIdx()
1086  << ", current telegram: " << telegram_cnt << ", last telegram in collector: " << m_points_collector.telegram_cnt
1087  << ", datagram(s) missing, 360-degree-pointcloud not published");
1088  if (m_points_collector.numEchos() > 1)
1089  {
1090  ROS_WARN_STREAM("## WARNING RosMsgpackPublisher::HandleMsgPackData(): " << m_points_collector.numEchos() << " echos received. Activate the echo filter in the launchfile to reduce system load (e.g. last echo only)");
1091  }
1092  }
1093  last_print_timestamp = fifo_clock::now();
1094  }
1095  m_points_collector = SegmentPointsCollector(telegram_cnt); // reset pointcloud collector
1096  }
1097  // ROS_INFO_STREAM("RosMsgpackPublisher::HandleMsgPackData(): segment_idx " << segment_idx << " of " << m_segment_count << ", " << m_points_collector.total_point_count << " points in collector");
1098  }
1099 
1100  // Publish PointCloud2 message for the current segment
1101  for (int cloud_cnt = 0; cloud_cnt < m_custom_pointclouds_cfg.size(); cloud_cnt++)
1102  {
1103  CustomPointCloudConfiguration& custom_pointcloud_cfg = m_custom_pointclouds_cfg[cloud_cnt];
1104  if (custom_pointcloud_cfg.publish() && !custom_pointcloud_cfg.fullframe())
1105  {
1106  PointCloud2Msg pointcloud_msg_custom_fields;
1107  convertPointsToCustomizedFieldsCloud(msgpack_data.timestamp_sec, msgpack_data.timestamp_nsec, lidar_timestamp_start_microsec, lidar_points, custom_pointcloud_cfg, pointcloud_msg_custom_fields);
1108  publishPointCloud2Msg(m_node, custom_pointcloud_cfg.publisher(), pointcloud_msg_custom_fields, std::max(1, (int)echo_count), segment_idx, custom_pointcloud_cfg.coordinateNotation(), custom_pointcloud_cfg.topic());
1109  ROS_DEBUG_STREAM("publishPointCloud2Msg: " << pointcloud_msg_custom_fields.width << "x" << pointcloud_msg_custom_fields.height << " pointcloud, " << pointcloud_msg_custom_fields.fields.size() << " fields/point, " << pointcloud_msg_custom_fields.data.size() << " bytes");
1110  }
1111  }
1112 #if defined RASPBERRY && RASPBERRY > 0 // laserscan messages deactivated on Raspberry for performance reasons
1113 #else
1114  LaserScanMsgMap laser_scan_msg_map; // laser_scan_msg_map[echo][layer] := LaserScan message given echo (Multiscan136: max 3 echos) and layer index (Multiscan136: 16 layer)
1115  convertPointsToLaserscanMsg(msgpack_data.timestamp_sec, msgpack_data.timestamp_nsec, lidar_points, total_point_count, laser_scan_msg_map, m_frame_id, false);
1116  publishLaserScanMsg(m_node, m_publisher_laserscan_segment, laser_scan_msg_map, std::max(1, (int)echo_count), segment_idx);
1117 #endif
1118 }
1119 
1120 /*
1121  * Returns this instance explicitely as an implementation of interface MsgPackExportListenerIF.
1122  */
sick_scansegment_xd::ScanSegmentParserOutput::LidarPoint::range
float range
Definition: scansegment_parser_output.h:139
sick_scansegment_xd::CustomPointCloudConfiguration::printValuesEnabled
static std::string printValuesEnabled(const std::map< std::string, bool > &mapped_values, const std::string &delim=",")
Definition: ros_msgpack_publisher.cpp:169
sick_scansegment_xd::ScanSegmentParserOutput::LidarPoint::elevation
float elevation
Definition: scansegment_parser_output.h:141
sick_scansegment_xd::ScanSegmentParserOutput::imudata
CompactImuData imudata
Definition: scansegment_parser_output.h:180
sick_scansegment_xd::CustomPointCloudConfiguration::topic
const std::string & topic(void) const
Definition: ros_msgpack_publisher.h:109
sick_scansegment_xd::RosMsgpackPublisher::ExportListener
virtual sick_scansegment_xd::MsgPackExportListenerIF * ExportListener(void)
Definition: ros_msgpack_publisher.cpp:1123
sick_scansegment_xd::ScanSegmentParserOutput::LidarPoint::echoIdx
int echoIdx
Definition: scansegment_parser_output.h:143
sick_scansegment_xd::RosMsgpackPublisher::~RosMsgpackPublisher
virtual ~RosMsgpackPublisher()
Definition: ros_msgpack_publisher.cpp:420
CustomizedPointXYZRAEI32f::CustomizedPointXYZRAEI32f
CustomizedPointXYZRAEI32f(uint32_t timestamp_sec, uint32_t timestamp_nsec, uint64_t lidar_timestamp_start_microsec, const sick_scansegment_xd::PointXYZRAEI32f &point)
Definition: ros_msgpack_publisher.cpp:529
sensor_msgs::Imu
::sensor_msgs::Imu_< std::allocator< void > > Imu
Definition: Imu.h:93
sick_scansegment_xd::CompactImuData::acceleration_z
float acceleration_z
Definition: scansegment_parser_output.h:107
sick_scansegment_xd::CustomPointCloudConfiguration::publisher
PointCloud2MsgPublisher & publisher(void)
Definition: ros_msgpack_publisher.h:113
sick_scansegment_xd::PointXYZRAEI32f::range
float range
Definition: ros_msgpack_publisher.h:78
sick_scansegment_xd::ScanSegmentParserOutput::LidarPoint::z
float z
Definition: scansegment_parser_output.h:137
sick_scansegment_xd::ScanSegmentParserOutput::LidarPoint::azimuth
float azimuth
Definition: scansegment_parser_output.h:140
sick_scansegment_xd::ScanSegmentParserOutput::LidarPoint::lidar_timestamp_microsec
uint64_t lidar_timestamp_microsec
Definition: scansegment_parser_output.h:145
pointcloud_utils.h
sick_scansegment_xd::CustomPointCloudConfiguration::frameid
const std::string & frameid(void) const
Definition: ros_msgpack_publisher.h:110
PointCloud2Msg
ros_sensor_msgs::PointCloud2 PointCloud2Msg
Definition: include/sick_scansegment_xd/common.h:121
sick_scansegment_xd::CompactImuData::orientation_z
float orientation_z
Definition: scansegment_parser_output.h:114
sick_scansegment_xd::RosMsgpackPublisher::HandleMsgPackData
virtual void HandleMsgPackData(const sick_scansegment_xd::ScanSegmentParserOutput &msgpack_data)
Definition: ros_msgpack_publisher.cpp:906
sick_scansegment_xd::ScanSegmentParserOutput::timestamp_nsec
uint32_t timestamp_nsec
Definition: scansegment_parser_output.h:187
s
XmlRpcServer s
sick_scansegment_xd::PointXYZRAEI32f::elevation
float elevation
Definition: ros_msgpack_publisher.h:80
sick_scansegment_xd::CustomPointCloudConfiguration::m_coordinate_notation
int m_coordinate_notation
Definition: ros_msgpack_publisher.h:140
sick_scansegment_xd::RosMsgpackPublisher::printCoverageTable
std::string printCoverageTable(const std::map< int, std::map< int, int >> &elevation_azimuth_histograms)
Definition: ros_msgpack_publisher.cpp:452
sick_scansegment_xd::CustomPointCloudConfiguration::m_layer_enabled
std::map< int8_t, bool > m_layer_enabled
Definition: ros_msgpack_publisher.h:145
sick_scansegment_xd
Definition: include/sick_scansegment_xd/common.h:138
sick_scansegment_xd::CustomPointCloudConfiguration::m_update_method
int m_update_method
Definition: ros_msgpack_publisher.h:141
sick_scansegment_xd::PointCloudFieldProperty
Container for the field properties of a PointCloud2Msg with all fields (where each field contains x,...
Definition: ros_msgpack_publisher.h:90
sick_scansegment_xd::CompactImuData::angular_velocity_x
float angular_velocity_x
Definition: scansegment_parser_output.h:108
sick_scansegment_xd::ScanSegmentParserOutput::LidarPoint::groupIdx
int groupIdx
Definition: scansegment_parser_output.h:142
sick_scansegment_xd::CustomPointCloudConfiguration::m_cfg_name
std::string m_cfg_name
Definition: ros_msgpack_publisher.h:136
sick_scansegment_xd::PointXYZRAEI32f::i
float i
Definition: ros_msgpack_publisher.h:81
sick_scansegment_xd::util::parseVector
void parseVector(const std::string str, std::vector< std::string > &vec, char delim=' ')
Definition: config.h:204
sick_scansegment_xd::RosMsgpackPublisher::publishPointCloud2Msg
void publishPointCloud2Msg(rosNodePtr node, PointCloud2MsgPublisher &publisher, PointCloud2Msg &pointcloud_msg, int32_t num_echos, int32_t segment_idx, int coordinate_notation, const std::string &topic)
Definition: ros_msgpack_publisher.cpp:473
sick_scan_xd::notifyPolarPointcloudListener
void notifyPolarPointcloudListener(rosNodePtr handle, const sick_scan_xd::PointCloud2withEcho *msg)
Definition: sick_generic_callback.cpp:94
ros_msgpack_publisher.h
sick_scansegment_xd::CustomPointCloudConfiguration::fieldEnabled
bool fieldEnabled(const std::string &fieldname)
Definition: ros_msgpack_publisher.h:114
INT8
int8_t INT8
Definition: BasicDatatypes.hpp:76
sick_scan_xd::notifyImuListener
void notifyImuListener(rosNodePtr handle, const ros_sensor_msgs::Imu *msg)
Definition: sick_generic_callback.cpp:114
sick_scan_xd::PointCloud2withEcho
Definition: sick_generic_callback.h:76
sick_generic_callback.h
sick_scansegment_xd::CustomPointCloudConfiguration::fullframe
bool fullframe(void) const
Definition: ros_msgpack_publisher.h:111
sick_scansegment_xd::CompactImuData::to_string
std::string to_string() const
Definition: compact_parser.cpp:150
sick_scansegment_xd::CustomPointCloudConfiguration::m_range_filter
sick_scan_xd::SickRangeFilter m_range_filter
Definition: ros_msgpack_publisher.h:142
ros::Publisher::publish
void publish(const boost::shared_ptr< M > &message) const
sick_scansegment_xd::PointXYZRAEI32f
Definition: ros_msgpack_publisher.h:69
rosQoS
int rosQoS
Definition: sick_ros_wrapper.h:170
f
f
sick_scansegment_xd::CompactImuData::angular_velocity_y
float angular_velocity_y
Definition: scansegment_parser_output.h:109
sick_scansegment_xd::CustomPointCloudConfiguration::print
void print(void) const
Definition: ros_msgpack_publisher.cpp:154
sick_scansegment_xd::ScanSegmentParserOutput::segmentIndex
int segmentIndex
Definition: scansegment_parser_output.h:192
sick_scansegment_xd::CustomPointCloudConfiguration
Configuration of customized pointclouds.
Definition: ros_msgpack_publisher.h:102
sick_scansegment_xd::CompactImuData::acceleration_x
float acceleration_x
Definition: scansegment_parser_output.h:105
sick_scansegment_xd::ScanSegmentParserOutput::LidarPoint::y
float y
Definition: scansegment_parser_output.h:136
ROS::now
ROS::Time now(void)
Definition: ros_wrapper.cpp:116
compact_parser.h
sick_scansegment_xd::RosMsgpackPublisher::printElevationAzimuthTable
std::string printElevationAzimuthTable(const std::vector< std::vector< sick_scansegment_xd::PointXYZRAEI32f >> &lidar_points)
Definition: ros_msgpack_publisher.cpp:425
sensor_msgs::LaserScan
::sensor_msgs::LaserScan_< std::allocator< void > > LaserScan
Definition: LaserScan.h:94
sick_scansegment_xd::CustomPointCloudConfiguration::m_field_enabled
std::map< std::string, bool > m_field_enabled
Definition: ros_msgpack_publisher.h:143
ROS_WARN_STREAM
#define ROS_WARN_STREAM(args)
Definition: sick_scan_logging.h:123
sick_scansegment_xd::ScanSegmentParserOutput::LidarPoint::reflectorbit
uint8_t reflectorbit
Definition: scansegment_parser_output.h:146
ROS_INFO_STREAM
#define ROS_INFO_STREAM(...)
Definition: sick_scan_ros2_example.cpp:71
sick_scansegment_xd::ScanSegmentParserOutput
Definition: scansegment_parser_output.h:118
sick_scansegment_xd::RosMsgpackPublisher::LaserScanMsgMap
std::map< int, std::map< int, ros_sensor_msgs::LaserScan > > LaserScanMsgMap
Definition: ros_msgpack_publisher.h:222
polar_to_cartesian_pointcloud_ros1.node
node
Definition: polar_to_cartesian_pointcloud_ros1.py:81
sick_scansegment_xd::CustomPointCloudConfiguration::m_echo_enabled
std::map< int8_t, bool > m_echo_enabled
Definition: ros_msgpack_publisher.h:144
rosPublisher< PointCloud2Msg >
sick_scansegment_xd::PointXYZRAEI32f::echo
int echo
Definition: ros_msgpack_publisher.h:83
sick_scansegment_xd::ScanSegmentParserOutput::telegramCnt
int telegramCnt
Definition: scansegment_parser_output.h:193
sick_scansegment_xd::ScanSegmentParserOutput::LidarPoint::x
float x
Definition: scansegment_parser_output.h:135
sick_scansegment_xd::RosMsgpackPublisher::SegmentPointsCollector
Definition: ros_msgpack_publisher.h:227
sick_scansegment_xd::Fifo
Definition: fifo.h:75
sick_scansegment_xd::CustomPointCloudConfiguration::publish
bool publish(void) const
Definition: ros_msgpack_publisher.h:108
sick_scansegment_xd::CustomPointCloudConfiguration::CustomPointCloudConfiguration
CustomPointCloudConfiguration()
Definition: ros_msgpack_publisher.h:105
range_max
float range_max
Definition: sick_scan_test.cpp:529
sick_scansegment_xd::ScanSegmentParserOutput::LidarPoint
Definition: scansegment_parser_output.h:129
sick_scansegment_xd::RosMsgpackPublisher::initLFPlayerFilterSettings
virtual bool initLFPlayerFilterSettings(const std::string &host_LFPlayerFilter)
Definition: ros_msgpack_publisher.cpp:222
sick_scansegment_xd::RosMsgpackPublisher::convertPointsToLaserscanMsg
void convertPointsToLaserscanMsg(uint32_t timestamp_sec, uint32_t timestamp_nsec, const std::vector< std::vector< sick_scansegment_xd::PointXYZRAEI32f >> &lidar_points, size_t total_point_count, LaserScanMsgMap &laser_scan_msg_map, const std::string &frame_id, bool is_fullframe)
Definition: ros_msgpack_publisher.cpp:667
sick_scansegment_xd::RosMsgpackPublisher::initLFPangleRangeFilterSettings
virtual bool initLFPangleRangeFilterSettings(const std::string &host_LFPangleRangeFilter)
Definition: ros_msgpack_publisher.cpp:195
ROS_DEBUG_STREAM
#define ROS_DEBUG_STREAM(args)
Definition: sick_scan_logging.h:113
sick_scansegment_xd::PointXYZRAEI32f::lidar_timestamp_microsec
uint64_t lidar_timestamp_microsec
Definition: ros_msgpack_publisher.h:84
ros::NodeHandle
fifo_timestamp
std::chrono::time_point< std::chrono::system_clock > fifo_timestamp
Definition: fifo.h:68
sick_scansegment_xd::Config
Definition: config.h:84
sick_scansegment_xd::PointXYZRAEI32f::layer
int layer
Definition: ros_msgpack_publisher.h:82
sick_scansegment_xd::ScanSegmentParserOutput::LidarPoint::i
float i
Definition: scansegment_parser_output.h:138
rosGetParam
bool rosGetParam(rosNodePtr nh, const std::string &param_name, T &param_value)
Definition: sick_ros_wrapper.h:167
sick_scansegment_xd::CustomPointCloudConfiguration::pointEnabled
bool pointEnabled(sick_scansegment_xd::PointXYZRAEI32f &lidar_point)
Definition: ros_msgpack_publisher.h:118
sick_scansegment_xd::CustomPointCloudConfiguration::m_infringed_enabled
std::map< int8_t, bool > m_infringed_enabled
Definition: ros_msgpack_publisher.h:147
sick_scansegment_xd::CompactImuData::angular_velocity_z
float angular_velocity_z
Definition: scansegment_parser_output.h:110
range_min
float range_min
Definition: sick_scan_test.cpp:528
sick_scansegment_xd::PointXYZRAEI32f::x
float x
Definition: ros_msgpack_publisher.h:75
sick_scansegment_xd::ScanSegmentParserOutput::timestamp_sec
uint32_t timestamp_sec
Definition: scansegment_parser_output.h:186
sick_scansegment_xd::RosMsgpackPublisher::RosMsgpackPublisher
RosMsgpackPublisher(const std::string &node_name="sick_scansegment_xd", const sick_scansegment_xd::Config &config=sick_scansegment_xd::Config())
Definition: ros_msgpack_publisher.cpp:264
sick_scansegment_xd::RosMsgpackPublisher::convertPointsToCustomizedFieldsCloud
void convertPointsToCustomizedFieldsCloud(uint32_t timestamp_sec, uint32_t timestamp_nsec, uint64_t lidar_timestamp_start_microsec, const std::vector< std::vector< sick_scansegment_xd::PointXYZRAEI32f >> &lidar_points, CustomPointCloudConfiguration &pointcloud_cfg, PointCloud2Msg &pointcloud_msg)
Definition: ros_msgpack_publisher.cpp:558
sick_scansegment_xd::MsgPackExportListenerIF
Definition: msgpack_exporter.h:73
sick_scansegment_xd::CompactDataParser::GetElevationDegFromLayerIdx
static float GetElevationDegFromLayerIdx(int layer_idx)
Definition: compact_parser.cpp:483
CustomizedPointXYZRAEI32f::CustomizedPointXYZRAEI32f
CustomizedPointXYZRAEI32f()
Definition: ros_msgpack_publisher.cpp:528
sick_scansegment_xd::CompactImuData::orientation_x
float orientation_x
Definition: scansegment_parser_output.h:112
sick_scansegment_xd::RosMsgpackPublisher::publishLaserScanMsg
void publishLaserScanMsg(rosNodePtr node, LaserscanMsgPublisher &laserscan_publisher, LaserScanMsgMap &laser_scan_msg_map, int32_t num_echos, int32_t segment_idx)
Definition: ros_msgpack_publisher.cpp:498
sick_scansegment_xd::ScanSegmentParserOutput::scandata
std::vector< Scangroup > scandata
Definition: scansegment_parser_output.h:175
sick_scansegment_xd::PointXYZRAEI32f::azimuth
float azimuth
Definition: ros_msgpack_publisher.h:79
ROS_ERROR_STREAM
#define ROS_ERROR_STREAM(...)
Definition: sick_scan_ros2_example.cpp:72
UINT32
uint32_t UINT32
Definition: BasicDatatypes.hpp:72
sick_scansegment_xd::CustomPointCloudConfiguration::m_reflector_enabled
std::map< int8_t, bool > m_reflector_enabled
Definition: ros_msgpack_publisher.h:146
sick_scansegment_xd::CompactImuData::acceleration_y
float acceleration_y
Definition: scansegment_parser_output.h:106
CustomizedPointXYZRAEI32f
Definition: ros_msgpack_publisher.cpp:525
sick_scansegment_xd::CustomPointCloudConfiguration::m_topic
std::string m_topic
Definition: ros_msgpack_publisher.h:138
sick_scansegment_xd::CompactImuData::orientation_w
float orientation_w
Definition: scansegment_parser_output.h:111
sick_scan_xd::notifyCartesianPointcloudListener
void notifyCartesianPointcloudListener(rosNodePtr handle, const sick_scan_xd::PointCloud2withEcho *msg)
Definition: sick_generic_callback.cpp:74
sick_scan_xd::RangeFilterResultHandling
enum sick_scan_xd::RangeFilterResultHandlingEnum RangeFilterResultHandling
config
config
rosDeclareParam
void rosDeclareParam(rosNodePtr nh, const std::string &param_name, const T &param_value)
Definition: sick_ros_wrapper.h:166
sick_scansegment_xd::CompactImuData::orientation_y
float orientation_y
Definition: scansegment_parser_output.h:113
multiscan_perftest_player.num_echos
int num_echos
Definition: multiscan_perftest_player.py:22
sick_scansegment_xd::CustomPointCloudConfiguration::m_frameid
std::string m_frameid
Definition: ros_msgpack_publisher.h:139
sick_scansegment_xd::CompactImuData::valid
bool valid
Definition: scansegment_parser_output.h:104
sick_scansegment_xd::CustomPointCloudConfiguration::coordinateNotation
int coordinateNotation(void) const
Definition: ros_msgpack_publisher.h:112
sick_scansegment_xd::CustomPointCloudConfiguration::cfgName
const std::string & cfgName(void) const
Definition: ros_msgpack_publisher.h:107
sick_scansegment_xd::CustomPointCloudConfiguration::m_publish
bool m_publish
Definition: ros_msgpack_publisher.h:137
sick_scan_xd::SickRangeFilter
Definition: sick_range_filter.h:85


sick_scan_xd
Author(s): Michael Lehning , Jochen Sprickerhof , Martin Günther
autogenerated on Fri Oct 25 2024 02:47:10