/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2024, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */

#include "xRawLogViewerMain.h"

#include <mrpt/containers/stl_containers_utils.h>
#include <mrpt/gui/WxUtils.h>
#include <mrpt/gui/about_box.h>
#include <mrpt/io/CFileGZInputStream.h>
#include <mrpt/io/CFileGZOutputStream.h>
#include <mrpt/maps/CColouredPointsMap.h>
#include <mrpt/maps/COccupancyGridMap2D.h>
#include <mrpt/maps/CSimplePointsMap.h>
#include <mrpt/math/ops_matrices.h>  // << ops
#include <mrpt/math/ops_vectors.h>   // << ops
#include <mrpt/math/wrap2pi.h>
#include <mrpt/obs/CObservation2DRangeScan.h>
#include <mrpt/obs/CObservation3DRangeScan.h>
#include <mrpt/obs/CObservationBeaconRanges.h>
#include <mrpt/obs/CObservationBearingRange.h>
#include <mrpt/obs/CObservationComment.h>
#include <mrpt/obs/CObservationGasSensors.h>
#include <mrpt/obs/CObservationOdometry.h>
#include <mrpt/obs/CObservationRange.h>
#include <mrpt/obs/CObservationStereoImages.h>
#include <mrpt/obs/CRawlog.h>
#include <mrpt/opengl/CSimpleLine.h>
#include <mrpt/opengl/CText3D.h>
#include <mrpt/poses/CPosePDFGaussian.h>
#include <mrpt/poses/CPosePDFParticles.h>
#include <mrpt/serialization/CArchive.h>
#include <mrpt/slam/CICP.h>
#include <mrpt/system/CDirectoryExplorer.h>
#include <mrpt/system/CTicTac.h>
#include <mrpt/system/datetime.h>
#include <mrpt/system/filesystem.h>
#include <mrpt/system/memory.h>
#include <mrpt/vision/CVideoFileWriter.h>
#include <wx/busyinfo.h>
#include <wx/dirdlg.h>
#include <wx/filedlg.h>
#include <wx/imaglist.h>
#include <wx/log.h>
#include <wx/msgdlg.h>
#include <wx/progdlg.h>
#include <wx/statbox.h>
#include <wx/textdlg.h>
#include <wx/tipdlg.h>

#include <iomanip>
#include <map>
#include <mrpt/math/interp_fit.hpp>

#include "CFormBatchSensorPose.h"
#include "CFormChangeSensorPositions.h"
#include "CFormEdit.h"
#include "CFormMotionModel.h"
#include "CFormPlayVideo.h"
#include "CFormRawMap.h"
#include "CIniEditor.h"
#include "COdometryParams.h"
#include "CScanAnimation.h"
#include "CScanMatching.h"
#include "xRawLogViewerApp.h"

//(*InternalHeaders(xRawLogViewerFrame)
#include <wx/artprov.h>
#include <wx/bitmap.h>
#include <wx/font.h>
#include <wx/image.h>
#include <wx/intl.h>
#include <wx/string.h>
#include <wx/tglbtn.h>
//*)

#include <wx/choicdlg.h>
#include <wx/choice.h>
#include <wx/config.h>
#include <wx/image.h>
#include <wx/textdlg.h>
#include <wx/tooltip.h>

#include "imgs/tree_icon1.xpm"
#include "imgs/tree_icon10.xpm"
#include "imgs/tree_icon11.xpm"
#include "imgs/tree_icon12.xpm"
#include "imgs/tree_icon2.xpm"
#include "imgs/tree_icon3.xpm"
#include "imgs/tree_icon4.xpm"
#include "imgs/tree_icon5.xpm"
#include "imgs/tree_icon6.xpm"
#include "imgs/tree_icon7.xpm"
#include "imgs/tree_icon8.xpm"
#include "imgs/tree_icon9.xpm"

using namespace mrpt;
using namespace mrpt::slam;
using namespace mrpt::maps;
using namespace mrpt::obs;
using namespace mrpt::opengl;
using namespace mrpt::system;
using namespace mrpt::math;
using namespace mrpt::config;
using namespace mrpt::gui;
using namespace mrpt::img;
using namespace mrpt::poses;
using namespace mrpt::serialization;
using namespace mrpt::rtti;
using namespace mrpt::vision;
using namespace mrpt::io;
using namespace std;

CRawlog rawlog;
TTimeStamp rawlog_first_timestamp = INVALID_TIMESTAMP;

CTicTac crono_Loading;
string loadedFileName = "noname.rawlog";
float timeToLoad = 0;
double experimentLenght = 0;

xRawLogViewerFrame* theMainWindow = nullptr;

extern std::unique_ptr<CConfigFile> iniFile;

// As a global variable to keep the user selections in memory.
CFormRawMap* formRawMap = nullptr;
CScanMatching* scanMatchingDialog = nullptr;

extern string global_fileToOpen;

#if defined(__WXMSW__)
string iniFileSect("CONF_WIN");
#elif defined(__UNIX__)
string iniFileSect("CONF_LIN");
#endif

// helper functions
enum wxbuildinfoformat
{
  short_f,
  long_f
};

// Used below. Must be at global scope for usage within STL.
struct TImageToSaveData
{
  TImageToSaveData() = default;
  TImageToSaveData(mrpt::img::CImage* _img, const char* str) : img(_img), channel_desc(str) {}
  mrpt::img::CImage* img{nullptr};
  std::string channel_desc;  // LEFT, RIGHT, etc...
};

bool operator<(const TImageToSaveData& a, const TImageToSaveData& b)
{
  return a.channel_desc < b.channel_desc;
}

static const long ID_BUTTON2 = wxNewId();
static const long ID_BUTTON3 = wxNewId();
static const long ID_STATICLINE2 = wxNewId();
static const long ID_BUTTON4 = wxNewId();
static const long ID_BUTTON5 = wxNewId();
static const long ID_BUTTON6 = wxNewId();
static const long ID_BUTTON7 = wxNewId();
static const long ID_STATICLINE3 = wxNewId();
static const long ID_BUTTON8 = wxNewId();
static const long ID_BUTTON9 = wxNewId();
static const long ID_STATICLINE4 = wxNewId();
static const long ID_BUTTON10 = wxNewId();
static const long ID_BUTTON11 = wxNewId();
static const long ID_STATICLINE1 = wxNewId();
static const long ID_STATICTEXT4 = wxNewId();
static const long ID_COMBO_IMG_DIRS = wxNewId();
static const long ID_CUSTOM5 = wxNewId();
static const long ID_PANEL1 = wxNewId();
static const long ID_TEXTCTRL1 = wxNewId();
static const long ID_PANEL3 = wxNewId();
static const long ID_BUTTON1 = wxNewId();
static const long ID_PANEL18 = wxNewId();
static const long ID_TEXTCTRL2 = wxNewId();
static const long ID_CUSTOM6 = wxNewId();
static const long ID_PANEL25 = wxNewId();
static const long ID_SPLITTERWINDOW2 = wxNewId();
static const long ID_TEXTCTRL3 = wxNewId();
static const long ID_NOTEBOOK3 = wxNewId();
static const long ID_PANEL6 = wxNewId();
static const long ID_CUSTOM2 = wxNewId();
static const long ID_CUSTOM3 = wxNewId();
static const long ID_PANEL7 = wxNewId();
static const long ID_CUSTOM1 = wxNewId();
static const long ID_PANEL4 = wxNewId();
static const long ID_PANEL8 = wxNewId();
static const long ID_STATICTEXT2 = wxNewId();
static const long ID_STATICBITMAP1 = wxNewId();
static const long ID_PANEL9 = wxNewId();
static const long ID_STATICTEXT1 = wxNewId();
static const long ID_STATICBITMAP2 = wxNewId();
static const long ID_PANEL13 = wxNewId();
static const long ID_STATICBITMAP3 = wxNewId();
static const long ID_PANEL14 = wxNewId();
static const long ID_STATICBITMAP7 = wxNewId();
static const long ID_PANEL24 = wxNewId();
static const long ID_NOTEBOOK2 = wxNewId();
static const long ID_PANEL10 = wxNewId();
static const long ID_PANEL11 = wxNewId();
static const long ID_PANEL12 = wxNewId();
static const long ID_PANEL15 = wxNewId();
static const long ID_CUSTOM4 = wxNewId();
static const long ID_PANEL17 = wxNewId();
static const long ID_PANEL16 = wxNewId();
static const long ID_XY_GLCANVAS = wxNewId();
static const long ID_STATICTEXT3 = wxNewId();
static const long ID_SLIDER1 = wxNewId();
static const long ID_PANEL20 = wxNewId();
static const long ID_STATICBITMAP4 = wxNewId();
static const long ID_PANEL21 = wxNewId();
static const long ID_STATICBITMAP5 = wxNewId();
static const long ID_PANEL22 = wxNewId();
static const long ID_STATICBITMAP6 = wxNewId();
static const long ID_PANEL23 = wxNewId();
static const long ID_PANEL_VIEW_3D_POINT_OPTIONS = wxNewId();
static const long ID_NOTEBOOK_3DOBS = wxNewId();
static const long ID_PANEL19 = wxNewId();
static const long ID_NOTEBOOK1 = wxNewId();
static const long ID_PANEL5 = wxNewId();
static const long ID_SPLITTERWINDOW3 = wxNewId();
static const long ID_PANEL2 = wxNewId();
static const long ID_SPLITTERWINDOW1 = wxNewId();
static const long ID_MENUITEM1 = wxNewId();
static const long ID_MENUITEM2 = wxNewId();
static const long ID_MENUITEM11 = wxNewId();
static const long ID_MENUITEM4 = wxNewId();
static const long ID_MENUITEM76 = wxNewId();
static const long ID_MENUITEM7 = wxNewId();
static const long ID_MENUITEM8 = wxNewId();
static const long ID_MENUITEM10 = wxNewId();
static const long ID_MENUITEM62 = wxNewId();
static const long ID_MENUITEM64 = wxNewId();
static const long ID_MENUITEM13 = wxNewId();
static const long ID_MENUITEM60 = wxNewId();
static const long ID_MENUITEM61 = wxNewId();
static const long ID_MENUITEM6 = wxNewId();
static const long ID_MENUITEM5 = wxNewId();
static const long ID_MENUITEM47 = wxNewId();
static const long ID_MENUITEM56 = wxNewId();
static const long ID_MENUITEM63 = wxNewId();
static const long ID_MENUITEM87 = wxNewId();
static const long ID_MENUITEM3 = wxNewId();
static const long ID_MENUITEM58 = wxNewId();
static const long ID_MENUITEM55 = wxNewId();
static const long ID_MENUITEM54 = wxNewId();
static const long idMenuQuit = wxNewId();
static const long ID_MENUITEM14 = wxNewId();
static const long ID_MENUITEM51 = wxNewId();
static const long ID_MENUITEM69 = wxNewId();
static const long ID_MENUITEM91 = wxNewId();
static const long ID_MENUITEM15 = wxNewId();
static const long ID_MENUITEM70 = wxNewId();
static const long ID_MENUITEM16 = wxNewId();
static const long ID_MENUITEM59 = wxNewId();
static const long ID_MENUITEM57 = wxNewId();
static const long ID_MENUITEM75 = wxNewId();
static const long ID_MENUITEM67 = wxNewId();
static const long ID_MENUITEM68 = wxNewId();
static const long ID_MENUITEM82 = wxNewId();
static const long ID_MENUITEM20 = wxNewId();
static const long ID_MENUITEM22 = wxNewId();
static const long ID_MENUITEM53 = wxNewId();
static const long ID_MENUITEM23 = wxNewId();
static const long ID_MENUITEM41 = wxNewId();
static const long ID_MENUITEM84 = wxNewId();
static const long ID_MENUITEM12 = wxNewId();
static const long ID_MENUITEM17 = wxNewId();
static const long ID_MENUITEM44 = wxNewId();
static const long ID_MENUITEM19 = wxNewId();
static const long ID_MENUITEM25 = wxNewId();
static const long ID_MENUITEM73 = wxNewId();
static const long ID_MENUITEM74 = wxNewId();
static const long ID_MENUITEM77 = wxNewId();
static const long ID_MENUITEM79 = wxNewId();
static const long ID_MENUITEM18 = wxNewId();
static const long ID_MENUITEM86 = wxNewId();
static const long ID_MENUITEM90 = wxNewId();
static const long ID_MENUITEM85 = wxNewId();
static const long ID_MENUITEM29 = wxNewId();
static const long ID_MENUITEM9 = wxNewId();
static const long ID_MENUITEM28 = wxNewId();
static const long ID_MENUITEM71 = wxNewId();
static const long ID_MENUITEM72 = wxNewId();
static const long ID_MENUITEM78 = wxNewId();
static const long ID_MENUITEM83 = wxNewId();
static const long ID_MENUITEM21 = wxNewId();
static const long ID_MENUITEM30 = wxNewId();
static const long ID_MENUITEM24 = wxNewId();
static const long ID_MENUITEM35 = wxNewId();
static const long ID_MENUITEM31 = wxNewId();
static const long ID_MENUITEM34 = wxNewId();
static const long ID_MENUITEM65 = wxNewId();
static const long ID_MENUITEM66 = wxNewId();
static const long ID_MENUITEM52 = wxNewId();
static const long ID_MENUITEM80 = wxNewId();
static const long ID_MENUITEM36 = wxNewId();
static const long ID_MENUITEM33 = wxNewId();
static const long ID_MENUITEM38 = wxNewId();
static const long ID_MENUITEM37 = wxNewId();
static const long ID_MENUITEM40 = wxNewId();
static const long ID_MENUITEM81 = wxNewId();
static const long ID_MENUITEM39 = wxNewId();
static const long ID_MENUITEM46 = wxNewId();
static const long ID_MENUITEM45 = wxNewId();
static const long ID_MENUITEM43 = wxNewId();
static const long ID_MENUITEM42 = wxNewId();
static const long ID_MENUITEM89 = wxNewId();
static const long ID_MENUITEM88 = wxNewId();
static const long ID_MENUITEM26 = wxNewId();
static const long ID_MENUITEM32 = wxNewId();
static const long ID_MENUITEM27 = wxNewId();
static const long idMenuAbout = wxNewId();
static const long ID_STATUSBAR1 = wxNewId();
static const long MNU_1 = wxNewId();
static const long ID_MENUITEM49 = wxNewId();
static const long ID_MENUITEM50 = wxNewId();
static const long ID_MENUITEM48 = wxNewId();
static const long ID_TIMER1 = wxNewId();
static const long ID_MENUITEM_RENAME_BY_SF_IDX = wxNewId();
static const long ID_SCROLLEDWINDOW2 = wxNewId();
static const long ID_TXT_SELECTED_INFO = wxNewId();

BEGIN_EVENT_TABLE(xRawLogViewerFrame, wxFrame)
//(*EventTable(xRawLogViewerFrame)
//*)
EVT_MENU_RANGE(wxID_FILE1, wxID_FILE9, xRawLogViewerFrame::OnMRUFile)
END_EVENT_TABLE()

