test_global_localization.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2018, the mcl_3dl authors
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  * * Redistributions in binary form must reproduce the above copyright
11  * notice, this list of conditions and the following disclaimer in the
12  * documentation and/or other materials provided with the distribution.
13  * * Neither the name of the copyright holder nor the names of its
14  * contributors may be used to endorse or promote products derived from
15  * this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27  * POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include <limits>
31 #include <random>
32 #include <vector>
33 
34 #include <ros/ros.h>
35 
36 #include <geometry_msgs/PoseArray.h>
37 #include <mcl_3dl_msgs/Status.h>
38 #include <nav_msgs/Odometry.h>
39 #include <sensor_msgs/Imu.h>
40 #include <sensor_msgs/PointCloud2.h>
42 #include <std_srvs/Trigger.h>
43 #include <tf2/utils.h>
45 
46 #include <gtest/gtest.h>
47 
48 namespace
49 {
50 void generateSamplePointcloud2(
51  sensor_msgs::PointCloud2& cloud,
52  const float x0,
53  const float y0,
54  const float x1,
55  const float y1,
56  const float offset_x,
57  const float offset_y,
58  const float offset_z,
59  const float offset_yaw)
60 {
61  std::random_device seed;
62  std::default_random_engine engine(seed());
63  std::normal_distribution<float> rand(0, 0.01);
64 
65  cloud.height = 1;
66  cloud.is_bigendian = false;
67  cloud.is_dense = false;
68  sensor_msgs::PointCloud2Modifier modifier(cloud);
69  modifier.setPointCloud2Fields(
70  4,
71  "x", 1, sensor_msgs::PointField::FLOAT32,
72  "y", 1, sensor_msgs::PointField::FLOAT32,
73  "z", 1, sensor_msgs::PointField::FLOAT32,
74  "intensity", 1, sensor_msgs::PointField::FLOAT32);
75 
76  class Point
77  {
78  public:
79  float x_, y_, z_;
80  Point(const float x, const float y, const float z)
81  : x_(x)
82  , y_(y)
83  , z_(z)
84  {
85  }
86  };
87  std::vector<Point> points;
88  const float grid_xy = 0.15;
89  const float grid_z = 0.1;
90  const float floor_size = 6.0;
91  // Draw floor
92  for (float x = -floor_size; x < floor_size; x += grid_xy)
93  for (float y = -floor_size; y < floor_size; y += grid_xy)
94  if (x0 < x && x < x1 && y0 < y && y < y1)
95  points.push_back(Point(x, y, 0.0));
96  // Draw objects
97  for (float x = -2; x < 1.6; x += grid_xy)
98  if (x0 < x && x < x1)
99  for (float z = 0.5; z < 1.5; z += grid_z)
100  points.push_back(Point(x, 1.6, z));
101  for (float y = -0.5; y < 1.6; y += grid_xy)
102  if (y0 < y && y < y1)
103  for (float z = 0.5; z < 2.0; z += grid_z)
104  points.push_back(Point(1.6, y, z));
105  for (float x = 0; x < 1.6; x += grid_xy)
106  if (x0 < x && x < x1)
107  for (float z = 0.5; z < 2.0; z += grid_z)
108  points.push_back(Point(x, -2.1 + x, z));
109 
110  const float o_cos = cosf(offset_yaw);
111  const float o_sin = sinf(offset_yaw);
112  modifier.resize(points.size());
113  cloud.width = points.size();
114  sensor_msgs::PointCloud2Iterator<float> iter_x(cloud, "x");
115  sensor_msgs::PointCloud2Iterator<float> iter_y(cloud, "y");
116  sensor_msgs::PointCloud2Iterator<float> iter_z(cloud, "z");
117 
118  for (const Point& p : points)
119  {
120  *iter_x = p.x_ * o_cos - p.y_ * o_sin + offset_x + rand(engine);
121  *iter_y = p.x_ * o_sin + p.y_ * o_cos + offset_y + rand(engine);
122  *iter_z = p.z_ + offset_z + rand(engine);
123  ++iter_x;
124  ++iter_y;
125  ++iter_z;
126  }
127 }
128 
129 inline sensor_msgs::PointCloud2 generateMapMsg()
130 {
131  sensor_msgs::PointCloud2 cloud;
132  generateSamplePointcloud2(cloud, -100, -100, 100, 100, 0, 0, 0, 0);
133  cloud.header.frame_id = "map";
134  return cloud;
135 }
136 inline sensor_msgs::PointCloud2 generateCloudMsg(
137  const float offset_x,
138  const float offset_y,
139  const float offset_z,
140  const float offset_yaw)
141 {
142  sensor_msgs::PointCloud2 cloud;
143  generateSamplePointcloud2(
144  cloud, -2, -2, 2, 2,
145  offset_x, offset_y, offset_z, offset_yaw);
146  cloud.header.frame_id = "laser";
147  cloud.header.stamp = ros::Time::now();
148  return cloud;
149 }
150 inline sensor_msgs::Imu generateImuMsg()
151 {
152  sensor_msgs::Imu imu;
153  imu.header.frame_id = "base_link";
154  imu.header.stamp = ros::Time::now();
155  imu.orientation.w = 1;
156  imu.linear_acceleration.z = 9.8;
157  return imu;
158 }
159 inline nav_msgs::Odometry generateOdomMsg(const float x)
160 {
161  nav_msgs::Odometry odom;
162  odom.header.frame_id = "odom";
163  odom.header.stamp = ros::Time::now();
164  odom.pose.pose.position.x = x;
165  odom.pose.pose.position.y = 5;
166  odom.pose.pose.orientation.w = 1;
167  return odom;
168 }
169 } // namespace
170 
171 class GlobalLocalization : public ::testing::TestWithParam<float>
172 {
173 };
174 
176  OdometryOffset, GlobalLocalization,
177  ::testing::Values(5.0, 100.0));
178 
180 {
181  geometry_msgs::PoseArray::ConstPtr poses;
182  mcl_3dl_msgs::Status::ConstPtr status;
183 
184  const boost::function<void(const geometry_msgs::PoseArray::ConstPtr&)> cb_pose =
185  [&poses](const geometry_msgs::PoseArray::ConstPtr& msg) -> void
186  {
187  poses = msg;
188  };
189  const boost::function<void(const mcl_3dl_msgs::Status::ConstPtr&)> cb_status =
190  [&status](const mcl_3dl_msgs::Status::ConstPtr& msg) -> void
191  {
192  status = msg;
193  };
194 
195  ros::NodeHandle nh("");
196  ros::Subscriber sub_pose = nh.subscribe("mcl_3dl/particles", 1, cb_pose);
197  ros::Subscriber sub_status = nh.subscribe("mcl_3dl/status", 1, cb_status);
198 
199  ros::ServiceClient src_global_localization =
200  nh.serviceClient<std_srvs::TriggerRequest, std_srvs::TriggerResponse>(
201  "global_localization");
202 
203  ros::Publisher pub_mapcloud = nh.advertise<sensor_msgs::PointCloud2>("mapcloud", 1, true);
204  ros::Publisher pub_cloud = nh.advertise<sensor_msgs::PointCloud2>("cloud", 1);
205  ros::Publisher pub_imu = nh.advertise<sensor_msgs::Imu>("imu/data", 1);
206  ros::Publisher pub_odom = nh.advertise<nav_msgs::Odometry>("odom", 1);
207 
208  pub_mapcloud.publish(generateMapMsg());
209  ros::Rate wait(10);
210  for (int i = 0; i < 100; i++)
211  {
212  wait.sleep();
213  ros::spinOnce();
214  if (poses)
215  break;
216  ASSERT_TRUE(ros::ok());
217  }
218 
219  for (float offset_x = -0.5; offset_x <= 0.51; offset_x += 1.0)
220  {
221  for (float offset_yaw = -M_PI / 2; offset_yaw <= M_PI / 2 + 0.1; offset_yaw += M_PI)
222  {
223  const float laser_frame_height = 0.5;
224  const float offset_y = 0.54;
225  const float offset_z = 0.0;
226 
227  ros::Rate rate(10);
228  // Wait until mcl_3dl initialization
229  while (ros::ok())
230  {
231  rate.sleep();
232  ros::spinOnce();
233  if (status && status->status == mcl_3dl_msgs::Status::NORMAL)
234  break;
235  pub_cloud.publish(
236  generateCloudMsg(offset_x, offset_y, offset_z - laser_frame_height, offset_yaw));
237  pub_imu.publish(generateImuMsg());
238  pub_odom.publish(generateOdomMsg(0.0));
239  }
240  ASSERT_TRUE(ros::ok());
241  for (int i = 0; i < 5; ++i)
242  {
243  // Publish odom multiple times to make mcl_3dl internal odometry diff zero
244  pub_odom.publish(generateOdomMsg(GetParam()));
245  ros::Duration(0.1).sleep();
246  ros::spinOnce();
247  }
248 
249  std_srvs::Trigger trigger;
250  ASSERT_TRUE(src_global_localization.call(trigger));
251  ros::spinOnce();
252 
253  // Wait until starting global localization
254  while (ros::ok())
255  {
256  rate.sleep();
257  ros::spinOnce();
258  if (status && status->status == mcl_3dl_msgs::Status::GLOBAL_LOCALIZATION)
259  break;
260  pub_cloud.publish(
261  generateCloudMsg(offset_x, offset_y, offset_z - laser_frame_height, offset_yaw));
262  pub_imu.publish(generateImuMsg());
263  pub_odom.publish(generateOdomMsg(GetParam()));
264  }
265  ASSERT_TRUE(ros::ok());
266 
267  // Wait until finishing global localization
268  while (ros::ok())
269  {
270  rate.sleep();
271  ros::spinOnce();
272  if (status && status->status == mcl_3dl_msgs::Status::NORMAL)
273  break;
274  pub_cloud.publish(
275  generateCloudMsg(offset_x, offset_y, offset_z - laser_frame_height, offset_yaw));
276  pub_imu.publish(generateImuMsg());
277  pub_odom.publish(generateOdomMsg(GetParam()));
278  }
279  ASSERT_TRUE(ros::ok());
280 
281  // Wait to improve accuracy
282  for (int i = 0; i < 40; ++i)
283  {
284  rate.sleep();
285  ros::spinOnce();
286  pub_cloud.publish(
287  generateCloudMsg(offset_x, offset_y, offset_z - laser_frame_height, offset_yaw));
288  pub_imu.publish(generateImuMsg());
289  pub_odom.publish(generateOdomMsg(GetParam()));
290  }
291  ASSERT_TRUE(ros::ok());
292 
293  ASSERT_TRUE(static_cast<bool>(status));
294  ASSERT_TRUE(static_cast<bool>(poses));
295 
296  const tf2::Transform true_pose(
297  tf2::Quaternion(0, 0, sinf(-offset_yaw / 2), cosf(-offset_yaw / 2)),
298  tf2::Vector3(
299  -(offset_x * cos(-offset_yaw) - offset_y * sin(-offset_yaw)),
300  -(offset_x * sin(-offset_yaw) + offset_y * cos(-offset_yaw)),
301  -offset_z));
302  bool found_true_positive(false);
303  float dist_err_min = std::numeric_limits<float>::max();
304  float ang_err_min = std::numeric_limits<float>::max();
305  for (const auto& pose : poses->poses)
306  {
307  tf2::Transform particle_pose;
308  tf2::fromMsg(pose, particle_pose);
309 
310  const tf2::Transform tf_diff = particle_pose.inverse() * true_pose;
311  const float dist_err = tf_diff.getOrigin().length();
312  const float ang_err = fabs(tf2::getYaw(tf_diff.getRotation()));
313  if (dist_err < 2e-1 && ang_err < 2e-1)
314  found_true_positive = true;
315 
316  if (dist_err_min > dist_err)
317  {
318  dist_err_min = dist_err;
319  ang_err_min = ang_err;
320  }
321  }
322  ASSERT_TRUE(found_true_positive)
323  << "Minimum position error: " << dist_err_min << std::endl
324  << "Angular error: " << ang_err_min << std::endl;
325  }
326  }
327 }
328 
329 int main(int argc, char** argv)
330 {
331  testing::InitGoogleTest(&argc, argv);
332  ros::init(argc, argv, "test_global_localization");
333  ros::NodeHandle nh; // workaround to keep the test node during the process life time
334 
335  return RUN_ALL_TESTS();
336 }
msg
ServiceClient serviceClient(const std::string &service_name, bool persistent=false, const M_string &header_values=M_string())
void publish(const boost::shared_ptr< M > &message) const
Subscriber subscribe(const std::string &topic, uint32_t queue_size, void(T::*fp)(M), T *obj, const TransportHints &transport_hints=TransportHints())
Quaternion getRotation() const
bool sleep() const
ROSCPP_DECL void init(int &argc, char **argv, const std::string &name, uint32_t options=0)
bool call(MReq &req, MRes &res)
TFSIMD_FORCE_INLINE const tfScalar & x() const
int main(int argc, char **argv)
TFSIMD_FORCE_INLINE const tfScalar & z() const
TF2SIMD_FORCE_INLINE Vector3 & getOrigin()
void fromMsg(const A &, B &b)
ROSCPP_DECL bool ok()
TFSIMD_FORCE_INLINE const tfScalar & y() const
Transform inverse() const
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
bool sleep()
double getYaw(const A &a)
INSTANTIATE_TEST_CASE_P(OdometryOffset, GlobalLocalization,::testing::Values(5.0, 100.0))
void wait(int seconds)
TEST_P(GlobalLocalization, Localize)
static Time now()
INLINE Rall1d< T, V, S > cos(const Rall1d< T, V, S > &arg)
ROSCPP_DECL void spinOnce()
INLINE Rall1d< T, V, S > sin(const Rall1d< T, V, S > &arg)


mcl_3dl
Author(s): Atsushi Watanabe
autogenerated on Wed May 12 2021 02:16:29