perception.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2015 Fetch Robotics Inc.
3  * Author: Michael Ferguson, Sriramvarun Nidamarthy
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <icp_2d.h>
20 #include <perception.h>
21 
22 #include <angles/angles.h>
23 #include <tf/tf.h>
24 #include <sensor_msgs/PointCloud2.h>
26 
27 #include <list>
28 #include <queue>
29 #include <string>
30 #include <vector>
31 
32 double getPoseDistance(const geometry_msgs::Pose a,
33  const geometry_msgs::Pose b)
34 {
35  double dx = a.position.x - b.position.x;
36  double dy = a.position.y - b.position.y;
37  return sqrt(dx*dx + dy*dy);
38 }
39 
41  running_(false),
42  tracking_frame_("odom"),
43  found_dock_(false)
44 {
45  ros::NodeHandle pnh("~");
46 
47  // Should we publish the debugging cloud
48  if (!pnh.getParam("debug", debug_))
49  {
50  debug_ = false;
51  }
52 
53  // Create coefficient vectors for a second order butterworth filter
54  // with a cutoff frequency of 10 Hz assuming the loop is updated at 50 Hz
55  // [b, a] = butter(order, cutoff_frequ/half_sampling_freq)
56  // [b, a] = butter(2, 10/25)
57  float b_arr[] = {0.20657, 0.41314, 0.20657};
58  float a_arr[] = {1.00000, -0.36953, 0.19582};
59  std::vector<float> b(b_arr, b_arr + sizeof(b_arr)/sizeof(float));
60  std::vector<float> a(a_arr, a_arr + sizeof(a_arr)/sizeof(float));
61  dock_pose_filter_.reset(new LinearPoseFilter2D(b, a));
62 
63  // Limit the average reprojection error of points onto
64  // the ideal dock. This prevents the robot docking
65  // with something that is very un-dock-like.
66  if (!pnh.getParam("max_alignment_error", max_alignment_error_))
67  {
68  max_alignment_error_ = 0.01;
69  }
70 
71  // Create ideal cloud
72  // Front face is 402mm long
73  for (double y = -0.205; y <= 0.205; y += 0.002)
74  {
75  geometry_msgs::Point p;
76  p.x = p.z = 0.0;
77  p.y = y;
78  ideal_cloud_.push_back(p);
79  front_cloud_.push_back(p);
80  }
81  // Each side is 175mm long, at 120 degree angle
82  for (double y = 0.0; y < 0.08759 /*0.08759*/; y += 0.002)
83  {
84  geometry_msgs::Point p;
85  p.x = tan(1.0471976)*y;
86  p.y = 0.205 + y;
87  p.z = 0.0;
88  ideal_cloud_.push_back(p);
89  p.y = -0.205 - y;
90  ideal_cloud_.insert(ideal_cloud_.begin(), p);
91  }
92 
93  // Debugging publishers first
94  if (debug_)
95  {
96  debug_points_ = nh.advertise<sensor_msgs::PointCloud2>("dock_points", 10);
97  }
98 
99  // Init base scan only after publishers are created
100  scan_sub_ = nh.subscribe("base_scan", 1, &DockPerception::callback, this);
101 
102  ROS_INFO_NAMED("perception","Dock perception initialized");
103 }
104 
105 bool DockPerception::start(const geometry_msgs::PoseStamped& pose)
106 {
107  running_ = false;
108  found_dock_ = false;
109  dock_ = pose;
110  running_ = true;
111  return true;
112 }
113 
115 {
116  running_ = false;
117  return true;
118 }
119 
120 bool DockPerception::getPose(geometry_msgs::PoseStamped& pose, std::string frame)
121 {
122  // All of this requires a lock on the dock_
123  boost::mutex::scoped_lock lock(dock_mutex_);
124 
125  if (!found_dock_)
126  return false;
127 
128  if (ros::Time::now() > dock_stamp_ + ros::Duration(0.35))
129  {
130  ROS_DEBUG_NAMED("dock_perception", "Dock pose timed out");
131  return false;
132  }
133 
134  // Check for a valid orientation.
135  tf::Quaternion q;
136  tf::quaternionMsgToTF(dock_.pose.orientation, q);
137  if (!isValid(q))
138  {
139  ROS_ERROR_STREAM_NAMED("perception",
140  "Dock orientation invalid.");
141  ROS_DEBUG_STREAM_NAMED("perception",
142  "Quaternion magnitude is "
143  << q.length()
144  << " Quaternion is ["
145  << q.x() << ", " << q.y() << ", "
146  << q.z() << ", " << q.w() << "]"
147  );
148  return false;
149  }
150 
151  pose = dock_;
152 
153  if (frame != "")
154  {
155  // Transform to desired frame
156  try
157  {
159  pose.header.frame_id,
160  pose.header.stamp,
161  ros::Duration(0.1));
162  listener_.transformPose(frame, pose, pose);
163  }
164  catch (tf::TransformException const &ex)
165  {
166  ROS_WARN_STREAM_THROTTLE(1.0, "Couldn't transform dock pose");
167  return false;
168  }
169  }
170 
171  return found_dock_;
172 }
173 
174 void DockPerception::callback(const sensor_msgs::LaserScanConstPtr& scan)
175 {
176  // Be lazy about search
177  if (!running_)
178  {
179  return;
180  }
181 
182  // Make sure goal is valid (orientation != 0 0 0 0)
183  if (dock_.header.frame_id == "" ||
184  (dock_.pose.orientation.z == 0.0 && dock_.pose.orientation.w == 0.0))
185  {
186  // Lock the dock_
187  boost::mutex::scoped_lock lock(dock_mutex_);
188 
189  // If goal is invalid, set to a point directly ahead of robot
190  for (size_t i = scan->ranges.size()/2; i < scan->ranges.size(); i++)
191  {
192  if (std::isfinite(scan->ranges[i]))
193  {
194  double angle = scan->angle_min + i * scan->angle_increment;
195  dock_.header = scan->header;
196  dock_.pose.position.x = cos(angle) * scan->ranges[i];
197  dock_.pose.position.y = sin(angle) * scan->ranges[i];
198  dock_.pose.orientation.x = 1.0;
199  dock_.pose.orientation.y = 0.0;
200  dock_.pose.orientation.z = 0.0;
201  dock_.pose.orientation.w = 0.0;
202  ROS_DEBUG_NAMED("dock_perception", "Set initial pose to (%f, %f, %f)",
203  dock_.pose.position.x, dock_.pose.position.y,
204  icp_2d::thetaFromQuaternion(dock_.pose.orientation));
205  break;
206  }
207  }
208  }
209 
210  // Make sure goal is in the tracking frame
211  if (dock_.header.frame_id != tracking_frame_)
212  {
213  boost::mutex::scoped_lock lock(dock_mutex_);
214  try
215  {
217  dock_.header.frame_id,
218  dock_.header.stamp,
219  ros::Duration(0.1));
221  }
222  catch (tf::TransformException const &ex)
223  {
224  ROS_WARN_STREAM_THROTTLE(1.0, "Couldn't transform dock pose to tracking frame");
225  return;
226  }
227  ROS_DEBUG_NAMED("dock_perception", "Transformed initial pose to (%f, %f, %f)",
228  dock_.pose.position.x, dock_.pose.position.y,
229  icp_2d::thetaFromQuaternion(dock_.pose.orientation));
230  }
231 
232  // Cluster the laser scan
234  laser_processor::ScanProcessor processor(*scan, mask);
235  processor.splitConnected(0.01); // TODO(enhancement) parameterize
236  processor.removeLessThan(5);
237 
238  ROS_DEBUG_STREAM_NAMED("dock_perception", "Found " << processor.getClusters().size() << " clusters");
239 
240  // Sort clusters based on distance to last dock
241  std::priority_queue<DockCandidatePtr, std::vector<DockCandidatePtr>, CompareCandidates> candidates;
242  for (std::list<laser_processor::SampleSet*>::iterator i = processor.getClusters().begin();
243  i != processor.getClusters().end();
244  i++)
245  {
246  DockCandidatePtr c = extract(*i);
247  if (c )// && c->valid(found_dock_))
248  {
249  candidates.push(c);
250  }
251  }
252  ROS_DEBUG_STREAM_NAMED("dock_perception", "Extracted " << candidates.size() << " clusters");
253 
254  // Extract ICP pose/fit on best clusters
255  DockCandidatePtr best;
256  geometry_msgs::Pose best_pose;
257  while (!candidates.empty())
258  {
259  geometry_msgs::Pose pose = dock_.pose;
260  double score = fit(candidates.top(), pose);
261  if (score >= 0)
262  {
263  best = candidates.top();
264  best_pose = pose;
265  break;
266  }
267  else // Let's see what's wrong with this point cloud.
268  {
269  if (debug_)
270  {
271  DockCandidatePtr not_best = candidates.top();
272 
273  // Create point cloud
274  sensor_msgs::PointCloud2 cloud;
275  cloud.header.stamp = scan->header.stamp;
276  cloud.header.frame_id = tracking_frame_;
277  cloud.width = cloud.height = 0;
278 
279  // Allocate space for points
280  sensor_msgs::PointCloud2Modifier cloud_mod(cloud);
281  cloud_mod.setPointCloud2FieldsByString(1, "xyz");
282  cloud_mod.resize(not_best->points.size());
283 
284  // Fill in points
285  sensor_msgs::PointCloud2Iterator<float> cloud_iter(cloud, "x");
286  for (size_t i = 0; i < not_best->points.size(); i++)
287  {
288  cloud_iter[0] = not_best->points[i].x;
289  cloud_iter[1] = not_best->points[i].y;
290  cloud_iter[2] = not_best->points[i].z;
291  ++cloud_iter;
292  }
293  debug_points_.publish(cloud);
294  ROS_INFO("publish points");
295  }
296  }
297  candidates.pop();
298  }
299 
300  // Did we find dock?
301  if (!best)
302  {
303  ROS_DEBUG_NAMED("dock_perception", "DID NOT FIND THE DOCK");
304  return;
305  }
306 
307  ROS_DEBUG_NAMED("dock_perception", "Found the dock.");
308 
309  // Update
310  if (debug_)
311  {
312  // Create point cloud
313  sensor_msgs::PointCloud2 cloud;
314  cloud.header.stamp = scan->header.stamp;
315  cloud.header.frame_id = tracking_frame_;
316  cloud.width = cloud.height = 0;
317 
318  // Allocate space for points
319  sensor_msgs::PointCloud2Modifier cloud_mod(cloud);
320  cloud_mod.setPointCloud2FieldsByString(1, "xyz");
321  cloud_mod.resize(best->points.size());
322 
323  // Fill in points
324  sensor_msgs::PointCloud2Iterator<float> cloud_iter(cloud, "x");
325  for (size_t i = 0; i < best->points.size(); i++)
326  {
327  cloud_iter[0] = best->points[i].x;
328  cloud_iter[1] = best->points[i].y;
329  cloud_iter[2] = best->points[i].z;
330  ++cloud_iter;
331  }
332  debug_points_.publish(cloud);
333  ROS_INFO("publish points");
334  }
335 
336  // Everything after this modifies the dock_
337  boost::mutex::scoped_lock lock(dock_mutex_);
338 
339  // Update stamp
340  dock_.header.stamp = scan->header.stamp;
341  dock_.header.frame_id = tracking_frame_;
342 
343  // If this is the first time we've found dock, take whole pose
344  if (!found_dock_)
345  {
346  dock_.pose = best_pose;
347  // Reset the dock pose filter.
348  dock_pose_filter_->reset();
349  // Set the filter state to the current pose estimate.
350  dock_pose_filter_->setFilterState(dock_.pose, dock_.pose);
351  }
352  else
353  {
354  // Check that pose is not too far from current pose
355  double d = getPoseDistance(dock_.pose, best_pose);
356  if (d > 0.05)
357  {
358  ROS_DEBUG_STREAM_NAMED("dock_perception", "Dock pose jumped: " << d);
359  return;
360  }
361  }
362 
363  // Filter the pose esitmate.
364  // dock_.pose = dock_pose_filter_->filter(best_pose);
365  dock_stamp_ = scan->header.stamp;
366  found_dock_ = true;
367 }
368 
370 {
371  DockCandidatePtr candidate(new DockCandidate());
372 
373  tf::Point tf_point;
374  tf::StampedTransform t_frame;
375  try
376  {
378  cluster->header.frame_id,
379  cluster->header.stamp,
380  ros::Duration(0.1));
382  cluster->header.frame_id,
383  ros::Time(0),
384  t_frame);
385  }
386  catch (tf::TransformException const &ex)
387  {
388  ROS_WARN_STREAM_THROTTLE(1.0, "Couldn't transform laser point");
389  return candidate;
390  }
391  // Transform each point into tracking frame
392  size_t i = 0;
393  for (laser_processor::SampleSet::iterator p = cluster->begin();
394  p != cluster->end();
395  p++, i++)
396  {
397  geometry_msgs::PointStamped pt;
398  pt.header = cluster->header;
399  pt.point.x = (*p)->x;
400  pt.point.y = (*p)->y;
401  pt.point.z = 0;
402  tf::pointMsgToTF(pt.point, tf_point);
403  tf_point = t_frame*tf_point;
404  tf::pointTFToMsg(tf_point, pt.point);
405  candidate->points.push_back(pt.point);
406  }
407 
408  // Get distance from cloud center to previous pose
409  geometry_msgs::Point centroid = icp_2d::getCentroid(candidate->points);
410  double dx = centroid.x - dock_.pose.position.x;
411  double dy = centroid.y - dock_.pose.position.y;
412  candidate->dist = (dx*dx + dy*dy);
413 
414  return candidate;
415 }
416 
417 double DockPerception::fit(const DockCandidatePtr& candidate, geometry_msgs::Pose& pose)
418 {
419  // Setup initial pose
420  geometry_msgs::Transform transform;
421  transform.translation.x = pose.position.x;
422  transform.translation.y = pose.position.y;
423  transform.rotation = pose.orientation;
424 
425  // Initial yaw. Presumably the initial goal orientation estimate.
426  tf::Quaternion init_pose, cand_pose;
427  quaternionMsgToTF(pose.orientation, init_pose);
428  if (!isValid(init_pose))
429  {
430  ROS_ERROR_STREAM_NAMED("perception",
431  "Initial dock orientation estimate is invalid.");
432  ROS_DEBUG_STREAM_NAMED("perception",
433  "Quaternion magnitude is "
434  << init_pose.length()
435  << " Quaternion is ["
436  << init_pose.x() << ", " << init_pose.y() << ", "
437  << init_pose.z() << ", " << init_pose.w() << "]"
438  );
439  return -1.0;
440  }
441 
442  // ICP the dock
443  double fitness = icp_2d::alignICP(ideal_cloud_, candidate->points, transform);
444  quaternionMsgToTF(transform.rotation, cand_pose);
445  if (!isValid(cand_pose))
446  {
447  ROS_WARN_STREAM_NAMED("perception",
448  "Dock candidate orientation estimate is invalid.");
449  ROS_DEBUG_STREAM_NAMED("perception",
450  "Quaternion magnitude is "
451  << cand_pose.length()
452  << " Quaternion is ["
453  << cand_pose.x() << ", " << cand_pose.y() << ", "
454  << cand_pose.z() << ", " << cand_pose.w() << "]"
455  );
456  }
457 
458  // If the dock orientation seems flipped, flip it.
459  // Check it by finding the relative roation between the two quaternions.
460  if (fabs(angles::normalize_angle(tf::getYaw(tf::inverse(cand_pose)*init_pose))) > 3.1415*(2.0/3.0) )
461  {
462  transform.rotation = tf::createQuaternionMsgFromYaw(3.1415 + tf::getYaw(transform.rotation));
463  }
464 
465  if (fitness >= 0.0)
466  {
467  // Initialize the number of times we retry if the fitness is bad.
468  double retry = 5;
469  // If the fitness is hosed or the angle is borked, try again.
470  quaternionMsgToTF(transform.rotation, cand_pose);
471  while (retry-- &&
472  (
473  fitness > max_alignment_error_ ||
474  fabs(angles::normalize_angle(tf::getYaw(tf::inverse(cand_pose)*init_pose))) > 3.1415/4.0
475  )
476  )
477  {
478  // Try one more time.
479 
480  // Perturb the pose to try to get it out of the local minima.
481  transform.translation.x += retry*(0.75/100.0)*static_cast<double>((rand() % 200) - 100);
482  transform.translation.y += retry*(0.75/100.0)*static_cast<double>((rand() % 200) - 100);
483  transform.rotation = tf::createQuaternionMsgFromYaw(retry*(0.28/100.0)*double((rand() % 200) - 100) + tf::getYaw(transform.rotation));
484 
485  // Rerun ICP.
486  fitness = icp_2d::alignICP(ideal_cloud_, candidate->points, transform);
487 
488  // If the dock orientation seems flipped, flip it.
489  quaternionMsgToTF(transform.rotation, cand_pose);
490  if (fabs(angles::normalize_angle(tf::getYaw(tf::inverse(cand_pose)*init_pose))) > 3.1415*(2.0/3.0) )
491  {
492  transform.rotation = tf::createQuaternionMsgFromYaw(3.1415 + tf::getYaw(transform.rotation));
493  }
494  }
495 
496  // If the dock orientation is still really borked, fail.
497  quaternionMsgToTF(transform.rotation, cand_pose);
498  if (!isValid(cand_pose))
499  {
500  ROS_ERROR_STREAM_NAMED("perception",
501  "Dock candidate orientation estimate is invalid.");
502  ROS_DEBUG_STREAM_NAMED("perception","Quaternion magnitude is "
503  << cand_pose.length()
504  << " Orientation is ["
505  << cand_pose.x() << ", " << cand_pose.y() << ", "
506  << cand_pose.z() << ", " << cand_pose.w() << "]"
507  );
508  return -1.0;
509  }
510  if (fabs(angles::normalize_angle(tf::getYaw(tf::inverse(cand_pose)*init_pose))) > 3.1415/2.0 )
511  {
512  fitness = -1.0;
513  }
514 
515  // Check that fitness is good enough
516  if (!found_dock_ && fabs(fitness) > max_alignment_error_)
517  {
518  // If not, signal no fit
519  fitness = -1.0;
520  }
521 
522  // If width of candidate is smaller than the width of dock
523  // then the whole dock is not visible...
524  if (candidate->width() < 0.375)
525  {
526  // ... and heading is unreliable when close to dock
527  ROS_DEBUG_STREAM_NAMED("perception", "Dock candidate width is unreliable.");
528  transform.rotation = pose.orientation;
529  fitness = 0.001234;
530  // Probably can use a different algorithm here, if necessary, which it might not be.
531  }
532 
533  // Transform ideal cloud, and store for visualization
534  candidate->points = icp_2d::transform(ideal_cloud_,
535  transform.translation.x,
536  transform.translation.y,
537  icp_2d::thetaFromQuaternion(transform.rotation));
538 
539  // Get pose
540  pose.position.x = transform.translation.x;
541  pose.position.y = transform.translation.y;
542  pose.position.z = transform.translation.z;
543  pose.orientation = transform.rotation;
544  return fitness;
545  }
546 
547  // Signal no fit
548  ROS_DEBUG_NAMED("dock_perception", "Did not converge");
549  ROS_INFO_STREAM("Did not converge.");
550  return -1.0;
551 }
552 
554 {
555  return 1e-3 >= fabs(1.0 - q.length());
556 }
d
tfScalar length() const
#define ROS_INFO_NAMED(name,...)
DockPerception(ros::NodeHandle &nh)
Definition: perception.cpp:40
static void pointMsgToTF(const geometry_msgs::Point &msg_v, Point &bt_v)
#define ROS_DEBUG_STREAM_NAMED(name, args)
void publish(const boost::shared_ptr< M > &message) const
#define ROS_ERROR_STREAM_NAMED(name, args)
Subscriber subscribe(const std::string &topic, uint32_t queue_size, void(T::*fp)(M), T *obj, const TransportHints &transport_hints=TransportHints())
DockCandidatePtr extract(laser_processor::SampleSet *cluster)
Extract a DockCandidate from a cluster, filling in the lengths and slopes of each line found using ra...
Definition: perception.cpp:369
double getPoseDistance(const geometry_msgs::Pose a, const geometry_msgs::Pose b)
Definition: perception.cpp:32
static double getYaw(const Quaternion &bt_q)
geometry_msgs::PoseStamped dock_
Definition: perception.h:92
std::vector< geometry_msgs::Point > transform(const std::vector< geometry_msgs::Point > &points, double x, double y, double theta)
Transform a vector of points in 2d.
Definition: icp_2d.cpp:41
TFSIMD_FORCE_INLINE const tfScalar & y() const
bool getPose(geometry_msgs::PoseStamped &pose, std::string frame="")
Get the pose of the dock.
Definition: perception.cpp:120
ros::Publisher debug_points_
Definition: perception.h:103
TFSIMD_FORCE_INLINE tfScalar angle(const Quaternion &q1, const Quaternion &q2)
boost::mutex dock_mutex_
Definition: perception.h:94
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
std::vector< geometry_msgs::Point > ideal_cloud_
Definition: perception.h:106
#define ROS_DEBUG_NAMED(name,...)
std::vector< geometry_msgs::Point > front_cloud_
Definition: perception.h:108
double alignICP(const std::vector< geometry_msgs::Point > source, const std::vector< geometry_msgs::Point > target, geometry_msgs::Transform &transform, size_t max_iterations=10, double min_delta_rmsd=0.000001)
Perform Iterative Closest Point (ICP) algorithm to align two point clouds in a two dimensional plane...
Definition: icp_2d.cpp:289
static void quaternionMsgToTF(const geometry_msgs::Quaternion &msg, Quaternion &bt)
double max_alignment_error_
Definition: perception.h:100
#define ROS_INFO(...)
double thetaFromQuaternion(const geometry_msgs::Quaternion &q)
Get the 2d rotation from a quaternion.
Definition: icp_2d.cpp:28
TFSIMD_FORCE_INLINE Quaternion inverse(const Quaternion &q)
An ordered set of Samples.
pose
ros::Subscriber scan_sub_
Definition: perception.h:81
geometry_msgs::Point getCentroid(const std::vector< geometry_msgs::Point > points)
Get the centroid of a set of points.
Definition: icp_2d.cpp:66
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
INLINE Rall1d< T, V, S > sqrt(const Rall1d< T, V, S > &arg)
def normalize_angle(angle)
std::string tracking_frame_
Low pass filter object for filtering dock poses.
Definition: perception.h:90
void lookupTransform(const std::string &target_frame, const std::string &source_frame, const ros::Time &time, StampedTransform &transform) const
#define ROS_WARN_STREAM_THROTTLE(rate, args)
unsigned int c
std::list< SampleSetConstPtr > & getClusters()
void callback(const sensor_msgs::LaserScanConstPtr &scan)
Callback to process laser scans.
Definition: perception.cpp:174
bool found_dock_
Definition: perception.h:96
static geometry_msgs::Quaternion createQuaternionMsgFromYaw(double yaw)
#define ROS_INFO_STREAM(args)
void transformPose(const std::string &target_frame, const geometry_msgs::PoseStamped &stamped_in, geometry_msgs::PoseStamped &stamped_out) const
bool getParam(const std::string &key, std::string &s) const
static bool isValid(const tf::Quaternion &q)
Method to check if the quaternion is valid.
Definition: perception.cpp:553
bool start(const geometry_msgs::PoseStamped &pose)
Start dock detection.
Definition: perception.cpp:105
double fit(const DockCandidatePtr &candidate, geometry_msgs::Pose &pose)
Try to fit a dock to candidate.
Definition: perception.cpp:417
static Time now()
INLINE Rall1d< T, V, S > cos(const Rall1d< T, V, S > &arg)
INLINE Rall1d< T, V, S > tan(const Rall1d< T, V, S > &arg)
LinearPoseFilter2DPtr dock_pose_filter_
Definition: perception.h:87
A cluster which is a candidate for a dock.
A mask for filtering out Samples based on range.
bool stop()
Stop tracking the dock.
Definition: perception.cpp:114
void setPointCloud2FieldsByString(int n_fields,...)
INLINE Rall1d< T, V, S > sin(const Rall1d< T, V, S > &arg)
ros::Time dock_stamp_
Definition: perception.h:98
tf::TransformListener listener_
Definition: perception.h:82
#define ROS_WARN_STREAM_NAMED(name, args)
static void pointTFToMsg(const Point &bt_v, geometry_msgs::Point &msg_v)


caster_app
Author(s): Ye Tian
autogenerated on Wed Dec 18 2019 03:34:44