17 #include <mrpt/gui/CDisplayWindowGUI.h>
20 #include <mrpt/3rdparty/tclap/CmdLine.h>
21 #include <mrpt/config.h>
22 #include <mrpt/config/CConfigFile.h>
23 #include <mrpt/core/round.h>
24 #include <mrpt/opengl/CEllipsoid3D.h>
25 #include <mrpt/opengl/CGridPlaneXY.h>
26 #include <mrpt/opengl/COpenGLScene.h>
27 #include <mrpt/opengl/stock_objects.h>
28 #include <mrpt/poses/CPosePDFGaussian.h>
29 #include <mrpt/poses/Lie/SO.h>
30 #include <mrpt/system/CDirectoryExplorer.h>
31 #include <mrpt/system/filesystem.h>
32 #include <mrpt/system/os.h>
33 #include <mrpt/system/progress.h>
34 #include <mrpt/system/string_utils.h>
38 #include "../libcfgpath/cfgpath.h"
40 constexpr
const char*
APP_NAME =
"icp-log-viewer";
49 "e",
"file-extension",
50 "Filename extension to look for. Default is `icplog`",
false,
"icplog",
54 "d",
"directory",
"Directory in which to search for *.icplog files.",
false,
58 "f",
"file",
"Load just this one single log *.icplog file.",
false,
59 "log.icplog",
"log.icplog",
cmd);
63 "One or more (comma separated) *.so files to load as plugins",
false,
64 "foobar.so",
"foobar.so",
cmd);
67 "",
"autoplay-period",
68 "The period (in seconds) between timestamps to load and show in autoplay "
70 false, 0.1,
"period [seconds]",
cmd);
75 auto glVizICP = mrpt::opengl::CSetOfObjects::Create();
76 mrpt::gui::CDisplayWindowGUI::Ptr win;
78 nanogui::Slider* slSelectorICP =
nullptr;
79 nanogui::Button *btnSelectorBack =
nullptr, *btnSelectorForw =
nullptr;
80 nanogui::Button* btnSelectorAutoplay =
nullptr;
81 bool isAutoPlayActive =
false;
82 double lastAutoPlayTime = .0;
84 std::array<nanogui::TextBox*, 5> lbICPStats = {
85 nullptr,
nullptr,
nullptr,
nullptr,
nullptr};
86 nanogui::CheckBox* cbShowInitialPose =
nullptr;
87 nanogui::Label* lbICPIteration =
nullptr;
88 nanogui::Slider* slIterationDetails =
nullptr;
90 const size_t MAX_VARIABLE_LIST = 100;
91 std::vector<nanogui::TextBox*> lbDynVariables;
93 nanogui::CheckBox* cbViewOrtho =
nullptr;
94 nanogui::CheckBox* cbCameraFollowsLocal =
nullptr;
95 nanogui::CheckBox* cbViewVoxelsAsPoints =
nullptr;
96 nanogui::CheckBox* cbViewVoxelsFreeSpace =
nullptr;
97 nanogui::CheckBox* cbColorizeLocalMap =
nullptr;
98 nanogui::CheckBox* cbColorizeGlobalMap =
nullptr;
100 nanogui::CheckBox* cbViewPairings =
nullptr;
102 nanogui::CheckBox* cbViewPairings_pt2pt =
nullptr;
103 nanogui::CheckBox* cbViewPairings_pt2pl =
nullptr;
104 nanogui::CheckBox* cbViewPairings_pt2ln =
nullptr;
106 nanogui::TextBox *tbLogPose =
nullptr, *tbInitialGuess =
nullptr,
107 *tbInit2Final =
nullptr, *tbCovariance =
nullptr,
108 *tbConditionNumber =
nullptr, *tbPairings =
nullptr;
110 nanogui::Slider* slPairingsPl2PlSize =
nullptr;
111 nanogui::Slider* slPairingsPl2LnSize =
nullptr;
112 nanogui::Slider* slGlobalPointSize =
nullptr;
113 nanogui::Slider* slLocalPointSize =
nullptr;
114 nanogui::Slider* slMidDepthField =
nullptr;
115 nanogui::Slider* slThicknessDepthField =
nullptr;
116 nanogui::Label * lbDepthFieldValues =
nullptr, *lbDepthFieldMid =
nullptr,
117 *lbDepthFieldThickness =
nullptr;
119 nanogui::Slider* slGTPose[6] = {
nullptr,
nullptr,
nullptr,
120 nullptr,
nullptr,
nullptr};
122 std::vector<std::string> layerNames_global, layerNames_local;
124 std::map<std::string, nanogui::CheckBox*> cbLayersByName_global;
125 std::map<std::string, nanogui::CheckBox*> cbLayersByName_local;
130 DelayedLoadLog() =
default;
133 : filename_(fileName), shortFileName_(shortFileName)
148 void dispose() { log_.reset(); }
151 const std::string& shortFileName()
const {
return shortFileName_; }
154 std::optional<mp2p_icp::LogRecord> log_;
158 std::vector<DelayedLoadLog> logRecords;
160 static void rebuild_3d_view(
bool regenerateMaps =
true);
164 void rebuild_3d_view_fast() { rebuild_3d_view(
false); }
166 void processAutoPlay()
168 if (!isAutoPlayActive)
return;
170 const double tNow = mrpt::Clock::nowDouble();
173 lastAutoPlayTime = tNow;
175 if (slSelectorICP->value() < slSelectorICP->range().second - 0.01f)
177 slSelectorICP->setValue(slSelectorICP->value() + 1);
182 void updateMiniCornerView()
184 auto gl_view = win->background_scene->getViewport(
"small-view");
185 if (!gl_view)
return;
187 mrpt::opengl::CCamera& view_cam = gl_view->getCamera();
189 view_cam.setAzimuthDegrees(win->camera().getAzimuthDegrees());
190 view_cam.setElevationDegrees(win->camera().getElevationDegrees());
191 view_cam.setZoomDistance(5);
196 using namespace std::string_literals;
198 mrpt::system::CDirectoryExplorer::TFileInfoList files;
203 ASSERT_DIRECTORY_EXISTS_(searchDir);
205 std::cout <<
"Searching in: '" << searchDir
206 <<
"' for files with extension '" <<
argExtension.getValue()
209 mrpt::system::CDirectoryExplorer::explore(
210 searchDir, FILE_ATTRIB_ARCHIVE, files);
211 mrpt::system::CDirectoryExplorer::filterByExtension(
213 mrpt::system::CDirectoryExplorer::sortByName(files);
215 std::cout <<
"Found " << files.size() <<
" ICP records." << std::endl;
220 std::cout <<
"Loading one single log file: " <<
argSingleFile.getValue()
228 for (
const auto& file : files)
229 logRecords.emplace_back(file.wholePath, file.name);
231 ASSERT_(!logRecords.empty());
235 const auto& lr = logRecords.front().get();
236 if (layerNames_global.empty() && lr.pcGlobal)
238 for (
const auto& layer : lr.pcGlobal->layers)
240 layerNames_global.push_back(layer.first);
241 std::cout <<
"Global point cloud: Found point layer='"
242 << layer.first <<
"'\n";
245 if (layerNames_local.empty() && lr.pcLocal)
247 for (
const auto& layer : lr.pcLocal->layers)
249 layerNames_local.push_back(layer.first);
250 std::cout <<
"Local point cloud: Found point layer='"
251 << layer.first <<
"'\n";
257 char appCfgFile[1024];
259 mrpt::config::CConfigFile appCfg(appCfgFile);
268 mrpt::gui::CDisplayWindowGUI_Params cp;
270 win = mrpt::gui::CDisplayWindowGUI::Create(
APP_NAME, 1024, 800, cp);
273 auto scene = mrpt::opengl::COpenGLScene::Create();
275 auto glGrid = mrpt::opengl::CGridPlaneXY::Create();
276 glGrid->setColor_u8(0xff, 0xff, 0xff, 0x10);
277 scene->insert(glGrid);
280 auto gl_base = mrpt::opengl::stock_objects::CornerXYZ(1.0
f);
281 gl_base->setName(
"Global");
282 gl_base->enableShowName();
283 scene->insert(gl_base);
285 scene->insert(glVizICP);
288 std::lock_guard<std::mutex> lck(win->background_scene_mtx);
289 win->background_scene = std::move(scene);
293 auto w = win->createManagedSubWindow(
"Control");
294 w->setPosition({5, 25});
296 w->setLayout(
new nanogui::BoxLayout(
297 nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 5, 2));
300 cbShowInitialPose = w->add<nanogui::CheckBox>(
"Show at INITIAL GUESS pose");
301 cbShowInitialPose->setCallback(
304 if (slIterationDetails->enabled())
306 const auto& r = slIterationDetails->range();
307 slIterationDetails->setValue(v ? r.first : r.second);
309 rebuild_3d_view_fast();
311 lbICPIteration = w->add<nanogui::Label>(
"Show ICP iteration:");
312 slIterationDetails = w->add<nanogui::Slider>();
313 slIterationDetails->setRange({.0f, 1.0f});
314 slIterationDetails->setCallback([](
float) { rebuild_3d_view_fast(); });
317 w->add<nanogui::Label>(
" ");
318 w->add<nanogui::Label>(mrpt::format(
319 "Select ICP record file (N=%u)",
320 static_cast<unsigned int>(logRecords.size())));
321 slSelectorICP = w->add<nanogui::Slider>();
322 slSelectorICP->setRange({.0f, logRecords.size() - 1});
323 slSelectorICP->setValue(0);
324 slSelectorICP->setCallback([&](
float ) { rebuild_3d_view(); });
326 for (
auto& lb : lbICPStats)
328 lb = w->add<nanogui::TextBox>(
" ");
330 lb->setAlignment(nanogui::TextBox::Alignment::Left);
331 lb->setEditable(
true);
336 auto pn = w->add<nanogui::Widget>();
337 pn->setLayout(
new nanogui::BoxLayout(nanogui::Orientation::Horizontal));
340 auto s = slSelectorICP;
343 pn->add<nanogui::Button>(
"", ENTYPO_ICON_CONTROLLER_FAST_BACKWARD);
344 btnSelectorBack->setCallback(
349 s->setValue(
s->value() - 1);
350 s->callback()(
s->value());
355 pn->add<nanogui::Button>(
"", ENTYPO_ICON_CONTROLLER_FAST_FORWARD);
356 btnSelectorForw->setCallback(
359 if (
s->value() <
s->range().second - 0.01f)
361 s->setValue(
s->value() + 1);
366 pn->add<nanogui::Label>(
" ");
368 btnSelectorAutoplay =
369 pn->add<nanogui::Button>(
"", ENTYPO_ICON_CONTROLLER_PLAY);
370 btnSelectorAutoplay->setFlags(nanogui::Button::ToggleButton);
372 btnSelectorAutoplay->setChangeCallback([&](
bool active)
373 { isAutoPlayActive = active; });
377 w->add<nanogui::Label>(
" ");
379 auto tabWidget = w->add<nanogui::TabWidget>();
381 auto* tab1 = tabWidget->createTab(
"Summary");
382 tab1->setLayout(
new nanogui::BoxLayout(
383 nanogui::Orientation::Vertical, nanogui::Alignment::Fill));
385 auto* tab2 = tabWidget->createTab(
"Variables");
386 tab2->setLayout(
new nanogui::BoxLayout(
387 nanogui::Orientation::Vertical, nanogui::Alignment::Fill));
389 auto* tab3 = tabWidget->createTab(
"Maps");
390 tab3->setLayout(
new nanogui::GroupLayout());
392 auto* tab4 = tabWidget->createTab(
"Pairings");
393 tab4->setLayout(
new nanogui::GroupLayout());
395 auto* tab5 = tabWidget->createTab(
"View");
396 tab5->setLayout(
new nanogui::GroupLayout());
398 auto* tab6 = tabWidget->createTab(
"Manual");
399 tab6->setLayout(
new nanogui::GroupLayout());
401 tabWidget->setActiveTab(0);
404 tab1->add<nanogui::Label>(
405 "ICP result pose [x y z yaw(deg) pitch(deg) roll(deg)]:")
407 tbLogPose = tab1->add<nanogui::TextBox>();
409 tbLogPose->setEditable(
true);
410 tbLogPose->setAlignment(nanogui::TextBox::Alignment::Left);
413 tab1->add<nanogui::Label>(
"Initial -> final pose change:")
415 tbInit2Final = tab1->add<nanogui::TextBox>();
417 tbInit2Final->setEditable(
false);
420 tab1->add<nanogui::Label>(
421 "Uncertainty: diagonal sigmas (x y z yaw pitch roll)")
423 tbCovariance = tab1->add<nanogui::TextBox>();
425 tbCovariance->setEditable(
false);
428 tab1->add<nanogui::Label>(
"Uncertainty: Covariance condition numbers")
430 tbConditionNumber = tab1->add<nanogui::TextBox>();
432 tbConditionNumber->setEditable(
false);
433 tab1->add<nanogui::Label>(
" ");
435 tab6->add<nanogui::Label>(
"Manual solution modification:");
436 const float handTunedRange[6] = {4.0, 4.0, 10.0,
437 0.5 * M_PI, 0.25 * M_PI, 0.5};
439 for (
int i = 0; i < 6; i++)
441 slGTPose[i] = tab6->add<nanogui::Slider>();
442 slGTPose[i]->setRange({-handTunedRange[i], handTunedRange[i]});
443 slGTPose[i]->setValue(0.0
f);
445 slGTPose[i]->setCallback(
448 const size_t idx = mrpt::round(slSelectorICP->value());
449 auto& lr = logRecords.at(idx).get();
451 auto p = lr.icpResult.optimal_tf.mean.asTPose();
453 lr.icpResult.optimal_tf.mean = mrpt::poses::CPose3D(p);
455 rebuild_3d_view_fast();
459 tab1->add<nanogui::Label>(
"Initial guess pose:");
460 tbInitialGuess = tab1->add<nanogui::TextBox>();
461 tbInitialGuess->setFontSize(14);
462 tbInitialGuess->setEditable(
true);
463 tbInitialGuess->setAlignment(nanogui::TextBox::Alignment::Left);
469 {{
"mm",
"mp2p_icp::metric_map_t binary serialized object (*.mm)"}},
471 if (outFile.empty())
return;
472 m.save_to_file(outFile);
477 tab2->add<nanogui::Label>(
"Dynamic variables:");
479 auto vscroll = tab2->add<nanogui::VScrollPanel>();
482 auto wrapper = vscroll->add<nanogui::Widget>();
485 wrapper->setLayout(
new nanogui::BoxLayout(
486 nanogui::Orientation::Vertical, nanogui::Alignment::Fill));
488 for (
size_t i = 0; i < MAX_VARIABLE_LIST; i++)
490 auto* tb = wrapper->add<nanogui::TextBox>(
"aaaa");
491 tb->setAlignment(nanogui::TextBox::Alignment::Left);
492 tb->setEditable(
true);
494 lbDynVariables.push_back(tb);
500 auto pn = tab3->add<nanogui::Widget>();
501 pn->setLayout(
new nanogui::BoxLayout(
502 nanogui::Orientation::Horizontal, nanogui::Alignment::Fill));
503 pn->add<nanogui::Button>(
"Export 'local' map...")
507 const size_t idx = mrpt::round(slSelectorICP->value());
508 auto& lr = logRecords.at(idx).get();
510 lambdaSave(*lr.pcLocal);
512 pn->add<nanogui::Button>(
"Export 'global' map...")
516 const size_t idx = mrpt::round(slSelectorICP->value());
517 auto& lr = logRecords.at(idx).get();
518 ASSERT_(lr.pcGlobal);
519 lambdaSave(*lr.pcGlobal);
523 tab3->add<nanogui::Label>(
"[GLOBAL map] Visible layers:");
525 for (
size_t i = 0; i < layerNames_global.size(); i++)
527 auto cb = tab3->add<nanogui::CheckBox>(layerNames_global.at(i));
528 cb->setChecked(
true);
529 cb->setCallback([](
bool) { rebuild_3d_view(); });
532 cbLayersByName_global[layerNames_global.at(i)] = cb;
535 tab3->add<nanogui::Label>(
"[LOCAL map] Visible layers:");
536 for (
size_t i = 0; i < layerNames_local.size(); i++)
538 auto cb = tab3->add<nanogui::CheckBox>(layerNames_local.at(i));
539 cb->setChecked(
true);
540 cb->setCallback([](
bool) { rebuild_3d_view(); });
543 cbLayersByName_local[layerNames_local.at(i)] = cb;
547 tbPairings = tab4->add<nanogui::TextBox>();
548 tbPairings->setFontSize(16);
549 tbPairings->setEditable(
false);
551 cbViewPairings = tab4->add<nanogui::CheckBox>(
"View pairings");
552 cbViewPairings->setCallback([](
bool) { rebuild_3d_view_fast(); });
554 cbViewPairings_pt2pt = tab4->add<nanogui::CheckBox>(
"View: point-to-point");
555 cbViewPairings_pt2pt->setChecked(
true);
556 cbViewPairings_pt2pt->setCallback([](
bool) { rebuild_3d_view_fast(); });
559 auto pn = tab4->add<nanogui::Widget>();
560 pn->setLayout(
new nanogui::GridLayout(
561 nanogui::Orientation::Horizontal, 3, nanogui::Alignment::Fill));
563 cbViewPairings_pt2pl =
564 pn->add<nanogui::CheckBox>(
"View: point-to-plane");
565 cbViewPairings_pt2pl->setChecked(
true);
566 cbViewPairings_pt2pl->setCallback([](
bool) { rebuild_3d_view_fast(); });
568 pn->add<nanogui::Label>(
"Plane size:");
569 slPairingsPl2PlSize = pn->add<nanogui::Slider>();
570 slPairingsPl2PlSize->setRange({-2.0f, 2.0f});
571 slPairingsPl2PlSize->setValue(-1.0
f);
572 slPairingsPl2PlSize->setCallback([&](
float)
573 { rebuild_3d_view_fast(); });
577 auto pn = tab4->add<nanogui::Widget>();
578 pn->setLayout(
new nanogui::GridLayout(
579 nanogui::Orientation::Horizontal, 3, nanogui::Alignment::Fill));
581 cbViewPairings_pt2ln =
582 pn->add<nanogui::CheckBox>(
"View: point-to-line");
583 cbViewPairings_pt2ln->setChecked(
true);
584 cbViewPairings_pt2ln->setCallback([](
bool) { rebuild_3d_view_fast(); });
586 pn->add<nanogui::Label>(
"Line length:");
587 slPairingsPl2LnSize = pn->add<nanogui::Slider>();
588 slPairingsPl2LnSize->setRange({-2.0f, 2.0f});
589 slPairingsPl2LnSize->setValue(-1.0
f);
590 slPairingsPl2LnSize->setCallback([&](
float)
591 { rebuild_3d_view_fast(); });
596 auto pn = tab5->add<nanogui::Widget>();
597 pn->setLayout(
new nanogui::GridLayout(
598 nanogui::Orientation::Horizontal, 2, nanogui::Alignment::Fill));
600 pn->add<nanogui::Label>(
"Global map point size");
602 slGlobalPointSize = pn->add<nanogui::Slider>();
603 slGlobalPointSize->setRange({1.0f, 10.0f});
604 slGlobalPointSize->setValue(2.0
f);
605 slGlobalPointSize->setCallback([&](
float) { rebuild_3d_view(); });
608 auto pn = tab5->add<nanogui::Widget>();
609 pn->setLayout(
new nanogui::GridLayout(
610 nanogui::Orientation::Horizontal, 2, nanogui::Alignment::Fill));
612 pn->add<nanogui::Label>(
"Local map point size");
614 slLocalPointSize = pn->add<nanogui::Slider>();
615 slLocalPointSize->setRange({1.0f, 10.0f});
616 slLocalPointSize->setValue(2.0
f);
617 slLocalPointSize->setCallback([&](
float) { rebuild_3d_view(); });
620 auto pn = tab5->add<nanogui::Widget>();
621 pn->setLayout(
new nanogui::GridLayout(
622 nanogui::Orientation::Horizontal, 2, nanogui::Alignment::Fill));
624 lbDepthFieldMid = pn->add<nanogui::Label>(
"Center depth clip plane:");
625 slMidDepthField = pn->add<nanogui::Slider>();
627 slMidDepthField->setRange({-2.0, 3.0});
628 slMidDepthField->setValue(1.0
f);
629 slMidDepthField->setCallback([&](
float) { rebuild_3d_view_fast(); });
632 auto pn = tab5->add<nanogui::Widget>();
633 pn->setLayout(
new nanogui::GridLayout(
634 nanogui::Orientation::Horizontal, 2, nanogui::Alignment::Fill));
636 lbDepthFieldThickness =
637 pn->add<nanogui::Label>(
"Max-Min depth thickness:");
638 slThicknessDepthField = pn->add<nanogui::Slider>();
640 slThicknessDepthField->setRange({-2.0, 6.0});
641 slThicknessDepthField->setValue(3.0);
642 slThicknessDepthField->setCallback([&](
float) { rebuild_3d_view_fast(); });
643 lbDepthFieldValues = tab5->add<nanogui::Label>(
" ");
645 cbViewOrtho = tab5->add<nanogui::CheckBox>(
"Orthogonal view");
646 cbViewOrtho->setCallback([&](
bool) { rebuild_3d_view_fast(); });
648 cbCameraFollowsLocal =
649 tab5->add<nanogui::CheckBox>(
"Camera follows 'local'");
650 cbCameraFollowsLocal->setCallback([&](
bool) { rebuild_3d_view_fast(); });
652 cbViewVoxelsAsPoints =
653 tab5->add<nanogui::CheckBox>(
"Render voxel maps as point clouds");
654 cbViewVoxelsAsPoints->setChecked(
true);
655 cbViewVoxelsAsPoints->setCallback([&](
bool) { rebuild_3d_view(); });
657 cbViewVoxelsFreeSpace =
658 tab5->add<nanogui::CheckBox>(
"Render free space of voxel maps");
659 cbViewVoxelsFreeSpace->setChecked(
false);
660 cbViewVoxelsFreeSpace->setCallback([&](
bool) { rebuild_3d_view(); });
662 cbColorizeLocalMap = tab5->add<nanogui::CheckBox>(
"Recolorize local map");
663 cbColorizeLocalMap->setCallback([&](
bool) { rebuild_3d_view(); });
665 cbColorizeGlobalMap = tab5->add<nanogui::CheckBox>(
"Recolorize global map");
666 cbColorizeGlobalMap->setCallback([&](
bool) { rebuild_3d_view(); });
669 w->add<nanogui::Label>(
" ");
670 w->add<nanogui::Button>(
"Quit", ENTYPO_ICON_ARROW_BOLD_LEFT)
671 ->setCallback([]() { win->setVisible(
false); });
673 win->setKeyboardCallback(
674 [&](
int key, [[maybe_unused]]
int scancode,
int action,
675 [[maybe_unused]]
int modifiers)
677 if (action != GLFW_PRESS && action != GLFW_REPEAT)
return false;
688 case GLFW_KEY_PAGE_DOWN:
691 case GLFW_KEY_PAGE_UP:
695 isAutoPlayActive = !isAutoPlayActive;
696 btnSelectorAutoplay->setPushed(isAutoPlayActive);
700 cbShowInitialPose->setChecked(
701 !cbShowInitialPose->checked());
702 cbShowInitialPose->callback()(cbShowInitialPose->checked());
708 nanogui::Slider* sl = slSelectorICP;
709 sl->setValue(sl->value() + increment);
710 if (sl->value() < 0) sl->setValue(0);
711 if (sl->value() > sl->range().second)
712 sl->setValue(sl->range().second);
719 win->performLayout();
720 win->camera().setCameraPointing(8.0
f, .0
f, .0
f);
721 win->camera().setAzimuthDegrees(110.0
f);
722 win->camera().setElevationDegrees(15.0
f);
723 win->camera().setZoomDistance(30.0
f);
726 #define LOAD_CB_STATE(CB_NAME__) do_cb(CB_NAME__, #CB_NAME__)
727 #define SAVE_CB_STATE(CB_NAME__) \
728 appCfg.write("", #CB_NAME__, CB_NAME__->checked())
730 #define LOAD_SL_STATE(SL_NAME__) do_sl(SL_NAME__, #SL_NAME__)
731 #define SAVE_SL_STATE(SL_NAME__) \
732 appCfg.write("", #SL_NAME__, SL_NAME__->value())
734 auto load_UI_state_from_user_config = [&]()
736 auto do_cb = [&](nanogui::CheckBox* cb,
const std::string& name)
737 { cb->setChecked(appCfg.read_bool(
"", name, cb->checked())); };
738 auto do_sl = [&](nanogui::Slider* sl,
const std::string& name)
739 { sl->setValue(appCfg.read_float(
"", name, sl->value())); };
741 LOAD_CB_STATE(cbColorizeLocalMap);
742 LOAD_CB_STATE(cbColorizeGlobalMap);
743 LOAD_CB_STATE(cbShowInitialPose);
744 LOAD_CB_STATE(cbViewOrtho);
745 LOAD_CB_STATE(cbCameraFollowsLocal);
746 LOAD_CB_STATE(cbViewVoxelsAsPoints);
747 LOAD_CB_STATE(cbViewPairings);
748 LOAD_CB_STATE(cbViewPairings_pt2pt);
749 LOAD_CB_STATE(cbViewPairings_pt2pl);
750 LOAD_CB_STATE(cbViewPairings_pt2ln);
752 LOAD_SL_STATE(slPairingsPl2PlSize);
753 LOAD_SL_STATE(slPairingsPl2LnSize);
754 LOAD_SL_STATE(slGlobalPointSize);
755 LOAD_SL_STATE(slLocalPointSize);
756 LOAD_SL_STATE(slMidDepthField);
757 LOAD_SL_STATE(slThicknessDepthField);
759 win->camera().setCameraPointing(
760 appCfg.read_float(
"",
"cam_x", win->camera().getCameraPointingX()),
761 appCfg.read_float(
"",
"cam_y", win->camera().getCameraPointingY()),
762 appCfg.read_float(
"",
"cam_z", win->camera().getCameraPointingZ()));
763 win->camera().setAzimuthDegrees(
764 appCfg.read_float(
"",
"cam_az", win->camera().getAzimuthDegrees()));
765 win->camera().setElevationDegrees(appCfg.read_float(
766 "",
"cam_el", win->camera().getElevationDegrees()));
767 win->camera().setZoomDistance(
768 appCfg.read_float(
"",
"cam_d", win->camera().getZoomDistance()));
770 auto save_UI_state_to_user_config = [&]()
772 SAVE_CB_STATE(cbColorizeLocalMap);
773 SAVE_CB_STATE(cbColorizeGlobalMap);
774 SAVE_CB_STATE(cbShowInitialPose);
775 SAVE_CB_STATE(cbViewOrtho);
776 SAVE_CB_STATE(cbCameraFollowsLocal);
777 SAVE_CB_STATE(cbViewVoxelsAsPoints);
778 SAVE_CB_STATE(cbViewPairings);
779 SAVE_CB_STATE(cbViewPairings_pt2pt);
780 SAVE_CB_STATE(cbViewPairings_pt2pl);
781 SAVE_CB_STATE(cbViewPairings_pt2ln);
783 SAVE_SL_STATE(slPairingsPl2PlSize);
784 SAVE_SL_STATE(slPairingsPl2LnSize);
785 SAVE_SL_STATE(slGlobalPointSize);
786 SAVE_SL_STATE(slLocalPointSize);
787 SAVE_SL_STATE(slMidDepthField);
788 SAVE_SL_STATE(slThicknessDepthField);
790 appCfg.write(
"",
"cam_x", win->camera().getCameraPointingX());
791 appCfg.write(
"",
"cam_y", win->camera().getCameraPointingY());
792 appCfg.write(
"",
"cam_z", win->camera().getCameraPointingZ());
793 appCfg.write(
"",
"cam_az", win->camera().getAzimuthDegrees());
794 appCfg.write(
"",
"cam_el", win->camera().getElevationDegrees());
795 appCfg.write(
"",
"cam_d", win->camera().getZoomDistance());
799 load_UI_state_from_user_config();
806 win->setVisible(
true);
808 win->addLoopCallback(
812 updateMiniCornerView();
815 nanogui::mainloop(1 , -1 );
820 save_UI_state_to_user_config();
825 template <
class MATRIX>
826 double conditionNumber(
const MATRIX& m)
829 std::vector<double> eVals;
830 m.eig_symmetric(eVecs, eVals);
831 return eVals.back() / eVals.front();
837 void rebuild_3d_view(
bool regenerateMaps)
839 using namespace std::string_literals;
841 const size_t idx = mrpt::round(slSelectorICP->value());
843 btnSelectorBack->setEnabled(!logRecords.empty() && idx > 0);
844 btnSelectorForw->setEnabled(
845 !logRecords.empty() && idx < logRecords.size() - 1);
847 if (idx >= logRecords.size())
return;
852 static std::optional<size_t> lastIdx;
853 bool mustResetIterationSlider =
false;
855 if (!lastIdx || (lastIdx && idx != *lastIdx))
858 if (lastIdx) logRecords.at(*lastIdx).dispose();
861 mustResetIterationSlider =
true;
866 const auto& lr = logRecords.at(idx).get();
868 lbICPStats[0]->setValue(logRecords.at(idx).shortFileName());
870 lbICPStats[1]->setValue(mrpt::format(
871 "ICP log #%zu | Local: ID:%u%s | Global: ID:%u%s", idx,
872 static_cast<unsigned int>(lr.pcLocal->id ? lr.pcLocal->id.value() : 0),
873 lr.pcLocal->label ? lr.pcLocal->label.value().c_str() :
"",
874 static_cast<unsigned int>(
875 lr.pcGlobal->id ? lr.pcGlobal->id.value() : 0),
876 lr.pcGlobal->label ? lr.pcGlobal->label.value().c_str() :
""));
878 lbICPStats[2]->setValue(mrpt::format(
879 "Quality: %.02f%% | Iters: %u | Term.Reason: %s",
880 100.0 * lr.icpResult.quality,
881 static_cast<unsigned int>(lr.icpResult.nIterations),
882 mrpt::typemeta::enum2str(lr.icpResult.terminationReason).c_str()));
884 lbICPStats[3]->setValue(
"Global: "s + lr.pcGlobal->contents_summary());
885 lbICPStats[4]->setValue(
"Local: "s + lr.pcLocal->contents_summary());
887 tbInitialGuess->setValue(lr.initialGuessLocalWrtGlobal.asString());
889 tbLogPose->setValue(lr.icpResult.optimal_tf.mean.asString());
893 for (
auto* lb : lbDynVariables) lb->setValue(
"");
896 for (
const auto& [name, value] : lr.dynamicVariables)
898 lbDynVariables[lbIdx++]->setValue(
899 mrpt::format(
"%s = %g", name.c_str(), value));
900 if (lbIdx >= MAX_VARIABLE_LIST)
break;
905 const auto poseChange =
906 lr.icpResult.optimal_tf.mean -
907 mrpt::poses::CPose3D(lr.initialGuessLocalWrtGlobal);
909 tbInit2Final->setValue(mrpt::format(
910 "|T|=%.03f [m] |R|=%.03f [deg]", poseChange.norm(),
912 mrpt::poses::Lie::SO<3>::log(poseChange.getRotationMatrix())
916 const auto poseFromCorner = mrpt::poses::CPose3D::Identity();
917 mrpt::poses::CPose3DPDFGaussian relativePose;
920 if (!lr.iterationsDetails.has_value() || lr.iterationsDetails->empty())
922 slIterationDetails->setEnabled(
false);
924 if (cbShowInitialPose->checked())
927 mrpt::poses::CPose3D(lr.initialGuessLocalWrtGlobal);
928 lbICPIteration->setCaption(
"Show ICP iteration: INITIAL");
932 relativePose = lr.icpResult.optimal_tf;
933 lbICPIteration->setCaption(
"Show ICP iteration: FINAL");
936 if (cbViewPairings->checked()) pairsToViz = &lr.icpResult.finalPairings;
940 slIterationDetails->setEnabled(
true);
941 slIterationDetails->setRange(
942 {.0f,
static_cast<float>(lr.iterationsDetails->size() - 1)});
944 if (mustResetIterationSlider)
945 slIterationDetails->setValue(
946 cbShowInitialPose->checked()
947 ? slIterationDetails->range().first
948 : slIterationDetails->range().second);
951 auto it = lr.iterationsDetails->begin();
953 size_t nIter = mrpt::round(slIterationDetails->value());
954 mrpt::keep_min(nIter, lr.iterationsDetails->size() - 1);
956 std::advance(it, nIter);
958 relativePose.mean = it->second.optimalPose;
959 if (cbViewPairings->checked()) pairsToViz = &it->second.pairings;
961 lbICPIteration->setCaption(
962 "Show ICP iteration: "s + std::to_string(it->first) +
"/"s +
963 std::to_string(lr.iterationsDetails->rbegin()->first));
968 for (
int i = 0; i < 6; i++)
969 s += mrpt::format(
"%.02f ", std::sqrt(relativePose.cov(i, i)));
972 " det(XYZ)=%.02e det(rot)=%.02e",
973 relativePose.cov.blockCopy<3, 3>(0, 0).det(),
974 relativePose.cov.blockCopy<3, 3>(3, 3).det());
976 tbCovariance->setValue(s);
980 const mrpt::poses::CPosePDFGaussian pose2D(relativePose);
983 tbConditionNumber->setValue(mrpt::format(
984 " cn{XYZ}=%.02f cn{SO(3)}=%.02f cn{SE(2)}=%.02f "
986 conditionNumber(relativePose.cov.blockCopy<3, 3>(0, 0)),
987 conditionNumber(relativePose.cov.blockCopy<3, 3>(3, 3)),
988 conditionNumber(pose2D.cov), conditionNumber(relativePose.cov)));
992 mrpt::opengl::stock_objects::CornerXYZSimple(0.75
f, 3.0
f);
993 glCornerFrom->setPose(poseFromCorner);
994 glVizICP->insert(glCornerFrom);
997 mrpt::opengl::stock_objects::CornerXYZSimple(0.85
f, 5.0
f);
998 glCornerLocal->setPose(relativePose.mean);
999 glCornerLocal->setName(
"Local");
1000 glCornerLocal->enableShowName(
true);
1001 glVizICP->insert(glCornerLocal);
1003 auto glCornerToCov = mrpt::opengl::CEllipsoid3D::Create();
1004 glCornerToCov->set3DsegmentsCount(16);
1005 glCornerToCov->enableDrawSolid3D(
true);
1006 glCornerToCov->setColor_u8(0xff, 0x00, 0x00, 0x40);
1008 glCornerToCov->setCovMatrixAndMean(
1009 relativePose.cov.blockCopy<3, 3>(0, 0),
1010 relativePose.mean.asVectorVal().head<3>());
1011 glVizICP->insert(glCornerToCov);
1017 for (
const auto& [lyName, cb] : cbLayersByName_global)
1020 cb->setCaption(lyName);
1021 if (
auto itL = lr.pcGlobal->layers.find(lyName);
1022 itL != lr.pcGlobal->layers.end())
1024 if (
auto pc = std::dynamic_pointer_cast<mrpt::maps::CPointsMap>(
1030 mrpt::system::unitsFormat(
static_cast<double>(pc->size())) +
1031 " points"s +
" | class="s +
1032 pc->GetRuntimeClass()->className);
1037 lyName +
" | class="s +
1038 itL->second->GetRuntimeClass()->className);
1043 if (!cb->checked())
continue;
1047 rpL.pointSize = slGlobalPointSize->value();
1048 rpL.render_voxelmaps_as_points = cbViewVoxelsAsPoints->checked();
1049 rpL.render_voxelmaps_free_space = cbViewVoxelsFreeSpace->checked();
1051 if (cbColorizeGlobalMap->checked())
1053 auto& cm = rpL.colorMode.emplace();
1054 cm.colorMap = mrpt::img::TColormap::cmHOT;
1059 static mrpt::opengl::CSetOfObjects::Ptr lastGlobalPts;
1061 if (!lastGlobalPts || regenerateMaps)
1065 rpL.second.color = mrpt::img::TColor(0xff, 0x00, 0x00, 0xff);
1067 auto glPts = lr.pcGlobal->get_visualization(rpGlobal);
1068 lastGlobalPts = glPts;
1072 mrpt::img::TColor(0xff, 0x00, 0x00, 0xff);
1074 glVizICP->insert(glPts);
1079 glVizICP->insert(lastGlobalPts);
1086 for (
const auto& [lyName, cb] : cbLayersByName_local)
1089 cb->setCaption(lyName);
1090 if (
auto itL = lr.pcLocal->layers.find(lyName);
1091 itL != lr.pcLocal->layers.end())
1093 if (
auto pc = std::dynamic_pointer_cast<mrpt::maps::CPointsMap>(
1099 mrpt::system::unitsFormat(
1100 static_cast<double>(pc->size()), 2,
false) +
1101 " points"s +
" | class="s +
1102 pc->GetRuntimeClass()->className);
1107 lyName +
" | class="s +
1108 itL->second->GetRuntimeClass()->className);
1113 if (!cb->checked())
continue;
1117 rpL.pointSize = slLocalPointSize->value();
1118 rpL.render_voxelmaps_as_points = cbViewVoxelsAsPoints->checked();
1119 rpL.render_voxelmaps_free_space = cbViewVoxelsFreeSpace->checked();
1120 if (cbColorizeLocalMap->checked())
1122 auto& cm = rpL.colorMode.emplace();
1123 cm.colorMap = mrpt::img::TColormap::cmHOT;
1128 static mrpt::opengl::CSetOfObjects::Ptr lastLocalPts;
1130 if (!lastLocalPts || regenerateMaps)
1134 rpL.second.color = mrpt::img::TColor(0x00, 0x00, 0xff, 0xff);
1136 auto glPts = lr.pcLocal->get_visualization(rpLocal);
1137 lastLocalPts = glPts;
1139 glVizICP->insert(glPts);
1144 glVizICP->insert(lastLocalPts);
1146 lastLocalPts->setPose(relativePose.mean);
1150 std::lock_guard<std::mutex> lck(win->background_scene_mtx);
1151 win->camera().setCameraProjective(!cbViewOrtho->checked());
1153 if (cbCameraFollowsLocal->checked())
1155 win->camera().setCameraPointing(
1156 relativePose.mean.x(), relativePose.mean.y(),
1157 relativePose.mean.z());
1161 const auto depthFieldMid =
std::pow(10.0, slMidDepthField->value());
1162 const auto depthFieldThickness =
1163 std::pow(10.0, slThicknessDepthField->value());
1165 const auto clipNear =
1166 std::max(1e-2, depthFieldMid - 0.5 * depthFieldThickness);
1167 const auto clipFar = depthFieldMid + 0.5 * depthFieldThickness;
1169 lbDepthFieldMid->setCaption(
1170 mrpt::format(
"Frustrum depth center: %.03f", depthFieldMid));
1171 lbDepthFieldThickness->setCaption(mrpt::format(
1172 "Frustum depth thickness: %.03f", depthFieldThickness));
1173 lbDepthFieldValues->setCaption(
1174 mrpt::format(
"Frustum: near=%.02f far=%.02f", clipNear, clipFar));
1176 win->background_scene->getViewport()->setViewportClipDistances(
1178 win->camera().setMaximumZoom(std::max<double>(1000, 3.0 * clipFar));
1201 tbPairings->setValue(
1202 "None selected (mark one of the checkboxes below)");
1207 auto gl_view = win->background_scene->createViewport(
"small-view");
1209 gl_view->setViewportPosition(0, 0, 0.1, 0.1 * 16.0 / 9.0);
1210 gl_view->setTransparent(
true);
1212 mrpt::opengl::CText::Ptr obj = mrpt::opengl::CText::Create(
"X");
1213 obj->setLocation(1.1, 0, 0);
1214 gl_view->insert(obj);
1217 mrpt::opengl::CText::Ptr obj = mrpt::opengl::CText::Create(
"Y");
1218 obj->setLocation(0, 1.1, 0);
1219 gl_view->insert(obj);
1222 mrpt::opengl::CText::Ptr obj = mrpt::opengl::CText::Create(
"Z");
1223 obj->setLocation(0, 0, 1.1);
1224 gl_view->insert(obj);
1226 gl_view->insert(mrpt::opengl::stock_objects::CornerXYZ());
1230 #else // MRPT_HAS_NANOGUI
1234 "This application requires a version of MRPT built with nanogui "
1238 #endif // MRPT_HAS_NANOGUI
1245 if (!
cmd.parse(argc, argv))
return 1;
1252 std::cout <<
"Loading plugin(s): " << plugins << std::endl;
1253 if (!mrpt::system::loadPluginModules(plugins, errMsg))
1255 std::cerr << errMsg << std::endl;
1263 catch (std::exception& e)
1265 std::cerr <<
"Exit due to exception:\n"
1266 << mrpt::exception_to_str(e) << std::endl;