xRawLogViewerFrame::xRawLogViewerFrame(wxWindow* parent, wxWindowID id) :
    m_fileHistory(9 /* Max file list*/)
{
  using This = xRawLogViewerFrame;  // shortcut!

  theMainWindow = this;

  // Load my custom icons:
  wxArtProvider::Push(new MyArtProvider);

  const wxFont monoFont(
      8, wxFontFamily::wxFONTFAMILY_TELETYPE, wxFontStyle::wxFONTSTYLE_NORMAL,
      wxFontWeight::wxFONTWEIGHT_NORMAL);

  //(*Initialize(xRawLogViewerFrame)
  wxMenu* Menu39;
  wxFlexGridSizer* FlexGridSizer4;
  wxFlexGridSizer* FlexGridSizer16;
  wxMenuItem* MenuItem33;
  wxMenuItem* MenuItem26;
  wxMenuItem* MenuItem25;
  wxMenuItem* MenuItem2;
  wxFlexGridSizer* FlexGridSizer3;
  wxMenuItem* MenuItem55;
  wxMenuItem* MenuItem1;
  wxMenuItem* MenuItem56;
  wxFlexGridSizer* FlexGridSizer5;
  wxFlexGridSizer* FlexGridSizer9;
  wxMenuItem* MenuItem22;
  wxFlexGridSizer* FlexGridSizer2;
  wxMenuItem* MenuItem17;
  wxMenu* Menu1;
  wxFlexGridSizer* FlexGridSizer7;
  wxMenuItem* MenuItem82;
  wxMenuItem* MenuItem75;
  wxMenuItem* MenuItem60;
  wxMenuItem* MenuItem12;
  wxMenuItem* MenuItem24;
  wxMenuItem* MenuItem69;
  wxMenuItem* MenuItem27;
  wxFlexGridSizer* fgzToolbar;
  wxFlexGridSizer* FlexGridSizer8;
  wxMenuItem* MenuItem70;
  wxMenuItem* MenuItem67;
  wxMenuItem* MenuItem65;
  wxFlexGridSizer* FlexGridSizer14;
  wxFlexGridSizer* FlexGridSizer13;
  wxMenuItem* MenuItem41;
  wxMenu* MenuItem81;
  wxMenuBar* MenuBar1;
  wxFlexGridSizer* FlexGridSizer6;
  wxFlexGridSizer* fgzMain;
  wxMenuItem* MenuItem43;
  wxMenu* Menu2;
  wxMenuItem* MenuItem18;
  wxMenuItem* MenuItem19;

  Create(
      parent, id, _("RawlogViewer - Part of the MRPT project"), wxDefaultPosition, wxDefaultSize,
      wxCAPTION | wxDEFAULT_FRAME_STYLE | wxSYSTEM_MENU | wxRESIZE_BORDER | wxCLOSE_BOX |
          wxMAXIMIZE_BOX | wxMINIMIZE_BOX,
      _T("id"));
  SetClientSize(wxSize(700, 500));
  {
    wxIcon FrameIcon;
    FrameIcon.CopyFromBitmap(
        wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("MAIN_ICON")), wxART_OTHER));
    SetIcon(FrameIcon);
  }
  fgzMain = new wxFlexGridSizer(3, 1, 0, 0);
  fgzMain->AddGrowableCol(0);
  fgzMain->AddGrowableRow(1);
  fgzToolbar = new wxFlexGridSizer(1, 16, 0, 0);
  fgzToolbar->AddGrowableCol(14);
  fgzToolbar->AddGrowableCol(15);

  btnToolbarOpen = new wxCustomButton(
      this, ID_BUTTON2, _("Load..."),
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_FOLDER")), wxART_TOOLBAR),
      wxDefaultPosition, wxSize(-1, 60), wxCUSTBUT_BUTTON | wxCUSTBUT_BOTTOM, wxDefaultValidator,
      _T("ID_BUTTON2"));
  btnToolbarOpen->SetBitmapDisabled(
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_FOLDER")), wxART_TOOLBAR));
  btnToolbarOpen->SetMargins(wxSize(5, 5));
  fgzToolbar->Add(
      btnToolbarOpen, 1,
      wxALL | wxFIXED_MINSIZE | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 1);
  Button1 = new wxCustomButton(
      this, ID_BUTTON3, _("Save as..."),
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_FILE_SAVE_AS")), wxART_TOOLBAR),
      wxDefaultPosition, wxSize(-1, 60), wxCUSTBUT_BUTTON | wxCUSTBUT_BOTTOM, wxDefaultValidator,
      _T("ID_BUTTON3"));
  Button1->SetBitmapDisabled(wxArtProvider::GetBitmap(
      wxART_MAKE_ART_ID_FROM_STR(_T("wxART_FILE_SAVE_AS")), wxART_TOOLBAR));
  Button1->SetMargins(wxSize(5, 5));
  fgzToolbar->Add(
      Button1, 1, wxALL | wxFIXED_MINSIZE | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 1);
  StaticLine2 = new wxStaticLine(
      this, ID_STATICLINE2, wxDefaultPosition, wxSize(3, 70), wxLI_VERTICAL, _T("ID_STATICLINE2"));
  fgzToolbar->Add(StaticLine2, 1, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 1);
  Button2 = new wxCustomButton(
      this, ID_BUTTON4, _("Edit..."),
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_COPY")), wxART_TOOLBAR),
      wxDefaultPosition, wxSize(-1, 60), wxCUSTBUT_BUTTON | wxCUSTBUT_BOTTOM, wxDefaultValidator,
      _T("ID_BUTTON4"));
  Button2->SetBitmapDisabled(
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_COPY")), wxART_TOOLBAR));
  Button2->SetMargins(wxSize(5, 5));
  fgzToolbar->Add(
      Button2, 1, wxALL | wxFIXED_MINSIZE | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 1);
  Button3 = new wxCustomButton(
      this, ID_BUTTON5, _("\"Raw-map\"..."),
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("ICON_RAWMAP")), wxART_TOOLBAR),
      wxDefaultPosition, wxSize(-1, 60), wxCUSTBUT_BUTTON | wxCUSTBUT_BOTTOM, wxDefaultValidator,
      _T("ID_BUTTON5"));
  Button3->SetBitmapDisabled(
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("ICON_RAWMAP")), wxART_TOOLBAR));
  Button3->SetMargins(wxSize(5, 5));
  fgzToolbar->Add(
      Button3, 1, wxALL | wxFIXED_MINSIZE | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 1);
  Button4 = new wxCustomButton(
      this, ID_BUTTON6, _("Motion model"),
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("ICON_MOTION")), wxART_TOOLBAR),
      wxDefaultPosition, wxSize(-1, 60), wxCUSTBUT_BUTTON | wxCUSTBUT_BOTTOM, wxDefaultValidator,
      _T("ID_BUTTON6"));
  Button4->SetBitmapDisabled(
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("ICON_MOTION")), wxART_TOOLBAR));
  Button4->SetMargins(wxSize(5, 5));
  fgzToolbar->Add(
      Button4, 1, wxALL | wxFIXED_MINSIZE | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 1);
  StaticLine3 = new wxStaticLine(
      this, ID_STATICLINE3, wxDefaultPosition, wxSize(3, 70), wxLI_VERTICAL, _T("ID_STATICLINE3"));
  fgzToolbar->Add(StaticLine3, 1, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 1);
  Button6 = new wxCustomButton(
      this, ID_BUTTON8, _("Browse..."),
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("ICON_ANIMATE_SCANS")), wxART_TOOLBAR),
      wxDefaultPosition, wxSize(-1, 60), wxCUSTBUT_BUTTON | wxCUSTBUT_BOTTOM, wxDefaultValidator,
      _T("ID_BUTTON8"));
  Button6->SetBitmapDisabled(wxArtProvider::GetBitmap(
      wxART_MAKE_ART_ID_FROM_STR(_T("ICON_ANIMATE_SCANS")), wxART_TOOLBAR));
  Button6->SetMargins(wxSize(5, 5));
  fgzToolbar->Add(
      Button6, 1, wxALL | wxFIXED_MINSIZE | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 1);
  Button7 = new wxCustomButton(
      this, ID_BUTTON9, _("Video..."),
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_TIP")), wxART_TOOLBAR),
      wxDefaultPosition, wxSize(-1, 60), wxCUSTBUT_BUTTON | wxCUSTBUT_BOTTOM, wxDefaultValidator,
      _T("ID_BUTTON9"));
  Button7->SetBitmapDisabled(
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_TIP")), wxART_TOOLBAR));
  Button7->SetMargins(wxSize(5, 5));
  fgzToolbar->Add(
      Button7, 1, wxALL | wxFIXED_MINSIZE | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 1);
  StaticLine4 = new wxStaticLine(
      this, ID_STATICLINE4, wxDefaultPosition, wxSize(3, 70), wxLI_VERTICAL, _T("ID_STATICLINE4"));
  fgzToolbar->Add(StaticLine4, 1, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 1);
  Button8 = new wxCustomButton(
      this, ID_BUTTON10, _("About"),
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("ICON_ABOUT")), wxART_TOOLBAR),
      wxDefaultPosition, wxSize(-1, 60), wxCUSTBUT_BUTTON | wxCUSTBUT_BOTTOM, wxDefaultValidator,
      _T("ID_BUTTON10"));
  Button8->SetBitmapDisabled(
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("ICON_ABOUT")), wxART_TOOLBAR));
  Button8->SetMargins(wxSize(5, 5));
  fgzToolbar->Add(
      Button8, 1, wxALL | wxFIXED_MINSIZE | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 1);
  Button9 = new wxCustomButton(
      this, ID_BUTTON11, _("Quit"),
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("ICON_QUIT")), wxART_TOOLBAR),
      wxDefaultPosition, wxSize(-1, 60), wxCUSTBUT_BUTTON | wxCUSTBUT_BOTTOM, wxDefaultValidator,
      _T("ID_BUTTON11"));
  Button9->SetBitmapDisabled(
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("ICON_QUIT")), wxART_TOOLBAR));
  Button9->SetMargins(wxSize(5, 5));
  fgzToolbar->Add(
      Button9, 1, wxALL | wxFIXED_MINSIZE | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 1);
  StaticLine1 = new wxStaticLine(
      this, ID_STATICLINE1, wxDefaultPosition, wxSize(3, 70), wxLI_VERTICAL, _T("ID_STATICLINE1"));
  fgzToolbar->Add(StaticLine1, 1, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 1);
  FlexGridSizer16 = new wxFlexGridSizer(2, 1, 0, 0);
  FlexGridSizer16->AddGrowableCol(0);
  StaticText4 = new wxStaticText(
      this, ID_STATICTEXT4, _("Image dirs:"), wxDefaultPosition, wxDefaultSize, 0,
      _T("ID_STATICTEXT4"));
  FlexGridSizer16->Add(StaticText4, 1, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 5);
  toolbarcomboImages = new wxComboBox(
      this, ID_COMBO_IMG_DIRS, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, nullptr, 0,
      wxDefaultValidator, _T("ID_COMBO_IMG_DIRS"));
  toolbarcomboImages->SetMinSize(wxSize(150, -1));
  toolbarcomboImages->SetToolTip(_("Found external images paths"));
  toolbarcomboImages->SetHelpText(_("Found external images paths"));
  FlexGridSizer16->Add(toolbarcomboImages, 1, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 5);
  fgzToolbar->Add(FlexGridSizer16, 1, wxEXPAND);

  edSelectedTimeInfo = new wxTextCtrl(
      this, ID_TXT_SELECTED_INFO, wxEmptyString, wxDefaultPosition, wxDefaultSize,
      wxTE_MULTILINE | wxTE_READONLY, wxDefaultValidator, _T("ID_TXT_SELECTED_INFO"));
  edSelectedTimeInfo->SetFont(monoFont);

  fgzToolbar->Add(edSelectedTimeInfo, 1, wxEXPAND, 0);

  fgzMain->Add(fgzToolbar, 1, wxEXPAND);
  //---
  SplitterWindow1 = new wxSplitterWindow(
      this, ID_SPLITTERWINDOW1, wxDefaultPosition, wxDefaultSize,
      wxSP_3D | wxSP_3DBORDER | wxSP_LIVE_UPDATE, _T("ID_SPLITTERWINDOW1"));
  SplitterWindow1->SetMinSize(wxSize(10, 10));
  SplitterWindow1->SetMinimumPaneSize(10);
  Panel1 = new wxPanel(
      SplitterWindow1, ID_PANEL1, wxDefaultPosition, wxSize(337, -1), wxTAB_TRAVERSAL,
      _T("ID_PANEL1"));
  FlexGridSizer2 = new wxFlexGridSizer(1, 1, 0, 0);
  FlexGridSizer2->AddGrowableCol(0);
  FlexGridSizer2->AddGrowableRow(0);
  m_treeView = new CRawlogTreeView(
      Panel1, ID_CUSTOM5, wxDefaultPosition, wxDefaultSize, wxVSCROLL, _T("ID_CUSTOM5"));
  FlexGridSizer2->Add(m_treeView, 1, wxEXPAND);
  Panel1->SetSizer(FlexGridSizer2);
  FlexGridSizer2->SetSizeHints(Panel1);
  Panel2 = new wxPanel(
      SplitterWindow1, ID_PANEL2, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL,
      _T("ID_PANEL2"));
  BoxSizer1 = new wxBoxSizer(wxHORIZONTAL);
  SplitterWindow3 = new wxSplitterWindow(
      Panel2, ID_SPLITTERWINDOW3, wxDefaultPosition, wxDefaultSize,
      wxSP_3D | wxSP_3DBORDER | wxSP_LIVE_UPDATE, _T("ID_SPLITTERWINDOW3"));
  SplitterWindow3->SetMinSize(wxSize(150, 150));
  SplitterWindow3->SetMinimumPaneSize(150);
  Panel3 = new wxPanel(
      SplitterWindow3, ID_PANEL3, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL,
      _T("ID_PANEL3"));
  BoxSizer2 = new wxBoxSizer(wxHORIZONTAL);
  memo = new wxTextCtrl(
      Panel3, ID_TEXTCTRL1, wxEmptyString, wxDefaultPosition, wxSize(327, 140),
      wxTE_MULTILINE | wxTE_READONLY | wxTE_WORDWRAP | wxNO_BORDER | wxVSCROLL, wxDefaultValidator,
      _T("ID_TEXTCTRL1"));
  memo->SetFont(monoFont);
  BoxSizer2->Add(memo, 1, wxEXPAND);
  Panel3->SetSizer(BoxSizer2);
  BoxSizer2->Fit(Panel3);
  BoxSizer2->SetSizeHints(Panel3);
  Panel5 = new wxPanel(
      SplitterWindow3, ID_PANEL5, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL,
      _T("ID_PANEL5"));
  BoxSizer3 = new wxBoxSizer(wxHORIZONTAL);
  Notebook1 = new wxSimplebook(Panel5, ID_NOTEBOOK1);
  pn_CSensorialFrame = new wxPanel(
      Notebook1, ID_PANEL6, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("ID_PANEL6"));
  FlexGridSizer6 = new wxFlexGridSizer(2, 1, 0, 0);
  FlexGridSizer6->AddGrowableCol(0);
  FlexGridSizer6->AddGrowableRow(1);
  Panel9 = new wxPanel(
      pn_CSensorialFrame, ID_PANEL18, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL,
      _T("ID_PANEL18"));
  btnEditComments = new wxButton(
      Panel9, ID_BUTTON1, _("Edit comments..."), wxDefaultPosition, wxDefaultSize, 0,
      wxDefaultValidator, _T("ID_BUTTON1"));
  FlexGridSizer6->Add(Panel9, 1, wxEXPAND);
  Notebook3 = new wxNotebook(
      pn_CSensorialFrame, ID_NOTEBOOK3, wxDefaultPosition, wxSize(-1, 150), 0, _T("ID_NOTEBOOK3"));
  Notebook3->SetMinSize(wxSize(-1, 150));
  memStats = new wxTextCtrl(
      Notebook3, ID_TEXTCTRL2, wxEmptyString, wxDefaultPosition, wxDefaultSize,
      wxTE_MULTILINE | wxTE_READONLY | wxVSCROLL, wxDefaultValidator, _T("ID_TEXTCTRL2"));
  memStats->SetMinSize(wxSize(-1, 150));
  memStats->SetFont(monoFont);
  memStats->SetToolTip(_("Statistics of the rawlog load"));

  txtException = new wxTextCtrl(
      Notebook3, ID_TEXTCTRL3, wxEmptyString, wxDefaultPosition, wxDefaultSize,
      wxTE_MULTILINE | wxTE_READONLY, wxDefaultValidator, _T("ID_TEXTCTRL3"));
  txtException->SetFont(monoFont);
  Notebook3->AddPage(memStats, _("Dataset statistics && info"), false);
  Notebook3->AddPage(txtException, _("End of load message"), false);
  FlexGridSizer6->Add(Notebook3, 1, wxEXPAND);
  pn_CSensorialFrame->SetSizer(FlexGridSizer6);
  FlexGridSizer6->Fit(pn_CSensorialFrame);
  FlexGridSizer6->SetSizeHints(pn_CSensorialFrame);
  pn_Action = new wxPanel(
      Notebook1, ID_PANEL7, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("ID_PANEL7"));
  BoxSizer8 = new wxBoxSizer(wxHORIZONTAL);
  plotAct2D_XY = new mpWindow(pn_Action, ID_CUSTOM2, wxDefaultPosition, wxDefaultSize, 0);
  BoxSizer8->Add(plotAct2D_XY, 1, wxEXPAND, 1);
  plotAct2D_PHI = new mpWindow(pn_Action, ID_CUSTOM3, wxDefaultPosition, wxDefaultSize, 0);
  BoxSizer8->Add(plotAct2D_PHI, 1, wxEXPAND, 1);
  pn_Action->SetSizer(BoxSizer8);
  BoxSizer8->Fit(pn_Action);
  BoxSizer8->SetSizeHints(pn_Action);

  pn_CObservationImage = new wxPanel(
      Notebook1, ID_PANEL9, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("ID_PANEL9"));
  FlexGridSizer3 = new wxFlexGridSizer(0, 1, 0, 0);
  FlexGridSizer3->AddGrowableCol(0);
  FlexGridSizer3->AddGrowableRow(1);
  StaticText2 = new wxStaticText(
      pn_CObservationImage, ID_STATICTEXT2, _("Right click on the images to see the popup menu:"),
      wxDefaultPosition, wxDefaultSize, 0, _T("ID_STATICTEXT2"));
  FlexGridSizer3->Add(
      StaticText2, 1, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5);

  ScrolledWindow2 = new wxScrolledWindow(
      pn_CObservationImage, ID_SCROLLEDWINDOW2, wxDefaultPosition, wxDefaultSize,
      wxHSCROLL | wxVSCROLL, _T("ID_SCROLLEDWINDOW2"));
  FlexGridSizerImg = new wxFlexGridSizer(0, 1, 0, 0);
  FlexGridSizerImg->AddGrowableCol(0);
  FlexGridSizerImg->AddGrowableRow(0);

  bmpObsImage = new CMyGLCanvas(ScrolledWindow2, ID_STATICBITMAP1);

  FlexGridSizerImg->Add(bmpObsImage, 1, wxEXPAND, 0);
  ScrolledWindow2->SetSizer(FlexGridSizerImg);
  FlexGridSizerImg->Fit(ScrolledWindow2);
  FlexGridSizerImg->SetSizeHints(ScrolledWindow2);

  FlexGridSizer3->Add(ScrolledWindow2, 1, wxEXPAND);
  pn_CObservationImage->SetSizer(FlexGridSizer3);
  FlexGridSizer3->Fit(pn_CObservationImage);
  FlexGridSizer3->SetSizeHints(pn_CObservationImage);
  pn_CObservationStereoImage = new wxPanel(
      Notebook1, ID_PANEL10, wxDefaultPosition, wxDefaultSize,
      wxTAB_TRAVERSAL | wxFULL_REPAINT_ON_RESIZE, _T("ID_PANEL10"));
  FlexGridSizer4 = new wxFlexGridSizer(2, 1, 0, 0);
  FlexGridSizer4->AddGrowableCol(0);
  FlexGridSizer4->AddGrowableRow(1);
  StaticText1 = new wxStaticText(
      pn_CObservationStereoImage, ID_STATICTEXT1,
      _("Right click on the images to see the popup menu:"), wxDefaultPosition, wxDefaultSize, 0,
      _T("ID_STATICTEXT1"));
  FlexGridSizer4->Add(
      StaticText1, 1, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5);
  Notebook2 = new wxNotebook(
      pn_CObservationStereoImage, ID_NOTEBOOK2, wxDefaultPosition, wxDefaultSize, 0,
      _T("ID_NOTEBOOK2"));
  Panel6 = new wxPanel(
      Notebook2, ID_PANEL13, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("ID_PANEL13"));
  FlexGridSizer5 = new wxFlexGridSizer(1, 1, 0, 0);
  FlexGridSizer5->AddGrowableCol(0);
  FlexGridSizer5->AddGrowableRow(0);
  bmpObsStereoLeft = new CMyGLCanvas(Panel6, ID_STATICBITMAP2);
  FlexGridSizer5->Add(bmpObsStereoLeft, 1, wxEXPAND, 0);
  Panel6->SetSizer(FlexGridSizer5);
  FlexGridSizer5->Fit(Panel6);
  FlexGridSizer5->SetSizeHints(Panel6);
  Panel7 = new wxPanel(
      Notebook2, ID_PANEL14, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("ID_PANEL14"));
  FlexGridSizer7 = new wxFlexGridSizer(1, 1, 0, 0);
  FlexGridSizer7->AddGrowableCol(0);
  FlexGridSizer7->AddGrowableRow(0);
  bmpObsStereoRight = new CMyGLCanvas(Panel7, ID_STATICBITMAP3);
  FlexGridSizer7->Add(bmpObsStereoRight, 1, wxEXPAND, 0);
  Panel7->SetSizer(FlexGridSizer7);
  FlexGridSizer7->Fit(Panel7);
  FlexGridSizer7->SetSizeHints(Panel7);
  Panel10 = new wxPanel(
      Notebook2, ID_PANEL24, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("ID_PANEL24"));
  FlexGridSizer13 = new wxFlexGridSizer(1, 1, 0, 0);
  FlexGridSizer13->AddGrowableCol(0);
  FlexGridSizer13->AddGrowableRow(0);
  bmpObsStereoDisp = new CMyGLCanvas(Panel10, ID_STATICBITMAP7);
  FlexGridSizer13->Add(bmpObsStereoDisp, 1, wxEXPAND, 0);
  Panel10->SetSizer(FlexGridSizer13);
  FlexGridSizer13->Fit(Panel10);
  FlexGridSizer13->SetSizeHints(Panel10);
  Notebook2->AddPage(Panel6, _("Left"), false);
  Notebook2->AddPage(Panel7, _("Right"), false);
  Notebook2->AddPage(Panel10, _("Disparity"), false);
  FlexGridSizer4->Add(Notebook2, 1, wxEXPAND);
  pn_CObservationStereoImage->SetSizer(FlexGridSizer4);
  FlexGridSizer4->Fit(pn_CObservationStereoImage);
  FlexGridSizer4->SetSizeHints(pn_CObservationStereoImage);
  pn_CObservationBeaconRanges = new wxPanel(
      Notebook1, ID_PANEL11, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("ID_PANEL11"));
  pn_CObservationGasSensors = new wxPanel(
      Notebook1, ID_PANEL12, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("ID_PANEL12"));
  pn_CObservationGPS = new wxPanel(
      Notebook1, ID_PANEL15, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("ID_PANEL15"));
  pn_CObservationBearingRange = new wxPanel(
      Notebook1, ID_PANEL16, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("ID_PANEL16"));
  BoxSizer4 = new wxBoxSizer(wxVERTICAL);
  plotRangeBearing =
      new mpWindow(pn_CObservationBearingRange, ID_CUSTOM4, wxDefaultPosition, wxDefaultSize, 0);
  BoxSizer4->Add(plotRangeBearing, 10, wxEXPAND);
  Panel8 = new wxPanel(
      pn_CObservationBearingRange, ID_PANEL17, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL,
      _T("ID_PANEL17"));
  BoxSizer4->Add(Panel8, 1, wxEXPAND);
  pn_CObservationBearingRange->SetSizer(BoxSizer4);
  BoxSizer4->Fit(pn_CObservationBearingRange);
  BoxSizer4->SetSizeHints(pn_CObservationBearingRange);

  // Panel[8]: pn_CObservation3DRangeScan
  pn_CObservation3DRangeScan = new wxPanel(
      Notebook1, ID_PANEL19, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("ID_PANEL19"));
  FlexGridSizer8 = new wxFlexGridSizer(1, 1, 0, 0);
  FlexGridSizer8->AddGrowableCol(0);
  FlexGridSizer8->AddGrowableRow(0);

  // Tabs for 3D observations channels & options:
  nb_3DObsChannels = new wxNotebook(
      pn_CObservation3DRangeScan, ID_NOTEBOOK_3DOBS, wxDefaultPosition, wxDefaultSize, 0,
      _T("ID_NOTEBOOK_3DOBS"));

  // Tab: 3D view + range + intensity:
  pn3Dobs_3D = new wxPanel(
      nb_3DObsChannels, ID_PANEL20, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL,
      _T("ID_PANEL20"));
  FlexGridSizer9 = new wxFlexGridSizer(1, 1, 0, 0);
  FlexGridSizer9->AddGrowableCol(0);
  FlexGridSizer9->AddGrowableRow(0);

  m_gl3DRangeScan = new CMyGLCanvas(
      pn3Dobs_3D, ID_XY_GLCANVAS, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL,
      _T("ID_XY_GLCANVAS"));
  FlexGridSizer9->Add(m_gl3DRangeScan, 1, wxEXPAND, 2);

  // OpenGL Views:
  bmp3Dobs_depth = m_gl3DRangeScan->getOpenGLSceneRef()->createViewport("depth");
  bmp3Dobs_depth->setCustomBackgroundColor({.0f, .0f, .0f});

  bmp3Dobs_int = m_gl3DRangeScan->getOpenGLSceneRef()->createViewport("intensity");
  bmp3Dobs_int->setCustomBackgroundColor({.0f, .0f, .0f});

  bmp3Dobs_3dcloud = m_gl3DRangeScan->getOpenGLSceneRef()->getViewport("main");

  // The correct sizes will be updated anyway in SelectObjectInTreeView()
  bmp3Dobs_depth->setViewportPosition(2, -50 - 2, 1.0, 50);
  bmp3Dobs_int->setViewportPosition(2, -100 - 2 * 2, 1.0, 50);

  // panel done:
  pn3Dobs_3D->SetSizer(FlexGridSizer9);
  FlexGridSizer9->Fit(pn3Dobs_3D);
  FlexGridSizer9->SetSizeHints(pn3Dobs_3D);

  pn3Dobs_Conf = new wxPanel(
      nb_3DObsChannels, ID_PANEL23, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL,
      _T("ID_PANEL23"));
  FlexGridSizer14 = new wxFlexGridSizer(1, 1, 0, 0);
  FlexGridSizer14->AddGrowableCol(0);
  FlexGridSizer14->AddGrowableRow(0);
  bmp3Dobs_conf = new CMyGLCanvas(pn3Dobs_Conf, ID_STATICBITMAP6);
  FlexGridSizer14->Add(bmp3Dobs_conf, 1, wxEXPAND, 0);
  pn3Dobs_Conf->SetSizer(FlexGridSizer14);
  FlexGridSizer14->Fit(pn3Dobs_Conf);
  FlexGridSizer14->SetSizeHints(pn3Dobs_Conf);

  pnViewOptions = new ViewOptions3DPoints(nb_3DObsChannels, ID_PANEL_VIEW_3D_POINT_OPTIONS);

  nb_3DObsChannels->AddPage(pn3Dobs_3D, _("3D/Range/Intensity"), true);
  nb_3DObsChannels->AddPage(pn3Dobs_Conf, _("Confidence"), false);
  nb_3DObsChannels->AddPage(pnViewOptions, _("Visualization options"), false);

  FlexGridSizer8->Add(nb_3DObsChannels, 1, wxEXPAND, 1);
  pn_CObservation3DRangeScan->SetSizer(FlexGridSizer8);
  FlexGridSizer8->Fit(pn_CObservation3DRangeScan);
  FlexGridSizer8->SetSizeHints(pn_CObservation3DRangeScan);
  Notebook1->AddPage(pn_CSensorialFrame, _("Rawlog information"), true);
  Notebook1->AddPage(pn_Action, _("2D Mov. Action"), false);
  Notebook1->AddPage(pn_CObservationImage, _("Obs: Image"), false);
  Notebook1->AddPage(pn_CObservationStereoImage, _("Obs: Stereo Image"), false);
  Notebook1->AddPage(pn_CObservationBeaconRanges, _("Obs: Beacon Ranges"), false);
  Notebook1->AddPage(pn_CObservationGasSensors, _("Obs: e-Noses"), false);
  Notebook1->AddPage(pn_CObservationGPS, _("Obs: GPS"), false);
  Notebook1->AddPage(pn_CObservationBearingRange, _("Obs: RangeBearing"), false);
  Notebook1->AddPage(pn_CObservation3DRangeScan, _("Obs: 3D"), false);
  BoxSizer3->Add(Notebook1, 1, wxEXPAND);
  Panel5->SetSizer(BoxSizer3);
  BoxSizer3->Fit(Panel5);
  BoxSizer3->SetSizeHints(Panel5);
  SplitterWindow3->SplitHorizontally(Panel3, Panel5);
  BoxSizer1->Add(SplitterWindow3, 1, wxEXPAND);
  Panel2->SetSizer(BoxSizer1);
  BoxSizer1->Fit(Panel2);
  BoxSizer1->SetSizeHints(Panel2);
  SplitterWindow1->SplitVertically(Panel1, Panel2);
  fgzMain->Add(SplitterWindow1, 1, wxEXPAND);

  // -----------------------
  // Bottom timeline
  // -----------------------
  createTimeLineObjects(fgzMain);

  // ----
  SetSizer(fgzMain);
  fgzMain->SetSizeHints(this);
  // ----

  // -----------------------
  // MENUS
  // -----------------------
  MenuBar1 = new wxMenuBar();
  Menu1 = new wxMenu();
  MenuItem3 = new wxMenuItem(
      Menu1, ID_MENUITEM1, _("Open..."), _("Loads a whole rawlog file in memory"), wxITEM_NORMAL);
  MenuItem3->SetBitmap(
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_FOLDER")), wxART_MENU));
  Menu1->Append(MenuItem3);
  MenuItem4 = new wxMenuItem(
      Menu1, ID_MENUITEM2, _("Save as..."), _("Saves the current rawlog in a file"), wxITEM_NORMAL);
  MenuItem4->SetBitmap(
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_FILE_SAVE_AS")), wxART_MENU));
  Menu1->Append(MenuItem4);
  MenuItem6 = new wxMenu();
  MenuItem13 =
      new wxMenuItem(MenuItem6, ID_MENUITEM11, _("New Menu"), wxEmptyString, wxITEM_NORMAL);
  MenuItem6->Append(MenuItem13);
  Menu1->Append(ID_MENUITEM4, _("Open recent"), MenuItem6, wxEmptyString);
  MenuItem73 = new wxMenuItem(
      Menu1, ID_MENUITEM76, _("Revert"), _("Reload the currently open file, discarding changes"),
      wxITEM_NORMAL);
  Menu1->Append(MenuItem73);
  MenuItem8 = new wxMenu();
  MenuItem9 = new wxMenuItem(
      MenuItem8, ID_MENUITEM7, _("Load a part only..."), _("Load a section of a long rawlog"),
      wxITEM_NORMAL);
  MenuItem8->Append(MenuItem9);
  MenuItem10 = new wxMenuItem(
      MenuItem8, ID_MENUITEM8, _("Count entries..."), _("Count the entries in a rawlog file"),
      wxITEM_NORMAL);
  MenuItem8->Append(MenuItem10);
  MenuItem12 = new wxMenuItem(
      MenuItem8, ID_MENUITEM10, _("Save all images to files.."),
      _("Save all the images in a rawlog to a given directory"), wxITEM_NORMAL);
  MenuItem8->Append(MenuItem12);
  MenuItem59 = new wxMenuItem(
      MenuItem8, ID_MENUITEM62, _("Conver to externally stored images..."), wxEmptyString,
      wxITEM_NORMAL);
  MenuItem8->Append(MenuItem59);
  MenuItem61 = new wxMenuItem(
      MenuItem8, ID_MENUITEM64, _("Convert to observations-only rawlog..."), wxEmptyString,
      wxITEM_NORMAL);
  MenuItem8->Append(MenuItem61);
  MenuItem8->AppendSeparator();
  MenuItem15 = new wxMenuItem(
      MenuItem8, ID_MENUITEM13, _("Append visual landmarks from stereo imgs..."),
      _("Precompute 3D landmarks from stereo images and save in another "
        "rawlog"),
      wxITEM_NORMAL);
  MenuItem8->Append(MenuItem15);
  MenuItem8->AppendSeparator();
  MenuItem57 = new wxMenuItem(
      MenuItem8, ID_MENUITEM60, _("Loss-less decimation..."),
      _("Accumulate observations from many timesteps in one"), wxITEM_NORMAL);
  MenuItem8->Append(MenuItem57);
  MenuItem58 = new wxMenuItem(
      MenuItem8, ID_MENUITEM61, _("Compact rawlog..."),
      _("Group consecutive actions & observations"), wxITEM_NORMAL);
  MenuItem8->Append(MenuItem58);
  Menu1->Append(ID_MENUITEM6, _("Operations on files"), MenuItem8, wxEmptyString);
  Menu1->AppendSeparator();
  MenuItem5 = new wxMenu();
  MenuItem7 = new wxMenuItem(
      MenuItem5, ID_MENUITEM5, _("a CARMEN log..."), _("Import a CARMEN \"log\" file"),
      wxITEM_NORMAL);
  MenuItem5->Append(MenuItem7);
  MenuItem44 = new wxMenuItem(
      MenuItem5, ID_MENUITEM47, _("a sequence of image files..."), wxEmptyString, wxITEM_NORMAL);
  MenuItem5->Append(MenuItem44);
  MenuItem53 =
      new wxMenuItem(MenuItem5, ID_MENUITEM56, _("a MOOS alog..."), wxEmptyString, wxITEM_NORMAL);
  MenuItem5->Append(MenuItem53);
  MenuItem60 =
      new wxMenuItem(MenuItem5, ID_MENUITEM63, _("a RTL log..."), wxEmptyString, wxITEM_NORMAL);
  MenuItem5->Append(MenuItem60);
  MenuItem83 = new wxMenuItem(
      MenuItem5, ID_MENUITEM87, _("a Bremen DLR log..."), wxEmptyString, wxITEM_NORMAL);
  MenuItem5->Append(MenuItem83);
  Menu1->Append(ID_MENUITEM3, _("Import"), MenuItem5, wxEmptyString);
  MenuItem51 = new wxMenu();
  MenuItem55 = new wxMenuItem(
      MenuItem51, ID_MENUITEM58, _("Plain text files..."),
      _("Generate text files for odometry and laser"), wxITEM_NORMAL);
  MenuItem51->Append(MenuItem55);
  MenuItem52 = new wxMenuItem(
      MenuItem51, ID_MENUITEM55, _("As a MOOS alog file..."), wxEmptyString, wxITEM_NORMAL);
  MenuItem51->Append(MenuItem52);
  Menu1->Append(ID_MENUITEM54, _("Export"), MenuItem51, wxEmptyString);
  Menu1->AppendSeparator();
  MenuItem1 = new wxMenuItem(
      Menu1, idMenuQuit, _("Quit\tAlt-F4"), _("Quit the application"), wxITEM_NORMAL);
  MenuItem1->SetBitmap(
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("ICON_QUIT")), wxART_MENU));
  Menu1->Append(MenuItem1);
  MenuBar1->Append(Menu1, _("&File"));
  Menu3 = new wxMenu();
  MenuItem16 = new wxMenuItem(
      Menu3, ID_MENUITEM14, _("Edit rawlog..."), _("Manipulate the rawlog entries"), wxITEM_NORMAL);
  MenuItem16->SetBitmap(
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_COPY")), wxART_MENU));
  Menu3->Append(MenuItem16);
  MenuItem48 = new wxMenuItem(
      Menu3, ID_MENUITEM51, _("Insert a field for comments"), wxEmptyString, wxITEM_NORMAL);
  Menu3->Append(MenuItem48);
  MenuItem66 =
      new wxMenuItem(Menu3, ID_MENUITEM69, _("Rename a sensor..."), wxEmptyString, wxITEM_NORMAL);
  Menu3->Append(MenuItem66);
  MenuItem86 = new wxMenuItem(
      Menu3, ID_MENUITEM91, _("Rename selected object only..."),
      _("Change the sensorLabel of one single observation"), wxITEM_NORMAL);
  Menu3->Append(MenuItem86);
  Menu3->AppendSeparator();
  MenuItem17 = new wxMenuItem(
      Menu3, ID_MENUITEM15, _("Change sensor/camera parameters..."),
      _("Change the poses of the sensors"), wxITEM_NORMAL);
  Menu3->Append(MenuItem17);
  MenuItem67 = new wxMenuItem(
      Menu3, ID_MENUITEM70, _("Batch change sensor poses..."),
      _("Change the pose of all sensors from a ini-like config file"), wxITEM_NORMAL);
  Menu3->Append(MenuItem67);
  Menu3->AppendSeparator();
  MenuItem18 = new wxMenuItem(
      Menu3, ID_MENUITEM16, _("Decimate records..."),
      _("Reduce the number of observations in the rawlog"), wxITEM_NORMAL);
  Menu3->Append(MenuItem18);
  MenuItem56 = new wxMenuItem(
      Menu3, ID_MENUITEM59, _("Loss-less decimation ..."),
      _("Accumulate observations from many timesteps in one"), wxITEM_NORMAL);
  Menu3->Append(MenuItem56);
  MenuItem54 = new wxMenuItem(
      Menu3, ID_MENUITEM57, _("Compact rawlog..."), _("Group consecutive actions & observations"),
      wxITEM_NORMAL);
  Menu3->Append(MenuItem54);
  MenuItem72 = new wxMenuItem(
      Menu3, ID_MENUITEM75, _("Convert into SF format..."),
      _("Convert a rawlog of observations-only into the actions-sensory "
        "frames format"),
      wxITEM_NORMAL);
  Menu3->Append(MenuItem72);
  Menu3->AppendSeparator();
  MenuItem64 = new wxMenuItem(
      Menu3, ID_MENUITEM67, _("Re-sort by timestamp"),
      _("Re order all the observations in the rawlog using their timestamps"), wxITEM_NORMAL);
  Menu3->Append(MenuItem64);
  MenuItem65 = new wxMenuItem(
      Menu3, ID_MENUITEM68, _("Shift timestamp by sensorLabel..."),
      _("Displaces in time all the observations of a given label by some "
        "amount"),
      wxITEM_NORMAL);
  Menu3->Append(MenuItem65);
  MenuItem79 = new wxMenuItem(
      Menu3, ID_MENUITEM82, _("Regenerate timestamps in SF"), wxEmptyString, wxITEM_NORMAL);
  Menu3->Append(MenuItem79);

  auto MenuItemRenameBySFIdx = new wxMenuItem(
      Menu3, ID_MENUITEM_RENAME_BY_SF_IDX, _("Rename by SF index"), wxEmptyString, wxITEM_NORMAL);
  Menu3->Append(MenuItemRenameBySFIdx);

  MenuBar1->Append(Menu3, _("&Edit"));
  Menu6 = new wxMenu();
  Menu14 = new wxMenu();
  MenuItem22 = new wxMenuItem(
      Menu14, ID_MENUITEM20, _("Modify motion model..."),
      _("Manipulate the uncertainty of odometry"), wxITEM_NORMAL);
  Menu14->Append(MenuItem22);
  MenuItem24 = new wxMenuItem(
      Menu14, ID_MENUITEM22, _("Recalculate actions with ICP..."), _("Compute ICP-based odometry"),
      wxITEM_NORMAL);
  Menu14->Append(MenuItem24);
  MenuItem50 = new wxMenuItem(
      Menu14, ID_MENUITEM53, _("Modify uncertainty of ICP-based actions..."), wxEmptyString,
      wxITEM_NORMAL);
  Menu14->Append(MenuItem50);
  MenuItem25 = new wxMenuItem(
      Menu14, ID_MENUITEM23, _("Recompute odometry from encoders config..."),
      _("Recompute odometry increments from encoders"), wxITEM_NORMAL);
  Menu14->Append(MenuItem25);
  MenuItem38 = new wxMenuItem(
      Menu14, ID_MENUITEM41, _("Force encoders info to \'false\'"),
      _("Can be used to force ignoring corrupted encoders data in old "
        "datasets"),
      wxITEM_NORMAL);
  Menu14->Append(MenuItem38);
  MenuItem80 = new wxMenuItem(
      Menu14, ID_MENUITEM84, _("Regenerate invalid odometry timestamps..."), wxEmptyString,
      wxITEM_NORMAL);
  Menu14->Append(MenuItem80);
  Menu6->Append(ID_MENUITEM12, _("&Odometry (actions)"), Menu14, wxEmptyString);
  Menu20 = new wxMenu();
  MenuItem19 = new wxMenuItem(
      Menu20, ID_MENUITEM17, _("Show the ICP module..."),
      _("A demonstration of scan-matching with ICP"), wxITEM_NORMAL);
  MenuItem19->SetBitmap(
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("ICON_ICP")), wxART_MENU));
  Menu20->Append(MenuItem19);
  MenuItem41 = new wxMenuItem(
      Menu20, ID_MENUITEM44, _("Show an animation with the scans..."), wxEmptyString,
      wxITEM_NORMAL);
  Menu20->Append(MenuItem41);
  MenuItem21 = new wxMenuItem(
      Menu20, ID_MENUITEM19, _("Count laser scans with zero valid ranges"),
      _("Count the numnber of scans without a single valid range"), wxITEM_NORMAL);
  Menu20->Append(MenuItem21);
  MenuItem27 = new wxMenuItem(
      Menu20, ID_MENUITEM25, _("Filter out erroneous range measurements..."),
      _("Heuristic to remove erroneous laser sensor readings"), wxITEM_NORMAL);
  Menu20->Append(MenuItem27);
  MenuItem70 = new wxMenuItem(
      Menu20, ID_MENUITEM73, _("Mark as invalid ranges larger than maximum..."), wxEmptyString,
      wxITEM_NORMAL);
  Menu20->Append(MenuItem70);
  MenuItem71 = new wxMenuItem(
      Menu20, ID_MENUITEM74, _("Change max. range for a given laser scan..."), wxEmptyString,
      wxITEM_NORMAL);
  Menu20->Append(MenuItem71);
  MenuItem74 = new wxMenuItem(
      Menu20, ID_MENUITEM77, _("Batch exclusion zone filtering..."), wxEmptyString, wxITEM_NORMAL);
  Menu20->Append(MenuItem74);
  MenuItem76 = new wxMenuItem(
      Menu20, ID_MENUITEM79, _("Batch exclusion angles filtering..."), wxEmptyString,
      wxITEM_NORMAL);
  Menu20->Append(MenuItem76);
  Menu6->Append(ID_MENUITEM18, _("&Laser scans"), Menu20, wxEmptyString);
  MenuItem81 = new wxMenu();
  MenuItem82 = new wxMenuItem(
      MenuItem81, ID_MENUITEM86, _("Recover camera params..."),
      _("Executes a Levenberg-Marquart optimization to recover the camera "
        "calibration matrix"),
      wxITEM_NORMAL);
  MenuItem81->Append(MenuItem82);
  mnuItemEnable3DCamAutoGenPoints = new wxMenuItem(
      MenuItem81, ID_MENUITEM90, _("Enable on-the-fly generate 3D point cloud"), wxEmptyString,
      wxITEM_CHECK);
  MenuItem81->Append(mnuItemEnable3DCamAutoGenPoints);
  Menu6->Append(ID_MENUITEM85, _("&3D depth cameras"), MenuItem81, wxEmptyString);
  Menu23 = new wxMenu();
  MenuItem31 = new wxMenuItem(
      Menu23, ID_MENUITEM29, _("Sequence of PNG files with images..."),
      _("Extract all the images of the rawlog to a given directory"), wxITEM_NORMAL);
  Menu23->Append(MenuItem31);
  MenuItem11 = new wxMenuItem(
      Menu23, ID_MENUITEM9, _("Show images as a video..."),
      _("Show the sequence of images as a video"), wxITEM_NORMAL);
  Menu23->Append(MenuItem11);
  MenuItem30 = new wxMenuItem(
      Menu23, ID_MENUITEM28, _("Generate 3D actions from visual odometry..."), wxEmptyString,
      wxITEM_NORMAL);
  Menu23->Append(MenuItem30);
  MenuItem68 = new wxMenuItem(
      Menu23, ID_MENUITEM71, _("Convert pairs of mono into stereo..."),
      "Create stereo images observations from pairs of monocular images", wxITEM_NORMAL);
  Menu23->Append(MenuItem68);
  MenuItem69 = new wxMenuItem(
      Menu23, ID_MENUITEM72, _("Batch rectify images..."), wxEmptyString, wxITEM_NORMAL);
  Menu23->Append(MenuItem69);
  MenuItem75 = new wxMenuItem(
      Menu23, ID_MENUITEM78, _("Rename externally stored image files..."), wxEmptyString,
      wxITEM_NORMAL);
  Menu23->Append(MenuItem75);
  mnuCreateAVI = new wxMenuItem(
      Menu23, ID_MENUITEM83, _("Create AVI video file..."), wxEmptyString, wxITEM_NORMAL);
  Menu23->Append(mnuCreateAVI);
  Menu6->Append(ID_MENUITEM21, _("&Images (Mono && Stereo)"), Menu23, wxEmptyString);
  Menu38 = new wxMenu();
  MenuItem32 = new wxMenuItem(
      Menu38, ID_MENUITEM30, _("Generate text file with gas sensor readings..."),
      _("Extract gas readings to a text file"), wxITEM_NORMAL);
  Menu38->Append(MenuItem32);
  MenuItem26 = new wxMenuItem(
      Menu38, ID_MENUITEM24, _("Filter out spureous gas readings..."),
      _("Heuristic to remove erroneous gas sensor readings"), wxITEM_NORMAL);
  Menu38->Append(MenuItem26);
  Menu6->Append(ID_MENUITEM35, _("Ga&s sensors"), Menu38, wxEmptyString);
  Menu39 = new wxMenu();
  MenuItem33 = new wxMenuItem(
      Menu39, ID_MENUITEM31, _("Generate text file with GPS readings..."),
      _("Extract GPS readings to a text file"), wxITEM_NORMAL);
  Menu39->Append(MenuItem33);
  MenuItem36 = new wxMenuItem(
      Menu39, ID_MENUITEM34, _("Summary of DGPS modes"),
      _("Show a summary of GPS modes found in the rawlog"), wxITEM_NORMAL);
  Menu39->Append(MenuItem36);
  MenuItem62 = new wxMenuItem(
      Menu39, ID_MENUITEM65, _("&Measure distance between GPS\'s..."), wxEmptyString,
      wxITEM_NORMAL);
  Menu39->Append(MenuItem62);
  MenuItem63 = new wxMenuItem(
      Menu39, ID_MENUITEM66, _("Regenerate &timestamp from sat time"), wxEmptyString,
      wxITEM_NORMAL);
  Menu39->Append(MenuItem63);
  MenuItem49 =
      new wxMenuItem(Menu39, ID_MENUITEM52, _("&Draw 3D path..."), wxEmptyString, wxITEM_NORMAL);
  Menu39->Append(MenuItem49);
  MenuItem77 = new wxMenuItem(
      Menu39, ID_MENUITEM80, _("Delete entries with NaN"), wxEmptyString, wxITEM_NORMAL);
  Menu39->Append(MenuItem77);
  Menu6->Append(ID_MENUITEM36, _("&GPS sensors"), Menu39, wxEmptyString);
  Menu40 = new wxMenu();
  MenuItem35 = new wxMenuItem(
      Menu40, ID_MENUITEM33, _("Generate text file with Beacon ranges..."),
      _("Extract range readings to a text file"), wxITEM_NORMAL);
  Menu40->Append(MenuItem35);
  MenuItem14 = new wxMenuItem(
      Menu40, ID_MENUITEM38, _("Remove specific measurements"), wxEmptyString, wxITEM_NORMAL);
  Menu40->Append(MenuItem14);
  Menu6->Append(ID_MENUITEM37, _("Range-only (&beacon detector) sensors"), Menu40, wxEmptyString);
  MenuItem20 = new wxMenu();
  MenuItem23 = new wxMenuItem(
      MenuItem20, ID_MENUITEM40, _("Generate text file with measurements..."), wxEmptyString,
      wxITEM_NORMAL);
  MenuItem20->Append(MenuItem23);
  MenuItem78 = new wxMenuItem(
      MenuItem20, ID_MENUITEM81, _("Remove specific landmark by ID..."), wxEmptyString,
      wxITEM_NORMAL);
  MenuItem20->Append(MenuItem78);
  Menu6->Append(
      ID_MENUITEM39, _("Range-bearing (landmark detector) sensors"), MenuItem20, wxEmptyString);
  MenuItem42 = new wxMenu();
  MenuItem43 = new wxMenuItem(
      MenuItem42, ID_MENUITEM46, _("Generate a text file with measurements..."), wxEmptyString,
      wxITEM_NORMAL);
  MenuItem42->Append(MenuItem43);
  Menu6->Append(ID_MENUITEM45, _("1D range finders (Ultrasonic,IR)"), MenuItem42, wxEmptyString);
  MenuItem39 = new wxMenu();
  MenuItem40 = new wxMenuItem(
      MenuItem39, ID_MENUITEM43, _("Generate text file with measurementes..."), wxEmptyString,
      wxITEM_NORMAL);
  MenuItem39->Append(MenuItem40);
  Menu6->Append(ID_MENUITEM42, _("Inertial Measurement Units"), MenuItem39, wxEmptyString);
  MenuItem84 = new wxMenu();
  MenuItem85 = new wxMenuItem(
      MenuItem84, ID_MENUITEM89, _("Generate &text file with all observations..."), wxEmptyString,
      wxITEM_NORMAL);
  MenuItem84->Append(MenuItem85);
  Menu6->Append(ID_MENUITEM88, _("&WiFi Strength"), MenuItem84, wxEmptyString);
  MenuBar1->Append(Menu6, _("&Sensors"));
  Menu4 = new wxMenu();
  MenuItem28 = new wxMenuItem(
      Menu4, ID_MENUITEM26, _("Open paths and &map generation module..."),
      _("Open the raw map/path samples dialog"), wxITEM_NORMAL);
  MenuItem28->SetBitmap(
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("ICON_RAWMAP")), wxART_MENU));
  Menu4->Append(MenuItem28);
  MenuItem34 = new wxMenuItem(
      Menu4, ID_MENUITEM32, _("&Generate odometry and laser text files..."),
      _("Extract odometry and laser scans to text files"), wxITEM_NORMAL);
  Menu4->Append(MenuItem34);
  MenuBar1->Append(Menu4, _("&Tools"));
  Menu2 = new wxMenu();
  MenuItem29 =
      new wxMenuItem(Menu2, ID_MENUITEM27, _("Show &tips..."), wxEmptyString, wxITEM_NORMAL);
  Menu2->Append(MenuItem29);
  MenuItem2 = new wxMenuItem(
      Menu2, idMenuAbout, _("&About\tF1"), _("Show info about this application"), wxITEM_NORMAL);
  MenuItem2->SetBitmap(
      wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("ICON_ABOUT")), wxART_MENU));
  Menu2->Append(MenuItem2);
  MenuBar1->Append(Menu2, _("&Help"));
  SetMenuBar(MenuBar1);
  StatusBar1 = new wxStatusBar(this, ID_STATUSBAR1, 0, _T("ID_STATUSBAR1"));
  int __wxStatusBarWidths_1[1] = {-1};
  int __wxStatusBarStyles_1[1] = {wxSB_NORMAL};
  StatusBar1->SetFieldsCount(1, __wxStatusBarWidths_1);
  StatusBar1->SetStatusStyles(1, __wxStatusBarStyles_1);
  SetStatusBar(StatusBar1);
  MenuItem37 = new wxMenuItem(
      (&mnuTree), MNU_1, _("Delete element"), _("Erases the selected element"), wxITEM_NORMAL);
  mnuTree.Append(MenuItem37);
  MenuItem45 = new wxMenu();
  MenuItem46 = new wxMenuItem(
      MenuItem45, ID_MENUITEM49, _("2D increment (from odometry)"), wxEmptyString, wxITEM_NORMAL);
  MenuItem45->Append(MenuItem46);
  MenuItem47 = new wxMenuItem(
      MenuItem45, ID_MENUITEM50, _("2D increment (from scan-matching)"), wxEmptyString,
      wxITEM_NORMAL);
  MenuItem45->Append(MenuItem47);
  mnuTree.Append(ID_MENUITEM48, _("Add action"), MenuItem45, wxEmptyString);

  timAutoLoad.SetOwner(this, ID_TIMER1);
  timAutoLoad.Start(50, true);

  // ----------------
  // Events
  // ----------------
  Bind(wxEVT_BUTTON, &This::OnFileOpen, this, ID_BUTTON2);
  Bind(wxEVT_BUTTON, &This::OnSaveFile, this, ID_BUTTON3);
  Bind(wxEVT_BUTTON, &This::OnEditRawlog, this, ID_BUTTON4);
  Bind(wxEVT_BUTTON, &This::OnRawMapOdo, this, ID_BUTTON5);
  Bind(wxEVT_BUTTON, &This::OnChangeMotionModel, this, ID_BUTTON6);
  Bind(wxEVT_BUTTON, &This::OnShowAnimateScans, this, ID_BUTTON8);
  Bind(wxEVT_BUTTON, &This::OnShowImagesAsVideo, this, ID_BUTTON9);
  Bind(wxEVT_BUTTON, &This::OnAbout, this, ID_BUTTON10);
  Bind(wxEVT_BUTTON, &This::OnQuit, this, ID_BUTTON11);
  Bind(wxEVT_BUTTON, &This::OnbtnEditCommentsClick1, this, ID_BUTTON1);
  Bind(wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGING, &This::OnNotebook1PageChanging, this, ID_NOTEBOOK1);
  Bind(wxEVT_MENU, &This::OnFileOpen, this, ID_MENUITEM1);
  Bind(wxEVT_MENU, &This::OnSaveFile, this, ID_MENUITEM2);
  Bind(wxEVT_MENU, &This::OnMenuRevert, this, ID_MENUITEM76);
  Bind(wxEVT_MENU, &This::OnLoadAPartOnly, this, ID_MENUITEM7);
  Bind(wxEVT_MENU, &This::OnFileCountEntries, this, ID_MENUITEM8);
  Bind(wxEVT_MENU, &This::OnFileSaveImages, this, ID_MENUITEM10);
  Bind(wxEVT_MENU, &This::OnMenuConvertExternallyStored, this, ID_MENUITEM62);
  Bind(wxEVT_MENU, &This::OnMenuConvertObservationOnly, this, ID_MENUITEM64);
  Bind(wxEVT_MENU, &This::OnFileGenVisualLMFromStereoImages, this, ID_MENUITEM13);
  Bind(wxEVT_MENU, &This::OnMenuLossLessDecFILE, this, ID_MENUITEM60);
  Bind(wxEVT_MENU, &This::OnMenCompactFILE, this, ID_MENUITEM61);
  Bind(wxEVT_MENU, &This::OnImportCARMEN, this, ID_MENUITEM5);
  Bind(wxEVT_MENU, &This::OnImportSequenceOfImages, this, ID_MENUITEM47);
  Bind(wxEVT_MENU, &This::OnMenuImportALOG, this, ID_MENUITEM56);
  Bind(wxEVT_MENU, &This::OnImportRTL, this, ID_MENUITEM63);
  Bind(wxEVT_MENU, &This::OnMenuItemImportBremenDLRLog, this, ID_MENUITEM87);
  Bind(wxEVT_MENU, &This::OnGenOdoLaser, this, ID_MENUITEM58);
  Bind(wxEVT_MENU, &This::OnMenuExportALOG, this, ID_MENUITEM55);
  Bind(wxEVT_MENU, &This::OnQuit, this, idMenuQuit);
  Bind(wxEVT_MENU, &This::OnEditRawlog, this, ID_MENUITEM14);
  Bind(wxEVT_MENU, &This::OnMenuInsertComment, this, ID_MENUITEM51);
  Bind(wxEVT_MENU, &This::OnMenuRenameSensor, this, ID_MENUITEM69);
  Bind(wxEVT_MENU, &This::OnMenuRenameSingleObs, this, ID_MENUITEM91);
  Bind(wxEVT_MENU, &This::OnChangeSensorPositions, this, ID_MENUITEM15);
  Bind(wxEVT_MENU, &This::OnMenuChangePosesBatch, this, ID_MENUITEM70);
  Bind(wxEVT_MENU, &This::OnDecimateRecords, this, ID_MENUITEM16);
  Bind(wxEVT_MENU, &This::OnMenuLossLessDecimate, this, ID_MENUITEM59);
  Bind(wxEVT_MENU, &This::OnMenuCompactRawlog, this, ID_MENUITEM57);
  Bind(wxEVT_MENU, &This::OnMenuConvertSF, this, ID_MENUITEM75);
  Bind(wxEVT_MENU, &This::OnMenuResortByTimestamp, this, ID_MENUITEM67);
  Bind(wxEVT_MENU, &This::OnMenuShiftTimestampsByLabel, this, ID_MENUITEM68);
  Bind(wxEVT_MENU, &This::OnMenuRegenerateTimestampBySF, this, ID_MENUITEM82);
  Bind(wxEVT_MENU, &This::OnChangeMotionModel, this, ID_MENUITEM20);
  Bind(wxEVT_MENU, &This::OnRecalculateActionsICP, this, ID_MENUITEM22);
  Bind(wxEVT_MENU, &This::OnMenuModifyICPActionsUncertainty, this, ID_MENUITEM53);
  Bind(wxEVT_MENU, &This::OnRecomputeOdometry, this, ID_MENUITEM23);
  Bind(wxEVT_MENU, &This::OnForceEncodersFalse, this, ID_MENUITEM41);
  Bind(wxEVT_MENU, &This::OnMenuRegenerateOdometryTimes, this, ID_MENUITEM84);
  Bind(wxEVT_MENU, &This::OnShowICP, this, ID_MENUITEM17);
  Bind(wxEVT_MENU, &This::OnShowAnimateScans, this, ID_MENUITEM44);
  Bind(wxEVT_MENU, &This::OnCountBadScans, this, ID_MENUITEM19);
  Bind(wxEVT_MENU, &This::OnFilterErroneousScans, this, ID_MENUITEM25);
  Bind(wxEVT_MENU, &This::OnMenuMarkLaserScanInvalid, this, ID_MENUITEM73);
  Bind(wxEVT_MENU, &This::OnMenuChangeMaxRangeLaser, this, ID_MENUITEM74);
  Bind(wxEVT_MENU, &This::OnMenuBatchLaserExclusionZones, this, ID_MENUITEM77);
  Bind(wxEVT_MENU, &This::OnLaserFilterAngles, this, ID_MENUITEM79);
  Bind(wxEVT_MENU, &This::OnMenuItem3DObsRecoverParams, this, ID_MENUITEM86);
  Bind(wxEVT_MENU, &This::OnGenerateSeqImgs, this, ID_MENUITEM29);
  Bind(wxEVT_MENU, &This::OnShowImagesAsVideo, this, ID_MENUITEM9);
  Bind(wxEVT_MENU, &This::OnMenuMono2Stereo, this, ID_MENUITEM71);
  Bind(wxEVT_MENU, &This::OnMenuRectifyImages, this, ID_MENUITEM72);
  Bind(wxEVT_MENU, &This::OnMenuRenameImageFiles, this, ID_MENUITEM78);
  Bind(wxEVT_MENU, &This::OnmnuCreateAVISelected, this, ID_MENUITEM83);
  Bind(wxEVT_MENU, &This::OnGenGasTxt, this, ID_MENUITEM30);
  Bind(wxEVT_MENU, &This::OnFilterSpureousGas, this, ID_MENUITEM24);
  Bind(wxEVT_MENU, &This::OnGenGPSTxt, this, ID_MENUITEM31);
  Bind(wxEVT_MENU, &This::OnSummaryGPS, this, ID_MENUITEM34);
  Bind(wxEVT_MENU, &This::OnMenuDistanceBtwGPSs, this, ID_MENUITEM65);
  Bind(wxEVT_MENU, &This::OnMenuRegenerateGPSTimestamps, this, ID_MENUITEM66);
  Bind(wxEVT_MENU, &This::OnMenuDrawGPSPath, this, ID_MENUITEM52);
  Bind(wxEVT_MENU, &This::OnMenuGPSDeleteNaN, this, ID_MENUITEM80);
  Bind(wxEVT_MENU, &This::OnMenuGenerateBeaconList, this, ID_MENUITEM33);
  Bind(wxEVT_MENU, &This::OnRemoveSpecificRangeMeas, this, ID_MENUITEM38);
  Bind(wxEVT_MENU, &This::OnGenerateTextFileRangeBearing, this, ID_MENUITEM40);
  Bind(wxEVT_MENU, &This::OnMenuRangeBearFilterIDs, this, ID_MENUITEM81);
  Bind(wxEVT_MENU, &This::OnRangeFinder1DGenTextFile, this, ID_MENUITEM46);
  Bind(wxEVT_MENU, &This::OnGenerateIMUTextFile, this, ID_MENUITEM43);
  Bind(wxEVT_MENU, &This::OnGenWifiTxt, this, ID_MENUITEM89);
  Bind(wxEVT_MENU, &This::OnRawMapOdo, this, ID_MENUITEM26);
  Bind(wxEVT_MENU, &This::OnGenOdoLaser, this, ID_MENUITEM32);
  Bind(wxEVT_MENU, &This::OnMenuShowTips, this, ID_MENUITEM27);
  Bind(wxEVT_MENU, &This::OnAbout, this, idMenuAbout);
  Bind(wxEVT_MENU, &This::OnMenuItem37Selected, this, MNU_1);
  Bind(wxEVT_MENU, &This::OnMenuItem46Selected, this, ID_MENUITEM49);
  Bind(wxEVT_MENU, &This::OnMenuItem47Selected, this, ID_MENUITEM50);
  Bind(wxEVT_TIMER, &This::OntimAutoLoadTrigger, this, ID_TIMER1);
  //*)

  Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &This::On3DObsPagesChange, this, ID_NOTEBOOK_3DOBS);
  Bind(wxEVT_MENU, &This::OnMenuRenameBySFIndex, this, ID_MENUITEM_RENAME_BY_SF_IDX);

  // "Manually" added code:
  // ----------------------------
  // Image list for the tree view:
  auto imgList = std::make_shared<wxImageList>(16, 16, true, 0);
  imgList->Add(wxIcon(tree_icon1_xpm));
  imgList->Add(wxIcon(tree_icon2_xpm));
  imgList->Add(wxIcon(tree_icon3_xpm));
  imgList->Add(wxIcon(tree_icon4_xpm));
  imgList->Add(wxIcon(tree_icon5_xpm));
  imgList->Add(wxIcon(tree_icon6_xpm));
  imgList->Add(wxIcon(tree_icon7_xpm));
  imgList->Add(wxIcon(tree_icon8_xpm));
  imgList->Add(wxIcon(tree_icon9_xpm));
  imgList->Add(wxIcon(tree_icon10_xpm));
  imgList->Add(wxIcon(tree_icon11_xpm));
  imgList->Add(wxIcon(tree_icon12_xpm));

  m_treeView->AssignImageList(imgList);

  m_treeView->ConnectSelectedItemChange(OntreeViewSelectionChanged);
  m_treeView->setWinParent(this);

  // Force this menu item starts checked.
  mnuItemEnable3DCamAutoGenPoints->Check(true);

  // The graphs:
  // -----------------------------------
  lyAction2D_XY = new mpFXYVector();
  lyAction2D_PHI = new mpFXYVector();

  wxPen penBlue(wxColour(0, 0, 255), 3);

  plotAct2D_XY->AddLayer(new mpScaleX());
  plotAct2D_XY->AddLayer(new mpScaleY());
  plotAct2D_XY->AddLayer(lyAction2D_XY);
  plotAct2D_XY->LockAspect(true);
  lyAction2D_XY->SetPen(penBlue);

  plotAct2D_PHI->AddLayer(new mpScaleX(_("PHI (deg)")));
  plotAct2D_PHI->AddLayer(lyAction2D_PHI);
  plotAct2D_PHI->LockAspect(true);
  lyAction2D_PHI->SetPen(penBlue);

  //  Range-bearing plot:
  lyRangeBearingLandmarks = new mpFXYVector();
  lyRangeBearingLandmarks->SetPen(penBlue);
  lyRangeBearingLandmarks->SetContinuity(false);

  plotRangeBearing->AddLayer(new mpScaleX());
  plotRangeBearing->AddLayer(new mpScaleY());
  plotRangeBearing->AddLayer(lyRangeBearingLandmarks);

  Maximize();  // Maximize the main window

  // Set sliders:
  // ----------------------
  SplitterWindow1->SetSashPosition(400);
  SplitterWindow3->SetSashPosition(400);

  // Set recent file list:
  // -------------------------
  MenuItem6->Destroy(MenuItem13);
  m_fileHistory.UseMenu(MenuItem6);

  for (int i = m_fileHistory.GetMaxFiles() - 1; i >= 0; i--)
  {
    string fil = iniFile->read_string("RecentFiles", format("file_%03u", i), "");
    if (fil.size() && mrpt::system::fileExists(fil)) m_fileHistory.AddFileToHistory(fil.c_str());
  }

  // 3D view: default view, with +X pointing to the right:
  m_gl3DRangeScan->setAzimuthDegrees(-80.0f);
  m_gl3DRangeScan->setElevationDegrees(30.0f);

  // Image directory selector on the toolbar:
  // --------------------------------------------
  Bind(wxEVT_COMBOBOX, &This::OnComboImageDirsChange, this, ID_COMBO_IMG_DIRS);

  // Construction of "global" dialog variables:
  // ----------------------------------------------
  formRawMap = new CFormRawMap(this);
  scanMatchingDialog = new CScanMatching(this);
}

