From 9747fad2ae6fccce5014014036e393b9fae43ed6 Mon Sep 17 00:00:00 2001 From: Michal Pelka Date: Wed, 30 Sep 2026 15:40:43 +0200 Subject: [PATCH 1/2] Fix "Update e57 poses" failing on files with intensity minimum > 0 (#551) libE57Format's Writer::NewData3D builds the intensity prototype node with value 0, which throws E57_ERROR_VALUE_OUT_OF_BOUNDS when the source file's intensityMinimum is above 0 (e.g. 1/65535 in Leica exports). Clamp the minimum to 0 before writing the header. Co-Authored-By: Claude Opus 5.5 --- core/src/e57_utils.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/e57_utils.cpp b/core/src/e57_utils.cpp index 185b159d..581ba3a2 100644 --- a/core/src/e57_utils.cpp +++ b/core/src/e57_utils.cpp @@ -481,6 +481,9 @@ namespace mandeye::e57io } header.pointCount = total; + // libE57Format builds the intensity prototype with value 0.0; FloatNode throws if 0 is outside [min, max] + if (header.pointFields.intensityField && header.intensityLimits.intensityMinimum > 0.0) + header.intensityLimits.intensityMinimum = 0.0; const int64_t di = writer.NewData3D(header); if (total > 0) From 0780c0e7e0d5956f7f50fec87e1fc9c19307e916 Mon Sep 17 00:00:00 2001 From: Michal Pelka Date: Wed, 30 Sep 2026 17:13:49 +0200 Subject: [PATCH 2/2] Step 2: make sessions created from e57 saveable Scans imported from e57 only existed in memory under a made-up .laz name, so "Save session as" / "Save subsession" wrote a session pointing to missing files. Add PointCloud::laz_in_memory_only, set it on e57 import, and export those clouds as .laz next to the session on save (visible ones only for subsessions). "Save session as" also no longer prompts for poses file names: sessions without them showed an error popup plus two save dialogs whose result replaced the derived names, so cancelling saved a session with empty poses paths. Poses always go to _poses.mrp / _ini_poses.mri. Co-Authored-By: Claude Opus 5.5 --- .../multi_view_tls_registration_gui.cpp | 81 +++++++------------ core/include/Core/point_cloud.h | 3 + 2 files changed, 31 insertions(+), 53 deletions(-) diff --git a/apps/multi_view_tls_registration/multi_view_tls_registration_gui.cpp b/apps/multi_view_tls_registration/multi_view_tls_registration_gui.cpp index e943c3f2..6a4d2c44 100644 --- a/apps/multi_view_tls_registration/multi_view_tls_registration_gui.cpp +++ b/apps/multi_view_tls_registration/multi_view_tls_registration_gui.cpp @@ -2587,6 +2587,28 @@ void openSession() } } +// Writes clouds that have no .laz on disk yet (laz_in_memory_only, e.g. e57 scans) into `dir` +// and points their file_name there, so the session about to be saved references real files. +// `visible_only` matches Session::save(..., is_subsession), which only lists visible clouds. +void exportInMemoryPointClouds(const fs::path& dir, bool visible_only) +{ + for (auto& pc : session.point_clouds_container.point_clouds) + { + if (!pc.laz_in_memory_only || (visible_only && !pc.visible)) + continue; + + const std::string laz_file_name = (dir / fs::path(pc.file_name).filename()).string(); + spdlog::info("Saving in-memory point cloud to: '{}'", laz_file_name); + if (exportLaz(laz_file_name, pc.points_local, pc.intensities, pc.timestamps)) + { + pc.file_name = laz_file_name; + pc.laz_in_memory_only = false; + } + else + spdlog::error("Error saving file: '{}'", laz_file_name); + } +} + std::string saveSession() { const std::string output_file_name = @@ -2602,60 +2624,11 @@ std::string saveSession() const auto dir = path.parent_path(); const auto stem = path.stem().string(); - // Build new names - std::string initial_poses_file_name = (dir / (stem + "_ini_poses.mri")).string(); - std::string poses_file_name = (dir / (stem + "_poses.mrp")).string(); - - if (session.point_clouds_container.initial_poses_file_name.empty()) - { - spdlog::info("Please assign initial_poses_file_name to session"); - spdlog::warn("Session is not saved!"); - - [[maybe_unused]] pfd::message message( - "Please assign initial_poses_file_name to session", - "Session is not saved. Please assign initial_poses_file_name to session. " - "Follow guidlines available here : " - "https://github.com/MapsHD/HDMapping/tree/main/doc/, " - "You can do this using button 'update initial poses from RESSO file'", - pfd::choice::ok, - pfd::icon::error); - message.result(); - - initial_poses_file_name = - mandeye::fd::SaveFileDialog("Initial poses file name", mandeye::fd::IniPoses_filter, initial_poses_file_name); - spdlog::info("Resso file to save: '{}'", initial_poses_file_name); - - if (initial_poses_file_name.size() > 0) - { - spdlog::info("Saving initial poses to: '{}'", initial_poses_file_name); - session.point_clouds_container.save_poses(initial_poses_file_name, false); - } - } - - if (session.point_clouds_container.poses_file_name.empty()) - { - spdlog::info("Please assign poses_file_name to session"); - spdlog::warn("Session is not saved!"); - - [[maybe_unused]] pfd::message message( - "Please assign poses_file_name to session", - "Session is not saved. Please assign poses_file_name to session. " - "Follow guidlines available here : " - "https://github.com/MapsHD/HDMapping/tree/main/doc/," - "You can do this using button 'update poses from RESSO file'", - pfd::choice::ok, - pfd::icon::error); - message.result(); - - poses_file_name = mandeye::fd::SaveFileDialog("Poses file name", mandeye::fd::Poses_filter, poses_file_name); - spdlog::info("Resso file to save: '{}'", poses_file_name); - if (poses_file_name.size() > 0) - { - spdlog::info("Saving poses to: '{}'", poses_file_name); - session.point_clouds_container.save_poses(poses_file_name, false); - } - } + // Build new names. Poses always go next to the session file; sessions opened from e57/laz have no poses files yet + const std::string initial_poses_file_name = (dir / (stem + "_ini_poses.mri")).string(); + const std::string poses_file_name = (dir / (stem + "_poses.mrp")).string(); + exportInMemoryPointClouds(dir, false); session.save(output_file_name, poses_file_name, initial_poses_file_name, false); writeGnssTumToSessionFile(output_file_name, tls_registration.gnss, tls_registration.tum); spdlog::info("Saving result to: '{}'", poses_file_name); @@ -2910,6 +2883,7 @@ void loadE57Files(const std::vector& input_file_names, bool fillInS PointCloud pc; // Name the cloud so it exports to a sensible .laz in fillInSession mode. pc.file_name = scans.size() > 1 ? (stem + "_" + std::to_string(si) + ".laz") : (stem + ".laz"); + pc.laz_in_memory_only = true; // Provenance for "Update e57 poses". pc.e57_source_path = abs_src; pc.e57_scan_index = static_cast(si); @@ -3206,6 +3180,7 @@ void saveSubsession() const auto initial_poses_file_name = (dir / (stem + "_ini_poses" + ".mri")).string(); const auto poses_file_name = (dir / (stem + "_poses" + ".mrp")).string(); + exportInMemoryPointClouds(dir, true); session.save(fs::path(output_file_name).string(), poses_file_name, initial_poses_file_name, true); writeGnssTumToSessionFile(fs::path(output_file_name).string(), tls_registration.gnss, tls_registration.tum); spdlog::info("Saving poses to: '{}'", poses_file_name); diff --git a/core/include/Core/point_cloud.h b/core/include/Core/point_cloud.h index 70d1e4d4..a41395e1 100644 --- a/core/include/Core/point_cloud.h +++ b/core/include/Core/point_cloud.h @@ -88,6 +88,9 @@ class PointCloud std::vector index_pairs; std::vector buckets; std::string file_name; + // True when file_name does not exist on disk yet (e.g. scan imported from E57); + // "Save session as" in step 2 exports the cloud as .laz next to the session. + bool laz_in_memory_only = false; // Provenance for scans imported from an E57 file (see openE57 in step 2). // Not serialized to session JSON; used by "Update e57 poses" to write the // refined m_pose back into the originating Data3D block.