42 #include <QApplication>
51 "rtabmap-report [\"Statistic/Id\"] [options] path\n"
53 "[Not built with Qt, statistics cannot be plotted]\n"
55 " path Directory containing rtabmap databases or path of a database.\n"
57 " --latex Print table formatted in LaTeX with results.\n"
58 " --kitti Compute error based on KITTI benchmark.\n"
59 " --relative Compute relative motion error between poses.\n"
60 " --loop Compute relative motion error of loop closures.\n"
61 " --scale Find the best scale for the map against the ground truth\n"
62 " and compute error based on the scaled path.\n"
63 " --poses Export poses to [path]_poses.txt, ground truth to [path]_gt.txt\n"
64 " and valid ground truth indices to [path]_indices.txt \n"
65 " --gt FILE.txt Use this file as ground truth (TUM RGB-D format). It will\n"
66 " override the ground truth set in database if there is one.\n"
67 " If extension is *.db, the optimized poses of that database will\n"
68 " be used as ground truth.\n"
69 " --gt_max_t # Maximum time interval (sec) to interpolate between a pose and\n"
70 " corresponding ground truth (default 1 sec).\n"
71 " --inc Incremental optimization. \n"
72 " --stats Show available statistics \"Statistic/Id\" to plot or get localization statistics (if path is a file). \n"
74 " --invert When reading many databases, put all curves from a same \n"
75 " database in same figure, instead of all same curves from \n"
76 " different database in same figure. When reading a single \n"
77 " database, the inverse will be done. \n"
78 " --ids Use IDs for x axis instead of time in the figures. \n"
79 " --start # Start from this node ID for the figures.\n"
80 " --export Export figures' data to txt files.\n"
81 " --export_prefix Prefix to filenames of exported figures' data (default is \"Stat\").\n"
83 " --report Export all evaluation statistics values in report.txt \n"
84 " --loc [#] Show localization statistics for each \"Statistic/Id\" per\n"
85 " session. Optionally set number 1=min,2=max,4=mean,8=stddev,16=total,32=nonnull%%\n"
86 " to show cumulative results on console (it is a mask, \n"
87 " we can combine those numbers, e.g., 63 for all) \n"
88 " --loc_delay # Delay to split sessions for localization statistics (default 60 seconds).\n"
89 " --ignore_inter_nodes Ignore intermediate poses and statistics.\n"
90 " --udebug Show debug log.\n"
91 " --help,-h Show usage\n\n");
139 bool outputLatex =
false;
140 bool outputScaled =
false;
141 bool outputPoses =
false;
142 bool outputKittiError =
false;
143 bool outputRelativeError =
false;
144 bool outputReport =
false;
145 bool outputLoopAccuracy =
false;
146 bool incrementalOptimization =
false;
147 bool showAvailableStats =
false;
148 bool invertFigures =
false;
149 bool ignoreInterNodes =
false;
152 bool exportFigures =
false;
153 std::string exportPrefix =
"Stat";
157 double gtMaxInterval = 1;
158 std::vector<std::string> statsToShow;
160 std::map<std::string, UPlot*> figures;
162 for(
int i=1;
i<argc; ++
i)
164 if(strcmp(
argv[
i],
"--help") == 0 || strcmp(
argv[
i],
"--h") == 0)
168 else if(strcmp(
argv[
i],
"--udebug") == 0)
172 else if(strcmp(
argv[
i],
"--latex") == 0)
176 else if(strcmp(
argv[
i],
"--kitti") == 0)
178 outputKittiError =
true;
180 else if(strcmp(
argv[
i],
"--relative") == 0)
182 outputRelativeError =
true;
184 else if(strcmp(
argv[
i],
"--scale") == 0)
188 else if(strcmp(
argv[
i],
"--poses") == 0)
192 else if(strcmp(
argv[
i],
"--loop") == 0)
194 outputLoopAccuracy =
true;
196 else if(strcmp(
argv[
i],
"--report") == 0)
200 else if(strcmp(
argv[
i],
"--inc") == 0)
202 incrementalOptimization =
true;
204 else if(strcmp(
argv[
i],
"--stats") == 0)
206 showAvailableStats =
true;
208 else if(strcmp(
argv[
i],
"--invert") == 0)
210 invertFigures =
true;
212 else if(strcmp(
argv[
i],
"--ids") == 0)
216 else if(strcmp(
argv[
i],
"--ignore_inter_nodes") == 0)
218 ignoreInterNodes =
true;
220 else if(strcmp(
argv[
i],
"--export") == 0)
222 exportFigures =
true;
224 else if(strcmp(
argv[
i],
"--export_prefix") == 0)
229 exportPrefix =
argv[
i];
230 printf(
"Export prefix=%s (--export_prefix)\n", exportPrefix.c_str());
234 printf(
"Missing value for \"--export_prefix\" option.\n");
238 else if(strcmp(
argv[
i],
"--gt") == 0)
244 printf(
"Ground truth file=%s (--gt)\n", gtFile.c_str());
247 printf(
"Wrong file format set for \"--gt\" option.\n");
253 printf(
"Missing value for \"--gt\" option.\n");
257 else if(strcmp(
argv[
i],
"--gt_max_t") == 0)
263 printf(
"Ground truth max interval=%f sec (--gt_max_t)\n", gtMaxInterval);
264 if(gtMaxInterval<0 || gtMaxInterval>10)
266 printf(
"\"--gt_max_t\" option should be between 0 and 10 sec, parsed %f sec.\n", gtMaxInterval);
272 printf(
"Missing value for \"--gt\" option.\n");
276 else if(strcmp(
argv[
i],
"--loc") == 0)
283 showLoc = atoi(
argv[
i]);
284 printf(
"Localization statistics=%d (--loc)\n", showLoc);
290 printf(
"Localization statistics (--loc)\n");
295 printf(
"Missing type for \"--loc\" option.\n");
299 else if(strcmp(
argv[
i],
"--loc_delay") == 0)
304 locDelay = atof(
argv[
i]);
305 printf(
"Localization delay=%fs (--loc_delay)\n", locDelay);
309 printf(
"Missing value for \"--loc_delay\" option.\n");
313 else if(strcmp(
argv[
i],
"--start") == 0)
318 startId = atoi(
argv[
i]);
319 printf(
"Figures will be plotted from id=%d (--start)\n", startId);
323 printf(
"Missing id for \"--start\" option.\n");
330 statsToShow.push_back(
argv[
i]);
339 invertFigures = !invertFigures;
341 std::map<std::string, std::vector<std::pair<std::string, std::vector<LocStats> > > > localizationMultiStats;
342 for(
size_t i=0;
i<statsToShow.size(); ++
i)
344 std::string figureTitle = statsToShow[
i];
348 printf(
"Plot %s\n", figureTitle.c_str());
350 fig->resize(QSize(640,480));
351 fig->setWindowTitle(figureTitle.c_str());
360 figures.insert(std::make_pair(figureTitle, fig));
363 if(showLoc & 0b111111)
365 localizationMultiStats.insert(std::make_pair(figureTitle, std::vector<std::pair<std::string, std::vector<LocStats> > >()));
374 std::map<double, Transform> externalGtPoses;
383 for(std::map<int, Transform>::iterator
iter=poses.begin();
iter!=poses.end(); ++
iter)
390 std::vector<float>
v;
394 externalGtPoses.insert(std::make_pair(
s,
iter->second));
403 std::map<int, Transform> poses;
404 std::map<int, double> stamps;
407 for(std::map<int, Transform>::iterator
iter=poses.begin();
iter!=poses.end(); ++
iter)
409 externalGtPoses.insert(std::make_pair(stamps.at(
iter->first),
iter->second));
413 if(externalGtPoses.size() < 2)
415 printf(
"Failed loading ground truth poses from \"%s\" (\"--gt\" option).\n", gtFile.c_str());
420 printf(
"Loading %ld ground truth poses from \"%s\".\n", externalGtPoses.size(), gtFile.c_str());
424 std::string fileName;
425 std::list<std::string> paths;
426 paths.push_back(
path);
427 std::vector<std::map<std::string, std::vector<float> > > outputLatexStatistics;
428 std::map<std::string, std::vector<float> > outputLatexStatisticsMap;
429 bool odomRAMSet =
false;
430 std::set<std::string> topDirs;
433 std::string currentPath = paths.front();
436 bool currentPathIsDatabase =
false;
441 currentPathIsDatabase=
true;
442 printf(
"Database: %s\n", currentPath.c_str());
449 std::list<std::string> subDirs;
450 if(!currentPathIsDatabase)
452 printf(
"Directory: %s\n", currentPath.c_str());
453 std::list<std::string> fileNames = currentDir.
getFileNames();
456 for(std::list<std::string>::iterator
iter = fileNames.begin();
iter!=fileNames.end(); ++
iter)
458 topDirs.insert(currentPath+
"/"+*
iter);
463 if(topDirs.find(currentPath) != topDirs.end())
465 if(outputLatexStatisticsMap.size())
467 outputLatexStatistics.push_back(outputLatexStatisticsMap);
468 outputLatexStatisticsMap.clear();
475 while(currentPathIsDatabase || !(fileName = currentDir.
getNextFileName()).empty())
477 int startIdPerDb = startId;
480 std::string filePath;
481 if(currentPathIsDatabase)
483 filePath = currentPath;
504 std::set<int> allIds = ids;
510 std::map<int, Transform> odomPoses, gtPoses;
511 std::map<int, double> odomStamps;
512 std::vector<float> cameraTime;
513 cameraTime.reserve(ids.size());
514 std::vector<float> odomTime;
515 odomTime.reserve(ids.size());
516 std::vector<float> slamTime;
517 slamTime.reserve(ids.size());
520 float maxOdomRAM = -1;
521 float maxMapRAM = -1;
523 if(currentPathIsDatabase && showAvailableStats)
525 std::map<std::string, int> availableStats;
526 for(std::set<int>::iterator
iter=ids.begin();
iter!=ids.end(); ++
iter)
530 for(std::map<std::string, float>::iterator jter=
stats.at(*iter).first.begin(); jter!=
stats.at(*iter).first.end(); ++jter)
532 if(availableStats.find(jter->first) != availableStats.end())
534 ++availableStats.at(jter->first);
538 availableStats.insert(std::make_pair(jter->first, 1));
543 printf(
"Showing available statistics in \"%s\":\n", filePath.c_str());
544 for(std::map<std::string, int>::iterator
iter=availableStats.begin();
iter!=availableStats.end(); ++
iter)
546 printf(
"%s (%d)\n",
iter->first.c_str(),
iter->second);
552 std::map<std::string, UPlotCurve*> curves;
553 if(statsToShow.empty())
555 for(std::map<std::string, UPlot*>::iterator
iter=figures.begin();
iter!=figures.end(); ++
iter)
557 curves.insert(std::make_pair(
iter->first,
iter->second->addCurve(filePath.c_str())));
558 if(!localizationMultiStats.empty())
559 localizationMultiStats.at(
iter->first).push_back(std::make_pair(fileName, std::vector<LocStats>()));
565 fig->setWindowTitle(filePath.c_str());
574 if(!figures.insert(std::make_pair(filePath.c_str(), fig)).second)
577 printf(
"Figure %s already added!\n", filePath.c_str());
581 for(
size_t i=0;
i<statsToShow.size(); ++
i)
583 curves.insert(std::make_pair(statsToShow[
i], fig->
addCurve(statsToShow[
i].c_str())));
584 if(!localizationMultiStats.empty())
585 localizationMultiStats.at(statsToShow[
i]).push_back(std::make_pair(fileName, std::vector<LocStats>()));
590 for(
size_t i=0;
i<statsToShow.size(); ++
i)
592 if(!localizationMultiStats.empty())
593 localizationMultiStats.at(statsToShow[
i]).push_back(std::make_pair(fileName, std::vector<LocStats>()));
598 std::set<int> mappingSessionIds;
599 if(!localizationMultiStats.empty())
604 for(std::map<int, Transform>::iterator
iter=poses.begin();
iter!=poses.end(); ++
iter)
611 std::vector<float>
v;
615 mappingSessionIds.insert(
m);
621 startIdPerDb = poses.rbegin()->first+1;
626 std::map<std::string, std::vector<float> > localizationSessionStats;
627 double previousStamp = 0.0;
628 std::map<int, int> allWeights;
629 for(std::set<int>::iterator
iter=allIds.begin();
iter!=allIds.end(); ++
iter)
636 std::vector<float>
v;
640 allWeights.insert(std::make_pair(*
iter,
w));
641 if((!ignoreInterNodes ||
w!=-1))
643 odomPoses.insert(std::make_pair(*
iter,
p));
644 odomStamps.insert(std::make_pair(*
iter,
s));
645 if(!externalGtPoses.empty())
647 std::map<double, rtabmap::Transform>::iterator nextIter = externalGtPoses.upper_bound(
s);
648 if(nextIter!=externalGtPoses.end())
650 std::map<double, rtabmap::Transform>::iterator previousIter = nextIter;
652 if(
s == previousIter->first || (nextIter->first-s <= gtMaxInterval && s-previousIter->
first <= gtMaxInterval))
654 UASSERT(
s-previousIter->first >= 0);
655 gtPoses.insert(std::make_pair(*
iter, previousIter->second.interpolate((
s-previousIter->first)/(nextIter->first-previousIter->first),nextIter->second)));
661 gtPoses.insert(std::make_pair(*
iter, gt));
664 if(!localizationMultiStats.empty() && mappingSessionIds.find(
m) != mappingSessionIds.end())
671 const std::map<std::string, float> & stat =
stats.at(*iter).first;
672 if(
uContains(stat, Statistics::kGtTranslational_rmse()))
674 rmse = stat.at(Statistics::kGtTranslational_rmse());
675 if(maxRMSE==-1 || maxRMSE < rmse)
680 if(
uContains(stat, std::string(
"Camera/TotalTime/ms")))
682 cameraTime.push_back(stat.at(std::string(
"Camera/TotalTime/ms")));
684 if(
uContains(stat, std::string(
"Odometry/TotalTime/ms")))
686 odomTime.push_back(stat.at(std::string(
"Odometry/TotalTime/ms")));
688 else if(
uContains(stat, std::string(
"Odometry/TimeEstimation/ms")))
690 odomTime.push_back(stat.at(std::string(
"Odometry/TimeEstimation/ms")));
693 if(
uContains(stat, std::string(
"RtabmapROS/TotalTime/ms")))
697 slamTime.push_back(stat.at(
"RtabmapROS/TotalTime/ms"));
700 else if(
uContains(stat, Statistics::kTimingTotal()))
704 slamTime.push_back(stat.at(Statistics::kTimingTotal()));
708 if(
uContains(stat, std::string(Statistics::kMemoryRAM_usage())))
710 float ram = stat.at(Statistics::kMemoryRAM_usage());
711 if(maxMapRAM==-1 || maxMapRAM < ram)
716 if(
uContains(stat, std::string(
"Odometry/RAM_usage/MB")))
718 float ram = stat.at(
"Odometry/RAM_usage/MB");
719 if(maxOdomRAM==-1 || maxOdomRAM < ram)
726 for(std::map<std::string, UPlotCurve*>::iterator jter=curves.begin(); jter!=curves.end(); ++jter)
729 for(std::map<std::string, std::vector<std::pair<std::string, std::vector<LocStats> > > >::
iterator jter=localizationMultiStats.begin();
730 jter!=localizationMultiStats.end();
736 double y = stat.at(jter->first);
743 jter->second->addValue(
x,
y);
746 if(!localizationMultiStats.empty())
748 if(previousStamp > 0 &&
fabs(
s - previousStamp) > locDelay &&
uContains(localizationSessionStats, jter->first))
751 for(std::map<std::string, std::vector<float> >::
iterator kter=localizationSessionStats.begin(); kter!=localizationSessionStats.end(); ++kter)
754 localizationMultiStats.at(kter->first).rbegin()->second.push_back(
values);
755 localizationSessionStats.at(kter->first).clear();
761 if(!
uContains(localizationSessionStats, jter->first))
763 localizationSessionStats.insert(std::make_pair(jter->first, std::vector<float>()));
765 localizationSessionStats.at(jter->first).push_back(
y);
775 for(std::map<std::string, std::vector<std::pair<std::string, std::vector<LocStats> > > >::
iterator jter=localizationMultiStats.begin();
776 jter!=localizationMultiStats.end();
779 if(
uContains(localizationSessionStats, jter->first) &&
780 !localizationSessionStats.at(jter->first).empty())
784 localizationMultiStats.at(jter->first).rbegin()->second.push_back(
values);
788 std::multimap<int, Link> links;
789 std::multimap<int, Link> allLinks;
793 std::multimap<int, Link> allBiLinks;
794 for(std::multimap<int, Link>::iterator jter=allLinks.begin(); jter!=allLinks.end(); ++jter)
796 if(jter->second.from() != jter->second.to() &&
800 allBiLinks.insert(std::make_pair(jter->second.to(), jter->second.inverse()));
802 allBiLinks.insert(*jter);
806 std::multimap<int, Link> loopClosureLinks;
807 for(std::multimap<int, Link>::iterator jter=allLinks.begin(); jter!=allLinks.end(); ++jter)
809 if(jter->second.from() == jter->second.to() ||
graph::findLink(links, jter->second.from(), jter->second.to(),
true) == links.end())
811 Link link = jter->second;
813 if(link.
from() != link.
to() &&
818 while(
uContains(allWeights, link.
to()) && allWeights.at(link.
to()) < 0)
820 std::multimap<int, Link>::iterator uter = allLinks.find(link.
to());
821 while(uter != allLinks.end() &&
822 uter->first==link.
to() &&
823 uter->second.from()>uter->second.to())
827 if(uter != allLinks.end())
829 link = link.
merge(uter->second, uter->second.type());
830 allLinks.erase(uter->first);
838 links.insert(std::make_pair(jter->first, link));
842 graph::findLink(loopClosureLinks, jter->second.from(), jter->second.to()) == loopClosureLinks.end())
844 loopClosureLinks.insert(*jter);
848 float bestScale = 1.0f;
850 float bestRMSEAng = -1;
851 float bestVoRMSE = -1;
853 float kitti_t_err = 0.0f;
854 float kitti_r_err = 0.0f;
855 float relative_t_err = 0.0f;
856 float relative_r_err = 0.0f;
857 float loop_t_err = 0.0f;
858 float loop_r_err = 0.0f;
862 std::map<int, Transform> posesOut;
863 std::multimap<int, Link> linksOut;
864 int firstId = *ids.begin();
866 bool useOdomGravity = Parameters::defaultMemUseOdomGravity();
870 for(std::map<int, Transform>::iterator
iter=odomPoses.begin();
iter!=odomPoses.end(); ++
iter)
875 std::map<int, Transform> posesWithLandmarks = odomPoses;
877 for(std::multimap<int, Link>::iterator
iter=links.begin();
iter!=links.end(); ++
iter)
882 if(posesWithLandmarks.find(
iter->second.from()) != posesWithLandmarks.end() && posesWithLandmarks.find(
iter->second.to()) == posesWithLandmarks.end())
884 posesWithLandmarks.insert(std::make_pair(
iter->second.to(), posesWithLandmarks.at(
iter->second.from())*
iter->second.transform()));
888 optimizer->
getConnectedGraph(firstId, posesWithLandmarks, links, posesOut, linksOut);
889 std::list<std::map<int, Transform> > intermediateGraphes;
890 std::map<int, Transform> poses = optimizer->
optimize(firstId, posesOut, linksOut, incrementalOptimization?&intermediateGraphes:0);
894 UWARN(
"Optimization failed! Try incremental optimization...");
898 UERROR(
"Incremental optimization also failed! Only original RMSE will be shown.");
903 UWARN(
"Incremental optimization succeeded!");
910 std::map<int, Transform>::iterator
iter=poses.begin();
911 while(
iter!=poses.end() &&
iter->first < 0)
916 std::map<int, Transform> groundTruth;
917 for(std::map<int, Transform>::const_iterator
iter=poses.begin();
iter!=poses.end(); ++
iter)
919 if(gtPoses.find(
iter->first) != gtPoses.end())
921 groundTruth.insert(*gtPoses.find(
iter->first));
925 outputScaled = outputScaled && groundTruth.size();
928 std::map<int, Transform> scaledPoses;
929 std::map<int, Transform> scaledOdomPoses;
931 for(std::map<int, Transform>::iterator
iter=poses.begin();
iter!=poses.end(); ++
iter)
937 scaledPoses.insert(std::make_pair(
iter->first,
t));
939 UASSERT(posesOut.find(
iter->first)!=posesOut.end());
940 t = posesOut.at(
iter->first).clone();
944 scaledOdomPoses.insert(std::make_pair(
iter->first,
t));
947 float translational_rmse = 0.0f;
948 float translational_mean = 0.0f;
949 float translational_median = 0.0f;
950 float translational_std = 0.0f;
951 float translational_min = 0.0f;
952 float translational_max = 0.0f;
953 float rotational_rmse = 0.0f;
954 float rotational_mean = 0.0f;
955 float rotational_median = 0.0f;
956 float rotational_std = 0.0f;
957 float rotational_min = 0.0f;
958 float rotational_max = 0.0f;
965 translational_median,
975 float translational_rmse_vo = translational_rmse;
982 translational_median,
993 if(bestRMSE!=-1 && translational_rmse > bestRMSE)
997 bestRMSE = translational_rmse;
998 bestVoRMSE = translational_rmse_vo;
999 bestRMSEAng = rotational_rmse;
1001 bestGtToMap = gtToMap;
1009 for(std::map<int, Transform>::iterator
iter=poses.begin();
iter!=poses.end(); ++
iter)
1011 iter->second.x()*=bestScale;
1012 iter->second.y()*=bestScale;
1013 iter->second.z()*=bestScale;
1014 iter->second = bestGtToMap *
iter->second;
1017 if(outputRelativeError)
1019 if(groundTruth.size() == poses.size())
1026 std::vector<Transform> gtPosesTmp;
1027 std::vector<Transform> rPoses;
1028 for(std::map<int, Transform>::iterator
iter=poses.begin();
iter!=poses.end(); ++
iter)
1030 if(groundTruth.find(
iter->first) != groundTruth.end())
1032 gtPosesTmp.push_back(groundTruth.at(
iter->first));
1033 rPoses.push_back(poses.at(
iter->first));
1036 if(!gtPosesTmp.empty())
1043 if(outputKittiError)
1045 if(groundTruth.size() == poses.size())
1052 printf(
"Cannot compute KITTI statistics as optimized poses and ground truth don't have the same size (%d vs %d).\n",
1053 (
int)poses.size(), (
int)groundTruth.size());
1061 dbName = dbName.substr(0, dbName.size()-3);
1063 std::multimap<int, Link> dummyLinks;
1064 std::map<int, double> stamps;
1065 if(!outputKittiError)
1067 for(std::map<int, Transform>::iterator
iter=poses.begin();
iter!=poses.end(); ++
iter)
1069 UASSERT(odomStamps.find(
iter->first) != odomStamps.end());
1070 stamps.insert(*odomStamps.find(
iter->first));
1075 printf(
"Could not export the poses to \"%s\"!?!\n",
path.c_str());
1081 if(!outputKittiError)
1083 for(std::map<int, Transform>::iterator
iter=odomPoses.begin();
iter!=odomPoses.end(); ++
iter)
1085 UASSERT(odomStamps.find(
iter->first) != odomStamps.end());
1086 stamps.insert(*odomStamps.find(
iter->first));
1091 printf(
"Could not export the odometry to \"%s\"!?!\n",
path.c_str());
1095 if(groundTruth.size())
1099 if(!outputKittiError)
1101 for(std::map<int, Transform>::iterator
iter=groundTruth.begin();
iter!=groundTruth.end(); ++
iter)
1103 UASSERT(odomStamps.find(
iter->first) != odomStamps.end());
1104 stamps.insert(*odomStamps.find(
iter->first));
1109 printf(
"Could not export the ground truth to \"%s\"!?!\n",
path.c_str());
1116 bool fillHeader =
false;
1117 std::ifstream
f(
"report.csv");
1123 std::ofstream myfile;
1124 myfile.open(
"report.csv", std::fstream::in | std::fstream::out |
std::fstream::app);
1127 myfile <<
"Rosbag name"<<
";"<<
"error linear (m)"<<
";"<<
"error linear max (m)"<<
";"<<
"error linear odom (m)"<<
";"
1128 <<
"error angular"<<
";"
1129 <<
"Slam avg (hz)"<<
";"<<
"Slam max (hz)"<<
";"
1130 <<
"Odom avg (hz)"<<
";"<<
"Odom max (hz)"<<std::endl;
1133 myfile <<fileName.c_str()<<
";"
1138 <<(1/(
uMean(slamTime)/1000.0))<<
";"
1139 <<(1/(
uMax(slamTime)/1000.0))<<
";"
1140 <<(1/(
uMean(odomTime)/1000.0))<<
";"
1141 <<(1/(
uMax(odomTime)/1000.0))<<
";"<<std::endl;
1145 if(outputLoopAccuracy && !groundTruth.empty() && !linksOut.empty())
1147 float sumDist = 0.0f;
1148 float sumAngle = 0.0f;
1150 for(std::multimap<int, Link>::iterator
iter=loopClosureLinks.begin();
iter!=loopClosureLinks.end(); ++
iter)
1152 if( groundTruth.find(
iter->second.from())!=groundTruth.end() &&
1153 groundTruth.find(
iter->second.to())!=groundTruth.end())
1158 t.r11(),
t.r12(),
t.r13(),
t.
x()*bestScale,
1159 t.r21(),
t.r22(),
t.r23(),
t.
y()*bestScale,
1160 t.r31(),
t.r32(),
t.r33(),
t.z()*bestScale);
1162 sumDist +=
diff.getNorm();
1163 sumAngle +=
diff.getAngle();
1171 loop_r_err *= 180/CV_PI;
1176 printf(
" %s (%d, s=%.3f):\terror lin=%.3fm (max=%s, odom=%.3fm) ang=%.1fdeg%s%s, %s: avg=%dms (max=%dms) loops=%d%s, odom: avg=%dms (max=%dms), camera: avg=%dms, %smap=%dMB\n",
1184 !outputKittiError?
"":
uFormat(
", KITTI: t_err=%.2f%% r_err=%.2f deg/100m", kitti_t_err, kitti_r_err*100).
c_str(),
1185 !outputRelativeError?
"":
uFormat(
", Relative: t_err=%.3fm r_err=%.2f deg", relative_t_err, relative_r_err).
c_str(),
1186 !localizationMultiStats.empty()?
"loc":
"slam",
1187 (
int)
uMean(slamTime), (
int)
uMax(slamTime),
1188 (
int)loopClosureLinks.size(),
1189 !outputLoopAccuracy?
"":
uFormat(
" (t_err=%.3fm r_err=%.2f deg)", loop_t_err, loop_r_err).
c_str(),
1190 (
int)
uMean(odomTime), (
int)
uMax(odomTime),
1191 (
int)
uMean(cameraTime),
1192 maxOdomRAM!=-1.0
f?
uFormat(
"RAM odom=%dMB ", (
int)maxOdomRAM).
c_str():
"",
1197 std::vector<float>
stats;
1198 stats.push_back(ids.size());
1199 stats.push_back(bestRMSE);
1200 stats.push_back(maxRMSE);
1201 stats.push_back(bestRMSEAng);
1205 stats.push_back(maxOdomRAM);
1206 stats.push_back(maxMapRAM);
1207 outputLatexStatisticsMap.insert(std::make_pair(filePath,
stats));
1209 if(maxOdomRAM != -1.0
f)
1223 currentPathIsDatabase =
false;
1226 if(!localizationMultiStats.empty() && showLoc!=-1)
1228 printf(
"---Localization results---\n");
1229 std::string prefix =
"header={";
1230 printf(
"%s", prefix.c_str());
1231 for(std::vector<std::pair<std::string, std::vector<LocStats> > >::
iterator iter=localizationMultiStats.begin()->second.begin();
1232 iter!=localizationMultiStats.begin()->second.end();)
1234 if(
iter!=localizationMultiStats.begin()->second.begin())
1236 printf(
"%s", std::string(prefix.size(),
' ').c_str());
1238 printf(
"%s",
iter->first.c_str());
1240 if(
iter!=localizationMultiStats.begin()->second.end())
1248 for(std::map<std::string, std::vector<std::pair<std::string, std::vector<LocStats> > > >::
iterator iter=localizationMultiStats.begin();
1249 iter!=localizationMultiStats.end();
1252 printf(
"%s\n",
iter->first.c_str());
1253 for(
int k=0; k<6; ++k)
1255 if(showLoc & (0
x1 << k))
1257 std::string prefix =
uFormat(
" %s=[",
1264 printf(
"%s", prefix.c_str());
1265 for(std::vector<std::pair<std::string, std::vector<LocStats> > >::
iterator jter=
iter->second.begin(); jter!=
iter->second.end();)
1267 if(jter!=
iter->second.begin())
1269 printf(
"%s", std::string(prefix.size(),
' ').c_str());
1271 for(
size_t j=0;
j<jter->second.size(); ++
j)
1276 k==0?jter->second[
j].min:
1277 k==1?jter->second[
j].max:
1278 k==2?jter->second[
j].mean:
1279 jter->second[
j].stddev);
1283 printf(
"%d",jter->second[
j].total);
1287 printf(
"%.2f", (jter->second[
j].nonNull*100));
1289 if(
j+1 < jter->second.size())
1295 if(jter!=
iter->second.end())
1303 iter->second.clear();
1307 for(std::list<std::string>::iterator
iter=subDirs.begin();
iter!=subDirs.end(); ++
iter)
1309 paths.push_front(*
iter);
1312 if(outputLatexStatisticsMap.size() && paths.empty())
1314 outputLatexStatistics.push_back(outputLatexStatisticsMap);
1315 outputLatexStatisticsMap.clear();
1319 if(outputLatex && outputLatexStatistics.size())
1321 printf(
"\nLaTeX output:\n----------------\n");
1322 printf(
"\\begin{table*}[!t]\n");
1323 printf(
"\\caption{$t_{end}$ is the absolute translational RMSE value at the end "
1324 "of the experiment as $ATE_{max}$ is the maximum during the experiment. "
1325 "$r_{end}$ is rotational RMSE value at the end of the experiment. "
1326 "$o_{avg}$ and $m_{avg}$ are the average computational time "
1327 "for odometry (front-end) and map update (back-end). "
1328 "$m_{avg}$ is the maximum computational time for map update. "
1329 "$O_{end}$ and $M_{end}$ are the RAM usage at the end of the experiment "
1330 "for odometry and map management respectively.}\n");
1331 printf(
"\\label{}\n");
1332 printf(
"\\centering\n");
1335 printf(
"\\begin{tabular}{l|c|c|c|c|c|c|c|c|c}\n");
1336 printf(
"\\cline{2-10}\n");
1337 printf(
" & Size & $t_{end}$ & $t_{max}$ & $r_{end}$ & $o_{avg}$ & $m_{avg}$ & $m_{max}$ & $O_{end}$ & $M_{end}$ \\\\\n");
1338 printf(
" & (nodes) & (m) & (m) & (deg) & (ms) & (ms) & (ms) & (MB) & (MB) \\\\\n");
1342 printf(
"\\begin{tabular}{l|c|c|c|c|c|c|c|c}\n");
1343 printf(
"\\cline{2-9}\n");
1344 printf(
" & Size & $t_{end}$ & $t_{max}$ & $r_{end}$ & $o_{avg}$ & $m_{avg}$ & $m_{max}$ & $M_{end}$ \\\\\n");
1345 printf(
" & (nodes) & (m) & (m) & (deg) & (ms) & (ms) & (ms) & (MB) \\\\\n");
1348 printf(
"\\hline\n");
1350 for(
unsigned int j=0;
j<outputLatexStatistics.size(); ++
j)
1352 if(outputLatexStatistics[
j].
size())
1354 std::vector<int> lowestIndex;
1355 if(outputLatexStatistics[
j].
size() > 1)
1357 std::vector<float> lowestValue(outputLatexStatistics[
j].begin()->
second.size(),-1);
1358 lowestIndex = std::vector<int>(lowestValue.size(),0);
1360 for(std::map<std::string, std::vector<float> >::
iterator iter=outputLatexStatistics[
j].begin();
iter!=outputLatexStatistics[
j].end(); ++
iter)
1362 UASSERT(lowestValue.size() ==
iter->second.size());
1363 for(
unsigned int i=0;
i<
iter->second.size(); ++
i)
1365 if(lowestValue[
i] == -1 || (
iter->second[
i]>0.0f && lowestValue[
i]>
iter->second[
i]))
1367 lowestValue[
i] =
iter->second[
i];
1368 lowestIndex[
i] = index;
1376 for(std::map<std::string, std::vector<float> >::
iterator iter=outputLatexStatistics[
j].begin();
iter!=outputLatexStatistics[
j].end(); ++
iter)
1380 printf(
"%d & ", (
int)
iter->second[0]);
1381 printf(
"%s%.3f%s & ", lowestIndex.size()&&lowestIndex[1]==index?
"\\textbf{":
"",
iter->second[1], lowestIndex.size()&&lowestIndex[1]==index?
"}":
"");
1382 printf(
"%s%.3f%s & ", lowestIndex.size()&&lowestIndex[2]==index?
"\\textbf{":
"",
iter->second[2], lowestIndex.size()&&lowestIndex[2]==index?
"}":
"");
1383 printf(
"%s%.2f%s & ", lowestIndex.size()&&lowestIndex[3]==index?
"\\textbf{":
"",
iter->second[3], lowestIndex.size()&&lowestIndex[3]==index?
"}":
"");
1384 printf(
"%s%d%s & ", lowestIndex.size()&&lowestIndex[4]==index?
"\\textbf{":
"", (
int)
iter->second[4], lowestIndex.size()&&lowestIndex[4]==index?
"}":
"");
1385 printf(
"%s%d%s & ", lowestIndex.size()&&lowestIndex[5]==index?
"\\textbf{":
"", (
int)
iter->second[5], lowestIndex.size()&&lowestIndex[5]==index?
"}":
"");
1386 printf(
"%s%d%s & ", lowestIndex.size()&&lowestIndex[6]==index?
"\\textbf{":
"", (
int)
iter->second[6], lowestIndex.size()&&lowestIndex[6]==index?
"}":
"");
1389 printf(
"%s%d%s & ", lowestIndex.size()&&lowestIndex[7]==index?
"\\textbf{":
"", (
int)
iter->second[7], lowestIndex.size()&&lowestIndex[7]==index?
"}":
"");
1391 printf(
"%s%d%s ", lowestIndex.size()&&lowestIndex[8]==index?
"\\textbf{":
"", (
int)
iter->second[8], lowestIndex.size()&&lowestIndex[8]==index?
"}":
"");
1395 printf(
"\\hline\n");
1400 printf(
"\\end{tabular}\n");
1401 printf(
"\\end{table*}\n----------------\n");
1406 for(std::map<std::string, UPlot*>::iterator
iter=figures.begin();
iter!=figures.end(); ++
iter)
1410 iter->second->frameData();
1414 QString
data =
iter->second->getAllCurveDataAsText();
1417 QString filePath = QString(exportPrefix.c_str()) + (exportPrefix.empty()?
"":
"-") +
iter->second->windowTitle().replace(
'/',
"-") +
".txt";
1418 QFile
file(filePath);
1419 if(
file.open(QIODevice::Text | QIODevice::WriteOnly))
1423 printf(
"Exported \"%s\".\n", filePath.toStdString().c_str());
1427 printf(
"ERROR: could not open file \"%s\" for writing!\n", filePath.toStdString().c_str());
1433 iter->second->show();