From 1ead385d8da597b7a54f45894fc0b0040cadd40e Mon Sep 17 00:00:00 2001 From: Michal Pelka Date: Fri, 28 Aug 2026 18:21:30 +0200 Subject: [PATCH 1/2] Fixes to multi livox calib Signed-off-by: Michal Pelka --- .../livox_mid_360_intrinsic_calibration.cpp | 73 +-- .../mandeye_mission_recorder_calibration.cpp | 429 ++++++++++++++---- 2 files changed, 360 insertions(+), 142 deletions(-) diff --git a/apps/livox_mid_360_intrinsic_calibration/livox_mid_360_intrinsic_calibration.cpp b/apps/livox_mid_360_intrinsic_calibration/livox_mid_360_intrinsic_calibration.cpp index c4bb4b09..00f5e39a 100644 --- a/apps/livox_mid_360_intrinsic_calibration/livox_mid_360_intrinsic_calibration.cpp +++ b/apps/livox_mid_360_intrinsic_calibration/livox_mid_360_intrinsic_calibration.cpp @@ -521,21 +521,12 @@ void project_gui() { if (ImGui::Button("Load 'LiDAR serial number to index' file (lidar****.sn) (step 1)")) { - static std::shared_ptr open_file; - std::vector input_file_names; - ImGui::PushItemFlag(ImGuiItemFlags_Disabled, (bool)open_file); - const auto t = [&]() - { - auto sel = pfd::open_file("Calibration files", "C:\\", sn_filter, true).result(); - for (int i = 0; i < sel.size(); i++) - { - input_file_names.push_back(sel[i]); - } - }; - std::thread t1(t); - t1.join(); + std::vector input_file_names = mandeye::fd::OpenFileDialog("Calibration files", mandeye::fd::sn_filter, true); - idToSn = MLvxCalib::GetIdToSnMapping(input_file_names[0]); + if (input_file_names.size() > 0) + { + idToSn = MLvxCalib::GetIdToSnMapping(input_file_names[0]); + } } } @@ -543,19 +534,7 @@ void project_gui() { if (ImGui::Button("Load calibration (*.json) (step 2)")) { - static std::shared_ptr open_file; - std::vector input_file_names; - ImGui::PushItemFlag(ImGuiItemFlags_Disabled, (bool)open_file); - const auto t = [&]() - { - auto sel = pfd::open_file("Calibration files", "C:\\", json_filter, true).result(); - for (int i = 0; i < sel.size(); i++) - { - input_file_names.push_back(sel[i]); - } - }; - std::thread t1(t); - t1.join(); + std::vector input_file_names = mandeye::fd::OpenFileDialog("Calibration files", mandeye::fd::json_filter, true); if (input_file_names.size() > 0) { @@ -583,17 +562,8 @@ void project_gui() ImGui::SameLine(); if (ImGui::Button("Save default calibration (optional before step 2)")) { - std::shared_ptr save_file; - std::string output_file_name = ""; - ImGui::PushItemFlag(ImGuiItemFlags_Disabled, (bool)save_file); - const auto t = [&]() - { - auto sel = pfd::save_file("Save *.json file", "", json_filter).result(); - output_file_name = sel; - std::cout << "Calibration file to save: '" << output_file_name << "'" << std::endl; - }; - std::thread t1(t); - t1.join(); + auto output_file_name = mandeye::fd::SaveFileDialog("Save *.json file", mandeye::fd::json_filter, ".json"); + std::cout << "Calibration file to save: '" << output_file_name << "'" << std::endl; if (output_file_name.size() > 0) { @@ -638,19 +608,7 @@ void project_gui() { if (ImGui::Button("Load pointcloud (lidar****.laz) (step 3)")) { - static std::shared_ptr open_file; - std::vector input_file_names; - ImGui::PushItemFlag(ImGuiItemFlags_Disabled, (bool)open_file); - const auto t = [&]() - { - auto sel = pfd::open_file("Point cloud files", "C:\\", LAS_LAZ_filter, true).result(); - for (int i = 0; i < sel.size(); i++) - { - input_file_names.push_back(sel[i]); - } - }; - std::thread t1(t); - t1.join(); + std::vector input_file_names = mandeye::fd::OpenFileDialog("Point cloud files", mandeye::fd::LAS_LAZ_filter, true); if (input_file_names.size() > 0) { @@ -819,17 +777,8 @@ void project_gui() if (ImGui::Button("Save result calibration as 'calibration.json' (step 5)")) { - std::shared_ptr save_file; - std::string output_file_name = ""; - ImGui::PushItemFlag(ImGuiItemFlags_Disabled, (bool)save_file); - const auto t = [&]() - { - auto sel = pfd::save_file("Save las or laz file", "C:\\", json_filter).result(); - output_file_name = sel; - std::cout << "las or laz file to save: '" << output_file_name << "'" << std::endl; - }; - std::thread t1(t); - t1.join(); + auto output_file_name = mandeye::fd::SaveFileDialog("Save calibration json file", mandeye::fd::json_filter, ".json"); + std::cout << "calibration json file to save: '" << output_file_name << "'" << std::endl; if (output_file_name.size() > 0) { diff --git a/apps/mandeye_mission_recorder_calibration/mandeye_mission_recorder_calibration.cpp b/apps/mandeye_mission_recorder_calibration/mandeye_mission_recorder_calibration.cpp index f9307022..a8c07bab 100644 --- a/apps/mandeye_mission_recorder_calibration/mandeye_mission_recorder_calibration.cpp +++ b/apps/mandeye_mission_recorder_calibration/mandeye_mission_recorder_calibration.cpp @@ -1,12 +1,11 @@ #include +#include #include #include #include #include -#include - #include #include @@ -16,6 +15,7 @@ #include "../lidar_odometry_step_1/lidar_odometry_utils.h" #include +#include #include @@ -76,9 +76,13 @@ std::vector point_cloud; std::unordered_map idToSn; std::unordered_map calibrations; +Eigen::Affine3d imu_calibration = Eigen::Affine3d::Identity(); std::string calibration_file_name; +// sentinel value for chosen_lidar meaning "the IMU is the manual-calibration target", not one of the LiDARs +constexpr int kImuCalibrationTarget = 2; + int chosen_lidar = -1; int chosen_imu = -1; @@ -221,6 +225,108 @@ void load_pc( laszip_close_reader(laszip_reader); } +Eigen::Affine3d LoadImuCalibrationFromFile(const std::string& filename) +{ + std::ifstream file(filename); + if (!file) + return Eigen::Affine3d::Identity(); + + nlohmann::json jsonData; + try + { + jsonData = nlohmann::json::parse(file); + } catch (const nlohmann::json::exception& e) + { + spdlog::error("JSON parsing error in file '{}': {}", filename, e.what()); + return Eigen::Affine3d::Identity(); + } + + if (!jsonData.contains("imuCalibration")) + return Eigen::Affine3d::Identity(); + + const auto& entry = jsonData["imuCalibration"]; + if (!entry.contains("data")) + return Eigen::Affine3d::Identity(); + + Eigen::Matrix4d value = Eigen::Matrix4d::Identity(); + const auto& raw = entry["data"]; + for (int i = 0; i < 4; ++i) + for (int j = 0; j < 4; ++j) + value(i, j) = raw[i * 4 + j]; + + if (entry.contains("order")) + { + std::string order = entry["order"].get(); + std::transform(order.begin(), order.end(), order.begin(), ::toupper); + if (order == "COLUMN") + value.transposeInPlace(); + } + + if (entry.contains("inverted")) + { + std::string inverted = entry["inverted"].get(); + std::transform(inverted.begin(), inverted.end(), inverted.begin(), ::toupper); + if (inverted == "TRUE") + value = value.inverse().eval(); + } + + return Eigen::Affine3d(value); +} + +void load_point_clouds_and_init(const std::vector& input_file_names) +{ + if (input_file_names.size() > 0) + for (size_t i = 0; i < input_file_names.size(); i++) + load_pc(input_file_names[i].c_str(), point_cloud, true, filter_threshold_xy); + + // Initialize imu_lidar according to imuSnToUse + imu_lidar.clear(); + for (const auto& [id, sn] : idToSn) + { + Checked imu; + imu.check = (sn == imuSnToUse); + imu_lidar.push_back(imu); + + if (imu.check) + chosen_imu = id; + } + + if (imu_lidar.size() == 1) + chosen_imu = 0; + + calibrated_lidar.clear(); + // Initialize calibrated_lidar according to calibrations data + for (const auto& [id, affine] : calibrations) + { + Checked calib; + // If affine is close to identity -> not calibrated; otherwise -> calibrated + Eigen::Matrix4d m = affine.matrix(); + calib.check = !(m.isApprox(Eigen::Matrix4d::Identity(), 1e-6)); + calibrated_lidar.push_back(calib); + + if (calib.check) + chosen_lidar = id; + } + + if (calibrated_lidar.size() == 1) + { + manual_calibration = true; // if only one LiDAR, force manual calibration + chosen_lidar = 0; + } + + { + std::ostringstream t0, t1; + t0 << calibrations.at(0).translation().transpose(); + t1 << calibrations.at(1).translation().transpose(); + spdlog::info( + "[DEBUG] after loading point clouds: chosen_lidar={} calibrations.at(0).translation()={} " + "calibrations.at(1).translation()={}", + chosen_lidar, + t0.str(), + t1.str()); + } +} + void settings_gui() { if (ImGui::Begin("Settings", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) @@ -302,10 +408,11 @@ void settings_gui() j["imuToUse"] = l1.c_str(); std::ofstream fs(input_file_name); - if (!fs.good()) - return; - fs << j.dump(2); - fs.close(); + if (fs.good()) + { + fs << j.dump(2); + fs.close(); + } } else { @@ -357,45 +464,7 @@ void settings_gui() { std::vector input_file_names; input_file_names = mandeye::fd::OpenFileDialog("Point cloud files", mandeye::fd::LAS_LAZ_filter, true); - - if (input_file_names.size() > 0) - for (size_t i = 0; i < input_file_names.size(); i++) - load_pc(input_file_names[i].c_str(), point_cloud, true, filter_threshold_xy); - - // Initialize imu_lidar according to imuSnToUse - imu_lidar.clear(); - for (const auto& [id, sn] : idToSn) - { - Checked imu; - imu.check = (sn == imuSnToUse); - imu_lidar.push_back(imu); - - if (imu.check) - chosen_imu = id; - } - - if (imu_lidar.size() == 1) - chosen_imu = 0; - - calibrated_lidar.clear(); - // Initialize calibrated_lidar according to calibrations data - for (const auto& [id, affine] : calibrations) - { - Checked calib; - // If affine is close to identity -> not calibrated; otherwise -> calibrated - Eigen::Matrix4d m = affine.matrix(); - calib.check = !(m.isApprox(Eigen::Matrix4d::Identity(), 1e-6)); - calibrated_lidar.push_back(calib); - - if (calib.check) - chosen_lidar = id; - } - - if (calibrated_lidar.size() == 1) - { - manual_calibration = true; // if only one LiDAR, force manual calibration - chosen_lidar = 0; - } + load_point_clouds_and_init(input_file_names); } } ImGui::EndDisabled(); @@ -415,6 +484,7 @@ void settings_gui() std::string name = idToSn.at(i); ImGui::RadioButton(name.c_str(), &chosen_lidar, i); } + ImGui::RadioButton("IMU", &chosen_lidar, kImuCalibrationTarget); if (chosen_lidar != -1) { @@ -429,7 +499,7 @@ void settings_gui() ImGui::Checkbox("Manual calibration", &manual_calibration); } - if (calibrated_lidar.size() > 1) + if (calibrated_lidar.size() > 1 && chosen_lidar != kImuCalibrationTarget) { if (ImGui::Button("Auto calibration")) { @@ -463,6 +533,13 @@ void settings_gui() std::vector pc0; std::vector pc1; + spdlog::info( + "[DEBUG] auto calibration pressed: chosen_lidar={} calibrated_lidar[0].check={} " + "calibrated_lidar[1].check={}", + chosen_lidar, + calibrated_lidar[0].check, + calibrated_lidar[1].check); + if (calibrated_lidar[0].check) { for (const auto& s : lidar0) @@ -473,7 +550,13 @@ void settings_gui() pc1.emplace_back(pp.x(), pp.y(), pp.z()); } - if (icp.compute(pc0, pc1, search_radius, number_of_iterations, m0)) + bool ok = icp.compute(pc0, pc1, search_radius, number_of_iterations, m0); + { + std::ostringstream t0; + t0 << m0.translation().transpose(); + spdlog::info("[DEBUG] icp.compute(lidar0) ok={} m0.translation()={}", ok, t0.str()); + } + if (ok) calibrations.at(0) = m0; } else @@ -485,9 +568,26 @@ void settings_gui() } for (const auto& t : lidar1) pc1.emplace_back(t.point.x(), t.point.y(), t.point.z()); - if (icp.compute(pc1, pc0, search_radius, number_of_iterations, m1)) + bool ok = icp.compute(pc1, pc0, search_radius, number_of_iterations, m1); + { + std::ostringstream t1; + t1 << m1.translation().transpose(); + spdlog::info("[DEBUG] icp.compute(lidar1) ok={} m1.translation()={}", ok, t1.str()); + } + if (ok) calibrations.at(1) = m1; } + + { + std::ostringstream t0, t1; + t0 << calibrations.at(0).translation().transpose(); + t1 << calibrations.at(1).translation().transpose(); + spdlog::info( + "[DEBUG] after auto calibration: calibrations.at(0).translation()={} " + "calibrations.at(1).translation()={}", + t0.str(), + t1.str()); + } } ImGui::SameLine(); @@ -504,7 +604,8 @@ void settings_gui() { if (chosen_lidar != -1) { - TaitBryanPose tb_pose = pose_tait_bryan_from_affine_matrix(calibrations.at(chosen_lidar)); + Eigen::Affine3d& target = (chosen_lidar == kImuCalibrationTarget) ? imu_calibration : calibrations.at(chosen_lidar); + TaitBryanPose tb_pose = pose_tait_bryan_from_affine_matrix(target); tb_pose.om = tb_pose.om * RAD_TO_DEG; tb_pose.fi = tb_pose.fi * RAD_TO_DEG; tb_pose.ka = tb_pose.ka * RAD_TO_DEG; @@ -540,16 +641,29 @@ void settings_gui() tb_pose.ka = tb_pose.ka * DEG_TO_RAD; Eigen::Affine3d m_pose = affine_matrix_from_pose_tait_bryan(tb_pose); - calibrations.at(chosen_lidar) = m_pose; + target = m_pose; + { + std::ostringstream t0, t1, timu; + t0 << calibrations.at(0).translation().transpose(); + t1 << calibrations.at(1).translation().transpose(); + timu << imu_calibration.translation().transpose(); + spdlog::info( + "[DEBUG] manual calibration: chosen_lidar={} calibrations.at(0).translation()={} " + "calibrations.at(1).translation()={} imu_calibration.translation()={}", + chosen_lidar, + t0.str(), + t1.str(), + timu.str()); + } } ImGui::Checkbox("Show gizmo", &show_gizmo); if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Drag the gizmo in the 3D view to adjust the pose of the selected LiDAR"); + ImGui::SetTooltip("Drag the gizmo in the 3D view to adjust the pose of the selected LiDAR or IMU"); if (show_gizmo) { - const Eigen::Affine3d& m = calibrations.at(chosen_lidar); + const Eigen::Affine3d& m = target; for (int c = 0; c < 4; c++) for (int r = 0; r < 4; r++) m_gizmo[c * 4 + r] = static_cast(m(r, c)); @@ -595,6 +709,15 @@ void settings_gui() if (new_calibration_file_name.size() > 0) { spdlog::info("Output file name: {}", new_calibration_file_name); + { + std::ostringstream t0, t1; + t0 << calibrations.at(0).translation().transpose(); + t1 << calibrations.at(1).translation().transpose(); + spdlog::info( + "[DEBUG] at save time: calibrations.at(0).translation()={} calibrations.at(1).translation()={}", + t0.str(), + t1.str()); + } nlohmann::json j; j["calibration"][idToSn.at(0)]["order"] = "ROW"; @@ -640,21 +763,81 @@ void settings_gui() else j["imuToUse"] = idToSn.at(1); + j["imuCalibration"]["order"] = "ROW"; + j["imuCalibration"]["inverted"] = "FALSE"; + j["imuCalibration"]["data"][0] = imu_calibration(0, 0); + j["imuCalibration"]["data"][1] = imu_calibration(0, 1); + j["imuCalibration"]["data"][2] = imu_calibration(0, 2); + j["imuCalibration"]["data"][3] = imu_calibration(0, 3); + j["imuCalibration"]["data"][4] = imu_calibration(1, 0); + j["imuCalibration"]["data"][5] = imu_calibration(1, 1); + j["imuCalibration"]["data"][6] = imu_calibration(1, 2); + j["imuCalibration"]["data"][7] = imu_calibration(1, 3); + j["imuCalibration"]["data"][8] = imu_calibration(2, 0); + j["imuCalibration"]["data"][9] = imu_calibration(2, 1); + j["imuCalibration"]["data"][10] = imu_calibration(2, 2); + j["imuCalibration"]["data"][11] = imu_calibration(2, 3); + j["imuCalibration"]["data"][12] = 0; + j["imuCalibration"]["data"][13] = 0; + j["imuCalibration"]["data"][14] = 0; + j["imuCalibration"]["data"][15] = 1; + std::ofstream fs(new_calibration_file_name); - if (!fs.good()) - return; - fs << j.dump(2); - fs.close(); + if (fs.good()) + { + fs << j.dump(2); + fs.close(); + } } } if (ImGui::IsItemHovered()) ImGui::SetTooltip("Calibration file must be located in same folder with data (each continousScanning_xxxx folders)"); } ImGui::EndDisabled(); + } + ImGui::End(); +} - ImGui::End(); +void drawAxesAt(const Eigen::Affine3d& m, float axisLength) +{ + glBegin(GL_LINES); + glColor3f(1.0f, 0.0f, 0.0f); + glVertex3f(m(0, 3), m(1, 3), m(2, 3)); + glVertex3f(m(0, 3) + axisLength * m(0, 0), m(1, 3) + axisLength * m(1, 0), m(2, 3) + axisLength * m(2, 0)); + + glColor3f(0.0f, 1.0f, 0.0f); + glVertex3f(m(0, 3), m(1, 3), m(2, 3)); + glVertex3f(m(0, 3) + axisLength * m(0, 1), m(1, 3) + axisLength * m(1, 1), m(2, 3) + axisLength * m(2, 1)); + + glColor3f(0.0f, 0.0f, 1.0f); + glVertex3f(m(0, 3), m(1, 3), m(2, 3)); + glVertex3f(m(0, 3) + axisLength * m(0, 2), m(1, 3) + axisLength * m(1, 2), m(2, 3) + axisLength * m(2, 2)); + glEnd(); +} + +void drawWireBoxAt(const Eigen::Affine3d& m, float halfExtent, const ImVec4& color) +{ + const Eigen::Vector3d corners_local[8] = { + { -halfExtent, -halfExtent, -halfExtent }, { halfExtent, -halfExtent, -halfExtent }, { halfExtent, halfExtent, -halfExtent }, + { -halfExtent, halfExtent, -halfExtent }, { -halfExtent, -halfExtent, halfExtent }, { halfExtent, -halfExtent, halfExtent }, + { halfExtent, halfExtent, halfExtent }, { -halfExtent, halfExtent, halfExtent }, + }; + + Eigen::Vector3d corners[8]; + for (int i = 0; i < 8; i++) + corners[i] = m * corners_local[i]; + + const int edges[12][2] = { { 0, 1 }, { 1, 2 }, { 2, 3 }, { 3, 0 }, { 4, 5 }, { 5, 6 }, + { 6, 7 }, { 7, 4 }, { 0, 4 }, { 1, 5 }, { 2, 6 }, { 3, 7 } }; + + glColor3f(color.x, color.y, color.z); + glBegin(GL_LINES); + for (const auto& e : edges) + { + glVertex3f(corners[e[0]].x(), corners[e[0]].y(), corners[e[0]].z()); + glVertex3f(corners[e[1]].x(), corners[e[1]].y(), corners[e[1]].z()); } - return; + glEnd(); } void display() @@ -696,27 +879,6 @@ void display() showAxes(); - if (calibration.size() > 0) - { - for (const auto& c : calibration) - { - Eigen::Affine3d m = c.second; - glBegin(GL_LINES); - glColor3f(1.0f, 0.0f, 0.0f); - glVertex3f(m(0, 3), m(1, 3), m(2, 3)); - glVertex3f(m(0, 3) + m(0, 0), m(1, 3) + m(1, 0), m(2, 3) + m(2, 0)); - - glColor3f(0.0f, 1.0f, 0.0f); - glVertex3f(m(0, 3), m(1, 3), m(2, 3)); - glVertex3f(m(0, 3) + m(0, 1), m(1, 3) + m(1, 1), m(2, 3) + m(2, 1)); - - glColor3f(0.0f, 0.0f, 1.0f); - glVertex3f(m(0, 3), m(1, 3), m(2, 3)); - glVertex3f(m(0, 3) + m(0, 2), m(1, 3) + m(1, 2), m(2, 3) + m(2, 2)); - glEnd(); - } - } - // point_cloud // if (manual_calibration) @@ -793,6 +955,38 @@ void display() glEnd(); } + // Draw coordinate systems on top of the point cloud / grid, regardless of depth. + glDisable(GL_DEPTH_TEST); + + if (calibration.size() > 0) + { + for (const auto& c : calibration) + { + Eigen::Affine3d m = c.second; + glBegin(GL_LINES); + glColor3f(1.0f, 0.0f, 0.0f); + glVertex3f(m(0, 3), m(1, 3), m(2, 3)); + glVertex3f(m(0, 3) + m(0, 0), m(1, 3) + m(1, 0), m(2, 3) + m(2, 0)); + + glColor3f(0.0f, 1.0f, 0.0f); + glVertex3f(m(0, 3), m(1, 3), m(2, 3)); + glVertex3f(m(0, 3) + m(0, 1), m(1, 3) + m(1, 1), m(2, 3) + m(2, 1)); + + glColor3f(0.0f, 0.0f, 1.0f); + glVertex3f(m(0, 3), m(1, 3), m(2, 3)); + glVertex3f(m(0, 3) + m(0, 2), m(1, 3) + m(1, 2), m(2, 3) + m(2, 2)); + glEnd(); + } + } + + if (calibrations.size() > 0) + { + drawWireBoxAt(imu_calibration, 0.03f, ImVec4(1.0f, 1.0f, 0.0f, 1.0f)); + drawAxesAt(imu_calibration, 0.15f); + } + + glEnable(GL_DEPTH_TEST); + ImGui_ImplOpenGL2_NewFrame(); ImGui_ImplGLUT_NewFrame(); ImGui::NewFrame(); @@ -909,7 +1103,7 @@ void display() m_gizmo, NULL); - Eigen::Affine3d& m = calibrations.at(chosen_lidar); + Eigen::Affine3d& m = (chosen_lidar == kImuCalibrationTarget) ? imu_calibration : calibrations.at(chosen_lidar); for (int c = 0; c < 4; c++) for (int r = 0; r < 4; r++) m(r, c) = static_cast(m_gizmo[c * 4 + r]); @@ -970,10 +1164,85 @@ int main(int argc, char* argv[]) { params.decimation = 0.03; + std::string cli_sn_path; + std::string cli_calibration_path; + std::vector cli_laz_paths; + for (int i = 1; i < argc; ++i) + { + const std::string arg = argv[i]; + const bool hasValue = i + 1 < argc; + + if (arg == "--sn" && hasValue) + cli_sn_path = argv[++i]; + else if (arg == "--calibration" && hasValue) + cli_calibration_path = argv[++i]; + else if (arg == "--laz" && hasValue) + cli_laz_paths.push_back(argv[++i]); + } + try { initGL(&argc, argv, winTitle, display, mouse); + if (!cli_sn_path.empty()) + { + idToSn = MLvxCalib::GetIdToSnMapping(cli_sn_path); + if (idToSn.size() > 0) + { + calibration_file_name = ""; + calibrations.clear(); + imuSnToUse = idToSn.at(0); + spdlog::info("Loaded LiDAR serial numbers from '{}':", cli_sn_path); + for (const auto& [id, sn] : idToSn) + spdlog::info(" - ID: {} --> SN: {}", id, sn.c_str()); + } + else + { + spdlog::error("Failed to load LiDAR serial number file '{}'", cli_sn_path); + } + } + + if (!cli_calibration_path.empty()) + { + if (idToSn.size() < 2) + { + spdlog::error( + "Cannot load calibration '{}' before a valid --sn file with at least 2 LiDARs is loaded", cli_calibration_path); + } + else + { + calibration_file_name = cli_calibration_path; + calibration = MLvxCalib::GetCalibrationFromFile(calibration_file_name); + imuSnToUse = MLvxCalib::GetImuSnToUse(calibration_file_name); + calibrations = MLvxCalib::CombineIntoCalibration(idToSn, calibration); + imu_calibration = LoadImuCalibrationFromFile(calibration_file_name); + + if (!calibration.empty()) + { + spdlog::info("Loaded calibration from '{}' for:", calibration_file_name); + for (const auto& [sn, _] : calibration) + spdlog::info(" -> {}", sn); + spdlog::info("imuSnToUse: {}", imuSnToUse); + } + else + { + spdlog::error("Failed to load calibration file '{}'", calibration_file_name); + } + } + } + + if (!cli_laz_paths.empty()) + { + if (calibrations.size() == 0) + { + spdlog::error("Cannot load point clouds before --sn and --calibration are loaded"); + } + else + { + load_point_clouds_and_init(cli_laz_paths); + } + } + glutMainLoop(); ImGui_ImplOpenGL2_Shutdown(); From 0cda3cd3e3410c419e62c6c42b08efd368c1dfee Mon Sep 17 00:00:00 2001 From: Michal Pelka Date: Mon, 31 Aug 2026 14:22:16 +0200 Subject: [PATCH 2/2] Include ImGuizmo.h after imgui.h to fix build ImGuizmo.h references ImDrawList/ImVec2/ImU32/ImGuiID/ImVec4 without declaring them and does not include imgui.h itself, so it must be included after imgui.h. The previous commit reordered it above imgui.h, breaking the build on all platforms. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01MgHkoR9v5D6Aot5RWmi67N --- .../mandeye_mission_recorder_calibration.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/mandeye_mission_recorder_calibration/mandeye_mission_recorder_calibration.cpp b/apps/mandeye_mission_recorder_calibration/mandeye_mission_recorder_calibration.cpp index a8c07bab..ab54ee17 100644 --- a/apps/mandeye_mission_recorder_calibration/mandeye_mission_recorder_calibration.cpp +++ b/apps/mandeye_mission_recorder_calibration/mandeye_mission_recorder_calibration.cpp @@ -1,11 +1,12 @@ #include -#include #include #include #include #include +#include + #include #include