22 #include <Eigen/Eigen> 23 #include <boost/algorithm/string.hpp> 24 #include <boost/filesystem.hpp> 33 #ifdef HAVE_PYTHONLIBS 42 int main(
int argc,
char **argv) {
50 PRINT_ERROR(
RED "ERROR: ./error_comparison <align_mode> <folder_groundtruth> <folder_algorithms>\n" RESET);
51 PRINT_ERROR(
RED "ERROR: rosrun ov_eval error_comparison <align_mode> <folder_groundtruth> <folder_algorithms>\n" RESET);
52 std::exit(EXIT_FAILURE);
56 std::string path_gts(argv[2]);
57 std::vector<boost::filesystem::path> path_groundtruths;
58 for (
const auto &p : boost::filesystem::recursive_directory_iterator(path_gts)) {
59 if (p.path().extension() ==
".txt") {
60 path_groundtruths.push_back(p.path());
63 std::sort(path_groundtruths.begin(), path_groundtruths.end());
66 for (
size_t i = 0; i < path_groundtruths.size(); i++) {
68 std::vector<double> times;
69 std::vector<Eigen::Matrix<double, 7, 1>> poses;
70 std::vector<Eigen::Matrix3d> cov_ori, cov_pos;
74 PRINT_INFO(
"[COMP]: %d poses in %s => length of %.2f meters\n", (
int)times.size(), path_groundtruths.at(i).filename().c_str(), length);
79 std::string path_algos(argv[3]);
80 std::vector<boost::filesystem::path> path_algorithms;
81 for (
const auto &entry : boost::filesystem::directory_iterator(path_algos)) {
82 if (boost::filesystem::is_directory(entry)) {
83 path_algorithms.push_back(entry.path());
86 std::sort(path_algorithms.begin(), path_algorithms.end());
93 std::map<std::string, std::vector<std::pair<ov_eval::Statistics, ov_eval::Statistics>>> algo_ate;
94 std::map<std::string, std::vector<std::pair<ov_eval::Statistics, ov_eval::Statistics>>> algo_ate_2d;
95 for (
const auto &p : path_algorithms) {
96 std::vector<std::pair<ov_eval::Statistics, ov_eval::Statistics>> temp;
97 for (
size_t i = 0; i < path_groundtruths.size(); i++) {
100 algo_ate.insert({p.filename().string(), temp});
101 algo_ate_2d.insert({p.filename().string(), temp});
105 std::vector<double> segments = {8.0, 16.0, 24.0, 32.0, 40.0, 48.0};
112 std::map<std::string, std::map<double, std::pair<ov_eval::Statistics, ov_eval::Statistics>>> algo_rpe;
113 for (
const auto &p : path_algorithms) {
114 std::map<double, std::pair<ov_eval::Statistics, ov_eval::Statistics>> temp;
115 for (
const auto &len : segments) {
118 algo_rpe.insert({p.filename().string(), temp});
126 for (
size_t i = 0; i < path_algorithms.size(); i++) {
129 PRINT_DEBUG(
"======================================\n");
130 PRINT_DEBUG(
"[COMP]: processing %s algorithm\n", path_algorithms.at(i).filename().c_str());
133 std::map<std::string, boost::filesystem::path> path_algo_datasets;
134 for (
auto &entry : boost::filesystem::directory_iterator(path_algorithms.at(i))) {
135 if (boost::filesystem::is_directory(entry)) {
136 path_algo_datasets.insert({entry.path().filename().string(), entry.path()});
141 for (
size_t j = 0; j < path_groundtruths.size(); j++) {
144 if (path_algo_datasets.find(path_groundtruths.at(j).stem().string()) == path_algo_datasets.end()) {
145 PRINT_ERROR(
RED "[COMP]: %s dataset does not have any runs for %s!!!!!\n" RESET, path_algorithms.at(i).filename().c_str(),
146 path_groundtruths.at(j).stem().c_str());
151 PRINT_DEBUG(
"[COMP]: processing %s algorithm => %s dataset\n", path_algorithms.at(i).filename().c_str(),
152 path_groundtruths.at(j).stem().c_str());
157 std::map<double, std::pair<ov_eval::Statistics, ov_eval::Statistics>> rpe_dataset;
158 for (
const auto &len : segments) {
163 std::vector<std::string> file_paths;
164 for (
auto &entry : boost::filesystem::directory_iterator(path_algo_datasets.at(path_groundtruths.at(j).stem().string()))) {
165 if (entry.path().extension() !=
".txt")
167 file_paths.push_back(entry.path().string());
169 std::sort(file_paths.begin(), file_paths.end());
172 for (
auto &path_esttxt : file_paths) {
174 std::string dataset = path_groundtruths.at(j).stem().string();
175 std::string path_gttxt = path_groundtruths.at(j).string();
183 ate_dataset_ori.values.push_back(error_ori.
rmse);
184 ate_dataset_pos.values.push_back(error_pos.
rmse);
189 ate_2d_dataset_ori.
values.push_back(error_ori_2d.
rmse);
190 ate_2d_dataset_pos.
values.push_back(error_pos_2d.
rmse);
193 std::map<double, std::pair<ov_eval::Statistics, ov_eval::Statistics>> error_rpe;
195 for (
const auto &elm : error_rpe) {
196 rpe_dataset.at(elm.first).first.values.insert(rpe_dataset.at(elm.first).first.values.end(), elm.second.first.values.begin(),
197 elm.second.first.values.end());
198 rpe_dataset.at(elm.first).first.timestamps.insert(rpe_dataset.at(elm.first).first.timestamps.end(),
199 elm.second.first.timestamps.begin(), elm.second.first.timestamps.end());
200 rpe_dataset.at(elm.first).second.values.insert(rpe_dataset.at(elm.first).second.values.end(), elm.second.second.values.begin(),
201 elm.second.second.values.end());
202 rpe_dataset.at(elm.first).second.timestamps.insert(rpe_dataset.at(elm.first).second.timestamps.end(),
203 elm.second.second.timestamps.begin(), elm.second.second.timestamps.end());
208 ate_dataset_ori.calculate();
209 ate_dataset_pos.calculate();
214 std::string prefix = (ate_dataset_ori.mean > 10 || ate_dataset_pos.mean > 10) ?
RED :
"";
215 PRINT_DEBUG(
"%s\tATE: mean_ori = %.3f | mean_pos = %.3f (%d runs)\n" RESET, prefix.c_str(), ate_dataset_ori.mean,
216 ate_dataset_pos.mean, (int)ate_dataset_pos.values.size());
217 PRINT_DEBUG(
"\tATE: std_ori = %.3f | std_pos = %.3f\n", ate_dataset_ori.std, ate_dataset_pos.std);
218 PRINT_DEBUG(
"\tATE 2D: mean_ori = %.3f | mean_pos = %.3f (%d runs)\n", ate_2d_dataset_ori.
mean, ate_2d_dataset_pos.
mean,
219 (
int)ate_2d_dataset_ori.
values.size());
220 PRINT_DEBUG(
"\tATE 2D: std_ori = %.5f | std_pos = %.5f\n", ate_2d_dataset_ori.
std, ate_2d_dataset_pos.
std);
221 for (
auto &seg : rpe_dataset) {
222 seg.second.first.calculate();
223 seg.second.second.calculate();
226 PRINT_DEBUG(
"\tRPE: seg %d - median_ori = %.4f | median_pos = %.4f (%d samples)\n", (
int)seg.first, seg.second.first.median,
227 seg.second.second.median, (
int)seg.second.second.values.size());
232 std::string algo = path_algorithms.at(i).filename().string();
233 algo_ate.at(algo).at(j).first = ate_dataset_ori;
234 algo_ate.at(algo).at(j).second = ate_dataset_pos;
235 algo_ate_2d.at(algo).at(j).first = ate_2d_dataset_ori;
236 algo_ate_2d.at(algo).at(j).second = ate_2d_dataset_pos;
239 for (
const auto &elm : rpe_dataset) {
240 algo_rpe.at(algo).at(elm.first).first.
values.insert(algo_rpe.at(algo).at(elm.first).first.values.end(),
241 elm.second.first.values.begin(), elm.second.first.values.end());
242 algo_rpe.at(algo).at(elm.first).first.timestamps.insert(algo_rpe.at(algo).at(elm.first).first.timestamps.end(),
243 elm.second.first.timestamps.begin(), elm.second.first.timestamps.end());
244 algo_rpe.at(algo).at(elm.first).second.values.insert(algo_rpe.at(algo).at(elm.first).second.values.end(),
245 elm.second.second.values.begin(), elm.second.second.values.end());
246 algo_rpe.at(algo).at(elm.first).second.timestamps.insert(algo_rpe.at(algo).at(elm.first).second.timestamps.end(),
247 elm.second.second.timestamps.begin(), elm.second.second.timestamps.end());
258 PRINT_INFO(
"============================================\n");
260 PRINT_INFO(
"============================================\n");
261 for (
size_t i = 0; i < path_groundtruths.size(); i++) {
262 std::string gtname = path_groundtruths.at(i).stem().string();
263 boost::replace_all(gtname,
"_",
"\\_");
264 PRINT_INFO(
" & \\textbf{%s}", gtname.c_str());
266 PRINT_INFO(
" & \\textbf{Average} \\\\\\hline\n");
267 for (
auto &algo : algo_ate) {
268 std::string algoname = algo.first;
269 boost::replace_all(algoname,
"_",
"\\_");
270 PRINT_INFO(algoname.c_str());
271 double sum_ori = 0.0;
272 double sum_pos = 0.0;
274 for (
auto &seg : algo.second) {
275 if (seg.first.values.empty() || seg.second.values.empty()) {
276 PRINT_INFO(
" & - / -");
278 seg.first.calculate();
279 seg.second.calculate();
280 PRINT_INFO(
" & %.3f / %.3f", seg.first.mean, seg.second.mean);
281 sum_ori += seg.first.mean;
282 sum_pos += seg.second.mean;
286 PRINT_INFO(
" & %.3f / %.3f \\\\\n", sum_ori / sum_ct, sum_pos / sum_ct);
288 PRINT_INFO(
"============================================\n");
291 PRINT_INFO(
"============================================\n");
292 PRINT_INFO(
"ATE 2D LATEX TABLE\n");
293 PRINT_INFO(
"============================================\n");
294 for (
size_t i = 0; i < path_groundtruths.size(); i++) {
295 std::string gtname = path_groundtruths.at(i).stem().string();
296 boost::replace_all(gtname,
"_",
"\\_");
297 PRINT_INFO(
" & \\textbf{%s}", gtname.c_str());
299 PRINT_INFO(
" & \\textbf{Average} \\\\\\hline\n");
300 for (
auto &algo : algo_ate_2d) {
301 std::string algoname = algo.first;
302 boost::replace_all(algoname,
"_",
"\\_");
303 PRINT_INFO(algoname.c_str());
304 double sum_ori = 0.0;
305 double sum_pos = 0.0;
307 for (
auto &seg : algo.second) {
308 if (seg.first.values.empty() || seg.second.values.empty()) {
309 PRINT_INFO(
" & - / -");
311 seg.first.calculate();
312 seg.second.calculate();
313 PRINT_INFO(
" & %.3f / %.3f", seg.first.mean, seg.second.mean);
314 sum_ori += seg.first.mean;
315 sum_pos += seg.second.mean;
319 PRINT_INFO(
" & %.3f / %.3f \\\\\n", sum_ori / sum_ct, sum_pos / sum_ct);
321 PRINT_INFO(
"============================================\n");
324 PRINT_INFO(
"============================================\n");
325 PRINT_INFO(
"RPE LATEX TABLE\n");
326 PRINT_INFO(
"============================================\n");
327 for (
const auto &len : segments) {
328 PRINT_INFO(
" & \\textbf{%dm}", (
int)len);
330 PRINT_INFO(
" \\\\\\hline\n");
331 for (
auto &algo : algo_rpe) {
332 std::string algoname = algo.first;
333 boost::replace_all(algoname,
"_",
"\\_");
334 PRINT_INFO(algoname.c_str());
335 for (
auto &seg : algo.second) {
336 seg.second.first.calculate();
337 seg.second.second.calculate();
338 PRINT_INFO(
" & %.3f / %.3f", seg.second.first.mean, seg.second.second.mean);
340 PRINT_INFO(
" \\\\\n");
342 PRINT_INFO(
"============================================\n");
344 #ifdef HAVE_PYTHONLIBS 347 std::vector<std::string> colors = {
"blue",
"red",
"black",
"green",
"cyan",
"magenta"};
348 std::vector<std::string> linestyle = {
"-",
"--",
"-."};
349 assert(algo_rpe.size() <= colors.size() * linestyle.size());
352 std::map<std::string, std::string> params_rpe;
353 params_rpe.insert({
"notch",
"false"});
354 params_rpe.insert({
"sym",
""});
362 double width = 1.0 / (algo_rpe.size() + 1);
363 double spacing = width / (algo_rpe.size() + 1);
364 std::vector<double>
xticks;
365 std::vector<std::string> labels;
368 for (
auto &algo : algo_rpe) {
372 ct_pos = 1 + ct_algo * (width + spacing);
374 for (
auto &seg : algo.second) {
375 xticks.push_back(ct_pos - (algo_rpe.size() * (width + spacing) - width) / 2);
376 labels.push_back(std::to_string((
int)seg.first));
378 linestyle.at(ct_algo / colors.size()), params_rpe);
379 ct_pos += 1 + 3 * width;
387 for (
const auto &algo : algo_rpe) {
388 std::map<std::string, std::string> params_empty;
389 params_empty.insert({
"label", algo.first});
390 params_empty.insert({
"linestyle", linestyle.at(ct_algo / colors.size())});
391 params_empty.insert({
"color", colors.at(ct_algo % colors.size())});
392 std::vector<double> vec_empty;
405 for (
auto &algo : algo_rpe) {
407 ct_pos = 1 + ct_algo * (width + spacing);
409 for (
auto &seg : algo.second) {
411 linestyle.at(ct_algo / colors.size()), params_rpe);
412 ct_pos += 1 + 3 * width;
420 for (
const auto &algo : algo_rpe) {
421 std::map<std::string, std::string> params_empty;
422 params_empty.insert({
"label", algo.first});
423 params_empty.insert({
"linestyle", linestyle.at(ct_algo / colors.size())});
424 params_empty.insert({
"color", colors.at(ct_algo % colors.size())});
425 std::vector<double> vec_empty;
Statistics object for a given set scalar time series values.
void xticks(const std::vector< Numeric > &ticks, const std::vector< std::string > &labels={}, const std::map< std::string, std::string > &keywords={})
static void load_data(std::string path_traj, std::vector< double > ×, std::vector< Eigen::Matrix< double, 7, 1 >> &poses, std::vector< Eigen::Matrix3d > &cov_ori, std::vector< Eigen::Matrix3d > &cov_pos)
This will load space separated trajectory into memory.
double rmse
Root mean squared for the given values.
double mean
Mean of the given values.
std::vector< double > values
Values (e.g. error or nees at a given time)
void figure_size(size_t w, size_t h)
bool plot(const std::vector< Numeric > &x, const std::vector< Numeric > &y, const std::map< std::string, std::string > &keywords)
static double get_total_length(const std::vector< Eigen::Matrix< double, 7, 1 >> &poses)
Will calculate the total trajectory distance.
void calculate()
Will calculate all values from our vectors of information.
void show(const bool block=true)
void calculate_ate(Statistics &error_ori, Statistics &error_pos)
Computes the Absolute Trajectory Error (ATE) for this trajectory.
int main(int argc, char **argv)
double std
Standard deviation of given values.
A single run for a given dataset.
void calculate_ate_2d(Statistics &error_ori, Statistics &error_pos)
Computes the Absolute Trajectory Error (ATE) for this trajectory in the 2d x-y plane.
static void setPrintLevel(const std::string &level)
#define PRINT_ERROR(x...)
void xlim(Numeric left, Numeric right)
void xlabel(const std::string &str, const std::map< std::string, std::string > &keywords={})
void subplot(long nrows, long ncols, long plot_number)
void title(const std::string &titlestr, const std::map< std::string, std::string > &keywords={})
void ylabel(const std::string &str, const std::map< std::string, std::string > &keywords={})
bool boxplot(const std::vector< NumericX > &x, const double &position, const double &width, const std::string &color, const std::string &linestyle, const std::map< std::string, std::string > &keywords={}, bool vert=true)
#define PRINT_DEBUG(x...)
void calculate_rpe(const std::vector< double > &segment_lengths, std::map< double, std::pair< Statistics, Statistics >> &error_rpe)
Computes the Relative Pose Error (RPE) for this trajectory.