xRawLogViewerFrame::~xRawLogViewerFrame()
{
  //(*Destroy(xRawLogViewerFrame)
  //*)

  // Destroy dialogs:
  delete formRawMap;
  formRawMap = nullptr;
  delete scanMatchingDialog;
  scanMatchingDialog = nullptr;

  // Close extra windows, if any:
  winGPSPath.reset();
}

void xRawLogViewerFrame::OnQuit(wxCommandEvent&) { Close(); }
void xRawLogViewerFrame::OnAbout(wxCommandEvent&)
{
  mrpt::gui::show_mrpt_about_box_wxWidgets(this, "RawLogViewer");
}

//------------------------------------------------------------------------
//    Asks the user for a rawlog file, return false if user cancels
//------------------------------------------------------------------------
bool xRawLogViewerFrame::AskForOpenRawlog(std::string& fil)
{
  wxString caption = wxT("Choose a file to open");
  wxString wildcard =
      wxT("RawLog files (*.rawlog,*.rawlog.gz)|*.rawlog;*.rawlog.gz|All "
          "files (*.*)|*.*");

  wxString defaultDir((iniFile->read_string(iniFileSect, "LastDir", ".").c_str()));

  wxString defaultFilename;
  wxFileDialog dialog(
      this, caption, defaultDir, defaultFilename, wildcard, wxFD_OPEN | wxFD_FILE_MUST_EXIST);

  if (dialog.ShowModal() == wxID_OK)
  {
    wxString fileName = dialog.GetPath();
    wxString filePath = dialog.GetDirectory();

    fil = string(fileName.mb_str());
    return true;
  }
  return false;
}

