apps/icp-log-viewer/main.cpp
Go to the documentation of this file.
1 /* -------------------------------------------------------------------------
2  * A repertory of multi primitive-to-primitive (MP2P) ICP algorithms in C++
3  * Copyright (C) 2018-2024 Jose Luis Blanco, University of Almeria
4  * See LICENSE for license information.
5  * ------------------------------------------------------------------------- */
6 
14 // The goal is to visualize these guys:
15 #include <mp2p_icp/LogRecord.h>
16 // using this:
17 #include <mrpt/gui/CDisplayWindowGUI.h>
18 
19 // other deps:
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> // unitsFormat()
35 
36 #include <iostream>
37 
38 #include "../libcfgpath/cfgpath.h"
39 
40 constexpr const char* APP_NAME = "icp-log-viewer";
41 constexpr int MID_FONT_SIZE = 14;
42 constexpr int SMALL_FONT_SIZE = 12;
43 constexpr int WINDOW_FIXED_WIDTH = 400;
44 
45 // =========== Declare supported cli switches ===========
46 static TCLAP::CmdLine cmd(APP_NAME);
47 
48 static TCLAP::ValueArg<std::string> argExtension(
49  "e", "file-extension",
50  "Filename extension to look for. Default is `icplog`", false, "icplog",
51  "icplog", cmd);
52 
53 static TCLAP::ValueArg<std::string> argSearchDir(
54  "d", "directory", "Directory in which to search for *.icplog files.", false,
55  ".", ".", cmd);
56 
57 static TCLAP::ValueArg<std::string> argSingleFile(
58  "f", "file", "Load just this one single log *.icplog file.", false,
59  "log.icplog", "log.icplog", cmd);
60 
61 static TCLAP::ValueArg<std::string> arg_plugins(
62  "l", "load-plugins",
63  "One or more (comma separated) *.so files to load as plugins", false,
64  "foobar.so", "foobar.so", cmd);
65 
66 static TCLAP::ValueArg<double> argAutoPlayPeriod(
67  "", "autoplay-period",
68  "The period (in seconds) between timestamps to load and show in autoplay "
69  "mode.",
70  false, 0.1, "period [seconds]", cmd);
71 
72 // =========== Declare global variables ===========
73 #if MRPT_HAS_NANOGUI
74 
75 auto glVizICP = mrpt::opengl::CSetOfObjects::Create();
76 mrpt::gui::CDisplayWindowGUI::Ptr win;
77 
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;
83 
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;
89 
90 const size_t MAX_VARIABLE_LIST = 100;
91 std::vector<nanogui::TextBox*> lbDynVariables;
92 
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;
99 
100 nanogui::CheckBox* cbViewPairings = nullptr;
101 
102 nanogui::CheckBox* cbViewPairings_pt2pt = nullptr;
103 nanogui::CheckBox* cbViewPairings_pt2pl = nullptr;
104 nanogui::CheckBox* cbViewPairings_pt2ln = nullptr;
105 
106 nanogui::TextBox *tbLogPose = nullptr, *tbInitialGuess = nullptr,
107  *tbInit2Final = nullptr, *tbCovariance = nullptr,
108  *tbConditionNumber = nullptr, *tbPairings = nullptr;
109 
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;
118 
119 nanogui::Slider* slGTPose[6] = {nullptr, nullptr, nullptr,
120  nullptr, nullptr, nullptr};
121 
122 std::vector<std::string> layerNames_global, layerNames_local;
123 
124 std::map<std::string, nanogui::CheckBox*> cbLayersByName_global;
125 std::map<std::string, nanogui::CheckBox*> cbLayersByName_local;
126 
127 class DelayedLoadLog
128 {
129  public:
130  DelayedLoadLog() = default;
131  DelayedLoadLog(
132  const std::string& fileName, const std::string& shortFileName)
133  : filename_(fileName), shortFileName_(shortFileName)
134  {
135  }
136 
138  {
139  if (!log_)
140  {
141  // Load now:
142  log_ = mp2p_icp::LogRecord::LoadFromFile(filename_);
143  }
144 
145  return log_.value();
146  }
147 
148  void dispose() { log_.reset(); }
149 
150  const std::string& filename() const { return filename_; }
151  const std::string& shortFileName() const { return shortFileName_; }
152 
153  private:
154  std::optional<mp2p_icp::LogRecord> log_;
155  std::string filename_, shortFileName_;
156 };
157 
158 std::vector<DelayedLoadLog> logRecords;
159 
160 static void rebuild_3d_view(bool regenerateMaps = true);
161 
162 namespace
163 {
164 void rebuild_3d_view_fast() { rebuild_3d_view(false); }
165 
166 void processAutoPlay()
167 {
168  if (!isAutoPlayActive) return;
169 
170  const double tNow = mrpt::Clock::nowDouble();
171  if (tNow - lastAutoPlayTime < argAutoPlayPeriod.getValue()) return;
172 
173  lastAutoPlayTime = tNow;
174 
175  if (slSelectorICP->value() < slSelectorICP->range().second - 0.01f)
176  {
177  slSelectorICP->setValue(slSelectorICP->value() + 1);
178  rebuild_3d_view();
179  }
180 }
181 
182 void updateMiniCornerView()
183 {
184  auto gl_view = win->background_scene->getViewport("small-view");
185  if (!gl_view) return;
186 
187  mrpt::opengl::CCamera& view_cam = gl_view->getCamera();
188 
189  view_cam.setAzimuthDegrees(win->camera().getAzimuthDegrees());
190  view_cam.setElevationDegrees(win->camera().getElevationDegrees());
191  view_cam.setZoomDistance(5);
192 }
193 
194 void main_show_gui()
195 {
196  using namespace std::string_literals;
197 
198  mrpt::system::CDirectoryExplorer::TFileInfoList files;
199 
200  if (!argSingleFile.isSet())
201  {
202  const std::string searchDir = argSearchDir.getValue();
203  ASSERT_DIRECTORY_EXISTS_(searchDir);
204 
205  std::cout << "Searching in: '" << searchDir
206  << "' for files with extension '" << argExtension.getValue()
207  << "'" << std::endl;
208 
209  mrpt::system::CDirectoryExplorer::explore(
210  searchDir, FILE_ATTRIB_ARCHIVE, files);
211  mrpt::system::CDirectoryExplorer::filterByExtension(
212  files, argExtension.getValue());
213  mrpt::system::CDirectoryExplorer::sortByName(files);
214 
215  std::cout << "Found " << files.size() << " ICP records." << std::endl;
216  }
217  else
218  {
219  // Load one single file:
220  std::cout << "Loading one single log file: " << argSingleFile.getValue()
221  << std::endl;
222 
223  files.resize(1);
224  files[0].wholePath = argSingleFile.getValue();
225  }
226 
227  // load files:
228  for (const auto& file : files)
229  logRecords.emplace_back(file.wholePath, file.name);
230 
231  ASSERT_(!logRecords.empty());
232 
233  // Obtain layer info from first entry:
234  {
235  const auto& lr = logRecords.front().get();
236  if (layerNames_global.empty() && lr.pcGlobal)
237  {
238  for (const auto& layer : lr.pcGlobal->layers)
239  {
240  layerNames_global.push_back(layer.first);
241  std::cout << "Global point cloud: Found point layer='"
242  << layer.first << "'\n";
243  }
244  }
245  if (layerNames_local.empty() && lr.pcLocal)
246  {
247  for (const auto& layer : lr.pcLocal->layers)
248  {
249  layerNames_local.push_back(layer.first);
250  std::cout << "Local point cloud: Found point layer='"
251  << layer.first << "'\n";
252  }
253  }
254  }
255 
256  // Get user app config file
257  char appCfgFile[1024];
258  ::get_user_config_file(appCfgFile, sizeof(appCfgFile), APP_NAME);
259  mrpt::config::CConfigFile appCfg(appCfgFile);
260 
261  /*
262  * -------------------------------------------------------------------
263  * GUI
264  * --------------------------------------------------------------------
265  */
266  nanogui::init();
267 
268  mrpt::gui::CDisplayWindowGUI_Params cp;
269  cp.maximized = true;
270  win = mrpt::gui::CDisplayWindowGUI::Create(APP_NAME, 1024, 800, cp);
271 
272  // Add a background scene:
273  auto scene = mrpt::opengl::COpenGLScene::Create();
274  {
275  auto glGrid = mrpt::opengl::CGridPlaneXY::Create();
276  glGrid->setColor_u8(0xff, 0xff, 0xff, 0x10);
277  scene->insert(glGrid);
278  }
279 
280  auto gl_base = mrpt::opengl::stock_objects::CornerXYZ(1.0f);
281  gl_base->setName("Global");
282  gl_base->enableShowName();
283  scene->insert(gl_base);
284 
285  scene->insert(glVizICP);
286 
287  {
288  std::lock_guard<std::mutex> lck(win->background_scene_mtx);
289  win->background_scene = std::move(scene);
290  }
291 
292  // Control GUI sub-window:
293  auto w = win->createManagedSubWindow("Control");
294  w->setPosition({5, 25});
295  w->requestFocus();
296  w->setLayout(new nanogui::BoxLayout(
297  nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 5, 2));
298  w->setFixedWidth(WINDOW_FIXED_WIDTH);
299 
300  cbShowInitialPose = w->add<nanogui::CheckBox>("Show at INITIAL GUESS pose");
301  cbShowInitialPose->setCallback(
302  [=](bool v)
303  {
304  if (slIterationDetails->enabled())
305  {
306  const auto& r = slIterationDetails->range();
307  slIterationDetails->setValue(v ? r.first : r.second);
308  }
309  rebuild_3d_view_fast();
310  });
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(); });
315 
316  //
317  w->add<nanogui::Label>(" "); // separator
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 /*v*/) { rebuild_3d_view(); });
325 
326  for (auto& lb : lbICPStats)
327  {
328  lb = w->add<nanogui::TextBox>(" ");
329  lb->setFontSize(MID_FONT_SIZE);
330  lb->setAlignment(nanogui::TextBox::Alignment::Left);
331  lb->setEditable(true);
332  }
333 
334  // navigation panel:
335  {
336  auto pn = w->add<nanogui::Widget>();
337  pn->setLayout(new nanogui::BoxLayout(nanogui::Orientation::Horizontal));
338 
339  // shortcut:
340  auto s = slSelectorICP;
341 
342  btnSelectorBack =
343  pn->add<nanogui::Button>("", ENTYPO_ICON_CONTROLLER_FAST_BACKWARD);
344  btnSelectorBack->setCallback(
345  [=]()
346  {
347  if (s->value() > 0)
348  {
349  s->setValue(s->value() - 1);
350  s->callback()(s->value());
351  }
352  });
353 
354  btnSelectorForw =
355  pn->add<nanogui::Button>("", ENTYPO_ICON_CONTROLLER_FAST_FORWARD);
356  btnSelectorForw->setCallback(
357  [=]()
358  {
359  if (s->value() < s->range().second - 0.01f)
360  {
361  s->setValue(s->value() + 1);
362  rebuild_3d_view();
363  }
364  });
365 
366  pn->add<nanogui::Label>(" "); // separator
367 
368  btnSelectorAutoplay =
369  pn->add<nanogui::Button>("", ENTYPO_ICON_CONTROLLER_PLAY);
370  btnSelectorAutoplay->setFlags(nanogui::Button::ToggleButton);
371 
372  btnSelectorAutoplay->setChangeCallback([&](bool active)
373  { isAutoPlayActive = active; });
374  }
375 
376  //
377  w->add<nanogui::Label>(" "); // separator
378 
379  auto tabWidget = w->add<nanogui::TabWidget>();
380 
381  auto* tab1 = tabWidget->createTab("Summary");
382  tab1->setLayout(new nanogui::BoxLayout(
383  nanogui::Orientation::Vertical, nanogui::Alignment::Fill));
384 
385  auto* tab2 = tabWidget->createTab("Variables");
386  tab2->setLayout(new nanogui::BoxLayout(
387  nanogui::Orientation::Vertical, nanogui::Alignment::Fill));
388 
389  auto* tab3 = tabWidget->createTab("Maps");
390  tab3->setLayout(new nanogui::GroupLayout());
391 
392  auto* tab4 = tabWidget->createTab("Pairings");
393  tab4->setLayout(new nanogui::GroupLayout());
394 
395  auto* tab5 = tabWidget->createTab("View");
396  tab5->setLayout(new nanogui::GroupLayout());
397 
398  auto* tab6 = tabWidget->createTab("Manual");
399  tab6->setLayout(new nanogui::GroupLayout());
400 
401  tabWidget->setActiveTab(0);
402 
403  tab1->add<nanogui::Label>(" ")->setFontSize(SMALL_FONT_SIZE);
404  tab1->add<nanogui::Label>(
405  "ICP result pose [x y z yaw(deg) pitch(deg) roll(deg)]:")
406  ->setFontSize(MID_FONT_SIZE);
407  tbLogPose = tab1->add<nanogui::TextBox>();
408  tbLogPose->setFontSize(MID_FONT_SIZE);
409  tbLogPose->setEditable(true);
410  tbLogPose->setAlignment(nanogui::TextBox::Alignment::Left);
411  tab1->add<nanogui::Label>(" ")->setFontSize(SMALL_FONT_SIZE);
412 
413  tab1->add<nanogui::Label>("Initial -> final pose change:")
414  ->setFontSize(MID_FONT_SIZE);
415  tbInit2Final = tab1->add<nanogui::TextBox>();
416  tbInit2Final->setFontSize(MID_FONT_SIZE);
417  tbInit2Final->setEditable(false);
418  tab1->add<nanogui::Label>(" ")->setFontSize(SMALL_FONT_SIZE);
419 
420  tab1->add<nanogui::Label>(
421  "Uncertainty: diagonal sigmas (x y z yaw pitch roll)")
422  ->setFontSize(MID_FONT_SIZE);
423  tbCovariance = tab1->add<nanogui::TextBox>();
424  tbCovariance->setFontSize(MID_FONT_SIZE);
425  tbCovariance->setEditable(false);
426  tab1->add<nanogui::Label>(" ")->setFontSize(SMALL_FONT_SIZE);
427 
428  tab1->add<nanogui::Label>("Uncertainty: Covariance condition numbers")
429  ->setFontSize(MID_FONT_SIZE);
430  tbConditionNumber = tab1->add<nanogui::TextBox>();
431  tbConditionNumber->setFontSize(MID_FONT_SIZE);
432  tbConditionNumber->setEditable(false);
433  tab1->add<nanogui::Label>(" ");
434 
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};
438 
439  for (int i = 0; i < 6; i++)
440  {
441  slGTPose[i] = tab6->add<nanogui::Slider>();
442  slGTPose[i]->setRange({-handTunedRange[i], handTunedRange[i]});
443  slGTPose[i]->setValue(0.0f);
444 
445  slGTPose[i]->setCallback(
446  [=](float v)
447  {
448  const size_t idx = mrpt::round(slSelectorICP->value());
449  auto& lr = logRecords.at(idx).get();
450 
451  auto p = lr.icpResult.optimal_tf.mean.asTPose();
452  p[i] = v;
453  lr.icpResult.optimal_tf.mean = mrpt::poses::CPose3D(p);
454 
455  rebuild_3d_view_fast();
456  });
457  }
458 
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);
464 
465  // Save map buttons:
466  auto lambdaSave = [&](const mp2p_icp::metric_map_t& m)
467  {
468  const std::string outFile = nanogui::file_dialog(
469  {{"mm", "mp2p_icp::metric_map_t binary serialized object (*.mm)"}},
470  true /*save*/);
471  if (outFile.empty()) return;
472  m.save_to_file(outFile);
473  };
474 
475  // tab 2: variables
476  {
477  tab2->add<nanogui::Label>("Dynamic variables:");
478 
479  auto vscroll = tab2->add<nanogui::VScrollPanel>();
480  vscroll->setFixedSize({WINDOW_FIXED_WIDTH - 15, 300});
481 
482  auto wrapper = vscroll->add<nanogui::Widget>();
483 
484  wrapper->setFixedSize({WINDOW_FIXED_WIDTH - 30, 300});
485  wrapper->setLayout(new nanogui::BoxLayout(
486  nanogui::Orientation::Vertical, nanogui::Alignment::Fill));
487 
488  for (size_t i = 0; i < MAX_VARIABLE_LIST; i++)
489  {
490  auto* tb = wrapper->add<nanogui::TextBox>("aaaa");
491  tb->setAlignment(nanogui::TextBox::Alignment::Left);
492  tb->setEditable(true);
493  tb->setFontSize(MID_FONT_SIZE);
494  lbDynVariables.push_back(tb);
495  }
496  }
497 
498  // tab 3: maps
499  {
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...")
504  ->setCallback(
505  [&]()
506  {
507  const size_t idx = mrpt::round(slSelectorICP->value());
508  auto& lr = logRecords.at(idx).get();
509  ASSERT_(lr.pcLocal);
510  lambdaSave(*lr.pcLocal);
511  });
512  pn->add<nanogui::Button>("Export 'global' map...")
513  ->setCallback(
514  [&]()
515  {
516  const size_t idx = mrpt::round(slSelectorICP->value());
517  auto& lr = logRecords.at(idx).get();
518  ASSERT_(lr.pcGlobal);
519  lambdaSave(*lr.pcGlobal);
520  });
521  }
522 
523  tab3->add<nanogui::Label>("[GLOBAL map] Visible layers:");
524 
525  for (size_t i = 0; i < layerNames_global.size(); i++)
526  {
527  auto cb = tab3->add<nanogui::CheckBox>(layerNames_global.at(i));
528  cb->setChecked(true);
529  cb->setCallback([](bool) { rebuild_3d_view(); });
530  cb->setFontSize(13);
531 
532  cbLayersByName_global[layerNames_global.at(i)] = cb;
533  }
534 
535  tab3->add<nanogui::Label>("[LOCAL map] Visible layers:");
536  for (size_t i = 0; i < layerNames_local.size(); i++)
537  {
538  auto cb = tab3->add<nanogui::CheckBox>(layerNames_local.at(i));
539  cb->setChecked(true);
540  cb->setCallback([](bool) { rebuild_3d_view(); });
541  cb->setFontSize(13);
542 
543  cbLayersByName_local[layerNames_local.at(i)] = cb;
544  }
545 
546  // tab4: pairings:
547  tbPairings = tab4->add<nanogui::TextBox>();
548  tbPairings->setFontSize(16);
549  tbPairings->setEditable(false);
550 
551  cbViewPairings = tab4->add<nanogui::CheckBox>("View pairings");
552  cbViewPairings->setCallback([](bool) { rebuild_3d_view_fast(); });
553 
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(); });
557 
558  {
559  auto pn = tab4->add<nanogui::Widget>();
560  pn->setLayout(new nanogui::GridLayout(
561  nanogui::Orientation::Horizontal, 3, nanogui::Alignment::Fill));
562 
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(); });
567 
568  pn->add<nanogui::Label>("Plane size:");
569  slPairingsPl2PlSize = pn->add<nanogui::Slider>();
570  slPairingsPl2PlSize->setRange({-2.0f, 2.0f});
571  slPairingsPl2PlSize->setValue(-1.0f);
572  slPairingsPl2PlSize->setCallback([&](float)
573  { rebuild_3d_view_fast(); });
574  }
575 
576  {
577  auto pn = tab4->add<nanogui::Widget>();
578  pn->setLayout(new nanogui::GridLayout(
579  nanogui::Orientation::Horizontal, 3, nanogui::Alignment::Fill));
580 
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(); });
585 
586  pn->add<nanogui::Label>("Line length:");
587  slPairingsPl2LnSize = pn->add<nanogui::Slider>();
588  slPairingsPl2LnSize->setRange({-2.0f, 2.0f});
589  slPairingsPl2LnSize->setValue(-1.0f);
590  slPairingsPl2LnSize->setCallback([&](float)
591  { rebuild_3d_view_fast(); });
592  }
593 
594  // tab5: view
595  {
596  auto pn = tab5->add<nanogui::Widget>();
597  pn->setLayout(new nanogui::GridLayout(
598  nanogui::Orientation::Horizontal, 2, nanogui::Alignment::Fill));
599 
600  pn->add<nanogui::Label>("Global map point size");
601 
602  slGlobalPointSize = pn->add<nanogui::Slider>();
603  slGlobalPointSize->setRange({1.0f, 10.0f});
604  slGlobalPointSize->setValue(2.0f);
605  slGlobalPointSize->setCallback([&](float) { rebuild_3d_view(); });
606  }
607  {
608  auto pn = tab5->add<nanogui::Widget>();
609  pn->setLayout(new nanogui::GridLayout(
610  nanogui::Orientation::Horizontal, 2, nanogui::Alignment::Fill));
611 
612  pn->add<nanogui::Label>("Local map point size");
613 
614  slLocalPointSize = pn->add<nanogui::Slider>();
615  slLocalPointSize->setRange({1.0f, 10.0f});
616  slLocalPointSize->setValue(2.0f);
617  slLocalPointSize->setCallback([&](float) { rebuild_3d_view(); });
618  }
619  {
620  auto pn = tab5->add<nanogui::Widget>();
621  pn->setLayout(new nanogui::GridLayout(
622  nanogui::Orientation::Horizontal, 2, nanogui::Alignment::Fill));
623 
624  lbDepthFieldMid = pn->add<nanogui::Label>("Center depth clip plane:");
625  slMidDepthField = pn->add<nanogui::Slider>();
626  }
627  slMidDepthField->setRange({-2.0, 3.0});
628  slMidDepthField->setValue(1.0f);
629  slMidDepthField->setCallback([&](float) { rebuild_3d_view_fast(); });
630 
631  {
632  auto pn = tab5->add<nanogui::Widget>();
633  pn->setLayout(new nanogui::GridLayout(
634  nanogui::Orientation::Horizontal, 2, nanogui::Alignment::Fill));
635 
636  lbDepthFieldThickness =
637  pn->add<nanogui::Label>("Max-Min depth thickness:");
638  slThicknessDepthField = pn->add<nanogui::Slider>();
639  }
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>(" ");
644 
645  cbViewOrtho = tab5->add<nanogui::CheckBox>("Orthogonal view");
646  cbViewOrtho->setCallback([&](bool) { rebuild_3d_view_fast(); });
647 
648  cbCameraFollowsLocal =
649  tab5->add<nanogui::CheckBox>("Camera follows 'local'");
650  cbCameraFollowsLocal->setCallback([&](bool) { rebuild_3d_view_fast(); });
651 
652  cbViewVoxelsAsPoints =
653  tab5->add<nanogui::CheckBox>("Render voxel maps as point clouds");
654  cbViewVoxelsAsPoints->setChecked(true);
655  cbViewVoxelsAsPoints->setCallback([&](bool) { rebuild_3d_view(); });
656 
657  cbViewVoxelsFreeSpace =
658  tab5->add<nanogui::CheckBox>("Render free space of voxel maps");
659  cbViewVoxelsFreeSpace->setChecked(false);
660  cbViewVoxelsFreeSpace->setCallback([&](bool) { rebuild_3d_view(); });
661 
662  cbColorizeLocalMap = tab5->add<nanogui::CheckBox>("Recolorize local map");
663  cbColorizeLocalMap->setCallback([&](bool) { rebuild_3d_view(); });
664 
665  cbColorizeGlobalMap = tab5->add<nanogui::CheckBox>("Recolorize global map");
666  cbColorizeGlobalMap->setCallback([&](bool) { rebuild_3d_view(); });
667 
668  // ----
669  w->add<nanogui::Label>(" "); // separator
670  w->add<nanogui::Button>("Quit", ENTYPO_ICON_ARROW_BOLD_LEFT)
671  ->setCallback([]() { win->setVisible(false); });
672 
673  win->setKeyboardCallback(
674  [&](int key, [[maybe_unused]] int scancode, int action,
675  [[maybe_unused]] int modifiers)
676  {
677  if (action != GLFW_PRESS && action != GLFW_REPEAT) return false;
678 
679  int increment = 0;
680  switch (key)
681  {
682  case GLFW_KEY_LEFT:
683  increment = -1;
684  break;
685  case GLFW_KEY_RIGHT:
686  increment = +1;
687  break;
688  case GLFW_KEY_PAGE_DOWN:
689  increment = +100;
690  break;
691  case GLFW_KEY_PAGE_UP:
692  increment = -100;
693  break;
694  case GLFW_KEY_SPACE:
695  isAutoPlayActive = !isAutoPlayActive;
696  btnSelectorAutoplay->setPushed(isAutoPlayActive);
697  break;
698  case 'i':
699  case 'I':
700  cbShowInitialPose->setChecked(
701  !cbShowInitialPose->checked());
702  cbShowInitialPose->callback()(cbShowInitialPose->checked());
703  break;
704  };
705 
706  if (increment != 0)
707  {
708  nanogui::Slider* sl = slSelectorICP; // shortcut
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);
713  rebuild_3d_view();
714  }
715 
716  return false;
717  });
718 
719  win->performLayout();
720  win->camera().setCameraPointing(8.0f, .0f, .0f);
721  win->camera().setAzimuthDegrees(110.0f);
722  win->camera().setElevationDegrees(15.0f);
723  win->camera().setZoomDistance(30.0f);
724 
725  // save and load UI state:
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())
729 
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())
733 
734  auto load_UI_state_from_user_config = [&]()
735  {
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())); };
740 
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);
751 
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);
758 
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()));
769  };
770  auto save_UI_state_to_user_config = [&]()
771  {
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);
782 
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);
789 
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());
796  };
797 
798  // load UI state from last session:
799  load_UI_state_from_user_config();
800 
801  rebuild_3d_view();
802 
803  // Main loop
804  // ---------------------
805  win->drawAll();
806  win->setVisible(true);
807 
808  win->addLoopCallback(
809  [&]()
810  {
811  processAutoPlay();
812  updateMiniCornerView();
813  });
814 
815  nanogui::mainloop(1 /*loop callbacks Hz*/, -1 /* no force repaint */);
816 
817  nanogui::shutdown();
818 
819  // save UI state:
820  save_UI_state_to_user_config();
821 }
822 
823 } // namespace
824 
825 template <class MATRIX> //
826 double conditionNumber(const MATRIX& m)
827 {
828  MATRIX eVecs;
829  std::vector<double> eVals;
830  m.eig_symmetric(eVecs, eVals);
831  return eVals.back() / eVals.front();
832 }
833 
834 // ==============================
835 // rebuild_3d_view
836 // ==============================
837 void rebuild_3d_view(bool regenerateMaps)
838 {
839  using namespace std::string_literals;
840 
841  const size_t idx = mrpt::round(slSelectorICP->value());
842 
843  btnSelectorBack->setEnabled(!logRecords.empty() && idx > 0);
844  btnSelectorForw->setEnabled(
845  !logRecords.empty() && idx < logRecords.size() - 1);
846 
847  if (idx >= logRecords.size()) return;
848 
849  glVizICP->clear();
850 
851  // Free memory
852  static std::optional<size_t> lastIdx;
853  bool mustResetIterationSlider = false;
854 
855  if (!lastIdx || (lastIdx && idx != *lastIdx))
856  {
857  // free memory:
858  if (lastIdx) logRecords.at(*lastIdx).dispose();
859 
860  // and note that we should show the first/last ICP iteration:
861  mustResetIterationSlider = true;
862  }
863  lastIdx = idx;
864 
865  // lazy load from disk happens in the "get()":
866  const auto& lr = logRecords.at(idx).get();
867 
868  lbICPStats[0]->setValue(logRecords.at(idx).shortFileName());
869 
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() : ""));
877 
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()));
883 
884  lbICPStats[3]->setValue("Global: "s + lr.pcGlobal->contents_summary());
885  lbICPStats[4]->setValue("Local: "s + lr.pcLocal->contents_summary());
886 
887  tbInitialGuess->setValue(lr.initialGuessLocalWrtGlobal.asString());
888 
889  tbLogPose->setValue(lr.icpResult.optimal_tf.mean.asString());
890 
891  // dyn variables:
892  {
893  for (auto* lb : lbDynVariables) lb->setValue("");
894 
895  size_t lbIdx = 0;
896  for (const auto& [name, value] : lr.dynamicVariables)
897  {
898  lbDynVariables[lbIdx++]->setValue(
899  mrpt::format("%s = %g", name.c_str(), value));
900  if (lbIdx >= MAX_VARIABLE_LIST) break;
901  }
902  }
903 
904  {
905  const auto poseChange =
906  lr.icpResult.optimal_tf.mean -
907  mrpt::poses::CPose3D(lr.initialGuessLocalWrtGlobal);
908 
909  tbInit2Final->setValue(mrpt::format(
910  "|T|=%.03f [m] |R|=%.03f [deg]", poseChange.norm(),
911  mrpt::RAD2DEG(
912  mrpt::poses::Lie::SO<3>::log(poseChange.getRotationMatrix())
913  .norm())));
914  }
915 
916  const auto poseFromCorner = mrpt::poses::CPose3D::Identity();
917  mrpt::poses::CPose3DPDFGaussian relativePose;
918  const mp2p_icp::Pairings* pairsToViz = nullptr;
919 
920  if (!lr.iterationsDetails.has_value() || lr.iterationsDetails->empty())
921  {
922  slIterationDetails->setEnabled(false);
923 
924  if (cbShowInitialPose->checked())
925  {
926  relativePose.mean =
927  mrpt::poses::CPose3D(lr.initialGuessLocalWrtGlobal);
928  lbICPIteration->setCaption("Show ICP iteration: INITIAL");
929  }
930  else
931  {
932  relativePose = lr.icpResult.optimal_tf;
933  lbICPIteration->setCaption("Show ICP iteration: FINAL");
934  }
935 
936  if (cbViewPairings->checked()) pairsToViz = &lr.icpResult.finalPairings;
937  }
938  else
939  {
940  slIterationDetails->setEnabled(true);
941  slIterationDetails->setRange(
942  {.0f, static_cast<float>(lr.iterationsDetails->size() - 1)});
943 
944  if (mustResetIterationSlider)
945  slIterationDetails->setValue(
946  cbShowInitialPose->checked()
947  ? slIterationDetails->range().first
948  : slIterationDetails->range().second);
949 
950  // final or partial solution?
951  auto it = lr.iterationsDetails->begin();
952 
953  size_t nIter = mrpt::round(slIterationDetails->value());
954  mrpt::keep_min(nIter, lr.iterationsDetails->size() - 1);
955 
956  std::advance(it, nIter);
957 
958  relativePose.mean = it->second.optimalPose;
959  if (cbViewPairings->checked()) pairsToViz = &it->second.pairings;
960 
961  lbICPIteration->setCaption(
962  "Show ICP iteration: "s + std::to_string(it->first) + "/"s +
963  std::to_string(lr.iterationsDetails->rbegin()->first));
964  }
965 
966  {
967  std::string s;
968  for (int i = 0; i < 6; i++)
969  s += mrpt::format("%.02f ", std::sqrt(relativePose.cov(i, i)));
970 
971  s += mrpt::format(
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());
975 
976  tbCovariance->setValue(s);
977  }
978 
979  // Extract SE(2) covariance:
980  const mrpt::poses::CPosePDFGaussian pose2D(relativePose);
981 
982  // Condition numbers:
983  tbConditionNumber->setValue(mrpt::format(
984  " cn{XYZ}=%.02f cn{SO(3)}=%.02f cn{SE(2)}=%.02f "
985  "cn{SE(3)}=%.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)));
989 
990  // 3D objects -------------------
991  auto glCornerFrom =
992  mrpt::opengl::stock_objects::CornerXYZSimple(0.75f, 3.0f);
993  glCornerFrom->setPose(poseFromCorner);
994  glVizICP->insert(glCornerFrom);
995 
996  auto glCornerLocal =
997  mrpt::opengl::stock_objects::CornerXYZSimple(0.85f, 5.0f);
998  glCornerLocal->setPose(relativePose.mean);
999  glCornerLocal->setName("Local");
1000  glCornerLocal->enableShowName(true);
1001  glVizICP->insert(glCornerLocal);
1002 
1003  auto glCornerToCov = mrpt::opengl::CEllipsoid3D::Create();
1004  glCornerToCov->set3DsegmentsCount(16);
1005  glCornerToCov->enableDrawSolid3D(true);
1006  glCornerToCov->setColor_u8(0xff, 0x00, 0x00, 0x40);
1007  // std::cout << "cov:\n" << relativePose.cov << "\n";
1008  glCornerToCov->setCovMatrixAndMean(
1009  relativePose.cov.blockCopy<3, 3>(0, 0),
1010  relativePose.mean.asVectorVal().head<3>());
1011  glVizICP->insert(glCornerToCov);
1012 
1013  // GLOBAL PC:
1014  mp2p_icp::render_params_t rpGlobal;
1015 
1016  rpGlobal.points.visible = false;
1017  for (const auto& [lyName, cb] : cbLayersByName_global)
1018  {
1019  // Update stats in the cb label:
1020  cb->setCaption(lyName); // default
1021  if (auto itL = lr.pcGlobal->layers.find(lyName);
1022  itL != lr.pcGlobal->layers.end())
1023  {
1024  if (auto pc = std::dynamic_pointer_cast<mrpt::maps::CPointsMap>(
1025  itL->second);
1026  pc)
1027  {
1028  cb->setCaption(
1029  lyName + " | "s +
1030  mrpt::system::unitsFormat(static_cast<double>(pc->size())) +
1031  " points"s + " | class="s +
1032  pc->GetRuntimeClass()->className);
1033  }
1034  else
1035  {
1036  cb->setCaption(
1037  lyName + " | class="s +
1038  itL->second->GetRuntimeClass()->className);
1039  }
1040  }
1041 
1042  // show/hide:
1043  if (!cb->checked()) continue; // hidden
1044  rpGlobal.points.visible = true;
1045 
1046  auto& rpL = rpGlobal.points.perLayer[lyName];
1047  rpL.pointSize = slGlobalPointSize->value();
1048  rpL.render_voxelmaps_as_points = cbViewVoxelsAsPoints->checked();
1049  rpL.render_voxelmaps_free_space = cbViewVoxelsFreeSpace->checked();
1050 
1051  if (cbColorizeGlobalMap->checked())
1052  {
1053  auto& cm = rpL.colorMode.emplace();
1054  cm.colorMap = mrpt::img::TColormap::cmHOT;
1055  cm.recolorizeByCoordinate = mp2p_icp::Coordinate::Z;
1056  }
1057  }
1058 
1059  static mrpt::opengl::CSetOfObjects::Ptr lastGlobalPts;
1060 
1061  if (!lastGlobalPts || regenerateMaps)
1062  {
1063  // Show all or selected layers:
1064  for (auto& rpL : rpGlobal.points.perLayer)
1065  rpL.second.color = mrpt::img::TColor(0xff, 0x00, 0x00, 0xff);
1066 
1067  auto glPts = lr.pcGlobal->get_visualization(rpGlobal);
1068  lastGlobalPts = glPts;
1069 
1070  // Show all or selected layers:
1071  rpGlobal.points.allLayers.color =
1072  mrpt::img::TColor(0xff, 0x00, 0x00, 0xff);
1073 
1074  glVizICP->insert(glPts);
1075  }
1076  else
1077  {
1078  // avoid the costly method of re-rendering maps, if possible:
1079  glVizICP->insert(lastGlobalPts);
1080  }
1081 
1082  // LOCAL PC:
1083  mp2p_icp::render_params_t rpLocal;
1084 
1085  rpLocal.points.visible = false;
1086  for (const auto& [lyName, cb] : cbLayersByName_local)
1087  {
1088  // Update stats in the cb label:
1089  cb->setCaption(lyName); // default
1090  if (auto itL = lr.pcLocal->layers.find(lyName);
1091  itL != lr.pcLocal->layers.end())
1092  {
1093  if (auto pc = std::dynamic_pointer_cast<mrpt::maps::CPointsMap>(
1094  itL->second);
1095  pc)
1096  {
1097  cb->setCaption(
1098  lyName + " | "s +
1099  mrpt::system::unitsFormat(
1100  static_cast<double>(pc->size()), 2, false) +
1101  " points"s + " | class="s +
1102  pc->GetRuntimeClass()->className);
1103  }
1104  else
1105  {
1106  cb->setCaption(
1107  lyName + " | class="s +
1108  itL->second->GetRuntimeClass()->className);
1109  }
1110  }
1111 
1112  // show/hide:
1113  if (!cb->checked()) continue; // hidden
1114  rpLocal.points.visible = true;
1115 
1116  auto& rpL = rpLocal.points.perLayer[lyName];
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())
1121  {
1122  auto& cm = rpL.colorMode.emplace();
1123  cm.colorMap = mrpt::img::TColormap::cmHOT;
1124  cm.recolorizeByCoordinate = mp2p_icp::Coordinate::Z;
1125  }
1126  }
1127 
1128  static mrpt::opengl::CSetOfObjects::Ptr lastLocalPts;
1129 
1130  if (!lastLocalPts || regenerateMaps)
1131  {
1132  // Show all or selected layers:
1133  for (auto& rpL : rpLocal.points.perLayer)
1134  rpL.second.color = mrpt::img::TColor(0x00, 0x00, 0xff, 0xff);
1135 
1136  auto glPts = lr.pcLocal->get_visualization(rpLocal);
1137  lastLocalPts = glPts;
1138 
1139  glVizICP->insert(glPts);
1140  }
1141  else
1142  {
1143  // avoid the costly method of re-rendering maps, if possible:
1144  glVizICP->insert(lastLocalPts);
1145  }
1146  lastLocalPts->setPose(relativePose.mean);
1147 
1148  // Global view options:
1149  {
1150  std::lock_guard<std::mutex> lck(win->background_scene_mtx);
1151  win->camera().setCameraProjective(!cbViewOrtho->checked());
1152 
1153  if (cbCameraFollowsLocal->checked())
1154  {
1155  win->camera().setCameraPointing(
1156  relativePose.mean.x(), relativePose.mean.y(),
1157  relativePose.mean.z());
1158  }
1159 
1160  // clip planes:
1161  const auto depthFieldMid = std::pow(10.0, slMidDepthField->value());
1162  const auto depthFieldThickness =
1163  std::pow(10.0, slThicknessDepthField->value());
1164 
1165  const auto clipNear =
1166  std::max(1e-2, depthFieldMid - 0.5 * depthFieldThickness);
1167  const auto clipFar = depthFieldMid + 0.5 * depthFieldThickness;
1168 
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));
1175 
1176  win->background_scene->getViewport()->setViewportClipDistances(
1177  clipNear, clipFar);
1178  win->camera().setMaximumZoom(std::max<double>(1000, 3.0 * clipFar));
1179  }
1180 
1181  // Pairings ------------------
1182 
1183  // viz pairings:
1184  if (pairsToViz)
1185  {
1187 
1188  rp.pt2pt.visible = cbViewPairings_pt2pt->checked();
1189  rp.pt2pl.visible = cbViewPairings_pt2pl->checked();
1190  rp.pt2pl.planePatchSize = std::pow(10.0, slPairingsPl2PlSize->value());
1191 
1192  rp.pt2ln.visible = cbViewPairings_pt2ln->checked();
1193  rp.pt2ln.lineLength = std::pow(10.0, slPairingsPl2LnSize->value());
1194 
1195  glVizICP->insert(pairsToViz->get_visualization(relativePose.mean, rp));
1196 
1197  tbPairings->setValue(pairsToViz->contents_summary());
1198  }
1199  else
1200  {
1201  tbPairings->setValue(
1202  "None selected (mark one of the checkboxes below)");
1203  }
1204 
1205  // XYZ corner overlay viewport:
1206  {
1207  auto gl_view = win->background_scene->createViewport("small-view");
1208 
1209  gl_view->setViewportPosition(0, 0, 0.1, 0.1 * 16.0 / 9.0);
1210  gl_view->setTransparent(true);
1211  {
1212  mrpt::opengl::CText::Ptr obj = mrpt::opengl::CText::Create("X");
1213  obj->setLocation(1.1, 0, 0);
1214  gl_view->insert(obj);
1215  }
1216  {
1217  mrpt::opengl::CText::Ptr obj = mrpt::opengl::CText::Create("Y");
1218  obj->setLocation(0, 1.1, 0);
1219  gl_view->insert(obj);
1220  }
1221  {
1222  mrpt::opengl::CText::Ptr obj = mrpt::opengl::CText::Create("Z");
1223  obj->setLocation(0, 0, 1.1);
1224  gl_view->insert(obj);
1225  }
1226  gl_view->insert(mrpt::opengl::stock_objects::CornerXYZ());
1227  }
1228 }
1229 
1230 #else // MRPT_HAS_NANOGUI
1231 static void main_show_gui()
1232 {
1233  THROW_EXCEPTION(
1234  "This application requires a version of MRPT built with nanogui "
1235  "support.");
1236 }
1237 
1238 #endif // MRPT_HAS_NANOGUI
1239 
1240 int main(int argc, char** argv)
1241 {
1242  try
1243  {
1244  // Parse arguments:
1245  if (!cmd.parse(argc, argv)) return 1; // should exit.
1246 
1247  // Load plugins:
1248  if (arg_plugins.isSet())
1249  {
1250  std::string errMsg;
1251  const auto plugins = arg_plugins.getValue();
1252  std::cout << "Loading plugin(s): " << plugins << std::endl;
1253  if (!mrpt::system::loadPluginModules(plugins, errMsg))
1254  {
1255  std::cerr << errMsg << std::endl;
1256  return 1;
1257  }
1258  }
1259 
1260  main_show_gui();
1261  return 0;
1262  }
1263  catch (std::exception& e)
1264  {
1265  std::cerr << "Exit due to exception:\n"
1266  << mrpt::exception_to_str(e) << std::endl;
1267  return 1;
1268  }
1269 }
mp2p_icp::pairings_render_params_t::pt2pl
render_params_pairings_pt2pl_t pt2pl
Definition: render_params.h:236
mp2p_icp::Pairings::contents_summary
virtual std::string contents_summary() const
Definition: Pairings.cpp:158
cmd
static TCLAP::CmdLine cmd(APP_NAME)
mp2p_icp::render_params_t::points
render_params_points_t points
Definition: render_params.h:190
main
int main(int argc, char **argv)
Definition: apps/icp-log-viewer/main.cpp:1240
mp2p_icp::pairings_render_params_t::pt2pt
render_params_pairings_pt2pt_t pt2pt
Definition: render_params.h:235
LogRecord.h
A record of the inputs and outputs of an ICP run.
arg_plugins
static TCLAP::ValueArg< std::string > arg_plugins("l", "load-plugins", "One or more (comma separated) *.so files to load as plugins", false, "foobar.so", "foobar.so", cmd)
s
XmlRpcServer s
mp2p_icp::render_params_pairings_pt2pl_t::visible
bool visible
Definition: render_params.h:212
kitti-run-seq.f
string f
Definition: kitti-run-seq.py:12
argSearchDir
static TCLAP::ValueArg< std::string > argSearchDir("d", "directory", "Directory in which to search for *.icplog files.", false, ".", ".", cmd)
mp2p_icp::Pairings
Definition: Pairings.h:78
mp2p_icp::LogRecord::LoadFromFile
static LogRecord LoadFromFile(const std::string &fileName)
Definition: LogRecord.h:86
mp2p_icp::render_params_pairings_pt2pt_t::visible
bool visible
Definition: render_params.h:203
mp2p_icp::Coordinate::Z
@ Z
mp2p_icp::LogRecord
Definition: LogRecord.h:30
mp2p_icp::render_params_pairings_pt2pl_t::planePatchSize
double planePatchSize
Definition: render_params.h:216
mp2p_icp::pairings_render_params_t::pt2ln
render_params_pairings_pt2ln_t pt2ln
Definition: render_params.h:237
testing::internal::string
::std::string string
Definition: gtest.h:1979
argAutoPlayPeriod
static TCLAP::ValueArg< double > argAutoPlayPeriod("", "autoplay-period", "The period (in seconds) between timestamps to load and show in autoplay " "mode.", false, 0.1, "period [seconds]", cmd)
mp2p_icp::render_params_pairings_pt2ln_t::visible
bool visible
Definition: render_params.h:223
mp2p_icp::render_params_points_t::allLayers
render_params_point_layer_t allLayers
Definition: render_params.h:166
mp2p_icp::render_params_point_layer_t::color
mrpt::img::TColor color
Definition: render_params.h:133
APP_NAME
constexpr const char * APP_NAME
Definition: apps/icp-log-viewer/main.cpp:40
mp2p_icp::render_params_t
Definition: render_params.h:184
argExtension
static TCLAP::ValueArg< std::string > argExtension("e", "file-extension", "Filename extension to look for. Default is `icplog`", false, "icplog", "icplog", cmd)
main_show_gui
static void main_show_gui()
Definition: apps/icp-log-viewer/main.cpp:1231
mp2p_icp::render_params_points_t::perLayer
std::map< layer_name_t, render_params_point_layer_t > perLayer
Definition: render_params.h:170
get_user_config_file
static void get_user_config_file(char *out, unsigned int maxlen, const char *appname)
Definition: cfgpath.h:96
mp2p_icp::render_params_pairings_pt2ln_t::lineLength
double lineLength
Definition: render_params.h:227
SMALL_FONT_SIZE
constexpr int SMALL_FONT_SIZE
Definition: apps/icp-log-viewer/main.cpp:42
mp2p_icp::Pairings::get_visualization
virtual auto get_visualization(const mrpt::poses::CPose3D &localWrtGlobal, const pairings_render_params_t &p=pairings_render_params_t()) const -> std::shared_ptr< mrpt::opengl::CSetOfObjects >
Definition: Pairings.cpp:175
PointMatcherSupport::get
const M::mapped_type & get(const M &m, const typename M::key_type &k)
Definition: Bibliography.cpp:57
mp2p_icp::metric_map_t
Generic container of pointcloud(s), extracted features and other maps.
Definition: metricmap.h:49
MID_FONT_SIZE
constexpr int MID_FONT_SIZE
Definition: apps/icp-log-viewer/main.cpp:41
mp2p_icp::pairings_render_params_t
Definition: render_params.h:231
PointMatcherSupport::pow
constexpr T pow(const T base, const std::size_t exponent)
Definition: libpointmatcher/pointmatcher/DataPointsFilters/utils/utils.h:47
mp2p_icp::render_params_points_t::visible
bool visible
Definition: render_params.h:162
kitti-batch-convert.filename
filename
Definition: kitti-batch-convert.py:6
WINDOW_FIXED_WIDTH
constexpr int WINDOW_FIXED_WIDTH
Definition: apps/icp-log-viewer/main.cpp:43
argSingleFile
static TCLAP::ValueArg< std::string > argSingleFile("f", "file", "Load just this one single log *.icplog file.", false, "log.icplog", "log.icplog", cmd)


mp2p_icp
Author(s):
autogenerated on Wed Oct 2 2024 02:45:24