polygon_filter.cpp
Go to the documentation of this file.
1 /*
2  * Software License Agreement (BSD License)
3  *
4  * Robot Operating System code by Eurotec B.V.
5  * Copyright (c) 2020, Eurotec B.V.
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above
13  * copyright notice, this list of conditions and the following
14  * disclaimer.
15  *
16  * 2. Redistributions in binary form must reproduce the above
17  * copyright notice, this list of conditions and the following
18  * disclaimer in the documentation and/or other materials provided
19  * with the distribution.
20  *
21  * 3. Neither the name of the copyright holder nor the names of its
22  * contributors may be used to endorse or promote products derived
23  * from this software without specific prior written permission.
24  *
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
30  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
31  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
32  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
33  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
34  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
35  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
36  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37  *
38  *
39  *
40  * polygon_filter.cpp
41  */
42 
44 #include <ros/ros.h>
45 #include <dynamic_reconfigure/server.h>
46 #include <laser_filters/PolygonFilterConfig.h>
47 #include <geometry_msgs/PolygonStamped.h>
48 #include <cstdio> // for EOF
49 #include <string>
50 #include <sstream>
51 #include <vector>
52 
54 inline double sign0(double x)
55 {
56  return x < 0.0 ? -1.0 : (x > 0.0 ? 1.0 : 0.0);
57 }
58 
59 void padPolygon(geometry_msgs::Polygon& polygon, double padding)
60 {
61  // pad polygon in place
62  for (unsigned int i = 0; i < polygon.points.size(); i++)
63  {
64  geometry_msgs::Point32& pt = polygon.points[ i ];
65  pt.x += sign0(pt.x) * padding;
66  pt.y += sign0(pt.y) * padding;
67  }
68 }
69 
70 double getNumberFromXMLRPC(XmlRpc::XmlRpcValue& value, const std::string& full_param_name)
71 {
72  // Make sure that the value we're looking at is either a double or an int.
74  {
75  std::string& value_string = value;
76  ROS_FATAL("Values in the polygon specification (param %s) must be numbers. Found value %s.",
77  full_param_name.c_str(), value_string.c_str());
78  throw std::runtime_error("Values in the polygon specification must be numbers");
79  }
80  return value.getType() == XmlRpc::XmlRpcValue::TypeInt ? (int)(value) : (double)(value);
81 }
82 
83 geometry_msgs::Polygon makePolygonFromXMLRPC(const XmlRpc::XmlRpcValue& polygon_xmlrpc,
84  const std::string& full_param_name)
85 {
86  // Make sure we have an array of at least 3 elements.
87  if (polygon_xmlrpc.getType() != XmlRpc::XmlRpcValue::TypeArray ||
88  polygon_xmlrpc.size() > 0 && polygon_xmlrpc.size() < 3)
89  {
90  ROS_FATAL("The polygon (parameter %s) must be specified as nested list on the parameter server with at least "
91  "3 points eg: [[x1, y1], [x2, y2], ..., [xn, yn]]",
92  full_param_name.c_str());
93 
94  throw std::runtime_error("The polygon must be specified as nested list on the parameter server with at least "
95  "3 points eg: [[x1, y1], [x2, y2], ..., [xn, yn]]");
96  }
97  geometry_msgs::Polygon polygon;
98  geometry_msgs::Point32 pt;
99 
100  for (int i = 0; i < polygon_xmlrpc.size(); ++i)
101  {
102  // Make sure each element of the list is an array of size 2. (x and y coordinates)
103  XmlRpc::XmlRpcValue point = polygon_xmlrpc[i];
104  if (point.getType() != XmlRpc::XmlRpcValue::TypeArray || point.size() != 2)
105  {
106  ROS_FATAL("The polygon (parameter %s) must be specified as list of lists on the parameter server eg: "
107  "[[x1, y1], [x2, y2], ..., [xn, yn]], but this spec is not of that form.",
108  full_param_name.c_str());
109  throw std::runtime_error("The polygon must be specified as list of lists on the parameter server eg: "
110  "[[x1, y1], [x2, y2], ..., [xn, yn]], but this spec is not of that form");
111  }
112 
113  pt.x = getNumberFromXMLRPC(point[0], full_param_name);
114  pt.y = getNumberFromXMLRPC(point[1], full_param_name);
115 
116  polygon.points.push_back(pt);
117  }
118  return polygon;
119 }
120 
121 std::vector<std::vector<float> > parseVVF(const std::string& input, std::string& error_return)
122 { // Source: https://github.com/ros-planning/navigation/blob/melodic-devel/costmap_2d/src/array_parser.cpp
123  std::vector<std::vector<float> > result;
124 
125  std::stringstream input_ss(input);
126  int depth = 0;
127  std::vector<float> current_vector;
128  while (!!input_ss && !input_ss.eof())
129  {
130  switch (input_ss.peek())
131  {
132  case EOF:
133  break;
134  case '[':
135  depth++;
136  if (depth > 2)
137  {
138  error_return = "Array depth greater than 2";
139  return result;
140  }
141  input_ss.get();
142  current_vector.clear();
143  break;
144  case ']':
145  depth--;
146  if (depth < 0)
147  {
148  error_return = "More close ] than open [";
149  return result;
150  }
151  input_ss.get();
152  if (depth == 1)
153  {
154  result.push_back(current_vector);
155  }
156  break;
157  case ',':
158  case ' ':
159  case '\t':
160  input_ss.get();
161  break;
162  default: // All other characters should be part of the numbers.
163  if (depth != 2)
164  {
165  std::stringstream err_ss;
166  err_ss << "Numbers at depth other than 2. Char was '" << char(input_ss.peek()) << "'.";
167  error_return = err_ss.str();
168  return result;
169  }
170  float value;
171  input_ss >> value;
172  if (!!input_ss)
173  {
174  current_vector.push_back(value);
175  }
176  break;
177  }
178  }
179 
180  if (depth != 0)
181  {
182  error_return = "Unterminated vector string.";
183  }
184  else
185  {
186  error_return = "";
187  }
188 
189  return result;
190 }
191 
192 geometry_msgs::Polygon makePolygonFromString(const std::string& polygon_string, const geometry_msgs::Polygon& last_polygon)
193 {
194  std::string error;
195  std::vector<std::vector<float> > vvf = parseVVF(polygon_string, error);
196 
197  if (error != "")
198  {
199  ROS_ERROR("Error parsing polygon parameter: '%s'", error.c_str());
200  ROS_ERROR(" Polygon string was '%s'.", polygon_string.c_str());
201  return last_polygon;
202  }
203 
204  geometry_msgs::Polygon polygon;
205  geometry_msgs::Point32 point;
206 
207  // convert vvf into points.
208  if (vvf.size() < 3 && vvf.size() > 0)
209  {
210  ROS_WARN("You must specify at least three points for the robot polygon");
211  return last_polygon;
212  }
213 
214  for (unsigned int i = 0; i < vvf.size(); i++)
215  {
216  if (vvf[ i ].size() == 2)
217  {
218  point.x = vvf[ i ][ 0 ];
219  point.y = vvf[ i ][ 1 ];
220  point.z = 0;
221  polygon.points.push_back(point);
222  }
223  else
224  {
225  ROS_ERROR("Points in the polygon specification must be pairs of numbers. Found a point with %d numbers.",
226  int(vvf[ i ].size()));
227  return last_polygon;
228  }
229  }
230 
231  return polygon;
232 }
233 
234 std::string polygonToString(geometry_msgs::Polygon polygon)
235 {
236  std::string polygon_string = "[";
237  bool first = true;
238  for (auto point : polygon.points) {
239  if (!first) {
240  polygon_string += ", ";
241  }
242  first = false;
243  polygon_string += "[" + std::to_string(point.x) + ", " + std::to_string(point.y) + "]";
244  }
245  polygon_string += "]";
246  return polygon_string;
247 }
248 
249 namespace laser_filters{
251 {
252 }
253 
255 {
256  XmlRpc::XmlRpcValue polygon_xmlrpc;
257  std::string polygon_string;
258  PolygonFilterConfig param_config;
259 
260  ros::NodeHandle private_nh("~" + getName());
261  dyn_server_.reset(new dynamic_reconfigure::Server<laser_filters::PolygonFilterConfig>(own_mutex_, private_nh));
262  dynamic_reconfigure::Server<laser_filters::PolygonFilterConfig>::CallbackType f;
263  f = boost::bind(&laser_filters::LaserScanPolygonFilter::reconfigureCB, this, _1, _2);
264  dyn_server_->setCallback(f);
265 
266  bool polygon_set = getParam("polygon", polygon_xmlrpc);
267  bool polygon_frame_set = getParam("polygon_frame", polygon_frame_);
268  bool invert_set = getParam("invert", invert_filter_);
269  polygon_ = makePolygonFromXMLRPC(polygon_xmlrpc, "polygon");
270 
271  double polygon_padding = 0;
272  getParam("polygon_padding", polygon_padding);
273 
274  polygon_string = polygonToString(polygon_);
275  param_config.polygon = polygon_string;
276  param_config.polygon_padding = polygon_padding;
277  param_config.invert = invert_filter_;
278  dyn_server_->updateConfig(param_config);
279 
280  polygon_pub_ = private_nh.advertise<geometry_msgs::PolygonStamped>("polygon", 1);
281 
282  if (!polygon_frame_set)
283  {
284  ROS_ERROR("polygon_frame is not set!");
285  }
286  if (!polygon_set)
287  {
288  ROS_ERROR("polygon is not set!");
289  }
290  if (!invert_set)
291  {
292  ROS_INFO("invert filter not set, assuming false");
293  invert_filter_ = false;
294  }
295 
296  return polygon_frame_set && polygon_set;
297 }
298 
299 bool LaserScanPolygonFilter::update(const sensor_msgs::LaserScan& input_scan,
300  sensor_msgs::LaserScan& output_scan)
301 {
302  boost::recursive_mutex::scoped_lock lock(own_mutex_);
303 
304  geometry_msgs::PolygonStamped polygon_stamped;
305  polygon_stamped.header.frame_id = polygon_frame_;
306  polygon_stamped.header.stamp = ros::Time::now();
307  polygon_stamped.polygon = polygon_;
308  polygon_pub_.publish(polygon_stamped);
309 
310  output_scan = input_scan;
311 
312  sensor_msgs::PointCloud2 laser_cloud;
313 
314  std::string error_msg;
315 
316  bool success = tf_.waitForTransform(
317  polygon_frame_, input_scan.header.frame_id,
318  input_scan.header.stamp + ros::Duration().fromSec(input_scan.ranges.size() * input_scan.time_increment),
319  ros::Duration(1.0), ros::Duration(0.01), &error_msg);
320  if (!success)
321  {
322  ROS_WARN("Could not get transform, ignoring laser scan! %s", error_msg.c_str());
323  return false;
324  }
325 
326  try
327  {
328  projector_.transformLaserScanToPointCloud(polygon_frame_, input_scan, laser_cloud, tf_);
329  }
330  catch (tf::TransformException& ex)
331  {
332  ROS_INFO_THROTTLE(.3, "Ignoring Scan: Waiting for TF");
333  return false;
334  }
335  const int i_idx_c = sensor_msgs::getPointCloud2FieldIndex(laser_cloud, "index");
336  const int x_idx_c = sensor_msgs::getPointCloud2FieldIndex(laser_cloud, "x");
337  const int y_idx_c = sensor_msgs::getPointCloud2FieldIndex(laser_cloud, "y");
338  const int z_idx_c = sensor_msgs::getPointCloud2FieldIndex(laser_cloud, "z");
339 
340  if (i_idx_c == -1 || x_idx_c == -1 || y_idx_c == -1 || z_idx_c == -1)
341  {
342  ROS_INFO_THROTTLE(.3, "x, y, z and index fields are required, skipping scan");
343  }
344 
345  const int i_idx_offset = laser_cloud.fields[i_idx_c].offset;
346  const int x_idx_offset = laser_cloud.fields[x_idx_c].offset;
347  const int y_idx_offset = laser_cloud.fields[y_idx_c].offset;
348  const int z_idx_offset = laser_cloud.fields[z_idx_c].offset;
349 
350  const int pstep = laser_cloud.point_step;
351  const long int pcount = laser_cloud.width * laser_cloud.height;
352  const long int limit = pstep * pcount;
353 
354  int i_idx, x_idx, y_idx, z_idx;
355  for (i_idx = i_idx_offset, x_idx = x_idx_offset, y_idx = y_idx_offset, z_idx = z_idx_offset;
356 
357  x_idx < limit;
358 
359  i_idx += pstep, x_idx += pstep, y_idx += pstep, z_idx += pstep)
360  {
361  // TODO works only for float data types and with an index field
362  // I'm working on it, see https://github.com/ros/common_msgs/pull/78
363  float x = *((float*)(&laser_cloud.data[x_idx]));
364  float y = *((float*)(&laser_cloud.data[y_idx]));
365  float z = *((float*)(&laser_cloud.data[z_idx]));
366  int index = *((int*)(&laser_cloud.data[i_idx]));
367 
368  tf::Point point(x, y, z);
369 
370  if (!invert_filter_)
371  {
372  if (inPolygon(point))
373  {
374  output_scan.ranges[index] = std::numeric_limits<float>::quiet_NaN();
375  }
376  }
377  else
378  {
379  if (!inPolygon(point))
380  {
381  output_scan.ranges[index] = std::numeric_limits<float>::quiet_NaN();
382  }
383  }
384  }
385 
386  return true;
387 }
388 
389 
391 {
392  int i, j;
393  bool c = false;
394 
395  for (i = 0, j = polygon_.points.size() - 1; i < polygon_.points.size(); j = i++)
396  {
397  if ((polygon_.points.at(i).y > point.y() != (polygon_.points.at(j).y > point.y()) &&
398  (point.x() < (polygon_.points[j].x - polygon_.points[i].x) * (point.y() - polygon_.points[i].y) /
399  (polygon_.points[j].y - polygon_.points[i].y) +
400  polygon_.points[i].x)))
401  c = !c;
402  }
403  return c;
404 }
405 
406 
407 void LaserScanPolygonFilter::reconfigureCB(laser_filters::PolygonFilterConfig& config, uint32_t level)
408 {
409  invert_filter_ = config.invert;
410  polygon_ = makePolygonFromString(config.polygon, polygon_);
411  padPolygon(polygon_, config.polygon_padding);
412 }
413 }
static int getPointCloud2FieldIndex(const sensor_msgs::PointCloud2 &cloud, const std::string &field_name)
#define ROS_FATAL(...)
laser_geometry::LaserProjection projector_
f
std::string polygonToString(geometry_msgs::Polygon polygon)
LaserScanMaskFilter removes points on directions defined in a mask from a laser scan.
std::shared_ptr< dynamic_reconfigure::Server< laser_filters::PolygonFilterConfig > > dyn_server_
bool waitForTransform(const std::string &target_frame, const std::string &source_frame, const ros::Time &time, const ros::Duration &timeout, const ros::Duration &polling_sleep_duration=ros::Duration(0.01), std::string *error_msg=NULL) const
double getNumberFromXMLRPC(XmlRpc::XmlRpcValue &value, const std::string &full_param_name)
void padPolygon(geometry_msgs::Polygon &polygon, double padding)
tf::Vector3 Point
#define ROS_WARN(...)
geometry_msgs::Polygon makePolygonFromXMLRPC(const XmlRpc::XmlRpcValue &polygon_xmlrpc, const std::string &full_param_name)
geometry_msgs::Polygon makePolygonFromString(const std::string &polygon_string, const geometry_msgs::Polygon &last_polygon)
Type const & getType() const
void publish(const boost::shared_ptr< M > &message) const
#define ROS_INFO_THROTTLE(period,...)
bool inPolygon(tf::Point &point) const
Duration & fromSec(double t)
#define ROS_INFO(...)
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
void reconfigureCB(laser_filters::PolygonFilterConfig &config, uint32_t level)
std::vector< std::vector< float > > parseVVF(const std::string &input, std::string &error_return)
bool getParam(const std::string &name, std::string &value) const
bool update(const sensor_msgs::LaserScan &input_scan, sensor_msgs::LaserScan &filtered_scan)
double sign0(double x)
Same as sign(x) but returns 0 if x is 0.
static Time now()
void transformLaserScanToPointCloud(const std::string &target_frame, const sensor_msgs::LaserScan &scan_in, sensor_msgs::PointCloud &cloud_out, tf::Transformer &tf, double range_cutoff, int channel_options=channel_option::Default)
#define ROS_ERROR(...)


laser_filters
Author(s): Tully Foote
autogenerated on Mon Feb 28 2022 22:39:24