//------------------------------------------------------------------------
//    Asks the user for a rawlog file, return false if user cancels
//------------------------------------------------------------------------
bool xRawLogViewerFrame::AskForSaveRawlog(std::string& fil)
{
  wxString caption = wxT("Save file...");
  wxString wildcard =
      wxT("RawLog files (*.rawlog,*.rawlog.gz)|*.rawlog;*.rawlog.gz|All "
          "files (*.*)|*.*");

  wxString defaultDir((iniFile->read_string(iniFileSect, "LastDir", ".").c_str()));

  wxString defaultFilename;
  wxFileDialog dialog(
      this, caption, defaultDir, defaultFilename, wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);

  if (dialog.ShowModal() == wxID_OK)
  {
    wxString fileName = dialog.GetPath();
    wxString filePath = dialog.GetDirectory();

    fil = string(fileName.mb_str());
    return true;
  }
  return false;
}

//------------------------------------------------------------------------
//                      Open a file dialog
//------------------------------------------------------------------------
void xRawLogViewerFrame::OnFileOpen(wxCommandEvent&)
{
  string fil;
  if (AskForOpenRawlog(fil)) loadRawlogFile(fil);
}

//------------------------------------------------------------------------
//                      Save as...
//------------------------------------------------------------------------
void xRawLogViewerFrame::OnSaveFile(wxCommandEvent&)
{
  wxString caption = wxT("Save as...");
  wxString wildcard =
      wxT("RawLog files (*.rawlog,*.rawlog.gz)|*.rawlog;*.rawlog.gz|All "
          "files (*.*)|*.*");

  wxString defaultDir((iniFile->read_string(iniFileSect, "LastDir", ".").c_str()));

  wxString defaultFilename = loadedFileName.c_str();
  wxFileDialog dialog(
      this, caption, defaultDir, defaultFilename, wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);

  if (dialog.ShowModal() == wxID_OK)
  {
    wxString fileName = dialog.GetPath();
    wxString filePath = dialog.GetDirectory();

    // Save the path
    WX_START_TRY

    iniFile->write(iniFileSect, "LastDir", string(filePath.mb_str()));

    // Save the file:
    loadedFileName = fileName.mb_str();

    CFileGZOutputStream fs(loadedFileName.c_str());

    int countLoop = 0, i, n = (int)rawlog.size();

    wxBusyCursor waitCursor;

    wxProgressDialog progDia(
        wxT("Saving rawlog to file"), wxT("Saving..."),
        n,     // range
        this,  // parent
        wxPD_CAN_ABORT | wxPD_APP_MODAL | wxPD_SMOOTH | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME |
            wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME);

    wxTheApp->Yield();  // Let the app. process messages

    // Comments:
    string comts;
    rawlog.getCommentText(comts);

    if (!comts.empty())
    {
      CObservationComment o;
      o.text = comts;
      archiveFrom(fs) << o;
    }

    for (i = 0; i < n; i++)
    {
      if (countLoop++ % 100 == 0)
      {
        if (!progDia.Update(i)) break;  // Exit the loop
        wxTheApp->Yield();              // Let the app. process messages
      }

      archiveFrom(fs) << *rawlog.getAsGeneric(i);
    }  // end for i

    progDia.Update(n);

    WX_END_TRY
  }
}

//------------------------------------------------------------------------
//               Edit rawlog dialog
//------------------------------------------------------------------------
void xRawLogViewerFrame::OnEditRawlog(wxCommandEvent&)
{
  CFormEdit dialog(this);

  dialog.cbObsLabel->Clear();
  for (auto i = listOfSensorLabels.begin(); i != listOfSensorLabels.end(); ++i)
    dialog.cbObsLabel->Append(i->first.c_str());

  dialog.slFrom->SetRange(0, (int)rawlog.size() - 1);
  dialog.slFrom->SetValue(0);

  dialog.spinFirst->SetRange(0, (int)rawlog.size() - 1);
  dialog.spinFirst->SetValue(dialog.slFrom->GetValue());

  dialog.slTo->SetRange(0, (int)rawlog.size() - 1);
  dialog.slTo->SetValue((int)rawlog.size() - 1);

  dialog.spinLast->SetRange(0, (int)rawlog.size() - 1);
  dialog.spinLast->SetValue(dialog.slTo->GetValue());

  // Fill all the observation classes:
  dialog.cbObsClass->Clear();
  vector<const mrpt::rtti::TRuntimeClassId*> lstClasses;
  lstClasses = mrpt::rtti::getAllRegisteredClasses();
  for (const auto& c : lstClasses)
    if (c->derivedFrom(CLASS_ID(CObservation))) dialog.cbObsClass->Append(c->className);

  dialog.Fit();
  dialog.ShowModal();

  // Update the views:
  rebuildTreeView();
}

//------------------------------------------------------------------------
//               Loads the rawlog into the application
//------------------------------------------------------------------------
void xRawLogViewerFrame::loadRawlogFile(const string& str, int first, int last)
{
  WX_START_TRY

  if (!fileExists(str))
  {
    wxMessageBox(
        string(string("File doesn't exist:\n") + str).c_str(), _("Error loading file"), wxOK, this);
    return;
  }

  // Add to MR files, and save the list:
  WX_START_TRY

  m_fileHistory.AddFileToHistory(str.c_str());

  for (size_t i = 0; i < m_fileHistory.GetCount(); i++)
    iniFile->write(
        "RecentFiles", format("file_%03u", static_cast<unsigned>(i)),
        string(m_fileHistory.GetHistoryFile(i).mb_str()));
  WX_END_TRY

  // Save the path
  string rawlog_path = extractFileDirectory(str);
  if (rawlog_path.empty())
    rawlog_path = "./";
  else if (*rawlog_path.rbegin() != '/' && *rawlog_path.rbegin() != '\\')
    rawlog_path += string("/");

  WX_START_TRY
  iniFile->write(iniFileSect, "LastDir", rawlog_path);
  WX_END_TRY

  // Set default delayed-load images base path:
  toolbarcomboImages->Clear();
  CImage::setImagesPathBase(CRawlog::detectImagesDirectory(str));

  // Add found dir to the combo:
  toolbarcomboImages->Append(CImage::getImagesPathBase().c_str());
  toolbarcomboImages->SetSelection(0);

  // An extra rectified images dir??
  // Other "Images*" directories??
  if (mrpt::system::directoryExists(rawlog_path))
  {
    CDirectoryExplorer::TFileInfoList lstFiles;
    CDirectoryExplorer::explore(rawlog_path, FILE_ATTRIB_DIRECTORY, lstFiles);
    for (auto i = lstFiles.begin(); i != lstFiles.end(); ++i)
      if (0 == os::_strcmpi(i->name.substr(0, 6).c_str(), "Images"))
      {
        wxString s = i->wholePath.c_str();
        if (toolbarcomboImages->FindString(s) == wxNOT_FOUND) toolbarcomboImages->Append(s);
      }
  }

#ifdef __WXMSW__
  SendMessage(
      (HWND)toolbarcomboImages->GetHandle(), CB_SETDROPPEDWIDTH,
      toolbarcomboImages->GetBestSize().GetWidth(), 0);
#endif
  // ToolBar1->Realize();

  wxBusyCursor waitCursor;

  CFileGZInputStream fil(str);

  uint64_t filSize = fil.getTotalBytesCount();

  const uint64_t progDialogMax =
      filSize >> 10;  // Size, in Kb's (to avoid saturatin the "int" in wxProgressDialog)

  loadedFileName = str;
  StatusBar1->SetStatusText((mrpt::format("Loading file: %s", str.c_str()).c_str()));

  wxString auxStr;
  wxProgressDialog progDia(
      wxT("Progress of rawlog load"), wxT("Loading..."),
      progDialogMax,  // range, in Kb's
      this,           // parent
      wxPD_CAN_ABORT | wxPD_APP_MODAL | wxPD_SMOOTH | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME |
          wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME);

  wxTheApp->Yield();  // Let the app. process messages
  progDia.SetSize(500, progDia.GetSize().GetHeight());
  progDia.Center();

  wxTheApp->Yield();  // Let the app. process messages

  // Clear first:
  rawlog.clear();

  crono_Loading.Tic();

  size_t countLoop = 0;
  int entryIndex = 0;
  bool keepLoading = true;
  bool alreadyWarnedTooLargeFile = false;
  string errorMsg;

  double last_ratio = -1;
  while (keepLoading)
  {
    if (countLoop++ % 10 == 0)
    {
      uint64_t fil_pos = fil.getPosition();
      double ratio = fil_pos / (1.0 * filSize);

      if (ratio - last_ratio >= 0.006)
      {
        last_ratio = ratio;

        unsigned long memUsg = getMemoryUsage();
        double memUsg_Mb = memUsg / (1024.0 * 1024.0);

        const uint64_t progPos = fil_pos >> 10;

        auxStr.sprintf(
            wxT("Loading... %u objects / Memory usage: %.03fMb"),
            static_cast<unsigned int>(rawlog.size()), memUsg_Mb);
        if (!progDia.Update(progPos < (progDialogMax - 1) ? progPos : (progDialogMax - 1), auxStr))
          keepLoading = false;
        progDia.Fit();
        wxTheApp->Yield();  // Let the app. process messages

        if (memUsg_Mb > 8000 && !alreadyWarnedTooLargeFile)
        {
          alreadyWarnedTooLargeFile = true;
          string msg;
          msg +=
              "It seems that the file you are loading will consume a "
              "large amount of memory if loaded in memory "
              "completely.\n";
          msg +=
              "This is just a warning to make you watch whether your "
              "system runs out of memory while still loading.\n";
          msg += "Do you want to continue loading this file?";
          if (wxNO == wxMessageBox(msg.c_str(), _("Warning"), wxYES_NO | wxICON_EXCLAMATION))
            keepLoading = false;
        }
      }
    }

    CSerializable::Ptr newObj;
    try
    {
      archiveFrom(fil) >> newObj;
      // Check type:
      if (newObj->GetRuntimeClass() == CLASS_ID(CSensoryFrame))
      {
        if (entryIndex >= first && (last == -1 || entryIndex <= last))
          rawlog.insert(std::dynamic_pointer_cast<CSensoryFrame>(newObj));
        entryIndex++;
      }
      else if (newObj->GetRuntimeClass() == CLASS_ID(CActionCollection))
      {
        if (entryIndex >= first && (last == -1 || entryIndex <= last))
          rawlog.insert(std::dynamic_pointer_cast<CActionCollection>(newObj));
        entryIndex++;
      }
      /* Added in MRPT 0.6.0: The new "observations only" format: */
      else if (newObj->GetRuntimeClass()->derivedFrom(CLASS_ID(CObservation)))
      {
        if (entryIndex >= first && (last == -1 || entryIndex <= last))
          rawlog.insert(std::dynamic_pointer_cast<CObservation>(newObj));
        entryIndex++;
      }
      /* FOR BACKWARD COMPATIBILITY: CPose2D was used previously instead
       of an "ActionCollection" object
                                        26-JAN-2006
       */
      else if (newObj->GetRuntimeClass() == CLASS_ID(CPose2D))
      {
        if (entryIndex >= first && (last == -1 || entryIndex <= last))
        {
          CPose2D::Ptr poseChange = std::dynamic_pointer_cast<CPose2D>(newObj);
          CActionCollection::Ptr temp = std::make_shared<CActionCollection>();
          CActionRobotMovement2D action;
          CActionRobotMovement2D::TMotionModelOptions options;
          action.computeFromOdometry(*poseChange, options);
          temp->insert(action);

          rawlog.insert(temp);
        }
        entryIndex++;
      }
      else if (newObj->GetRuntimeClass() == CLASS_ID(CRawlog))
      {
        CRawlog::Ptr rw = std::dynamic_pointer_cast<CRawlog>(newObj);
        rawlog = std::move(*rw);
      }
      else
      {
        // Unknown class:
        // New in MRPT v1.5.0: Allow loading some other classes:
        rawlog.insert(newObj);
      }

      // Passed last?
      if (last != -1 && entryIndex > last) keepLoading = false;
    }
    catch (std::bad_alloc&)
    {
      // Probably we're in a 32 bit machine and we rose up to 2Gb of
      // mem... free
      //  some, give a warning and go on.
      if (rawlog.size() > 10000)
      {
        size_t NN = rawlog.size() - 10000;
        while (rawlog.size() > NN) rawlog.remove(NN);
      }
      else
        rawlog.clear();

      wxString s =
          _("OUT OF MEMORY: The last part of the rawlog has been freed "
            "to allow the program to continue.\n");
#if MRPT_WORD_SIZE == 32
      s << _(
          "  This is a 32bit machine, so the maximum memory available is "
          "2Gb despite of the real RAM installed.");
#endif
      wxMessageBox(s);

      keepLoading = false;
    }
    catch (exception& e)
    {
      errorMsg = mrpt::exception_to_str(e);
      keepLoading = false;
    }
    catch (...)
    {
      keepLoading = false;
    }
  }  // end while keep loading

  timeToLoad = crono_Loading.Tac();
  progDia.Update(progDialogMax);

  //// Update the views:
  rebuildTreeView();

  //// Set error msg:
  txtException->SetValue(errorMsg.c_str());

  // Seems something bad happened?
  if (!rawlog.size())
  {
    wxMessageBox(
        _("If the file is not really empty, perhaps the format is invalid "
          "or a more recent\nversion of RawLogViewer is required to load "
          "the file.\n Please check the tab \"End of load message\" for "
          "further details."),
        _("Zero entries loaded"), wxOK, this);
  }

  WX_END_TRY
}

//------------------------------------------------------------------------
//           Rebuilds the tree view with data in "rawlog"
//------------------------------------------------------------------------
void xRawLogViewerFrame::rebuildTreeView()
{
  wxString s;
  float totalDistance = 0;
  bool firstSF = true;
  TTimeStamp tim_start = INVALID_TIMESTAMP;
  TTimeStamp tim_last = INVALID_TIMESTAMP;
  int countLoop = 0;

  Notebook1->ChangeSelection(0);
  m_selectedObj.reset();      // = nullptr;
  curSelectedObject.reset();  // = nullptr;

  wxProgressDialog progDia(
      wxT("Constructing the tree view"), wxT("Creating the tree..."),
      (int)rawlog.size(),  // range
      this,                // parent
      wxPD_APP_MODAL | wxPD_AUTO_HIDE);

  wxTheApp->Yield();  // Let the app. process messages

  WX_START_TRY

  wxBusyCursor waitCursor;

  listOfSensorLabels.clear();

  // Refresh the custom tree view:
  m_treeView->setRawlogName(loadedFileName);
  m_treeView->setRawlogSource(&rawlog);

  // Does this need to be so complicated? -> Yes: In Linux (UNICODE) the
  // straightforward
  //  implementation runs *very* slow.
  map<const TRuntimeClassId*, wxString> mapStrings;

  size_t updateProgressBarSteps = (rawlog.size() / 20) + 1;

  using TListOfObjectsOccurs = std::map<const TRuntimeClassId*, size_t>;
  TListOfObjectsOccurs listOfObjects;

  // The elements:
  for (unsigned int i = 0; i < rawlog.size(); i++)
  {
    if (countLoop++ % updateProgressBarSteps == 0)
    {
      progDia.Update(i);
      wxTheApp->Yield();  // Let the app. process messages
    }

    // Process element:
    s.clear();
    switch (rawlog.getType(i))
    {
      case CRawlog::etActionCollection:
      {
        CActionCollection::Ptr acts = rawlog.getAsAction(i);

        // Distance:
        CPose2D est;
        if (acts->getBestMovementEstimation())
        {
          acts->getBestMovementEstimation()->poseChange->getMean(est);
          totalDistance += est.norm();
        }
      }
      break;

      case CRawlog::etSensoryFrame:
      {
        CSerializable::Ptr obj = rawlog.getAsObservations(i);
        if (CLASS_ID(CSensoryFrame) != obj->GetRuntimeClass())
          THROW_EXCEPTION("Expected an object of class CSensoryFrame!!");

        CSensoryFrame::Ptr sf = std::dynamic_pointer_cast<CSensoryFrame>(obj);

        if (firstSF)
        {
          if (sf->size())
          {
            firstSF = false;
            tim_start = (*sf->begin())->timestamp;
          }
        }
        if (sf->size()) tim_last = (*sf->begin())->timestamp;  // Keep the last one

        size_t j, n = sf->size();

        for (j = 0; j < n; j++)
        {
          CObservation::Ptr obs = sf->getObservationByIndex(j);

          // Stats:
          listOfObjects[obs->GetRuntimeClass()]++;
          TInfoPerSensorLabel& dd = listOfSensorLabels[obs->sensorLabel];
          dd.addOcurrence(obs->timestamp);
          if (dd.first == INVALID_TIMESTAMP) dd.first = obs->timestamp;
          dd.last = obs->timestamp;
        }

      }  // end SensoryFrame
      break;

      case CRawlog::etObservation:
      {
        CObservation::Ptr obs = rawlog.getAsObservation(i);

        if (tim_start == INVALID_TIMESTAMP) tim_start = obs->timestamp;

        tim_last = obs->timestamp;  // Keep the last one

        // Stats:
        listOfObjects[obs->GetRuntimeClass()]++;

        // 0-based timestamp:
        TInfoPerSensorLabel& dd = listOfSensorLabels[obs->sensorLabel];
        dd.addOcurrence(obs->timestamp);
        if (dd.first == INVALID_TIMESTAMP) dd.first = obs->timestamp;
        dd.last = obs->timestamp;

        // For odometry measurements: total distance:
        if (obs->GetRuntimeClass() == CLASS_ID(CObservationOdometry))
        {
          CObservationOdometry::Ptr odoObs = std::dynamic_pointer_cast<CObservationOdometry>(obs);

          static CPose2D oldOdo;
          static bool oldOdo_first = true;

          if (oldOdo_first)
          {
            oldOdo_first = false;
            oldOdo = odoObs->odometry;
          }
          else
          {
            CPose2D inc = odoObs->odometry - oldOdo;
            oldOdo = odoObs->odometry;

            totalDistance += inc.norm();
          }
        }

      }  // end Observation
      break;
      default:
      {
        auto obj = rawlog.getAsGeneric(i);
        listOfObjects[obj->GetRuntimeClass()]++;
      }
      break;
    };  // end switch type

  }  // end for i

  if (tim_start != INVALID_TIMESTAMP && tim_last != INVALID_TIMESTAMP)
    experimentLenght = timeDifference(tim_start, tim_last);

  // Statistics:
  // ---------------------------
  memStats->Clear();

  memStats->AppendText(format("Time to load file                 : %.03fms\n", 1000 * timeToLoad));
  memStats->AppendText(format("Rawlog entries                    : %u\n", (unsigned)rawlog.size()));
  memStats->AppendText(format("Traveled distance (from odometry) : %.02f meters\n", totalDistance));

  if (m_treeView->getFirstTimestamp() != INVALID_TIMESTAMP)
  {
    rawlog_first_timestamp = m_treeView->getFirstTimestamp();
    memStats->AppendText(format(
        "Dataset first time-stamp (UTC)    : %s\n",
        mrpt::system::dateTimeToString(m_treeView->getFirstTimestamp()).c_str()));
  }

  memStats->AppendText(format(
      "Dataset duration                  : %s (hh:mm:ss,  %.03f "
      "secs.)\n",
      formatTimeInterval(experimentLenght).c_str(), experimentLenght));

  // Stats of object classes:
  memStats->AppendText(
      "\nSummary of classes found in the rawlog:\n"
      "-----------------------------------------\n");

  if (experimentLenght == 0) experimentLenght = 1;

  for (const auto& oc : listOfObjects)
  {
    const char* className = oc.first->className;
    size_t count = oc.second;
    memStats->AppendText(format(
        " %8u %25s : %5.03f Hz\n", (unsigned)count, className,
        double(count > 1 ? count - 1 : 1) / experimentLenght));
  }

  // Stats of object classes:
  memStats->AppendText(
      "\nSummary of 'sensorLabels' found in the rawlog:\n"
      "-------------------------------------------------\n");

  for (const auto& ipsl : listOfSensorLabels)
  {
    size_t count = ipsl.second.getOccurences();
    TTimeStamp tf = ipsl.second.first;
    TTimeStamp tl = ipsl.second.last;
    double Hz = 0, dur = 0;
    if (tf != INVALID_TIMESTAMP && tl != INVALID_TIMESTAMP)
    {
      dur = mrpt::system::timeDifference(tf, tl);
      Hz = double(count > 1 ? count - 1 : 1) / dur;
    }

    memStats->AppendText(format(
        " %8u %25s : %5.03f Hz for %.04f s, with %.03f s max delay "
        "btw readings.\n",
        (unsigned)count, ipsl.first.c_str(), Hz, dur, ipsl.second.max_ellapsed_tim_between_obs));
  }

  memStats->ShowPosition(0);

  SelectObjectInTreeView(CSerializable::Ptr());
  m_treeView->Refresh();

  // -----------------------------------------
  // Rebuild bottom timeline view
  // -----------------------------------------
  m_timeline.resetAfterDatasetChanged();
  rebuildBottomTimeLine();

  WX_END_TRY
}

// Selection has changed:
void xRawLogViewerFrame::OntreeViewSelectionChanged(
    wxWindow* me,
    CRawlogTreeView* the_tree,
    TRawlogTreeViewEvent /*ev*/,
    int /*item_index*/,
    const mrpt::serialization::CSerializable::Ptr& item_data)
{
  auto* win = (xRawLogViewerFrame*)me;
  win->SelectObjectInTreeView(item_data);
  the_tree->SetFocus();
}

#if wxUSE_STARTUP_TIPS
class CMyTips : public wxTipProvider
{
 public:
  CMyTips(size_t n) : wxTipProvider(n) {}
  wxString GetTip() override
  {
    size_t idx = m_currentTip++;

    static const size_t N_TIPS = 5;

    switch (idx % N_TIPS)
    {
      case 0:
        return _(
            "To have a first overview of a dataset with odometry "
            "and "
            "laser scans, select 'Tools' -> 'maps & paths "
            "generation "
            "module' -> 'Map from odometry'.\n If the dataset is "
            "very "
            "large, select a portion of it, or use the "
            "'decimation' "
            "slide.");
      case 1:
        return _(
            "When observations are selected in the tree-view at "
            "the "
            "left, extended information will be shown for that "
            "observation: timestamp, a visualization of the laser "
            "scan/image, etc.");
      case 2:
        return _(
            "Portions of a rawlog can be stripped out in Edit "
            "->Edit "
            "Rawlog");
      case 3:
        return _(
            "When a rawlog is loaded, the panel at the bottom "
            "displays "
            "a summary of the log, including approximate overall "
            "distance traveled by the vehicle, overall time, and "
            "the "
            "frequency of each sensor.");
      case 4:
        return _(
            "There is an ICP experimenting module that can be "
            "opened "
            "from the toolbar button 'ICP'");
    }
    return wxString();
  }
};
#endif

//------------------------------------------------------------------------
// Auto-load the file passed in the cmd line (if any)
//------------------------------------------------------------------------
void xRawLogViewerFrame::OntimAutoLoadTrigger(wxTimerEvent&)
{
  // To fix a strange bug in windows!: The window is not drawn correctly:
  Panel5->Refresh();

  // Now: Open it:
  if (global_fileToOpen.size())
    loadRawlogFile(global_fileToOpen);
  else
    rebuildTreeView();

  // Tips:
  showNextTip();
}

//------------------------------------------------------------------------
// Show next tip
//------------------------------------------------------------------------
void xRawLogViewerFrame::showNextTip(bool forceShow)
{
#if wxUSE_STARTUP_TIPS
  // Show tips?
  bool user_wants_to_show = iniFile->read_bool("tips", "show", true);

  if (forceShow || user_wants_to_show)
  {
    size_t last_tip = iniFile->read_int("tips", "next", 0);
    auto* myTips = new CMyTips(last_tip);
    bool cont_showing = ::wxShowTip(this, myTips);

    // save cont_showing:
    iniFile->write("tips", "show", cont_showing);
    size_t next_tip = myTips->GetCurrentTip();
    iniFile->write("tips", "next", (int)next_tip);

    delete myTips;
  }
#endif
}

//------------------------------------------------------------------------
// Tabs can only be changed programmatically.
//------------------------------------------------------------------------
void xRawLogViewerFrame::OnNotebook1PageChanging(wxNotebookEvent& event) { event.Veto(); }

//------------------------------------------------------------------------
// Changes the motion model parameters.
//------------------------------------------------------------------------
void xRawLogViewerFrame::OnChangeMotionModel(wxCommandEvent&)
{
  // Create the dialog:
  CFormMotionModel formMotionModel(this);
  formMotionModel.ShowModal();
}

