Corentin Chauvin-Hameau – 2019-2020
Coverage Path Planning for an underwater robot surveying a marine farm
experiment_stats.cpp
Go to the documentation of this file.
1 /**
2  * @file
3  *
4  * \brief Definition of a node to measure statistics about the one wall experiment
5  * \author Corentin Chauvin-Hameau
6  * \date 2020
7  */
8 
9 #include "experiment_stats.hpp"
10 #include "mf_common/common.hpp"
11 #include "mf_common/spline.hpp"
12 #include "mf_common/EulerPose.h"
13 #include "mf_common/Array2D.h"
14 #include "mf_common/Float32Array.h"
15 #include "mf_farm_simulator/Algae.h"
16 #include "mf_farm_simulator/Alga.h"
17 #include "mf_mapping/LoadGP.h"
18 #include <nav_msgs/Odometry.h>
19 #include <nav_msgs/Path.h>
20 #include <geometry_msgs/PoseStamped.h>
21 #include <geometry_msgs/Pose.h>
22 #include <ros/package.h>
23 #include <ros/ros.h>
24 #include <eigen3/Eigen/Dense>
25 #include <fstream>
26 #include <iostream>
27 #include <sstream>
28 #include <string>
29 #include <vector>
30 
31 
32 #include <sensor_msgs/Image.h>
33 #include <sensor_msgs/image_encodings.h>
34 
35 
36 using std::cout;
37 using std::endl;
38 using std::string;
39 using std::vector;
40 
41 
42 namespace mfcpp {
43 
44 
46  nh_("~"),
47  tf_listener_(tf_buffer_)
48 {
49  init_node();
50 }
51 
52 
54 {
55 
56 }
57 
58 
60 {
61  // ROS parameters
62  string out_file_name; // path of the output file for the results of the test
63 
64  nh_.param<float>("main_freq", main_freq_, 1.0);
65  nh_.param<string>("out_file_name", out_file_name, "/tmp/control_out.txt");
66  nh_.param<float>("gp_threshold", gp_threshold_, 0.5);
67  nh_.param<bool>("save_gp", save_gp_, false);
68  nh_.param<bool>("load_gp", load_gp_, false);
69  nh_.param<string>("gp_file_name", gp_file_name_, "/tmp/gp_mean.txt");
70 
71  // Other variables
72  odom_received_ = false;
73  path_received_ = false;
74  gp_cov_received_ = false;
75  gp_eval_received_ = false;
76  algae_received_ = false;
77  start_time_ = ros::Time::now().toSec();
78  moved_distance_ = 0;
79 
80  out_file_.open(out_file_name);
81  if (out_file_.is_open())
82  out_file_ << "t, d, x, y, z, x_ref, y_ref, z_ref, pos_err, orient_err, gp_cov_trace, gp_diff_norm" << endl;
83  else
84  ROS_WARN("[experiment_stats] Couldn't open file '%s' in write mode", out_file_name.c_str());
85 
86  // ROS subscribers
87  odom_sub_ = nh_.subscribe<nav_msgs::Odometry>("odom", 1, &ExperimentStatsNode::odom_cb, this);
88  path_sub_ = nh_.subscribe<nav_msgs::Path>("path", 1, &ExperimentStatsNode::path_cb, this);
89  gp_mean_sub_ = nh_.subscribe<mf_common::Float32Array>("gp_mean", 1, &ExperimentStatsNode::gp_mean_cb, this);
90  gp_cov_sub_ = nh_.subscribe<mf_common::Array2D>("gp_cov", 1, &ExperimentStatsNode::gp_cov_cb, this);
91  gp_eval_sub_ = nh_.subscribe<mf_common::Array2D>("gp_eval", 1, &ExperimentStatsNode::gp_eval_cb, this);
92  algae_sub_ = nh_.subscribe<mf_farm_simulator::Algae>("algae", 1, &ExperimentStatsNode::algae_cb, this);
93 
94  // ROS publishers
95  ref_pub_ = nh_.advertise<geometry_msgs::Pose>("reference", 0);
96  error_pub_ = nh_.advertise<mf_common::EulerPose>("error", 0);
97  diff_img_pub_ = nh_.advertise<sensor_msgs::Image>("diff_gp_img", 0);
98 
99  // ROS services
100  load_gp_client_ = nh_.serviceClient<mf_mapping::LoadGP>("load_gp_srv");
101 
102 }
103 
104 
106 {
107  ros::Rate loop_rate(main_freq_);
108  ros::Time t_path = ros::Time::now(); // last time path has been published
109  bool experiment_over = false;
110 
111  while (ros::ok() && !experiment_over) {
112  ros::spinOnce();
113 
114  // Attempt to load the GP if requested
115  if (load_gp_ && load_gp_client_.exists()) {
116  vector<float> gp_mean;
117  bool gp_loaded = load_gp(gp_file_name_, gp_mean);
118 
119  if (gp_loaded) {
120  mf_mapping::LoadGP srv;
121  srv.request.mean = gp_mean;
122 
123  bool srv_called = load_gp_client_.call(srv);
124 
125  if (srv_called && srv.response.gp_loaded) {
126  load_gp_ = false;
127  ROS_INFO("[experiment_stats] GP loaded");
128  } else if (!srv_called || (srv_called && !srv.response.gp_not_yet_init)){
129  ROS_WARN("[experiment_stats] Could not load GP");
130  }
131  }
132  }
133 
134  // Compute statistics
137  // Compute tracking error
138  geometry_msgs::Pose reference_pose = find_closest(path_.poses, odom_.pose.pose);
139  mf_common::EulerPose error;
140  diff_pose(reference_pose, odom_.pose.pose, error);
141 
142  // Compute trace of GP covariance and information
143  float gp_cov_trace = 0.0;
144  float information = 0.0;
145  int size_gp = gp_cov_.size();
146 
147  for (int k = 0; k < size_gp; k++) {
148  gp_cov_trace += gp_cov_[k];
149 
150  float weight = 0.0;
151  if (gp_mean_[k] > gp_threshold_)
152  weight = 1;
153 
154  information += weight * (init_gp_cov_[k] - gp_cov_[k]);
155  }
156 
157  // Compare evaluated GP and algae disease heatmaps
158  float gp_diff_norm = compute_diff(gp_eval_, algae_heatmaps_[0]);
159 
160  // Write statistics in the output file
161  write_output(odom_.pose.pose, reference_pose, error, gp_cov_trace, information, gp_diff_norm);
162 
163  // Publish statistics
164  ref_pub_.publish(reference_pose);
165  error_pub_.publish(error);
166 
167  // Detect end of the experiment
168  geometry_msgs::TransformStamped wall_robot_tf;
169 
170  try {
171  wall_robot_tf = tf_buffer_.lookupTransform("wall", "base_link", ros::Time(0));
172  }
173  catch (tf2::TransformException &ex) {
174  ROS_WARN("[experiment_stats] %s", ex.what());
175  continue;
176  }
177 
178  if (wall_robot_tf.transform.translation.y < 0 && wall_robot_tf.transform.translation.z < 0) {
179  experiment_over = true;
180  ROS_INFO("========== EXPERIMENT OVER ===========");
181  }
182  }
183 
184  loop_rate.sleep();
185  }
186 
187  // Save the GP if requested
189  save_gp();
190 
191  // Close the output CSV file
192  if (out_file_.is_open())
193  out_file_.close();
194 }
195 
196 
198  const std::vector<geometry_msgs::PoseStamped> &path,
199  const geometry_msgs::Pose &pose)
200 {
201  if (path.size() == 0)
202  return geometry_msgs::Pose();
203 
204  float min_dist = distance2(path[0].pose.position, pose.position);
205  int best_k = 0;
206 
207  for (int k = 1; k < path.size(); k++) {
208  float d = distance2(path[k].pose.position, pose.position);
209 
210  if (d < min_dist) {
211  min_dist = d;
212  best_k = k;
213  }
214  }
215 
216  return path[best_k].pose;
217 }
218 
219 
221  const std::vector<std::vector<float>> &array1,
222  const std::vector<std::vector<float>> &array2)
223 {
224  const vector<vector<float>> *small_array;
225  const vector<vector<float>> *big_array;
226 
227  // Find the smallest array
228  if (array1.size() < array2.size()) {
229  small_array = &array1;
230  big_array = &array2;
231  } else {
232  small_array = &array2;
233  big_array = &array1;
234  }
235 
236  float size_x[2] = {(float)(*small_array).size(), (float)(*big_array).size()};
237  float size_y[2] = {(float)(*small_array)[0].size(), (float)(*big_array)[0].size()};
238 
239  // Find the maximum of the two arrays for scaling
240  typedef const vector<vector<float>>* Array2DPtr;
241  Array2DPtr arrays[2] = {small_array, big_array};
242  float maximums[2] = {0, 0};
243 
244  for (int k = 0; k < 2; k++) {
245  for (int x = 0; x < size_x[k]; x++) {
246  for (int y = 0; y < size_y[k]; y++) {
247  if ((*(arrays[k]))[x][y] > maximums[k])
248  maximums[k] = (*(arrays[k]))[x][y];
249  }
250  }
251  }
252 
253  // Prepare the difference image
254  sensor_msgs::Image diff_img;
255  diff_img.header.stamp = ros::Time::now();
256  diff_img.height = size_x[0];
257  diff_img.width = size_y[0];
258  diff_img.encoding = sensor_msgs::image_encodings::MONO8;
259  diff_img.is_bigendian = false;
260  diff_img.step = size_y[0];
261  diff_img.data.resize(size_x[0]*size_y[0]);
262 
263  // Compute the 2-norm of the two arrays
264  float norm = 0.0;
265 
266  for (int k = 0; k < size_x[0]; k++) {
267  int x = k * float(size_x[1] / size_x[0]);
268 
269  for (int l = 0; l < size_y[0]; l++) {
270  int y = l * float(size_y[1] / size_y[0]);
271  float small_val = (*small_array)[k][l];
272  float big_val = (*big_array)[x][y];
273 
274  norm += pow(small_val/maximums[0] - big_val/maximums[1], 2);
275  diff_img.data[k*size_y[0] + l] = fabs(small_val/maximums[0] - big_val/maximums[1])*255;
276  }
277  }
278 
279  diff_img_pub_.publish(diff_img);
280  return sqrt(norm) / (size_x[0]*size_y[0]);
281 }
282 
283 
285  const geometry_msgs::Pose &pose,
286  const geometry_msgs::Pose &reference,
287  const mf_common::EulerPose &error,
288  float gp_cov_trace,
289  float information,
290  float gp_diff_norm)
291 {
292  if (!out_file_.is_open())
293  return;
294 
295  float error_pos = sqrt(error.x*error.x + error.y*error.y + error.z*error.z);
296  float error_orient = sqrt(error.pitch*error.pitch + error.yaw*error.yaw); // the roll is not controlled
297  double t = ros::Time::now().toSec() - start_time_;
298 
299  out_file_ << t << ", "
300  << moved_distance_ << ", "
301  << pose.position.x << ", "
302  << pose.position.y << ", "
303  << pose.position.z << ", "
304  << reference.position.x << ", "
305  << reference.position.y << ", "
306  << reference.position.z << ", "
307  << error_pos << ", "
308  << error_orient << ", "
309  << gp_cov_trace << ", "
310  << information << ", "
311  << gp_diff_norm << endl;
312 }
313 
314 
316  std::string file_name,
317  std::vector<float> &gp_mean)
318 {
319  // Open file to write to
320  std::ifstream file(gp_file_name_);
321 
322  if (!file.is_open()) {
323  ROS_WARN("[experiment_stats] Couldn't open file '%s' in write mode", gp_file_name_.c_str());
324  return false;
325  }
326 
327  // Read the file
328  ROS_INFO("[experiment_stats] Loading GP mean from '%s'\n", gp_file_name_.c_str());
329  gp_mean.resize(0);
330 
331  for (string line; std::getline(file, line); ) {
332  if (!line.empty()) {
333  gp_mean.emplace_back(stof(line));
334  }
335  }
336 
337  return true;
338 }
339 
340 
342 {
343  // Open file to write to
344  std::ofstream file(gp_file_name_);
345 
346  if (!file.is_open()) {
347  ROS_WARN("[experiment_stats] Couldn't open file '%s' in write mode", gp_file_name_.c_str());
348  return;
349  }
350 
351  // Write the GP mean in the file
352  printf("[experiment_stats] Saving GP to '%s'\n", gp_file_name_.c_str());
353 
354  for (int k = 0; k < gp_mean_.size(); k++) {
355  file << gp_mean_[k] << endl;
356  }
357 
358  // Close the file
359  file.close();
360 }
361 
362 
363 void ExperimentStatsNode::odom_cb(const nav_msgs::Odometry msg)
364 {
365  // Increment moved distance
366  if (odom_received_) {
367  float dt = (msg.header.stamp - odom_.header.stamp).toSec();
368  float v = msg.twist.twist.linear.x;
369 
370  moved_distance_ += v*dt;
371  }
372 
373  // Store odometry
374  odom_ = msg;
375  odom_received_ = true;
376 }
377 
378 
379 void ExperimentStatsNode::path_cb(const nav_msgs::Path msg)
380 {
381  path_ = msg;
382  path_received_ = true;
383 }
384 
385 
386 void ExperimentStatsNode::gp_mean_cb(const mf_common::Float32Array msg)
387 {
388  gp_mean_ = msg.data;
389  gp_mean_received_ = true;
390 }
391 
392 
393 void ExperimentStatsNode::gp_cov_cb(const mf_common::Array2D msg)
394 {
395  int n = msg.data.size();
396  gp_cov_.resize(n);
397 
398  for (int k = 0; k < n; k++)
399  gp_cov_[k] = msg.data[k].data[k];
400 
401  if (!gp_cov_received_) {
403  gp_cov_received_ = true;
404  }
405 }
406 
407 
408 void ExperimentStatsNode::gp_eval_cb(const mf_common::Array2D msg)
409 {
410  gp_eval_.resize(msg.data.size());
411 
412  for (int k = 0; k < msg.data.size(); k++) {
413  gp_eval_[k].resize(msg.data[k].data.size());
414 
415  for (int l = 0; l < msg.data[k].data.size(); l++) {
416  gp_eval_[k][l] = msg.data[k].data[l];
417  }
418  }
419 
420  gp_eval_received_ = true;
421 }
422 
423 
424 void ExperimentStatsNode::algae_cb(const mf_farm_simulator::Algae msg)
425 {
426  algae_heatmaps_.resize(msg.algae.size());
427 
428  for (int k = 0; k < msg.algae.size(); k++) {
429  const mf_farm_simulator::Alga *alga = &msg.algae[k];
430  algae_heatmaps_[k].resize(alga->disease_heatmap.size());
431 
432  for (int l = 0; l < alga->disease_heatmap.size(); l++) {
433  algae_heatmaps_[k][l].resize(alga->disease_heatmap[l].data.size());
434 
435  for (int m = 0; m < alga->disease_heatmap[l].data.size(); m++) {
436  algae_heatmaps_[k][l][m] = alga->disease_heatmap[l].data[m];
437  }
438  }
439  }
440 
441  algae_received_ = true;
442 }
443 
444 
445 } // namespace mfcpp
446 
447 
448 
449 /**
450  * \brief Main function called before node initialisation
451  */
452 int main(int argc, char **argv)
453 {
454  ros::init(argc, argv, "experiment_stats");
455  mfcpp::ExperimentStatsNode experiment_stats_node;
456  experiment_stats_node.run_node();
457 
458  return 0;
459 }
void save_gp()
Saves the mean of the Gaussian Process in a file.
void algae_cb(const mf_farm_simulator::Algae msg)
Callback for farm algae.
float moved_distance_
Distance moved by the robot since the start.
Definition: common.hpp:23
ros::Subscriber path_sub_
Subscriber for the reference path.
bool gp_cov_received_
Whether the covariance of the GP has been received.
std::vector< float > init_gp_cov_
Diagonal of the initial covariance of the GP.
float gp_threshold_
Threshold to consider a GP component in information gain computation.
ros::Subscriber odom_sub_
Subscriber for robot odometry.
float main_freq_
Frequency of the main loop.
void gp_mean_cb(const mf_common::Float32Array msg)
Callback for the mean of the Gaussian Process.
void gp_eval_cb(const mf_common::Array2D msg)
Callback for the evaluated GP.
std::vector< float > gp_cov_
Diagonal of the covariance of the Gaussian Proces.
void diff_pose(const geometry_msgs::Pose &p1, const geometry_msgs::Pose &p2, geometry_msgs::Pose &diff)
Computes the difference between two poses.
Definition: common.hpp:273
double start_time_
Start time of the test.
ros::Publisher ref_pub_
Publisher for the reference pose.
Declaration of common functions.
ros::Publisher diff_img_pub_
Publisher for the image of the difference between the mapped GP and the algae heatmap.
nav_msgs::Path path_
Reference path to follow.
bool odom_received_
Whether odometry has been received.
Declaration of a class for 3D spline interpolation.
ros::Subscriber gp_cov_sub_
Subscriber for covariance of the GP.
std::vector< std::vector< std::vector< float > > > algae_heatmaps_
Disease heatmaps of the algae.
std::vector< float > gp_mean_
Mean of the Gaussian Process.
void gp_cov_cb(const mf_common::Array2D msg)
Callback for the covariance of the Gaussian Process.
bool load_gp(std::string file_name, std::vector< float > &gp_mean)
Loads the GP mean from a file.
bool path_received_
Whether the reference path has been received.
Class to measure statistics about experiments.
ros::Subscriber algae_sub_
Subscriber for the farm algae.
void odom_cb(const nav_msgs::Odometry msg)
Callback for odometry.
void path_cb(const nav_msgs::Path msg)
Callback for the reference path.
bool gp_mean_received_
Whether the mean of the GP has been received.
float compute_diff(const std::vector< std::vector< float >> &array1, const std::vector< std::vector< float >> &array2)
Computes the difference between two arrays.
float distance2(const geometry_msgs::Point &p1, const geometry_msgs::Point &p2)
Computes the square of the euclidean distance between two positions.
Definition: common.hpp:61
bool save_gp_
Whether to save the Gaussian Process at the end.
ros::NodeHandle nh_
Node handler.
std::vector< std::vector< float > > gp_eval_
Evaluated GP.
std::ofstream out_file_
Output CSV file containg data of the experiment.
bool load_gp_
Whether to load the Gaussian Process at the beginning.
geometry_msgs::Pose find_closest(const std::vector< geometry_msgs::PoseStamped > &path, const geometry_msgs::Pose &pose)
Finds the closest pose in a path.
Declaration of a node to measure statistics about experiments.
int main(int argc, char **argv)
Main function called before node initialisation.
ros::Subscriber gp_mean_sub_
Subscriber for mean of the GP.
ros::Subscriber gp_eval_sub_
Subscriber for the evaluated GP.
ros::Publisher error_pub_
Publisher for the tracking error.
bool gp_eval_received_
Whether the evaluated GP has been received.
nav_msgs::Odometry odom_
Last odometry message received.
void init_node()
Initialises the node and its parameters.
bool algae_received_
Whether the farm algae have been received.
void write_output(const geometry_msgs::Pose &pose, const geometry_msgs::Pose &reference, const mf_common::EulerPose &error, float gp_cov_trace, float information, float gp_diff_norm)
Writes the output statistics in the out file.
void run_node()
Runs the node.
ros::ServiceClient load_gp_client_
Service client for loading the GP.
tf2_ros::Buffer tf_buffer_
Buffer for tf2.