//------------------------------------------------------------------------
// Menu cmd: Show images of a file as a video:
//------------------------------------------------------------------------
void xRawLogViewerFrame::OnShowImagesAsVideo(wxCommandEvent&)
{
  CFormPlayVideo diag(this);

  diag.getImageDirsCombo()->Clear();
  for (unsigned int i = 0; i < toolbarcomboImages->GetCount(); i++)
    diag.getImageDirsCombo()->Append(toolbarcomboImages->GetString(i));

  diag.getImageDirsCombo()->SetSelection(toolbarcomboImages->GetSelection());

  diag.ShowModal();

  toolbarcomboImages->SetSelection(diag.getImageDirsCombo()->GetSelection());
}

//------------------------------------------------------------------------
//               Dialog: Build map directly from odometry/gps
//------------------------------------------------------------------------
void xRawLogViewerFrame::OnRawMapOdo(wxCommandEvent&)
{
  if (rawlog.empty())
  {
    wxMessageBox(_("Please load a rawlog first!"), _("Rawlog is empty"), wxOK, this);
    return;
  }

  // Set slider values:
  //  If they have changed, we have a different rawlog loaded, thus we
  //  select the whole range by default:
  bool selectMaxRange = ((size_t)formRawMap->slFrom->GetMax()) != rawlog.size() - 1;
  formRawMap->slFrom->SetRange(0, (int)rawlog.size() - 1);
  formRawMap->slTo->SetRange(0, (int)rawlog.size() - 1);

  formRawMap->edFirst->SetRange(0, (int)rawlog.size() - 1);
  formRawMap->edLast->SetRange(0, (int)rawlog.size() - 1);

  if (selectMaxRange)
  {
    formRawMap->slFrom->SetValue(0);
    formRawMap->slTo->SetValue((int)rawlog.size() - 1);
  }

  // Clear the graphs:
  formRawMap->plotMap->DelAllLayers(true, false);

  // Show:
  formRawMap->edFirst->SetValue(formRawMap->slFrom->GetValue());
  formRawMap->edLast->SetValue(formRawMap->slTo->GetValue());

  // Enable "results" buttons:
  formRawMap->btnSaveTxt->Disable();
  formRawMap->btnSave3D->Disable();
  formRawMap->btnSavePath->Disable();
  formRawMap->btnSaveTxt->Disable();
  formRawMap->btnSaveObsPath->Disable();
  formRawMap->btnView3D->Disable();

  formRawMap->ShowModal();
}

void xRawLogViewerFrame::OnMenuGenerateBeaconList(wxCommandEvent&)
{
  WX_START_TRY

  wxString caption = wxT("Save as...");
  wxString wildcard = wxT("Text files (*.txt)|*.txt|All files (*.*)|*.*");
  wxString defaultDir((iniFile->read_string(iniFileSect, "LastDir", ".").c_str()));
  wxString defaultFilename = (loadedFileName + string("_beacons.txt")).c_str();
  wxFileDialog dialog(
      this, caption, defaultDir, defaultFilename, wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);

  if (dialog.ShowModal() == wxID_OK)
  {
    wxString fileName = dialog.GetPath();
    string fil(fileName.mbc_str());

    int i, M = 0, n = (int)rawlog.size();
    //            bool	    ref_valid = false;

    FILE* f = os::fopen(fil.c_str(), "wt");
    if (!f) THROW_EXCEPTION("Cannot open output file for write.");

    for (i = 0; i < n; i++)
    {
      switch (rawlog.getType(i))
      {
        case CRawlog::etSensoryFrame:
        {
          CSensoryFrame::Ptr sf = rawlog.getAsObservations(i);
          CObservationBeaconRanges::Ptr obs = sf->getObservationByClass<CObservationBeaconRanges>();

          if (obs)
          {
            ::fprintf(f, "%u ", i);

            for (unsigned int k = 0; k < 10; k++)
            {
              float rng = 0;
              if (k < obs->sensedData.size()) rng = obs->sensedData[k].sensedDistance;

              if (std::isnan(rng)) rng = 0;

              ::fprintf(f, "%f ", rng);
            }
            ::fprintf(f, "\n");
            M++;
          }
        }
        break;

        case CRawlog::etObservation:
        {
          CObservation::Ptr o = rawlog.getAsObservation(i);
          if (o->GetRuntimeClass() != CLASS_ID(CObservationBeaconRanges)) break;
          CObservationBeaconRanges::Ptr obs =
              std::dynamic_pointer_cast<CObservationBeaconRanges>(o);
          ::fprintf(f, "%u ", i);

          for (unsigned int k = 0; k < 10; k++)
          {
            float rng = 0;
            if (k < obs->sensedData.size()) rng = obs->sensedData[k].sensedDistance;

            if (std::isnan(rng)) rng = 0;

            ::fprintf(f, "%f ", rng);
          }
          ::fprintf(f, "\n");
          M++;
        }
        break;

        default:
          break;
      }
    }

    os::fclose(f);

    char auxStr[100];
    os::sprintf(auxStr, sizeof(auxStr), "%u entries saved!", M);
    wxMessageBox(auxStr, _("Done"), wxOK, this);
  }

  WX_END_TRY
}

string xRawLogViewerFrame::AskForImageFileFormat()
{
#define SIZE_lstImgFormats 5
  wxString lstImgFormats[SIZE_lstImgFormats] = {_("jpg"), _("png"), _("bmp"), _("tif"), _("pgm")};

  int ret = wxGetSingleChoiceIndex(
      _("Choose the format of the output images:"), _("Images format"), SIZE_lstImgFormats,
      lstImgFormats, this);

  if (ret == -1)
    return string("");
  else
    return string(lstImgFormats[ret].mb_str());
}

void xRawLogViewerFrame::OnGenOdoLaser(wxCommandEvent&)
{
  WX_START_TRY

  if (!rawlog.size())
  {
    ::wxMessageBox(_("Load a rawlog first."));
    return;
  }

  const wxString& target_dir_wx = ::wxDirSelector(
      _("Select the directory where the files will be saved"),
      (iniFile->read_string(iniFileSect, "LastDir", ".").c_str()), wxDD_DEFAULT_STYLE,
      wxDefaultPosition, this);

  if (target_dir_wx.empty()) return;

  const string target_dir = string(target_dir_wx.mb_str());

  const string fil_odo =
      target_dir + string("/") + mrpt::system::extractFileName(loadedFileName) + string("_ODO.txt");
  const string fil_odo_times = target_dir + string("/") +
                               mrpt::system::extractFileName(loadedFileName) +
                               string("_ODO_times.txt");

  const string prefix_laser =
      target_dir + string("/") + mrpt::system::extractFileName(loadedFileName) + string("_LASER_");

  unsigned int i, n = (unsigned int)rawlog.size();

  bool genTimes = rawlog_first_timestamp != INVALID_TIMESTAMP;
  if (!genTimes)
    ::wxMessageBox(
        _("It seems that there are no valid timestamps in the rawlog. "
          "The "
          "time files will not be generated."));

  FILE* f_odo = os::fopen(fil_odo, "wt");
  ASSERT_(f_odo);
  FILE* f_odo_times = nullptr;
  if (genTimes)
  {
    f_odo_times = os::fopen(fil_odo_times, "wt");
    ASSERT_(f_odo_times);
  }

  // Prepare for possibly several lasers: label -> < file: data, file:
  // times, file: pose >
  std::map<string, std::pair<FILE*, std::pair<FILE*, FILE*>>> lstFiles;

  CPose2D lastOdo;
  bool lastOdo_ok = false;

  unsigned int nOdo = 0, nLaser = 0;

  wxBusyCursor cursor_wait;

  wxProgressDialog progDia(
      wxT("Exporting as text files"), wxT("Saving..."),
      n,     // range
      this,  // parent
      wxPD_CAN_ABORT | wxPD_APP_MODAL | wxPD_SMOOTH | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME |
          wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME);

  for (i = 0; i < n; i++)
  {
    if (i % 50 == 0)
    {
      if (!progDia.Update(i)) break;
      wxTheApp->Yield();  // Let the app. process messages
    }

    switch (rawlog.getType(i))
    {
      case CRawlog::etActionCollection:
      {
        CActionCollection::Ptr acts = rawlog.getAsAction(i);
        CActionRobotMovement2D::Ptr action = acts->getBestMovementEstimation();
        if (!action) THROW_EXCEPTION_FMT("No odometry action found in rawlog entry %i!", i);

        CPose2D poseIncrement = action->poseChange->getMeanVal();

        ::fprintf(f_odo, "%f %f %f\n", poseIncrement.x(), poseIncrement.y(), poseIncrement.phi());
        nOdo++;

        if (genTimes)
        {
          double t = 0;
          if (action->timestamp != INVALID_TIMESTAMP)
            // t =
            // mrpt::system::timeDifference(rawlog_first_timestamp,
            // action->timestamp);
            t = mrpt::Clock::toDouble(action->timestamp);

          ::fprintf(f_odo_times, "%f\n", t);
        }
      }
      break;
      case CRawlog::etSensoryFrame:
      {
        CSensoryFrame::Ptr sf = rawlog.getAsObservations(i);
        // int nScans=0;
        for (unsigned int k = 0; k < sf->size(); k++)
        {
          if (sf->getObservationByIndex(k)->GetRuntimeClass() == CLASS_ID(CObservation2DRangeScan))
          {
            auto obs = sf->getObservationByIndexAs<CObservation2DRangeScan::Ptr>(k);

            // Get files from list, or create them the first
            // time:
            std::pair<FILE*, std::pair<FILE*, FILE*>>* files = nullptr;
            std::map<string, std::pair<FILE*, std::pair<FILE*, FILE*>>>::iterator it;
            if ((it = lstFiles.find(obs->sensorLabel)) == lstFiles.end())
            {
              files = &lstFiles[obs->sensorLabel];
              // Open files:
              files->first = os::fopen(prefix_laser + obs->sensorLabel + string(".txt"), "wt");
              ASSERT_(files->first);
              files->second.first =
                  os::fopen(prefix_laser + obs->sensorLabel + string("_times.txt"), "wt");
              ASSERT_(files->second.first);
              files->second.second =
                  os::fopen(prefix_laser + obs->sensorLabel + string("_poses.txt"), "wt");
              ASSERT_(files->second.second);
            }
            else
            {
              files = &lstFiles[obs->sensorLabel];
            }

            for (size_t j = 0; j < obs->getScanSize(); j++)
              ::fprintf(
                  files->first, "%f ", obs->getScanRangeValidity(j) ? obs->getScanRange(j) : 0);
            ::fprintf(files->first, "\n");

            nLaser++;

            if (genTimes)
            {
              double t = 0;
              if (obs->timestamp != INVALID_TIMESTAMP)
                // t =
                // mrpt::system::timeDifference(rawlog_first_timestamp,
                // obs->timestamp);
                t = mrpt::Clock::toDouble(obs->timestamp);
              ::fprintf(files->second.first, "%f\n", t);
            }

            // dump pose data
            double y, p, r;
            obs->sensorPose.getYawPitchRoll(y, p, r);
            ::fprintf(files->second.second, "%f\t%f\t%f\n", y, p, r);
          }
        }
      }
      break;

      case CRawlog::etObservation:
      {
        CObservation::Ptr o = rawlog.getAsObservation(i);

        if (IS_CLASS(*o, CObservation2DRangeScan))
        {
          CObservation2DRangeScan::Ptr obs = std::dynamic_pointer_cast<CObservation2DRangeScan>(o);

          // Get files from list, or create them the first time:
          std::pair<FILE*, std::pair<FILE*, FILE*>>* files = nullptr;
          std::map<string, std::pair<FILE*, std::pair<FILE*, FILE*>>>::iterator it;
          if ((it = lstFiles.find(obs->sensorLabel)) == lstFiles.end())
          {
            files = &lstFiles[obs->sensorLabel];
            // Open files:
            files->first = os::fopen(prefix_laser + obs->sensorLabel + string(".txt"), "wt");
            ASSERT_(files->first);
            files->second.first =
                os::fopen(prefix_laser + obs->sensorLabel + string("_times.txt"), "wt");
            ASSERT_(files->second.first);
            files->second.second =
                os::fopen(prefix_laser + obs->sensorLabel + string("_poses.txt"), "wt");
            ASSERT_(files->second.second);
          }
          else
          {
            files = &lstFiles[obs->sensorLabel];
          }

          for (size_t j = 0; j < obs->getScanSize(); j++)
            ::fprintf(files->first, "%f ", obs->getScanRangeValidity(j) ? obs->getScanRange(j) : 0);
          ::fprintf(files->first, "\n");

          nLaser++;

          if (genTimes)
          {
            double t = 0;
            if (obs->timestamp != INVALID_TIMESTAMP)
              // t =
              // mrpt::system::timeDifference(rawlog_first_timestamp,
              // obs->timestamp);
              t = mrpt::Clock::toDouble(obs->timestamp);

            ::fprintf(files->second.first, "%f\n", t);
          }

          // dump pose data
          double y, p, r;
          obs->sensorPose.getYawPitchRoll(y, p, r);
          ::fprintf(files->second.second, "%f\t%f\t%f\n", y, p, r);
        }
        else if (IS_CLASS(*o, CObservationOdometry))
        {
          CObservationOdometry::Ptr odo = std::dynamic_pointer_cast<CObservationOdometry>(o);

          CPose2D poseIncrement(0, 0, 0);
          if (!lastOdo_ok)
          {
            lastOdo_ok = true;
          }
          else
          {
            poseIncrement = odo->odometry - lastOdo;
            lastOdo = odo->odometry;
          }

          ::fprintf(f_odo, "%f %f %f\n", poseIncrement.x(), poseIncrement.y(), poseIncrement.phi());
          nOdo++;

          if (genTimes)
          {
            double t = 0;
            if (odo->timestamp != INVALID_TIMESTAMP)
              // t =
              // mrpt::system::timeDifference(rawlog_first_timestamp,
              // odo->timestamp);
              t = mrpt::Clock::toDouble(odo->timestamp);
            ::fprintf(f_odo_times, "%f\n", t);
          }
        }
      }
      break;

      // Error:
      default:
        THROW_EXCEPTION("Unknown element type in the rawlog");

    }  // end switch.
  }

  progDia.Update(n);

  os::fclose(f_odo);
  if (f_odo_times) os::fclose(f_odo_times);

  for (auto l : lstFiles)
  {
    os::fclose(l.second.first);
    os::fclose(l.second.second.first);
    os::fclose(l.second.second.second);
  }

  wxMessageBox(
      wxString::Format(_("%u odometry & %u laser entries saved."), nOdo, nLaser), _("Done"), wxOK,
      this);

  WX_END_TRY
}

void xRawLogViewerFrame::OnShowICP(wxCommandEvent&)
{
  if (rawlog.size() < 1)
  {
    wxMessageBox(_("Please load a rawlog first!"), _("Rawlog is empty"), wxOK, this);
    return;
  }

  scanMatchingDialog->Hide();
  scanMatchingDialog->Show();
  scanMatchingDialog->Maximize();
}

void xRawLogViewerFrame::OnLoadAPartOnly(wxCommandEvent&)
{
  string fil;
  if (!AskForOpenRawlog(fil)) return;

  // Query the first and last entries to load:
  wxString strFirst = wxGetTextFromUser(
      _("Enter the first entry index to load:"), _("Load a part of rawlog:"), _("0"));
  wxString strLast = wxGetTextFromUser(
      _("Enter the last entry index to load (-1:Last):"), _("Load a part of rawlog:"), _("-1"));
  long first;
  strFirst.ToLong(&first);
  long last;
  strLast.ToLong(&last);

  loadRawlogFile(fil, first, last);
}

void xRawLogViewerFrame::OnFileCountEntries(wxCommandEvent&)
{
  WX_START_TRY

  string str;
  if (!AskForOpenRawlog(str)) return;

  wxBusyCursor waitCursor;
  CFileGZInputStream fil(str);
  auto filSize = (unsigned int)fil.getTotalBytesCount();

  wxString auxStr;
  wxProgressDialog progDia(
      wxT("Progress of rawlog read"), wxT("Parsing file..."),
      filSize,  // range
      this,     // parent
      wxPD_CAN_ABORT | wxPD_APP_MODAL | wxPD_SMOOTH | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME |
          wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME);

  wxTheApp->Yield();  // Let the app. process messages

  unsigned int countLoop = 0;
  int entryIndex = 0;
  bool keepLoading = true;
  string errorMsg;

  while (keepLoading)
  {
    if (countLoop++ % 100 == 0)
    {
      auxStr.sprintf(wxT("Parsing file... %u objects"), entryIndex);
      if (!progDia.Update((int)fil.getPosition(), auxStr)) keepLoading = false;
      wxTheApp->Yield();  // Let the app. process messages
    }

    CSerializable::Ptr newObj;
    try
    {
      archiveFrom(fil) >> newObj;
      // Check type:
      if (newObj->GetRuntimeClass() == CLASS_ID(CSensoryFrame) ||
          newObj->GetRuntimeClass() == CLASS_ID(CActionCollection) ||
          newObj->GetRuntimeClass() == CLASS_ID(CPose2D))
      {
        entryIndex++;
      }
      else
      {
        // Unknown class:
        THROW_EXCEPTION("Unknown class found in the file!");
      }
    }
    catch (exception& e)
    {
      errorMsg = mrpt::exception_to_str(e);
      keepLoading = false;
    }
    catch (...)
    {
      keepLoading = false;
    }
  }  // end while keep loading

  progDia.Update(filSize);

  // Set error msg:
  char auxStr2[100];
  os::sprintf(auxStr2, sizeof(auxStr2), "There are %i entries in the rawlog file", entryIndex);
  wxMessageBox(auxStr2, _("Count done"), wxOK, this);

  WX_END_TRY
}

void xRawLogViewerFrame::OnFileSaveImages(wxCommandEvent&)
{
  WX_START_TRY

  string str;
  if (!AskForOpenRawlog(str)) return;

  // ask for the output directory:
  wxDirDialog dirDialog(
      this, _("Choose the output directory for the images"), _("."), 0, wxDefaultPosition);

  if (dirDialog.ShowModal() != wxID_OK) return;
  string outDir(dirDialog.GetPath().mb_str());

  // Let the user choose the image format:
  string imgFileExtension = AskForImageFileFormat();
  if (imgFileExtension.empty()) return;

  wxBusyCursor waitCursor;
  CFileGZInputStream fil(str);
  auto filSize = (unsigned int)fil.getTotalBytesCount();

  wxString auxStr;
  wxProgressDialog progDia(
      wxT("Progress"), wxT("Parsing file..."),
      filSize,  // range
      this,     // parent
      wxPD_CAN_ABORT | wxPD_APP_MODAL | wxPD_SMOOTH | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME |
          wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME);

  wxTheApp->Yield();  // Let the app. process messages

  unsigned int countLoop = 0;
  int imgSaved = 0;
  bool keepLoading = true;
  string errorMsg;

  while (keepLoading)
  {
    if (countLoop++ % 5 == 0)
    {
      auxStr.sprintf(wxT("Parsing file... %u objects"), countLoop);
      if (!progDia.Update((int)fil.getPosition(), auxStr)) keepLoading = false;
      wxTheApp->Yield();  // Let the app. process messages
    }

    CSerializable::Ptr newObj;
    try
    {
      archiveFrom(fil) >> newObj;
      // Check type:
      if (newObj->GetRuntimeClass() == CLASS_ID(CSensoryFrame))
      {
        CSensoryFrame::Ptr SF = std::dynamic_pointer_cast<CSensoryFrame>(newObj);

        for (unsigned k = 0; k < SF->size(); k++)
        {
          if (SF->getObservationByIndex(k)->GetRuntimeClass() == CLASS_ID(CObservationStereoImages))
          {
            auto obsSt = SF->getObservationByIndexAs<CObservationStereoImages::Ptr>(k);
            bool savedOk = obsSt->imageLeft.saveToFile(format(
                "%s/img_stereo_%u_left_%05u.%s", outDir.c_str(), k, imgSaved,
                imgFileExtension.c_str()));

            ASSERT_(savedOk);

            if (obsSt->hasImageRight)
            {
              savedOk = obsSt->imageRight.saveToFile(format(
                  "%s/img_stereo_%u_right_%05u.%s", outDir.c_str(), k, imgSaved,
                  imgFileExtension.c_str()));
              ASSERT_(savedOk);
            }

            if (obsSt->hasImageDisparity)
            {
              savedOk = obsSt->imageDisparity.saveToFile(format(
                  "%s/img_stereo_%u_disp_%05u.%s", outDir.c_str(), k, imgSaved,
                  imgFileExtension.c_str()));
              ASSERT_(savedOk);
            }
            imgSaved++;
          }
          if (SF->getObservationByIndex(k)->GetRuntimeClass() == CLASS_ID(CObservationImage))
          {
            auto obsIm = SF->getObservationByIndexAs<CObservationImage::Ptr>(k);

            bool savedOk = obsIm->image.saveToFile(format(
                "%s/img_monocular_%u_%05u.%s", outDir.c_str(), k, imgSaved,
                imgFileExtension.c_str()));
            ASSERT_(savedOk);
            imgSaved++;
          }
        }
      }
      else if (
          newObj->GetRuntimeClass() == CLASS_ID(CActionCollection) ||
          newObj->GetRuntimeClass() == CLASS_ID(CPose2D))
      {
      }
      else
      {
        // Unknown class:
        THROW_EXCEPTION("Unknown class found in the file!");
      }

      newObj.reset();
    }
    catch (exception& e)
    {
      errorMsg = mrpt::exception_to_str(e);
      keepLoading = false;
    }
    catch (...)
    {
      keepLoading = false;
    }
  }  // end while keep loading

  progDia.Update(filSize);

  // Set error msg:
  wxMessageBox((format("Images saved: %i", imgSaved).c_str()), _("Done"), wxOK, this);

  WX_END_TRY
}

void xRawLogViewerFrame::OnFileGenVisualLMFromStereoImages(wxCommandEvent&) {}

void xRawLogViewerFrame::OnMRUFile(wxCommandEvent& event)
{
  wxString f(m_fileHistory.GetHistoryFile(event.GetId() - wxID_FILE1));
  if (!f.empty()) loadRawlogFile(string(f.mb_str()));
}

void xRawLogViewerFrame::OnChangeSensorPositions(wxCommandEvent&)
{
  CFormChangeSensorPositions dialog(this);

  dialog.edLabel->Clear();
  for (auto i = listOfSensorLabels.begin(); i != listOfSensorLabels.end(); ++i)
    dialog.edLabel->Append(i->first.c_str());

  dialog.ShowModal();
}

void xRawLogViewerFrame::OnDecimateRecords(wxCommandEvent&)
{
  WX_START_TRY

  CRawlog newRawLog;
  newRawLog.setCommentText(rawlog.getCommentText());

  wxString strDecimation = wxGetTextFromUser(
      _("The number of observations will be decimated (only 1 out of M "
        "will "
        "be kept). Enter the decimation ratio M:"),
      _("Decimation"), _("1"));
  long DECIMATE_RATIO;
  strDecimation.ToLong(&DECIMATE_RATIO);

  ASSERT_(DECIMATE_RATIO >= 1);
  wxBusyCursor busyCursor;
  wxTheApp->Yield();  // Let the app. process messages

  size_t i, N = rawlog.size();

  // ------------------------------------------------------------------------------
  // METHOD TO BE MEMORY EFFICIENT:
  //  To free the memory of the current rawlog entries as we create the
  //  new one, then call "clearWithoutDelete" at the end.
  // ------------------------------------------------------------------------------
  CSensoryFrame::Ptr last_sf;  // empty ptr
  CActionRobotMovement2D::TMotionModelOptions odometryOptions;
  bool cummMovementInit = false;
  long SF_counter = 0;

  // Reset cummulative pose change:
  CPose2D accumMovement(0, 0, 0);

  // For each entry:
  for (i = 0; i < N; i++)
  {
    CSerializable::Ptr obj = rawlog.getAsGeneric(i);
    bool objToBeDeleted = true;  // Will be set to false if "obj" cannot be deleted now.

    if (rawlog.getType(i) == CRawlog::etActionCollection)
    {
      // Accumulate Actions
      // ----------------------
      CActionCollection::Ptr curActs = std::dynamic_pointer_cast<CActionCollection>(obj);
      CActionRobotMovement2D::Ptr mov = curActs->getBestMovementEstimation();
      if (mov)
      {
        // Accumulate from odometry:
        accumMovement = accumMovement + mov->poseChange->getMeanVal();

        // Copy the probabilistic options from the first entry we
        // find:
        if (!cummMovementInit)
        {
          odometryOptions = mov->motionModelConfiguration;
          cummMovementInit = true;
        }
      }
    }
    else if (rawlog.getType(i) == CRawlog::etSensoryFrame)
    {
      // Decimate Observations
      // ---------------------------
      if (!last_sf)
      {
        last_sf = std::dynamic_pointer_cast<CSensoryFrame>(obj);
        objToBeDeleted = false;  // Do not delete this one
      }

      if (++SF_counter >= DECIMATE_RATIO)
      {
        SF_counter = 0;

        // INSERT OBSERVATION:
        newRawLog.insert(last_sf);
        last_sf.reset();  // = nullptr;

        // INSERT ACTIONS:
        CActionCollection actsCol;
        if (cummMovementInit)
        {
          CActionRobotMovement2D cummMovement;
          cummMovement.computeFromOdometry(accumMovement, odometryOptions);
          actsCol.insert(cummMovement);
          // Reset odometry accumulation:
          accumMovement = CPose2D(0, 0, 0);
        }
        newRawLog.insert(actsCol);
      }
    }
    else
    {
      THROW_EXCEPTION("This is only for rawlogs based on sensory frames.");
    }

    // Delete object?
    if (objToBeDeleted) obj.reset();  // delete obj;

  }  // end for i each entry

  // Clear the list only (objects already deleted)
  rawlog.clear();

  // Copy as new log:
  rawlog = newRawLog;

  rebuildTreeView();

  WX_END_TRY
}

void xRawLogViewerFrame::OnCountBadScans(wxCommandEvent&)
{
  WX_START_TRY

  wxBusyCursor waitCursor;
  int nEntries = (int)rawlog.size();

  wxString auxStr;
  wxProgressDialog progDia(
      wxT("Progress"), wxT("Parsing rawlog..."),
      nEntries,  // range
      this,      // parent
      wxPD_CAN_ABORT | wxPD_APP_MODAL | wxPD_SMOOTH | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME |
          wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME);

  wxTheApp->Yield();  // Let the app. process messages

  int invalidScans = 0;
  string errorMsg;

  for (int countLoop = 0; countLoop < nEntries; countLoop++)
  {
    if (countLoop % 5 == 0)
    {
      auxStr.sprintf(wxT("Parsing rawlog... %u objects"), countLoop);
      if (!progDia.Update(countLoop, auxStr)) break;
      wxTheApp->Yield();  // Let the app. process messages
    }

    try
    {
      if (rawlog.getType(countLoop) == CRawlog::etSensoryFrame)
      {
        CSensoryFrame::Ptr SF = rawlog.getAsObservations(countLoop);

        for (size_t k = 0; k < SF->size(); k++)
        {
          if (SF->getObservationByIndex(k)->GetRuntimeClass() == CLASS_ID(CObservation2DRangeScan))
          {
            auto obsScan = SF->getObservationByIndexAs<CObservation2DRangeScan::Ptr>(k);
            bool thisValid = false;

            for (size_t i = 0; i < obsScan->getScanSize(); i++)
            {
              if (obsScan->getScanRangeValidity(i)) thisValid = true;
              if (std::isnan(obsScan->getScanRange(i))) thisValid = false;
            }

            if (!thisValid) invalidScans++;
          }
        }
      }  // end for each entry
      else if (rawlog.getType(countLoop) == CRawlog::etObservation)
      {
        CObservation::Ptr o = rawlog.getAsObservation(countLoop);

        if (o->GetRuntimeClass() == CLASS_ID(CObservation2DRangeScan))
        {
          CObservation2DRangeScan::Ptr obsScan =
              std::dynamic_pointer_cast<CObservation2DRangeScan>(o);
          bool thisValid = false;

          for (size_t k = 0; k < obsScan->getScanSize(); k++)
          {
            if (obsScan->getScanRangeValidity(k)) thisValid = true;
            if (std::isnan(obsScan->getScanRange(k))) thisValid = false;
          }

          if (!thisValid) invalidScans++;
        }
      }  // end for each entry
    }
    catch (exception& e)
    {
      errorMsg = mrpt::exception_to_str(e);
      break;
    }
    catch (...)
    {
      break;
    }
  }  // end while keep loading

  progDia.Update(nEntries);

  wxMessageBox(
      (format("Found %u range scans with no valid range values.", invalidScans).c_str()), _("Done"),
      wxOK, this);

  WX_END_TRY
}

void xRawLogViewerFrame::OnFilterSpureousGas(wxCommandEvent&)
{
  WX_START_TRY

  wxString strMaxChange = wxGetTextFromUser(
      _("Maximum change between readings (volts):"), _("Filter out gas readings 'spikes':"),
      _("0.05"));
  double maxChange;
  strMaxChange.ToCDouble(&maxChange);

  wxBusyCursor waitCursor;
  int nEntries = (int)rawlog.size();

  wxString auxStr;
  wxProgressDialog progDia(
      wxT("Progress"), wxT("Parsing rawlog..."),
      nEntries,  // range
      this,      // parent
      wxPD_CAN_ABORT | wxPD_APP_MODAL | wxPD_SMOOTH | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME |
          wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME);

  wxTheApp->Yield();  // Let the app. process messages

  int nFilt = 0, nReadings = 0;
  string errorMsg;
  CObservationGasSensors::Ptr obs_1, obs_2;

  for (int countLoop = 0; countLoop < nEntries; countLoop++)
  {
    if (countLoop % 5 == 0)
    {
      auxStr.sprintf(wxT("Parsing rawlog... %u objects"), countLoop);
      if (!progDia.Update(countLoop, auxStr)) break;
      wxTheApp->Yield();  // Let the app. process messages
    }

    try
    {
      switch (rawlog.getType(countLoop))
      {
        case CRawlog::etSensoryFrame:
        {
          CSensoryFrame::Ptr SF = rawlog.getAsObservations(countLoop);

          for (size_t k = 0; k < SF->size(); k++)
          {
            if (SF->getObservationByIndex(k)->GetRuntimeClass() == CLASS_ID(CObservationGasSensors))
            {
              auto obs = SF->getObservationByIndexAs<CObservationGasSensors::Ptr>(k);

              // Do we have 3 consecutive readings??
              if (obs_1 && obs_2)
              {
                // Process:
                ASSERT_(obs->m_readings.size() == obs_1->m_readings.size());
                ASSERT_(obs->m_readings.size() == obs_2->m_readings.size());

                for (size_t j = 0; j < obs->m_readings.size(); j++)
                {
                  ASSERT_(
                      obs->m_readings[j].readingsVoltage.size() ==
                      obs_1->m_readings[j].readingsVoltage.size());
                  ASSERT_(
                      obs->m_readings[j].readingsVoltage.size() ==
                      obs_2->m_readings[j].readingsVoltage.size());

                  for (size_t i = 0; i < obs->m_readings[j].readingsVoltage.size(); i++)
                  {
                    nReadings++;
                    // Compute difference for "t-1":
                    if (fabs(
                            obs_1->m_readings[j].readingsVoltage[i] -
                            obs->m_readings[j].readingsVoltage[i]) > maxChange &&
                        fabs(
                            obs_1->m_readings[j].readingsVoltage[i] -
                            obs_2->m_readings[j].readingsVoltage[i]) > maxChange)
                    {
                      obs_1->m_readings[j].readingsVoltage[i] =
                          0.5f * (obs->m_readings[j].readingsVoltage[i] +
                                  obs_2->m_readings[j].readingsVoltage[i]);
                      nFilt++;
                    }
                  }
                }
              }

              // Shift:
              obs_2 = obs_1;
              obs_1 = obs;
            }
          }
        }
        break;

        case CRawlog::etObservation:
        {
          CObservation::Ptr o = rawlog.getAsObservation(countLoop);

          if (IS_CLASS(*o, CObservationGasSensors))
          {
            CObservationGasSensors::Ptr obs = std::dynamic_pointer_cast<CObservationGasSensors>(o);

            // Do we have 3 consecutive readings??
            if (obs_1 && obs_2)
            {
              // Process:
              ASSERT_(obs->m_readings.size() == obs_1->m_readings.size());
              ASSERT_(obs->m_readings.size() == obs_2->m_readings.size());

              for (size_t j = 0; j < obs->m_readings.size(); j++)
              {
                ASSERT_(
                    obs->m_readings[j].readingsVoltage.size() ==
                    obs_1->m_readings[j].readingsVoltage.size());
                ASSERT_(
                    obs->m_readings[j].readingsVoltage.size() ==
                    obs_2->m_readings[j].readingsVoltage.size());

                for (size_t k = 0; k < obs->m_readings[j].readingsVoltage.size(); k++)
                {
                  nReadings++;
                  // Compute difference for "t-1":
                  if (fabs(
                          obs_1->m_readings[j].readingsVoltage[k] -
                          obs->m_readings[j].readingsVoltage[k]) > maxChange &&
                      fabs(
                          obs_1->m_readings[j].readingsVoltage[k] -
                          obs_2->m_readings[j].readingsVoltage[k]) > maxChange)
                  {
                    obs_1->m_readings[j].readingsVoltage[k] =
                        0.5f * (obs->m_readings[j].readingsVoltage[k] +
                                obs_2->m_readings[j].readingsVoltage[k]);
                    nFilt++;
                  }
                }
              }
            }

            // Shift:
            obs_2 = obs_1;
            obs_1 = obs;
          }
        }
        break;

        default:
          break;
      }  // end for each entry
    }
    catch (exception& e)
    {
      errorMsg = mrpt::exception_to_str(e);
      break;
    }
    catch (...)
    {
      break;
    }
  }  // end while keep loading

  progDia.Update(nEntries);

  wxMessageBox(
      (format("%u out of %u readings have been filtered out!", nFilt, nReadings).c_str()),
      _("Done"), wxOK, this);

  WX_END_TRY
}

void xRawLogViewerFrame::OnRemoveSpecificRangeMeas(wxCommandEvent&)
{
  WX_START_TRY

  wxString strStart = wxGetTextFromUser(
      _("First index of the rawlog to remove from:"), _("Remove range-only measurements."), _("0"));
  long start_filt;
  strStart.ToLong(&start_filt);

  wxString strEnd = wxGetTextFromUser(
      _("Last index of the rawlog to remove from (-1:cancel & exit):"),
      _("Remove range-only measurements."), _("-1"));
  long end_filt;
  strEnd.ToLong(&end_filt);

  if (end_filt < 0) return;

  wxString strIndex = wxGetTextFromUser(
      _("Index of the range within the observation (-1:None)"),
      _("Remove range-only measurements."), _("0"));
  unsigned long indx_filt;
  strIndex.ToULong(&indx_filt);

  vector<float> lastValidRanges;

  wxBusyCursor waitCursor;
  long i, n = (long)rawlog.size();

  int nFilt = 0, nReadings = 0;
  string errorMsg;
  CObservationBeaconRanges::Ptr obs_1, obs_2;
  size_t q;

  for (i = start_filt; i <= end_filt; i++)
  {
    switch (rawlog.getType(i))
    {
      case CRawlog::etSensoryFrame:
      {
        CSensoryFrame::Ptr sf = rawlog.getAsObservations(i);
        CObservationBeaconRanges::Ptr obs = sf->getObservationByClass<CObservationBeaconRanges>();
        if (obs)
        {
          ASSERT_(indx_filt < obs->sensedData.size());
          obs->sensedData[indx_filt].sensedDistance = 0;
        }
      }
      break;
      case CRawlog::etObservation:
      {
        CObservation::Ptr o = rawlog.getAsObservation(i);
        if (o->GetRuntimeClass()->derivedFrom(CLASS_ID(CObservation)))
        {
          CObservationBeaconRanges::Ptr obs =
              std::dynamic_pointer_cast<CObservationBeaconRanges>(o);
          ASSERT_(indx_filt < obs->sensedData.size());
          obs->sensedData[indx_filt].sensedDistance = 0;
        }
      }
      break;

      default:
        break;
    };  // end
  }

  for (i = 0; i < n; i++)
  {
    switch (rawlog.getType(i))
    {
      case CRawlog::etSensoryFrame:
      {
        CSensoryFrame::Ptr sf = rawlog.getAsObservations(i);
        CObservationBeaconRanges::Ptr obs = sf->getObservationByClass<CObservationBeaconRanges>();

        if (obs)
        {
          nReadings++;

          // Do we have 2 consecutive readings??
          if (obs_1 && obs_2)
          {
            ASSERT_(obs->sensedData.size() == obs_1->sensedData.size());
            ASSERT_(obs_2->sensedData.size() == obs_1->sensedData.size());

            for (q = 0; q < obs->sensedData.size(); q++)
            {
              bool filter = false;

              if (std::isnan(obs->sensedData[q].sensedDistance))
                obs->sensedData[q].sensedDistance = 0;

              filter = std::isnan(obs_2->sensedData[q].sensedDistance);

              if (!filter)
                filter =
                    (obs->sensedData[q].sensedDistance == obs_1->sensedData[q].sensedDistance &&
                     obs_1->sensedData[q].sensedDistance == obs_2->sensedData[q].sensedDistance);

              if (filter)
              {
                obs_2->sensedData[q].sensedDistance = 0;
              }
              nFilt++;
            }
          }

          // Shift:
          obs_2 = obs_1;
          obs_1 = obs;
        }
      }
      default:
        break;
    };
  }

  wxMessageBox(
      (format("%u out of %u readings have been filtered out!", nFilt, nReadings).c_str()),
      _("Done"), wxOK, this);

  WX_END_TRY
}

void xRawLogViewerFrame::OnForceEncodersFalse(wxCommandEvent&)
{
  WX_START_TRY

  bool clearEncoders = wxYES == wxMessageBox(
                                    _("Set 'hasEncodersInfo' fields to false?"),
                                    _("Select operations"), wxYES_NO, this);
  bool clearVelocities = wxYES == wxMessageBox(
                                      _("Set 'hasVelocities' fields to false?"),
                                      _("Select operations"), wxYES_NO, this);

  wxBusyCursor waitCursor;
  int nEntries = (int)rawlog.size();

  wxString auxStr;
  wxProgressDialog progDia(
      wxT("Progress"), wxT("Parsing rawlog..."),
      nEntries,  // range
      this,      // parent
      wxPD_CAN_ABORT | wxPD_APP_MODAL | wxPD_SMOOTH | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME |
          wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME);

  wxTheApp->Yield();  // Let the app. process messages

  int nChanges = 0;
  string errorMsg;

  for (int countLoop = 0; countLoop < nEntries; countLoop++)
  {
    if (countLoop % 20 == 0)
    {
      auxStr.sprintf(wxT("Parsing rawlog... %u objects"), countLoop);
      if (!progDia.Update(countLoop, auxStr)) break;
      wxTheApp->Yield();  // Let the app. process messages
    }

    try
    {
      if (rawlog.getType(countLoop) == CRawlog::etActionCollection)
      {
        CActionCollection::Ptr acts = rawlog.getAsAction(countLoop);
        for (size_t k = 0; k < acts->size(); k++)
        {
          CAction::Ptr act = acts->get(k);
          if (act->GetRuntimeClass() == CLASS_ID(CActionRobotMovement2D))
          {
            CActionRobotMovement2D::Ptr a = std::dynamic_pointer_cast<CActionRobotMovement2D>(act);
            if (a->hasEncodersInfo && clearEncoders)
            {
              a->hasEncodersInfo = false;
              nChanges++;
            }
            if (a->hasVelocities && clearVelocities)
            {
              a->hasVelocities = false;
              nChanges++;
            }
          }
        }
      }  // end for each entry
    }
    catch (exception& e)
    {
      errorMsg = mrpt::exception_to_str(e);
      break;
    }
    catch (...)
    {
      break;
    }
  }  // end while keep loading

  progDia.Update(nEntries);

  wxMessageBox((format("%u entries have been modified", nChanges).c_str()), _("Done"), wxOK, this);

  WX_END_TRY
}

void doFilterErrScans(
    CObservation::Ptr& obs, size_t& invalidSegments, size_t& invalidRanges, bool& touched)
{
  if (obs->GetRuntimeClass()->derivedFrom(CLASS_ID(CObservation2DRangeScan)))
  {
    CObservation2DRangeScan::Ptr obsScan = std::dynamic_pointer_cast<CObservation2DRangeScan>(obs);

    // Try to filter our spureous ranges:
    // --------------------------------------------

    // Build a vector: each element is true if
    //    element[k] is the element[k] -/+ a value in [0.10-2.0], and
    //    viceversa with [k-1]
    std::vector<bool> ringing(obsScan->getScanSize(), false);
    unsigned int k;

    for (k = 1; k < (obsScan->getScanSize() - 1); k++)
    {
      if (obsScan->getScanRangeValidity(k) && obsScan->getScanRangeValidity(k - 1) &&
          obsScan->getScanRangeValidity(k + 1))
      {
        int dirPrior = 0, dirPost = 0;

        if (obsScan->getScanRange(k) > (obsScan->getScanRange(k - 1) + 0.03f) &&
            obsScan->getScanRange(k) < (obsScan->getScanRange(k - 1) + 2.00f))
          dirPrior = 1;
        if (obsScan->getScanRange(k) < (obsScan->getScanRange(k - 1) - 0.03f) &&
            obsScan->getScanRange(k) > (obsScan->getScanRange(k - 1) - 2.00f))
          dirPrior = -1;

        if (obsScan->getScanRange(k) > (obsScan->getScanRange(k + 1) + 0.03f) &&
            obsScan->getScanRange(k) < (obsScan->getScanRange(k + 1) + 2.00f))
          dirPost = -1;
        if (obsScan->getScanRange(k) < (obsScan->getScanRange(k + 1) - 0.03f) &&
            obsScan->getScanRange(k) > (obsScan->getScanRange(k + 1) - 2.00f))
          dirPost = 1;

        if ((dirPrior == 1 && dirPost == -1) || (dirPrior == -1 && dirPost == 1)) ringing[k] = true;
      }
    }

    // Look for segments of 'K' consecutive ringing ranges, and mark
    // them as not valid!!
    int ringingStart = -1;
    for (k = 1; k < (obsScan->getScanSize() - 1); k++)
    {
      if (ringing[k])
      {
        if (ringingStart == -1)
        {
          // First one in a segment:
          ringingStart = k;
        }
      }
      else
      {
        // End of segment?
        if (ringingStart != -1)
        {
          // Length:
          size_t len = (k - 1) - ringingStart;
          if (len > 2)
          {
            // Mark them as invalid!!
            for (size_t p = ringingStart; p < k; p++, invalidRanges++)
            {
              obsScan->setScanRangeValidity(p, false);
              touched = true;
            }

            invalidSegments++;
          }
        }
        // We are out of any segment just now:
        ringingStart = -1;
      }
    }  // end for "k" & mark as invalid
  }    // end-if is a laser scan
}

void xRawLogViewerFrame::OnFilterErroneousScans(wxCommandEvent&)
{
  WX_START_TRY

  wxMessageBox(
      _("This process will look for spurious ranges, e.g. caused by "
        "\ndirect "
        "sunlight in an indoor laser scanner."),
      _("Filter 2D range scans"), wxOK, this);

  wxBusyCursor waitCursor;
  int nEntries = (int)rawlog.size();

  wxString auxStr;
  wxProgressDialog progDia(
      wxT("Progress"), wxT("Parsing rawlog..."),
      nEntries,  // range
      this,      // parent
      wxPD_CAN_ABORT | wxPD_APP_MODAL | wxPD_SMOOTH | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME |
          wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME);

  wxTheApp->Yield();  // Let the app. process messages

  size_t invalidSegments = 0, invalidRanges = 0;
  string lstTouched;
  string errorMsg;

  for (int countLoop = 0; countLoop < nEntries; countLoop++)
  {
    if (countLoop % 5 == 0)
    {
      auxStr.sprintf(wxT("Parsing rawlog... %u objects"), countLoop);
      if (!progDia.Update(countLoop, auxStr)) break;
      wxTheApp->Yield();  // Let the app. process messages
    }

    try
    {
      // Check type:
      switch (rawlog.getType(countLoop))
      {
        case CRawlog::etSensoryFrame:
        {
          // This is a SF:
          CSensoryFrame::Ptr SF = rawlog.getAsObservations(countLoop);
          bool touched = false;

          for (size_t j = 0; j < SF->size(); j++)
          {
            CObservation::Ptr obs = SF->getObservationByIndex(j);
            doFilterErrScans(obs, invalidSegments, invalidRanges, touched);
          }  // end for each Observation in the SF

          // Add to the list of modified entries:
          if (touched)
          {
            if (lstTouched.size() < 1000)
              lstTouched += format("%u ", countLoop);
            else
            {
              static bool noMore = false;
              if (!noMore) lstTouched += "...";
              noMore = true;
            }
          }
        }
        break;

        case CRawlog::etObservation:
        {
          // This is a SF:
          CObservation::Ptr obs = rawlog.getAsObservation(countLoop);
          bool touched = false;

          doFilterErrScans(obs, invalidSegments, invalidRanges, touched);

          // Add to the list of modified entries:
          if (touched)
          {
            if (lstTouched.size() < 1000)
              lstTouched += format("%u ", countLoop);
            else
            {
              static bool noMore = false;
              if (!noMore) lstTouched += "...";
              noMore = true;
            }
          }
        }
        break;

        default:
          break;
      };  // end switch
    }
    catch (exception& e)
    {
      errorMsg = mrpt::exception_to_str(e);
      break;
    }
    catch (...)
    {
      break;
    }
  }  // end while keep loading

  progDia.Update(nEntries);

  wxMessageBox(
      format(
          "Number of bad segments detected & marked as invalid: %u \n(%u "
          "individual ranges. Rawlog indexes modified:\n%s",
          (unsigned)invalidSegments, (unsigned)invalidRanges, lstTouched.c_str()),
      _("Done"), wxOK, this);

  WX_END_TRY
}

void xRawLogViewerFrame::OnRecalculateActionsICP(wxCommandEvent&)
{
  WX_START_TRY

  wxMessageBox(
      _("Please, modify as desired all the ICP algorithm options\nand the "
        "type of reference map from the following dialog, then close it to "
        "start recomputing the actions from scan matching."));

  // Take options from "scanMatchingDialog"
  scanMatchingDialog->btnRunICP->Hide();
  scanMatchingDialog->ShowModal();
  scanMatchingDialog->btnRunICP->Show();
  scanMatchingDialog->btnRunICP->Fit();

  // Ask for the min. ICP goodness:
  float minICPGoodness = 0.4f;

  // The reference and the new maps:
  CSimplePointsMap refMapPt;
  COccupancyGridMap2D refMapGrid;
  CSimplePointsMap newMapPt;
  CICP icp;
  CICP::TReturnInfo icpInfo;

  // The 2 SFs and the action between:
  CSensoryFrame::Ptr SF_ref;           // = nullptr;
  CSensoryFrame::Ptr SF_new;           // = nullptr;
  CActionCollection::Ptr act_between;  // = nullptr;

  CPosePDF::Ptr poseEst;

  // Load ICP options:
  // ------------------------------------------
  CConfigFileMemory icpCfg(string(scanMatchingDialog->edOptICP->GetValue().mb_str()));
  icp.options.loadFromConfigFile(icpCfg, "ICP");

  // EXTRA options:
  CPose2D initialEst(0, 0, 0);

  // Create reference map & load its options:
  // ------------------------------------------
  bool useGridMap = scanMatchingDialog->rbGrid->GetValue();
  if (!useGridMap)
  {
    CConfigFileMemory refCfg(string(scanMatchingDialog->edOptRefPnt->GetValue().mb_str()));
    refMapPt.insertionOptions.loadFromConfigFile(refCfg, "InsertionOptions");
  }
  else
  {
    CConfigFileMemory refCfg(string(scanMatchingDialog->edOptRefGrid->GetValue().mb_str()));
    float gridRes = refCfg.read_float("Construction", "resolution", 0.05f);
    refMapGrid.setSize(-10, 10, -10, 10, gridRes);
    refMapGrid.insertionOptions.loadFromConfigFile(refCfg, "InsertionOptions");
  }

  CMetricMap* refMap = useGridMap ? (CMetricMap*)&refMapGrid : (CMetricMap*)&refMapPt;

  // Load new map options:
  // ----------------------------
  {
    CConfigFileMemory refCfg(string(scanMatchingDialog->edOptAlignMap->GetValue().mb_str()));
    newMapPt.insertionOptions.loadFromConfigFile(refCfg, "InsertionOptions");
  }

  wxBusyCursor waitCursor;
  int nEntries = (int)rawlog.size();

  wxString auxStr;
  wxProgressDialog progDia(
      wxT("Progress"), wxT("Parsing rawlog..."),
      nEntries,  // range
      this,      // parent
      wxPD_CAN_ABORT | wxPD_APP_MODAL | wxPD_SMOOTH | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME |
          wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME);

  wxTheApp->Yield();  // Let the app. process messages

  string errorMsg;

  for (int countLoop = 0; countLoop < nEntries; countLoop++)
  {
    if (countLoop % 20 == 0)
    {
      auxStr.sprintf(wxT("Parsing rawlog... %u objects"), countLoop);
      if (!progDia.Update(countLoop, auxStr)) break;
      wxTheApp->Yield();  // Let the app. process messages
    }

    try
    {
      // Check type:
      if (rawlog.getType(countLoop) == CRawlog::etActionCollection)
      {
        act_between = rawlog.getAsAction(countLoop);
      }
      else if (rawlog.getType(countLoop) == CRawlog::etSensoryFrame)
      {
        // This is a SF:
        SF_new = rawlog.getAsObservations(countLoop);

        if (SF_new && SF_ref && act_between)
        {
          // Insert the observations:
          // --------------------------------------
          refMap->clear();
          newMapPt.clear();

          SF_ref->insertObservationsInto(*refMap);
          CPose3D newMapRobotPose(initialEst);
          SF_new->insertObservationsInto(newMapPt, newMapRobotPose);

          poseEst = icp.Align(refMap, (CMetricMap*)&newMapPt, initialEst, icpInfo);

          // The final estimation:
          // --------------------------------------
          CPose2D estMean;
          CMatrixDouble33 estCov;
          poseEst->getCovarianceAndMean(estCov, estMean);

          if (icpInfo.goodness > minICPGoodness)
          {
            // Add SM-based action or overwrite:
            CActionRobotMovement2D::Ptr act =
                act_between->getMovementEstimationByType(CActionRobotMovement2D::emScan2DMatching);
            if (!act)
            {
              // Create:
              CActionRobotMovement2D newAct;
              newAct.estimationMethod = CActionRobotMovement2D::emScan2DMatching;
              newAct.poseChange = CPosePDF::Ptr(new CPosePDFGaussian(estMean, estCov));
              act_between->insert(newAct);
            }
            else
            {
              // Overwrite:
              act->estimationMethod = CActionRobotMovement2D::emScan2DMatching;
              act->poseChange = CPosePDF::Ptr(new CPosePDFGaussian(estMean, estCov));
            }
          }
          else
          {
            // Remove SM-based action if it existed
            for (int k = ((int)act_between->size()) - 1; k >= 0; k--)
            {
              if (act_between->get(k)->GetRuntimeClass() == CLASS_ID(CActionRobotMovement2D))
              {
                CActionRobotMovement2D::Ptr act =
                    std::dynamic_pointer_cast<CActionRobotMovement2D>(act_between->get(k));
                if (act->estimationMethod == CActionRobotMovement2D::emScan2DMatching)
                  act_between->eraseByIndex(k);
              }
            }
          }

        }  // end if both new/ref SFs.

        // For the next iteration:
        SF_ref = SF_new;

      }  // end if it's a SF
    }
    catch (exception& e)
    {
      errorMsg = mrpt::exception_to_str(e);
      break;
    }
    catch (...)
    {
      break;
    }
  }  // end while keep loading

  progDia.Update(nEntries);

  rebuildTreeView();

  WX_END_TRY
}

// --------------------------------------------------------------
//
//    			POPUP MENU ON TREE VIEW
//
// --------------------------------------------------------------
// Right click on the tree view:
/*
void xRawLogViewerFrame::OntreeViewItemRightClick(wxTreeEvent& event)
{
  try
  {
    if (curSelectedObject)
    {
      // MNU: Delete
      MenuItem37->Enable( curSelectedObject->GetRuntimeClass() !=
CLASS_ID(CActionCollection) && curSelectedObject->GetRuntimeClass() !=
CLASS_ID(CSensoryFrame));
      // MNU: Add action
      MenuItem46->Enable( curSelectedObject->GetRuntimeClass() ==
CLASS_ID(CActionCollection) );
      MenuItem47->Enable( curSelectedObject->GetRuntimeClass() ==
CLASS_ID(CActionCollection) );
    }
  }
  catch (...){}

  treeView->PopupMenu( &mnuTree,event.GetPoint() );
}
*/

// Menu: Delete an element from the rawlog
void xRawLogViewerFrame::OnMenuItem37Selected(wxCommandEvent&)
{
  WX_START_TRY

  if (curSelectedObject)
  {
    if (curSelectedObject->GetRuntimeClass()->derivedFrom(CLASS_ID(CObservation)))
    {
      bool done = false;
      // Look for its SF:
      for (size_t i = 0; !done && i < rawlog.size(); i++)
      {
        if (rawlog.getType(i) == CRawlog::etSensoryFrame)
        {
          CSensoryFrame::Ptr sf = rawlog.getAsObservations(i);
          for (size_t k = 0; !done && k < sf->size(); k++)
          {
            if (sf->getObservationByIndex(k) == curSelectedObject)
            {
              sf->eraseByIndex(k);
              done = true;
              break;
            }
          }
        }
        else if (rawlog.getType(i) == CRawlog::etActionCollection)
        {
          CActionCollection::Ptr acts = rawlog.getAsAction(i);
          for (size_t k = 0; !done && k < acts->size(); k++)
          {
            if (acts->get(k) == curSelectedObject)
            {
              acts->eraseByIndex(k);
              done = true;
              break;
            }
          }
        }
      }
    }
  }

  rebuildTreeView();
  WX_END_TRY
}

void xRawLogViewerFrame::OnMenuRenameBySFIndex(wxCommandEvent&)
{
  WX_START_TRY

  wxTextEntryDialog dlg1(this, "Enter SF 0-based index:", "Rename by SF index", "0");
  dlg1.SetTextValidator(wxFILTER_DIGITS);
  if (dlg1.ShowModal() != wxID_OK) return;

  wxTextEntryDialog dlg2(this, "Enter new sensor label:", "Rename by SF index", "NEW_NAME");
  if (dlg2.ShowModal() != wxID_OK) return;

  unsigned long obsIdx = 0;
  if (!dlg1.GetValue().ToULong(&obsIdx)) return;

  const auto newName = dlg2.GetValue().ToStdString();

  for (const auto& e : rawlog)
  {
    auto sf = std::dynamic_pointer_cast<CSensoryFrame>(e);
    if (!sf) continue;

    if (sf->size() < obsIdx) continue;
    sf->getObservationByIndex(obsIdx)->sensorLabel = newName;
  }

  rebuildTreeView();
  WX_END_TRY
}

// Menu: New action 2D (SM)
void xRawLogViewerFrame::OnMenuItem47Selected(wxCommandEvent&)
{
  WX_START_TRY
  if (curSelectedObject)
  {
    if (curSelectedObject->GetRuntimeClass() == CLASS_ID(CActionCollection))
    {
      CActionCollection::Ptr acts = std::dynamic_pointer_cast<CActionCollection>(curSelectedObject);

      float odo_x = atof(string(wxGetTextFromUser(
                                    _("Estimated displacement, X (meters):"),
                                    _("Enter new action parameter:"), _("0"))
                                    .mb_str())
                             .c_str());
      float odo_y = atof(string(wxGetTextFromUser(
                                    _("Estimated displacement, Y (meters):"),
                                    _("Enter new action parameter:"), _("0"))
                                    .mb_str())
                             .c_str());
      float odo_phi = DEG2RAD(atof(string(wxGetTextFromUser(
                                              _("Estimated displacement, PHI (degrees):"),
                                              _("Enter new action parameter:"), _("0"))
                                              .mb_str())
                                       .c_str()));

      CPose2D estMean(odo_x, odo_y, odo_phi);
      CMatrixDouble33 estCov;
      estCov.setDiagonal(3, 1e-6);

      CActionRobotMovement2D newAct;
      newAct.estimationMethod = CActionRobotMovement2D::emScan2DMatching;
      newAct.poseChange = CPosePDF::Ptr(new CPosePDFGaussian(estMean, estCov));

      acts->insert(newAct);
    }
  }
  rebuildTreeView();
  WX_END_TRY
}
// Menu: New action 2D (odometry)
void xRawLogViewerFrame::OnMenuItem46Selected(wxCommandEvent&)
{
  WX_START_TRY
  if (curSelectedObject)
  {
    if (curSelectedObject->GetRuntimeClass() == CLASS_ID(CActionCollection))
    {
      CActionCollection::Ptr acts = std::dynamic_pointer_cast<CActionCollection>(curSelectedObject);

      float odo_x = atof(string(wxGetTextFromUser(
                                    _("Estimated displacement, X (meters):"),
                                    _("Enter new action parameter:"), _("0"))
                                    .mb_str())
                             .c_str());
      float odo_y = atof(string(wxGetTextFromUser(
                                    _("Estimated displacement, Y (meters):"),
                                    _("Enter new action parameter:"), _("0"))
                                    .mb_str())
                             .c_str());
      float odo_phi = DEG2RAD(atof(string(wxGetTextFromUser(
                                              _("Estimated displacement, PHI (degrees):"),
                                              _("Enter new action parameter:"), _("0"))
                                              .mb_str())
                                       .c_str()));

      CPose2D odo(odo_x, odo_y, odo_phi);

      CActionRobotMovement2D newAct;
      CActionRobotMovement2D::TMotionModelOptions odoOpts;  // Default values
      newAct.computeFromOdometry(odo, odoOpts);

      acts->insert(newAct);
    }
  }
  rebuildTreeView();
  WX_END_TRY
}

void xRawLogViewerFrame::OnMenuExpandAll(wxCommandEvent&)
{
  // treeView->ExpandAll();
}

void xRawLogViewerFrame::OnMenuCollapseAll(wxCommandEvent&)
{
  // treeView->CollapseAll();
}

void xRawLogViewerFrame::OnRecomputeOdometry(wxCommandEvent&)
{
  WX_START_TRY

  COdometryParams dialog(this);

  if (dialog.ShowModal())
  {
    float K_left, K_right, D;

    K_left = atof(string(dialog.edKL->GetValue().mb_str()).c_str());
    K_right = atof(string(dialog.edKR->GetValue().mb_str()).c_str());
    D = atof(string(dialog.edD->GetValue().mb_str()).c_str());

    size_t M = 0;

    mrpt::poses::CPose2D auxLastAbsOdo;
    bool auxLastAbsOdo_valid = false;

    {
      wxBusyCursor waitCursor;

      for (size_t i = 0; i < rawlog.size(); i++)
      {
        if (rawlog.getType(i) == CRawlog::etActionCollection)
        {
          CActionCollection::Ptr acts =
              std::dynamic_pointer_cast<CActionCollection>(rawlog.getAsAction(i));
          CActionRobotMovement2D::Ptr act =
              acts->getMovementEstimationByType(CActionRobotMovement2D::emOdometry);
          if (act)
          {
            if (!act->hasEncodersInfo)
            {
              wxMessageBox((format(
                                "An odometry measurement was found at "
                                "entry %i which does not\ncontain "
                                "encoders info: Cannot recompute "
                                "odometry without this information!",
                                (unsigned)i)
                                .c_str()));
              return;
            }
            act->computeFromEncoders(K_left, K_right, D);
            M++;
          }
        }

        if (rawlog.getAsGeneric(i)->GetRuntimeClass() == CLASS_ID(CObservationOdometry))
        {
          CObservationOdometry::Ptr obs =
              std::dynamic_pointer_cast<CObservationOdometry>(rawlog.getAsGeneric(i));
          CObservationOdometry* odo = obs.get();
          if (!odo->hasEncodersInfo)
          {
            wxMessageBox((format(
                              "An odometry measurement was found at "
                              "entry "
                              "%i which does not\ncontain encoders info: "
                              "Cannot recompute odometry without this "
                              "information!",
                              (unsigned)i)
                              .c_str()));
            return;
          }

          // Create aux odo increment to recompute the global odo:
          CActionRobotMovement2D auxOdoIncr;
          auxOdoIncr.hasEncodersInfo = true;
          auxOdoIncr.encoderLeftTicks = odo->encoderLeftTicks;
          auxOdoIncr.encoderRightTicks = odo->encoderRightTicks;
          auxOdoIncr.computeFromEncoders(K_left, K_right, D);

          if (!auxLastAbsOdo_valid)
          {
            auxLastAbsOdo_valid = true;
            auxLastAbsOdo = odo->odometry;
            // and don't modify this odo val.
          }
          else
          {
            odo->odometry = auxLastAbsOdo + auxOdoIncr.rawOdometryIncrementReading;
            auxLastAbsOdo = odo->odometry;
          }
          M++;
        }
      }
    }

    wxMessageBox((format("%u entries modified!", (unsigned)M).c_str()));
  }
  WX_END_TRY
}

// Generate text file with 1D range measurements
void xRawLogViewerFrame::OnRangeFinder1DGenTextFile(wxCommandEvent&)
{
  WX_START_TRY

  wxString caption = wxT("Save as...");
  wxString wildcard = wxT("Text files (*.txt)|*.txt|All files (*.*)|*.*");
  wxString defaultDir((iniFile->read_string(iniFileSect, "LastDir", ".").c_str()));
  wxString defaultFilename = (loadedFileName + string("_1DrangeFinders.txt")).c_str();
  wxFileDialog dialog(
      this, caption, defaultDir, defaultFilename, wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);

  if (dialog.ShowModal() == wxID_OK)
  {
    wxString fileName = dialog.GetPath();
    string fil(fileName.mbc_str());

    int i, M = 0, n = (int)rawlog.size();
    FILE* f = os::fopen(fil.c_str(), "wt");
    if (!f) THROW_EXCEPTION("Cannot open output file for write.");

    for (i = 0; i < n; i++)
    {
      if (rawlog.getType(i) == CRawlog::etSensoryFrame)
      {
        CSensoryFrame::Ptr sf = rawlog.getAsObservations(i);
        for (size_t k = 0; k < sf->size(); k++)
        {
          if (sf->getObservationByIndex(k)->GetRuntimeClass() == CLASS_ID(CObservationRange))
          {
            auto obs = sf->getObservationByIndexAs<CObservationRange::Ptr>(k);

            vector<float> rowOfRangesByID(100, 0);
            for (auto it = obs->begin(); it != obs->end(); ++it)
            {
              ASSERT_(it->sensorID < rowOfRangesByID.size());
              rowOfRangesByID[it->sensorID] = it->sensedDistance;
            }

            ::fprintf(f, "%06u ", i);
            for (float q : rowOfRangesByID) ::fprintf(f, "%03.04f ", q);
            ::fprintf(f, "\n");
            M++;
          }
        }  // end for k
      }
      else if (rawlog.getType(i) == CRawlog::etObservation)
      {
        CObservation::Ptr o = rawlog.getAsObservation(i);

        if (IS_CLASS(*o, CObservationRange))
        {
          CObservationRange::Ptr obs = std::dynamic_pointer_cast<CObservationRange>(o);

          deque<CObservationRange::TMeasurement>::iterator it;
          vector<float> rowOfRangesByID(100, 0);
          for (it = obs->sensedData.begin(); it != obs->sensedData.end(); it++)
          {
            ASSERT_(it->sensorID < rowOfRangesByID.size());
            rowOfRangesByID[it->sensorID] = it->sensedDistance;
          }

          ::fprintf(f, "%06u ", i);
          for (float q : rowOfRangesByID) ::fprintf(f, "%03.04f ", q);
          ::fprintf(f, "\n");
          M++;
        }
      }
    }

    os::fclose(f);

    wxMessageBox((format("%u entries saved!", M).c_str()), _("Done"), wxOK, this);
  }

  WX_END_TRY
}

void xRawLogViewerFrame::OnMenuModifyICPActionsUncertainty(wxCommandEvent&)
{
  WX_START_TRY

  float std_xy = atof(string(wxGetTextFromUser(
                                 _("Standard deviation of x and y (meters):"),
                                 _("ICP-based odometry uncertainty:"), _("0.01"))
                                 .mb_str())
                          .c_str());
  float std_phi = DEG2RAD(atof(string(wxGetTextFromUser(
                                          _("Standard deviation of phi (degrees):"),
                                          _("ICP-based odometry uncertainty:"), _("0.2"))
                                          .mb_str())
                                   .c_str()));

  float std_xy2 = square(std_xy);
  float std_phi2 = square(std_phi);

  size_t M = 0;

  {
    wxBusyCursor waitCursor;

    for (size_t i = 0; i < rawlog.size(); i++)
    {
      if (rawlog.getType(i) == CRawlog::etActionCollection)
      {
        CActionCollection::Ptr acts = CActionCollection::Ptr(rawlog.getAsAction(i));
        for (unsigned int j = 0; j < acts->size(); j++)
        {
          CAction::Ptr act = acts->get(j);

          if (act->GetRuntimeClass()->derivedFrom(CLASS_ID(CActionRobotMovement2D)))
          {
            CActionRobotMovement2D::Ptr actMov =
                std::dynamic_pointer_cast<CActionRobotMovement2D>(act);

            if (actMov->estimationMethod == CActionRobotMovement2D::emScan2DMatching)
            {
              ASSERT_(actMov->poseChange->GetRuntimeClass() == CLASS_ID(CPosePDFGaussian));

              CPosePDFGaussian::Ptr aux =
                  std::dynamic_pointer_cast<CPosePDFGaussian>(actMov->poseChange.get_ptr());
              aux->cov.setZero();
              aux->cov(0, 0) = aux->cov(1, 1) = std_xy2;
              aux->cov(2, 2) = std_phi2;

              actMov->motionModelConfiguration.modelSelection = CActionRobotMovement2D::mmGaussian;
              actMov->rawOdometryIncrementReading = aux->mean;
              M++;
            }
          }
        }
      }
    }
  }

  wxMessageBox((format("%u entries modified!", (unsigned)M).c_str()));

  WX_END_TRY
}

void xRawLogViewerFrame::OnShowAnimateScans(wxCommandEvent&)
{
  if (rawlog.empty())
  {
    wxMessageBox(_("Please load a rawlog first!"), _("Rawlog is empty"), wxOK, this);
    return;
  }

  CScanAnimation scanAnimation(this);
  scanAnimation.Maximize();

  scanAnimation.ShowModal();
}

void xRawLogViewerFrame::OnMenuShowTips(wxCommandEvent&) { showNextTip(true); }

void xRawLogViewerFrame::OnbtnEditCommentsClick(wxCommandEvent&) {}
void xRawLogViewerFrame::OnMenuInsertComment(wxCommandEvent&) {}
// Asks for a sensor label:
std::vector<std::string> xRawLogViewerFrame::AskForObservationByLabelMultiple(
    const std::string& title)
{
  std::vector<std::string> labels;

  if (listOfSensorLabels.empty())
  {
    wxMessageBox(
        _("No sensors were found with proper sensor labels. Labels are "
          "required for this operation."));
    return std::vector<std::string>();
  }

  // List of labels:
  wxArrayString lstLabels;
  std::vector<std::string> lstLabelsStd;
  for (auto i = listOfSensorLabels.begin(); i != listOfSensorLabels.end(); ++i)
  {
    lstLabelsStd.push_back(i->first);
    lstLabels.Add(i->first.c_str());
  }

  wxArrayInt sels;

#if (wxMAJOR_VERSION >= 3) || ((wxMAJOR_VERSION == 2) && (wxMINOR_VERSION >= 9))
  wxGetSelectedChoices(sels, title.c_str(), _("Sensor Labels"), lstLabels, this);
#else
  wxGetMultipleChoices(sels, title.c_str(), _("Sensor Labels"), lstLabels, this);
#endif

  labels.resize(sels.Count());
  for (size_t i = 0; i < labels.size(); i++) labels[i] = lstLabelsStd[sels[i]];

  return labels;
}

// Asks for a sensor label:
std::string xRawLogViewerFrame::AskForObservationByLabel(const std::string& title)
{
  if (listOfSensorLabels.empty())
  {
    wxMessageBox(
        _("No sensors were found with proper sensor labels. Labels are "
          "required for this operation."));
    return string();
  }

  // List of labels:
  wxArrayString lstLabels;
  for (auto i = listOfSensorLabels.begin(); i != listOfSensorLabels.end(); ++i)
  {
    lstLabels.Add(i->first.c_str());
  }

  wxString ret = wxGetSingleChoice(title.c_str(), _("Sensor Labels"), lstLabels, this);
  if (ret.IsEmpty()) return string();

  return string(ret.mb_str());
}

// Changes the label of a sensor:
void xRawLogViewerFrame::OnMenuRenameSensor(wxCommandEvent&)
{
  WX_START_TRY

  const string the_label = AskForObservationByLabel("Choose the label of the sensor to change:");

  wxString new_label =
      wxGetTextFromUser(_("Enter the new sensor label"), _("New label:"), the_label.c_str(), this);
  if (new_label.IsEmpty()) return;

  const string the_new_label = string(new_label.mb_str());

  if (the_new_label == the_label) return;

  size_t i, n = rawlog.size();
  unsigned int nChanges = 0;

  for (i = 0; i < n; i++)
  {
    switch (rawlog.getType(i))
    {
      case CRawlog::etSensoryFrame:
      {
        CSensoryFrame::Ptr sf = rawlog.getAsObservations(i);
        CObservation::Ptr o;
        while ((o = sf->getObservationBySensorLabel(the_label, 0)))
        {
          o->sensorLabel = the_new_label;
          nChanges++;
        }
      }
      break;

      case CRawlog::etObservation:
      {
        CObservation::Ptr o = rawlog.getAsObservation(i);

        if (o->sensorLabel == the_label)
        {
          o->sensorLabel = the_new_label;
          nChanges++;
        }
      }
      break;

      default:
        break;
    }  // end switch type

  }  // end for

  wxMessageBox(wxString::Format(_("%u changes"), nChanges), _("Done"), wxOK, this);

  // Update the views:
  rebuildTreeView();

  WX_END_TRY
}

void xRawLogViewerFrame::OnMenuChangePosesBatch(wxCommandEvent&)
{
  CFormBatchSensorPose dialog(this);
  if (dialog.ShowModal())
  {
    WX_START_TRY

    wxBusyCursor busy;

    // Load the "ini-file" from the text control:
    CConfigFileMemory cfg(string(dialog.edText->GetValue().mb_str()));

    // make a list  "sensor_label -> sensor_pose" by parsing the
    // ini-file:
    using TSensor2PoseMap = std::map<std::string, mrpt::poses::CPose3D>;
    TSensor2PoseMap desiredSensorPoses;
    std::map<std::string, mrpt::obs::CObservationImage> desiredCamParams;

    std::vector<std::string> sections;
    cfg.getAllSections(sections);

    for (auto& section : sections)
    {
      if (section.empty()) continue;

      // Get sensor label:
      string label = cfg.read_string(section, "sensorLabel", "");
      if (label.empty()) continue;

      CPose3D the_pose(
          cfg.read_double(section, "pose_x", 0, true), cfg.read_double(section, "pose_y", 0, true),
          cfg.read_double(section, "pose_z", 0, true),
          DEG2RAD(cfg.read_double(section, "pose_yaw", 0)),
          DEG2RAD(cfg.read_double(section, "pose_pitch", 0)),
          DEG2RAD(cfg.read_double(section, "pose_roll", 0)));

      // insert:
      desiredSensorPoses[label] = the_pose;

      // Camera params?
      CVectorDouble calib, distort;
      cfg.read_vector(section, "calib_params", CVectorDouble(), calib);
      cfg.read_vector(section, "distort_params", CVectorDouble(), distort);

      if (calib.empty() || distort.empty()) continue;

      ASSERT_(calib.size() == 4);
      ASSERT_(distort.size() == 4);

      CMatrixDouble33& I = desiredCamParams[label].cameraParams.intrinsicParams;
      I.setZero(3, 3);
      I(2, 2) = 1;
      I(0, 0) = calib[0];
      I(1, 1) = calib[1];
      I(0, 2) = calib[2];
      I(1, 2) = calib[3];

      desiredCamParams[label].cameraParams.setDistortionParamsVector(distort);
    }  // end for sections

    if (desiredSensorPoses.empty())
    {
      wxMessageBox(_("No valid 'sensorLabel' entry was found in the text"), _("Error"), wxOK, this);
      return;
    }

    // now apply the changes:
    wxProgressDialog progDia(
        wxT("Modifying rawlog"), wxT("Processing..."),
        rawlog.size(),  // range
        this,           // parent
        wxPD_CAN_ABORT | wxPD_APP_MODAL | wxPD_SMOOTH | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME |
            wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME);

    wxTheApp->Yield();  // Let the app. process messages

    size_t changes = 0;
    int countLoop = 0;
    bool keepDoing = true;
    for (CRawlog::iterator it = rawlog.begin(); it != rawlog.end() && keepDoing; it++, countLoop++)
    {
      CObservation::Ptr obs;

      // Go thru all the obs. of a SF, or just the obs. for non-SF
      // rawlogs:
      size_t idx_in_sf = 0;
      bool isSingleObs;
      while (true)
      {
        isSingleObs = false;

        if (it.getType() == CRawlog::etObservation)
        {
          obs = std::dynamic_pointer_cast<CObservation>(*it);
          isSingleObs = true;
        }
        else if (it.getType() == CRawlog::etSensoryFrame)
        {
          CSensoryFrame::Ptr sf = std::dynamic_pointer_cast<CSensoryFrame>(*it);

          if (idx_in_sf >= sf->size()) break;
          obs = sf->getObservationByIndex(idx_in_sf++);
        }
        else
          break;

        if (obs)
        {
          // Check the sensor label:
          auto i = desiredSensorPoses.find(obs->sensorLabel);
          if (i != desiredSensorPoses.end())
          {
            obs->setSensorPose(i->second);
            changes++;
          }

          // Check for camera params:
          auto c = desiredCamParams.find(obs->sensorLabel);
          if (c != desiredCamParams.end())
          {
            if (!IS_CLASS(*obs, CObservationImage))
              THROW_EXCEPTION_FMT(
                  "Camera parameters found for non-image "
                  "observation class: %s",
                  obs->sensorLabel.c_str());

            CObservationImage::Ptr img = std::dynamic_pointer_cast<CObservationImage>(obs);
            img->cameraParams = c->second.cameraParams;
          }

          if (isSingleObs) break;
        }
        else
          break;
      }

      if (countLoop++ % 100 == 0)
      {
        if (!progDia.Update(
                countLoop,
                wxString::Format(wxT("Processing... (%i objects processed)"), countLoop)))
          keepDoing = false;
        wxTheApp->Yield();  // Let the app. process messages
      }
    }

    progDia.Update(rawlog.size());  // Close dialog.

    wxMessageBox(
        wxString::Format(
            _("%i entries modified for %i sensor labels."), (int)changes,
            (int)desiredSensorPoses.size()),
        _("Done"), wxOK, this);

    WX_END_TRY
  }
}

void doFilterInvalidRange(CObservation::Ptr& obs, size_t& invalidRanges)
{
  if (obs->GetRuntimeClass()->derivedFrom(CLASS_ID(CObservation2DRangeScan)))
  {
    CObservation2DRangeScan::Ptr obsScan = std::dynamic_pointer_cast<CObservation2DRangeScan>(obs);
    for (size_t k = 0; k < obsScan->getScanSize(); k++)
      if (obsScan->getScanRange(k) >= obsScan->maxRange)
      {
        obsScan->setScanRangeValidity(k, false);
        invalidRanges++;
      }
  }
}

void xRawLogViewerFrame::OnMenuMarkLaserScanInvalid(wxCommandEvent&)
{
  WX_START_TRY

  wxBusyCursor waitCursor;
  int nEntries = (int)rawlog.size();

  wxString auxStr;
  wxProgressDialog progDia(
      wxT("Progress"), wxT("Parsing rawlog..."),
      nEntries,  // range
      this,      // parent
      wxPD_CAN_ABORT | wxPD_APP_MODAL | wxPD_SMOOTH | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME |
          wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME);

  wxTheApp->Yield();  // Let the app. process messages

  size_t invalidRanges = 0;

  for (int countLoop = 0; countLoop < nEntries; countLoop++)
  {
    if (countLoop % 50 == 0)
    {
      auxStr.sprintf(wxT("Parsing rawlog... %u objects"), countLoop);
      if (!progDia.Update(countLoop, auxStr)) break;
      wxTheApp->Yield();  // Let the app. process messages
    }

    // Check type:
    switch (rawlog.getType(countLoop))
    {
      case CRawlog::etSensoryFrame:
      {
        // This is a SF:
        CSensoryFrame::Ptr SF = rawlog.getAsObservations(countLoop);

        for (size_t j = 0; j < SF->size(); j++)
        {
          CObservation::Ptr obs = SF->getObservationByIndex(j);
          doFilterInvalidRange(obs, invalidRanges);
        }  // end for each Observation in the SF
      }
      break;

      case CRawlog::etObservation:
      {
        // This is a SF:
        CObservation::Ptr obs = rawlog.getAsObservation(countLoop);
        doFilterInvalidRange(obs, invalidRanges);
      }
      break;

      default:
        break;
    };  // end switch

  }  // end while keep loading

  progDia.Update(nEntries);

  wxMessageBox(
      (format("Number of invalid ranges marked: %i", (int)invalidRanges).c_str()), _("Done"), wxOK,
      this);

  WX_END_TRY
}

void xRawLogViewerFrame::OnMenuChangeMaxRangeLaser(wxCommandEvent&)
{
  WX_START_TRY

  std::string lab = AskForObservationByLabel("Select the laser sensor");
  if (lab.empty()) return;

  wxString strMaxR = wxGetTextFromUser(
      _("Enter the new maximum range (in meters):"), _("Maximum range:"), _("81.0"));
  double maxR;
  strMaxR.ToCDouble(&maxR);

  wxBusyCursor waitCursor;
  int nEntries = (int)rawlog.size();

  wxString auxStr;
  wxProgressDialog progDia(
      wxT("Progress"), wxT("Parsing rawlog..."),
      nEntries,  // range
      this,      // parent
      wxPD_CAN_ABORT | wxPD_APP_MODAL | wxPD_SMOOTH | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME |
          wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME);

  wxTheApp->Yield();  // Let the app. process messages

  size_t N = 0;

  for (int countLoop = 0; countLoop < nEntries; countLoop++)
  {
    if (countLoop % 50 == 0)
    {
      auxStr.sprintf(wxT("Parsing rawlog... %u objects"), countLoop);
      if (!progDia.Update(countLoop, auxStr)) break;
      wxTheApp->Yield();  // Let the app. process messages
    }

    // Check type:
    switch (rawlog.getType(countLoop))
    {
      case CRawlog::etSensoryFrame:
      {
        // This is a SF:
        CSensoryFrame::Ptr SF = rawlog.getAsObservations(countLoop);

        for (size_t j = 0; j < SF->size(); j++)
        {
          CObservation::Ptr obs = SF->getObservationByIndex(j);
          if (obs->sensorLabel == lab && IS_CLASS(*obs, CObservation2DRangeScan))
          {
            CObservation2DRangeScan::Ptr o =
                std::dynamic_pointer_cast<CObservation2DRangeScan>(obs);
            o->maxRange = maxR;
            N++;
          }
        }  // end for each Observation in the SF
      }
      break;

      case CRawlog::etObservation:
      {
        // This is a SF:
        CObservation::Ptr obs = rawlog.getAsObservation(countLoop);
        if (obs->sensorLabel == lab && IS_CLASS(*obs, CObservation2DRangeScan))
        {
          CObservation2DRangeScan::Ptr o = std::dynamic_pointer_cast<CObservation2DRangeScan>(obs);
          o->maxRange = maxR;
          N++;
        }
      }
      break;

      default:
        break;
    };  // end switch

  }  // end while keep loading

  progDia.Update(nEntries);

  wxMessageBox((format("Number of changes: %i", (int)N).c_str()), _("Done"), wxOK, this);

  WX_END_TRY
}

void xRawLogViewerFrame::OnbtnEditCommentsClick1(wxCommandEvent&)
{
  CIniEditor dlg(this);

  string s;
  rawlog.getCommentText(s);
  dlg.edText->SetValue(s.c_str());

  if (dlg.ShowModal())
  {
    rawlog.setCommentText(string(dlg.edText->GetValue().mb_str()));
    rebuildTreeView();
  }
}

void xRawLogViewerFrame::OnMenuRevert(wxCommandEvent&)
{
  if (mrpt::system::fileExists(loadedFileName)) loadRawlogFile(loadedFileName);
}

void xRawLogViewerFrame::OnMenuBatchLaserExclusionZones(wxCommandEvent&)
{
  CFormBatchSensorPose dialog(this);

  if (dialog.ShowModal())
  {
    WX_START_TRY

    wxBusyCursor busy;

    // Load the "ini-file" from the text control:
    CConfigFileMemory cfg(string(dialog.edText->GetValue().mb_str()));

    // make a list  "sensor_label -> list of exclusion polygons" by
    // parsing the ini-file:
    using TPolygonList = map<string, vector<CPolygon>>;
    TPolygonList lstExclusions;

    std::vector<std::string> sections;
    cfg.getAllSections(sections);

    unsigned int nExclZones = 0;

    for (auto& section : sections)
    {
      if (section.empty()) continue;

      // Get sensor label:
      string label = cfg.read_string(section, "sensorLabel", "");
      if (label.empty()) continue;

      unsigned int N = 1;

      for (;;)
      {
        vector<double> x, y;
        cfg.read_vector(section, format("exclusionZone%u_x", N), vector<double>(0), x);
        cfg.read_vector(section, format("exclusionZone%u_y", N++), vector<double>(0), y);

        if (!x.empty() && !y.empty())
        {
          ASSERT_(x.size() == y.size());
          mrpt::math::CPolygon p;
          p.setAllVertices(x, y);
          lstExclusions[label].push_back(p);
          nExclZones++;
        }
        else
          break;
      }

    }  // end for sections

    if (lstExclusions.empty())
    {
      wxMessageBox(_("No valid exclusion zones found in the text"), _("Error"), wxOK, this);
      return;
    }

    // now apply the changes:
    wxProgressDialog progDia(
        wxT("Modifying rawlog"), wxT("Processing..."),
        rawlog.size(),  // range
        this,           // parent
        wxPD_CAN_ABORT | wxPD_APP_MODAL | wxPD_SMOOTH | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME |
            wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME);

    wxTheApp->Yield();  // Let the app. process messages

    size_t changes = 0;
    int countLoop = 0;
    bool keepDoing = true;
    for (CRawlog::iterator it = rawlog.begin(); it != rawlog.end() && keepDoing; it++, countLoop++)
    {
      CObservation2DRangeScan::Ptr obs;

      // Go thru all the obs. of a SF, or just the obs. for non-SF
      // rawlogs:
      size_t idx_in_sf = 0;
      bool isSingleObs;
      while (true)
      {
        isSingleObs = false;

        if (it.getType() == CRawlog::etObservation)
        {
          if (IS_CLASS(**it, CObservation2DRangeScan))
          {
            obs = std::dynamic_pointer_cast<CObservation2DRangeScan>(*it);
            isSingleObs = true;
          }
          else
            break;  // not a laser scan
        }
        else if (it.getType() == CRawlog::etSensoryFrame)
        {
          CSensoryFrame::Ptr sf = std::dynamic_pointer_cast<CSensoryFrame>(*it);

          obs = sf->getObservationByClass<CObservation2DRangeScan>(idx_in_sf++);
          if (!obs)
          {
            idx_in_sf = 0;
            break;
          }
        }
        else
          break;

        if (obs)
        {
          // Check the sensor label:
          auto i = lstExclusions.find(obs->sensorLabel);
          if (i != lstExclusions.end())
          {
            obs->filterByExclusionAreas(i->second);
            changes++;
          }
          if (isSingleObs) break;
        }
        else
          break;
      }

      if (countLoop++ % 100 == 0)
      {
        if (!progDia.Update(
                countLoop,
                wxString::Format(wxT("Processing... (%i objects processed)"), countLoop)))
          keepDoing = false;
        wxTheApp->Yield();  // Let the app. process messages
      }
    }

    progDia.Update(rawlog.size());  // Close dialog.

    wxMessageBox(
        wxString::Format(
            _("%i entries modified for %i sensor labels and %u "
              "exclusion "
              "areas."),
            (int)changes, (int)lstExclusions.size(), nExclZones),
        _("Done"), wxOK, this);

    WX_END_TRY
  }
}

void xRawLogViewerFrame::OnComboImageDirsChange(wxCommandEvent&)
{
  wxString dir = toolbarcomboImages->GetStringSelection();

  string dirc = string(dir.mb_str());

  if (mrpt::system::fileExists(dirc))
  {
    CImage::setImagesPathBase(dirc);
    // wxMessageBox( _("The current directory for external images has
    // been set to:\n")+dir , _("External images"));

    m_treeView->SetSelectedItem(m_treeView->GetSelectedItem(), true);
  }
  else
  {
    wxMessageBox(_("The directory:\n") + dir + _("does not exist."), _("External images"));
  }
}

void xRawLogViewerFrame::OnLaserFilterAngles(wxCommandEvent&)
{
  CFormBatchSensorPose dialog(this);

  if (dialog.ShowModal())
  {
    WX_START_TRY

    wxBusyCursor busy;

    // Load the "ini-file" from the text control:
    CConfigFileMemory cfg(string(dialog.edText->GetValue().mb_str()));

    // make a list  "sensor_label -> list of exclusion polygons" by
    // parsing the ini-file:
    using TExclAreasList = map<string, vector<pair<double, double>>>;
    TExclAreasList lstExclusions;

    std::vector<std::string> sections;
    cfg.getAllSections(sections);

    unsigned int nExclZones = 0;

    for (auto& section : sections)
    {
      if (section.empty()) continue;

      // Get sensor label:
      string label = cfg.read_string(section, "sensorLabel", "");
      if (label.empty()) continue;

      // Load forbiden angles;
      unsigned int N = 1;
      for (;;)
      {
        const double ini =
            DEG2RAD(cfg.read_double(section, format("exclusionAngles%u_ini", N), -1000));
        const double end =
            DEG2RAD(cfg.read_double(section, format("exclusionAngles%u_end", N++), -1000));

        if (ini > -M_PI && end > -M_PI)
        {
          lstExclusions[label].push_back(make_pair(ini, end));
          nExclZones++;
        }
        else
          break;
      }
    }  // end for sections

    if (lstExclusions.empty())
    {
      wxMessageBox(_("No valid exclusion zones found in the text"), _("Error"), wxOK, this);
      return;
    }

    // now apply the changes:
    wxProgressDialog progDia(
        wxT("Modifying rawlog"), wxT("Processing..."),
        rawlog.size(),  // range
        this,           // parent
        wxPD_CAN_ABORT | wxPD_APP_MODAL | wxPD_SMOOTH | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME |
            wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME);

    wxTheApp->Yield();  // Let the app. process messages

    size_t changes = 0;
    int countLoop = 0;
    bool keepDoing = true;
    for (CRawlog::iterator it = rawlog.begin(); it != rawlog.end() && keepDoing; it++, countLoop++)
    {
      CObservation2DRangeScan::Ptr obs;

      // Go thru all the obs. of a SF, or just the obs. for non-SF
      // rawlogs:
      size_t idx_in_sf = 0;
      bool isSingleObs;
      while (true)
      {
        isSingleObs = false;

        if (it.getType() == CRawlog::etObservation)
        {
          if (IS_CLASS(**it, CObservation2DRangeScan))
          {
            obs = std::dynamic_pointer_cast<CObservation2DRangeScan>(*it);
            isSingleObs = true;
          }
          else
            break;  // not a laser scan
        }
        else if (it.getType() == CRawlog::etSensoryFrame)
        {
          CSensoryFrame::Ptr sf = std::dynamic_pointer_cast<CSensoryFrame>(*it);

          obs = sf->getObservationByClass<CObservation2DRangeScan>(idx_in_sf++);
          if (!obs)
          {
            idx_in_sf = 0;
            break;
          }
        }
        else
          break;

        if (obs)
        {
          // Check the sensor label:
          auto i = lstExclusions.find(obs->sensorLabel);
          if (i != lstExclusions.end())
          {
            obs->filterByExclusionAngles(i->second);
            changes++;
          }
          if (isSingleObs) break;
        }
        else
          break;
      }

      if (countLoop++ % 100 == 0)
      {
        if (!progDia.Update(
                countLoop,
                wxString::Format(wxT("Processing... (%i objects processed)"), countLoop)))
          keepDoing = false;
        wxTheApp->Yield();  // Let the app. process messages
      }
    }

    progDia.Update(rawlog.size());  // Close dialog.

    wxMessageBox(
        wxString::Format(
            _("%i entries modified for %i sensor labels and %u "
              "exclusion "
              "areas."),
            (int)changes, (int)lstExclusions.size(), nExclZones),
        _("Done"), wxOK, this);

    WX_END_TRY
  }
}

void xRawLogViewerFrame::OnMenuRangeBearFilterIDs(wxCommandEvent&)
{
  WX_START_TRY

  wxString strID =
      wxGetTextFromUser(_("Enter the landmark ID to remove:"), _("Landmark ID"), _("0"));
  long ID;
  strID.ToLong(&ID);

  wxBusyCursor waitCursor;
  int nEntries = (int)rawlog.size();

  wxString auxStr;
  wxProgressDialog progDia(
      wxT("Progress"), wxT("Parsing rawlog..."),
      nEntries,  // range
      this,      // parent
      wxPD_CAN_ABORT | wxPD_APP_MODAL | wxPD_SMOOTH | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME |
          wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME);

  wxTheApp->Yield();  // Let the app. process messages

  size_t N = 0;

  for (int countLoop = 0; countLoop < nEntries; countLoop++)
  {
    if (countLoop % 50 == 0)
    {
      auxStr.sprintf(wxT("Parsing rawlog... %u objects"), countLoop);
      if (!progDia.Update(countLoop, auxStr)) break;
      wxTheApp->Yield();  // Let the app. process messages
    }

    // Check type:
    switch (rawlog.getType(countLoop))
    {
      case CRawlog::etSensoryFrame:
      {
        // This is a SF:
        CSensoryFrame::Ptr SF = rawlog.getAsObservations(countLoop);

        for (size_t j = 0; j < SF->size(); j++)
        {
          CObservation::Ptr obs = SF->getObservationByIndex(j);
          if (IS_CLASS(*obs, CObservationBearingRange))
          {
            CObservationBearingRange::Ptr o =
                std::dynamic_pointer_cast<CObservationBearingRange>(obs);
            for (size_t k = 0; k < o->sensedData.size(); k++)
            {
              if (ID == o->sensedData[k].landmarkID)
              {
                o->sensedData.erase(o->sensedData.begin() + k);
                N++;
              }
            }
          }
        }  // end for each Observation in the SF
      }
      break;

      case CRawlog::etObservation:
      {
        // This is a SF:
        CObservation::Ptr obs = rawlog.getAsObservation(countLoop);
        if (IS_CLASS(*obs, CObservationBearingRange))
        {
          CObservationBearingRange::Ptr o =
              std::dynamic_pointer_cast<CObservationBearingRange>(obs);
          for (size_t k = 0; k < o->sensedData.size(); k++)
          {
            if (ID == o->sensedData[k].landmarkID)
            {
              o->sensedData.erase(o->sensedData.begin() + k);
              N++;
            }
          }
        }
      }
      break;

      default:
        break;
    };  // end switch

  }  // end while keep loading

  progDia.Update(nEntries);

  wxMessageBox((format("Number of changes: %i", (int)N).c_str()), _("Done"), wxOK, this);

  WX_END_TRY
}

void xRawLogViewerFrame::OnMenuRegenerateTimestampBySF(wxCommandEvent&)
{
  WX_START_TRY

  wxBusyCursor waitCursor;
  const size_t nEntries = rawlog.size();

  wxString auxStr;
  wxProgressDialog progDia(
      wxT("Progress"), wxT("Parsing rawlog..."),
      nEntries,  // range
      this,      // parent
      wxPD_CAN_ABORT | wxPD_APP_MODAL | wxPD_SMOOTH | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME |
          wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME);

  wxTheApp->Yield();  // Let the app. process messages

  size_t N = 0;

  for (size_t countLoop = 0; countLoop < nEntries; countLoop++)
  {
    if (countLoop % 50 == 0)
    {
      auxStr.sprintf(wxT("Parsing rawlog... %u objects"), countLoop);
      if (!progDia.Update(countLoop, auxStr)) break;
      wxTheApp->Yield();  // Let the app. process messages
    }

    // Check type:
    switch (rawlog.getType(countLoop))
    {
      case CRawlog::etSensoryFrame:
      {
        // This is a SF:
        CSensoryFrame::Ptr SF = rawlog.getAsObservations(countLoop);

        // Take a timestamp for this SF:
        TTimeStamp tim = INVALID_TIMESTAMP;
        for (size_t j = 0; j < SF->size(); j++)
        {
          CObservation::Ptr obs = SF->getObservationByIndex(j);
          if (obs->timestamp != INVALID_TIMESTAMP)
          {
            tim = obs->timestamp;
            break;
          }
        }  // end for each Observation in the SF
        if (tim != INVALID_TIMESTAMP)
        {
          for (size_t j = 0; j < SF->size(); j++)
          {
            CObservation::Ptr obs = SF->getObservationByIndex(j);
            if (obs->timestamp == INVALID_TIMESTAMP)
            {
              obs->timestamp = tim;
              N++;
            }
          }  // end for each Observation in the SF
        }
      }
      break;

      default:
        break;
    };  // end switch

  }  // end while keep loading

  progDia.Update(nEntries);

  wxMessageBox((format("Number of changes: %i", (int)N).c_str()), _("Done"), wxOK, this);

  WX_END_TRY
}
void xRawLogViewerFrame::OnmnuCreateAVISelected(wxCommandEvent&)
{
  WX_START_TRY

  std::string senLabel = AskForObservationByLabel("Select the camera:");
  if (senLabel.empty()) return;

  wxString caption = wxT("Save AVI video...");
  wxString wildcard = wxT("AVI files (*.avi)|*.avi|All files (*.*)|*.*");
  wxString defaultDir((iniFile->read_string(iniFileSect, "LastDir", ".").c_str()));
  wxString defaultFilename = ((loadedFileName + format("_%s.avi", senLabel.c_str())).c_str());
  wxFileDialog dialog(
      this, caption, defaultDir, defaultFilename, wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);

  if (dialog.ShowModal() != wxID_OK) return;

  const string outAviFilename = string(dialog.GetPath().mb_str());

  float FPS = 20.0;  // For the AVI

  wxBusyCursor waitCursor;
  int nEntries = (int)rawlog.size();

  wxString auxStr;
  wxProgressDialog progDia(
      wxT("Progress"), wxT("Parsing rawlog..."),
      nEntries,  // range
      this,      // parent
      wxPD_CAN_ABORT | wxPD_APP_MODAL | wxPD_SMOOTH | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME |
          wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME);

  wxTheApp->Yield();  // Let the app. process messages

  //  possible <channel_desc>: left, right, disparity (more in the
  //  future?)
  std::vector<std::string> outVideosIdx;
  mrpt::vision::CVideoFileWriter outVideos[20];

  int nFrames = 0;
  string errorMsg;

  for (int countLoop = 0; countLoop < nEntries; countLoop++)
  {
    if (countLoop % 5 == 0)
    {
      auxStr.sprintf(wxT("Processing rawlog... %u image frames"), countLoop);
      if (!progDia.Update(countLoop, auxStr)) break;
      progDia.Fit();
      wxTheApp->Yield();  // Let the app. process messages
    }

    try
    {
      std::set<TImageToSaveData> imgsForVideo;

      if (rawlog.getType(countLoop) == CRawlog::etSensoryFrame)
      {
        CSensoryFrame::Ptr SF = rawlog.getAsObservations(countLoop);

        CObservationImage::Ptr obsImg = SF->getObservationByClass<CObservationImage>();
        if (obsImg)
        {
          imgsForVideo.insert(TImageToSaveData(&obsImg->image, "IMAGE"));
        }
        else
        {
          CObservationStereoImages::Ptr obsStereoImg =
              SF->getObservationByClass<CObservationStereoImages>();
          if (obsStereoImg)
          {
            imgsForVideo.insert(TImageToSaveData(&obsStereoImg->imageLeft, "LEFT"));
            if (obsStereoImg->hasImageRight)
              imgsForVideo.insert(TImageToSaveData(&obsStereoImg->imageRight, "RIGHT"));
            if (obsStereoImg->hasImageDisparity)
              imgsForVideo.insert(TImageToSaveData(&obsStereoImg->imageDisparity, "DISP"));
          }
          else
          {
            CObservation3DRangeScan::Ptr obs3D =
                SF->getObservationByClass<CObservation3DRangeScan>();
            if (obs3D)
            {
              imgsForVideo.insert(TImageToSaveData(&obs3D->intensityImage, "INTENSITY"));
            }
          }
        }
      }  // end for each entry
      else if (rawlog.getType(countLoop) == CRawlog::etObservation)
      {
        CObservation::Ptr o = rawlog.getAsObservation(countLoop);
        if (IS_CLASS(*o, CObservationImage))
        {
          CObservationImage::Ptr obsImg = std::dynamic_pointer_cast<CObservationImage>(o);
          imgsForVideo.insert(TImageToSaveData(&obsImg->image, "IMAGE"));
        }
        else if (IS_CLASS(*o, CObservationStereoImages))
        {
          CObservationStereoImages::Ptr obsStereoImg =
              std::dynamic_pointer_cast<CObservationStereoImages>(o);
          imgsForVideo.insert(TImageToSaveData(&obsStereoImg->imageLeft, "LEFT"));
          if (obsStereoImg->hasImageRight)
            imgsForVideo.insert(TImageToSaveData(&obsStereoImg->imageRight, "RIGHT"));
          if (obsStereoImg->hasImageDisparity)
            imgsForVideo.insert(TImageToSaveData(&obsStereoImg->imageDisparity, "DISP"));
        }
        else if (IS_CLASS(*o, CObservation3DRangeScan))
        {
          CObservation3DRangeScan::Ptr obs3D =
              std::dynamic_pointer_cast<CObservation3DRangeScan>(o);
          imgsForVideo.insert(TImageToSaveData(&obs3D->intensityImage, "INTENSITY"));
        }
      }  // end for each entry

      // If we have images in "imgsForVideo", save them to their video
      // (AVI) files,
      //  and create them upon first usage:
      for (const auto& d : imgsForVideo)
      {
        size_t idx = mrpt::containers::find_in_vector(d.channel_desc, outVideosIdx);
        if (string::npos == idx)  // new?
        {
          idx = outVideosIdx.size();
          outVideosIdx.push_back(d.channel_desc);
        }

        // The video writter for this channel:
        mrpt::vision::CVideoFileWriter& vid = outVideos[idx];

        if (!vid.isOpen())  // Open file upon first usage:
        {
          const string filname = mrpt::system::extractFileDirectory(outAviFilename) + string("/") +
                                 mrpt::system::extractFileName(outAviFilename) + string("_") +
                                 d.channel_desc + string(".avi");

          if (!vid.open(
                  filname, FPS, TImageSize(d.img->getWidth(), d.img->getHeight()),
#ifdef MRPT_OS_WINDOWS
                  ""
#else
                  "XVID"
#endif
                  ))
            throw std::runtime_error("Error creating the AVI video file...");
        }
        // and save video frame:
        CImage imgAux;
        d.img->colorImage(imgAux);
        vid << imgAux;
        nFrames++;
        d.img->unload();
      }  // end for each image
    }
    catch (exception& e)
    {
      errorMsg = mrpt::exception_to_str(e);
      break;
    }
    catch (...)
    {
      break;
    }
  }  // end while keep loading

  progDia.Update(nEntries);

  if (!errorMsg.empty()) wxMessageBox(errorMsg.c_str(), _("Error"));

  wxMessageBox((format("Saved %u images.", nFrames).c_str()), _("Done"), wxOK, this);

  WX_END_TRY
}

void xRawLogViewerFrame::OnMenuRegenerateOdometryTimes(wxCommandEvent&)
{
  WX_START_TRY

  wxBusyCursor waitCursor;
  int nEntries = (int)rawlog.size();

  wxString auxStr;
  wxProgressDialog progDia(
      wxT("Progress"), wxT("Parsing rawlog..."),
      nEntries,  // range
      this,      // parent
      wxPD_CAN_ABORT | wxPD_APP_MODAL | wxPD_SMOOTH | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME |
          wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME);

  wxTheApp->Yield();  // Let the app. process messages

  int nChanges = 0;
  string errorMsg;

  mrpt::system::TTimeStamp lastObsTime = INVALID_TIMESTAMP;
  CActionRobotMovement2D::Ptr lastOdo;

  for (int countLoop = 0; countLoop < nEntries; countLoop++)
  {
    if (countLoop % 20 == 0)
    {
      auxStr.sprintf(wxT("Parsing rawlog... %u objects"), countLoop);
      if (!progDia.Update(countLoop, auxStr)) break;
      wxTheApp->Yield();  // Let the app. process messages
    }

    try
    {
      if (rawlog.getType(countLoop) == CRawlog::etActionCollection)
      {
        CActionCollection::Ptr acts = rawlog.getAsAction(countLoop);
        for (size_t k = 0; k < acts->size(); k++)
        {
          CAction::Ptr act = acts->get(k);
          if (act->GetRuntimeClass() == CLASS_ID(CActionRobotMovement2D))
            lastOdo = std::dynamic_pointer_cast<CActionRobotMovement2D>(act);
        }
      }
      else if (rawlog.getType(countLoop) == CRawlog::etSensoryFrame)
      {
        CSensoryFrame::Ptr sf = rawlog.getAsObservations(countLoop);
        if (sf->size() > 0)
        {
          TTimeStamp thisObsTime = (*sf->begin())->timestamp;
          if (thisObsTime != INVALID_TIMESTAMP)
          {
            if (lastOdo && lastObsTime != INVALID_TIMESTAMP)
            {  // Do average:
              lastOdo->timestamp = mrpt::Clock::time_point(mrpt::Clock::duration(
                  (lastObsTime.time_since_epoch().count() >> 1) +
                  (thisObsTime.time_since_epoch().count() >> 1)));
              lastOdo.reset();
              nChanges++;
            }
            lastObsTime = thisObsTime;
          }
        }
      }
    }
    catch (exception& e)
    {
      errorMsg = mrpt::exception_to_str(e);
      break;
    }
    catch (...)
    {
      break;
    }
  }  // end while keep loading

  progDia.Update(nEntries);

  wxMessageBox((format("%u entries have been modified", nChanges).c_str()), _("Done"), wxOK, this);

  WX_END_TRY
}

// Recover 3D camera params from range data:
void xRawLogViewerFrame::OnMenuItem3DObsRecoverParams(wxCommandEvent&)
{
  WX_START_TRY

  wxBusyCursor waitCursor;
  int nEntries = (int)rawlog.size();

  wxString auxStr;
  wxProgressDialog progDia(
      wxT("Progress"), wxT("Parsing rawlog..."),
      nEntries,  // range
      this,      // parent
      wxPD_CAN_ABORT | wxPD_APP_MODAL | wxPD_SMOOTH | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME |
          wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME);

  wxTheApp->Yield();  // Let the app. process messages

  int nChanges = 0;
  string errorMsg;

  bool firstObs = true;
  TCamera optimal_params;

  for (int countLoop = 0; countLoop < nEntries; countLoop++)
  {
    if (countLoop % 20 == 0)
    {
      auxStr.sprintf(wxT("Parsing rawlog... %u objects"), countLoop);
      if (!progDia.Update(countLoop, auxStr)) break;
      wxTheApp->Yield();  // Let the app. process messages
    }

    try
    {
      if (rawlog.getType(countLoop) == CRawlog::etObservation)
      {
        CObservation::Ptr obs = rawlog.getAsObservation(countLoop);
        if (IS_CLASS(*obs, CObservation3DRangeScan))
        {
          CObservation3DRangeScan::Ptr o = std::dynamic_pointer_cast<CObservation3DRangeScan>(obs);

          if (firstObs)
          {
            const double avrErr =
                CObservation3DRangeScan::recoverCameraCalibrationParameters(*o, optimal_params);

            if (wxNO == wxMessageBox(
                            (format(
                                 "Calibration with first "
                                 "observation:\nAverage reprojection "
                                 "error=%.04fpx.\n Accept and apply to "
                                 "ALL 3D observations?",
                                 avrErr)
                                 .c_str()),
                            _("Warning"), wxYES_NO | wxICON_EXCLAMATION))
              break;

            firstObs = false;
          }

          // For the rest (including the first obs):
          o->cameraParams = optimal_params;
          nChanges++;
        }
      }
    }
    catch (exception& e)
    {
      errorMsg = mrpt::exception_to_str(e);
      break;
    }
    catch (...)
    {
      break;
    }
  }  // end while keep loading

  progDia.Update(nEntries);

  wxMessageBox((format("%u entries have been modified", nChanges).c_str()), _("Done"), wxOK, this);

  WX_END_TRY
}

size_t TInfoPerSensorLabel::getOccurences() const { return timOccurs.size(); }
void TInfoPerSensorLabel::addOcurrence(Clock::time_point obsTim)
{
  double ellapsed_tim = .0;
  if (!timOccurs.empty()) ellapsed_tim = mrpt::system::timeDifference(timOccurs.back(), obsTim);

  timOccurs.push_back(obsTim);

  if (ellapsed_tim > max_ellapsed_tim_between_obs) max_ellapsed_tim_between_obs = ellapsed_tim;
}

void xRawLogViewerFrame::OnMenuRenameSingleObs(wxCommandEvent&)
{
  WX_START_TRY

  if (!curSelectedObject) return;

  if (!curSelectedObject->GetRuntimeClass()->derivedFrom(CLASS_ID(CObservation))) return;

  CObservation::Ptr obj = std::dynamic_pointer_cast<CObservation>(curSelectedObject);

  const wxString new_label = wxGetTextFromUser(
      _("Enter the new sensor label for selected object"), _("New label:"),
      obj->sensorLabel.c_str(), this);
  if (new_label.IsEmpty()) return;

  const string the_new_label = string(new_label.mb_str());

  obj->sensorLabel = the_new_label;

  // Update the views:
  rebuildTreeView();

  WX_END_TRY
}

void xRawLogViewerFrame::On3DObsPagesChange(wxBookCtrlEvent& event)
{
  if (event.GetSelection() == 0)
  {
    // Re-generate 3D points (to reflect changes in viz options):
    SelectObjectInTreeView(curSelectedObject);

    m_gl3DRangeScan->Refresh();
    // DONT!: wxTheApp->Yield();
  }
}

// Recalculate bottom-time line sizes, etc.
void xRawLogViewerFrame::OnSize(wxSizeEvent& e)
{
  OnSizeOrMaximize();
  e.Skip();
}

void xRawLogViewerFrame::OnMaximize(wxMaximizeEvent& e)
{
  OnSizeOrMaximize();
  e.Skip();
}
void xRawLogViewerFrame::OnSizeOrMaximize()
{
  static double last_tim = 0;
  const double t = mrpt::Clock::nowDouble();
  if (t - last_tim > 0.1)
  {
    last_tim = t;
    rebuildBottomTimeLine();
  }
}
