Skip to content

Fix ts_ns unit bug in PointCloud LAS/LAZ loader - #473

Closed
michalpelka wants to merge 7 commits into
MapsHD:mainfrom
michalpelka:mp/fix-pointcloud-ts-ns-units
Closed

Fix ts_ns unit bug in PointCloud LAS/LAZ loader#473
michalpelka wants to merge 7 commits into
MapsHD:mainfrom
michalpelka:mp/fix-pointcloud-ts-ns-units

Conversation

@michalpelka

Copy link
Copy Markdown
Contributor

Summary

  • calib::PointCloud::load() cast gps_time (seconds, per LAS spec) directly to ts_ns without converting to nanoseconds
  • ts_ns is treated as nanoseconds everywhere else it's used: Trajectory::nearest(), the gps_time round-trip on export in TrajectoryViewer.cpp, and nearest-image-by-timestamp matching
  • Without the * 1e9 conversion, loaded point timestamps were off by ~9 orders of magnitude from every value they get compared against, making nearest-image/nearest-pose matching against point timestamps meaningless

Fixes #472

Test plan

  • Load a .laz file through camera_lidar_calibration or camera_lidar_trajectory_viewer and confirm a loaded point's ts_ns is now in the same order of magnitude as the corresponding trajectory/image timestamp

🤖 Generated with Claude Code

https://claude.ai/code/session_01DiH2pr8ruiHu6k7Y2wSXS2

Michal and others added 7 commits July 30, 2026 22:54
We compile automatic jacoians (24 k chars headers) without debug symbols.
pair_wise_iterative_closest_point.cpp stayed in CORE_BASE_SOURCES while
pose_graph_loop_closure.cpp (which calls PairWiseICP::compute) moved to
CORE_MATH_SOURCES, splitting a symbol and its only definition across two
static libs with a link-order dependency in the wrong direction. This broke
linking for any executable that pulls in PoseGraphLoopClosure without
referencing PairWiseICP directly, e.g. multi_view_tls_registration_step_2.

pair_wise_iterative_closest_point.cpp also includes an auto-generated
Jacobian header, so it belongs in core_math anyway.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DiH2pr8ruiHu6k7Y2wSXS2
pair_wise_iterative_closest_point.cpp (moved into core_math in the previous
commit) calls get_rgd_index_3d(), which lived in hash_utils.cpp under
CORE_BASE_SOURCES -- reintroducing the same cross-archive circular
dependency, just in the opposite direction (core_math needing a symbol
from core/core_no_gui instead of the other way around). Nothing else in
CORE_BASE_SOURCES/CORE_GUI_SOURCES calls into core_math, so moving
hash_utils.cpp there too makes the dependency one-directional again
(core/core_no_gui -> core_math, never the reverse).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DiH2pr8ruiHu6k7Y2wSXS2
…lors

Ports the three raylib/ImGui tools from the sibling mandeye-colors repo as
new apps (camera_lidar_calibration, camera_lidar_trajectory_viewer,
camera_lidar_intrinsics_calib), backed by a new calib_core static library
for their shared non-GUI logic (camera projection math, LAS/LAZ loading,
trajectory CSV parsing, CLI args). Reuses HDMapping's existing raylib/
imgui_raylib/rlimgui/Eigen/LASzip/OpenCV/json wiring instead of vendoring
mandeye-colors' own duplicate copies of those dependencies.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DiH2pr8ruiHu6k7Y2wSXS2
All three text fields that take a file or directory path (image, point
cloud, intrinsics, calibration, session/CAMERA_0 dirs, export/ROS/COLMAP
output paths) now have a "Browse..." button backed by portable-file-dialogs,
matching the mandeye::fd wrapper core already uses elsewhere in HDMapping.

Implemented as calib_core's own calib::fd (rather than reusing
core/include/Core/pfd_wrapper.hpp directly) since that wrapper only builds
into the GUI-enabled `core` target, and linking `core` here would pull in
core_math/session/SLAM code these apps otherwise don't depend on --
portable-file-dialogs itself is a single vendored header with no dependency
on `core`, so wrapping it directly in calib_core is cheap.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DiH2pr8ruiHu6k7Y2wSXS2
… apps

Re-ports camera_lidar_trajectory_viewer's TrajectoryViewer.cpp from
mandeye-colors' mp/roi branch (the initial import was mistakenly based on
master, before the ROI work landed there): adds a Roi struct (calib_core's
Camera.h), SLERP pose interpolation instead of nearest-neighbor lookups,
distortion-aware point projection, a "Geometry" coloring strategy (closest
camera by depth) alongside the existing temporal one, ROI-filtered
colorization with a Camera-ID/In-ROI render mode and an ROI overlay on the
image preview, and colored/uncolored point-count stats.

Also fixes three real bugs surfaced by manually running the apps:

- Fixed-size windows (1400x900 etc.) could be taller than the screen once
  the OS menu bar + title bar are accounted for, silently pushing the top
  of the control panel off-screen. Added fitWindowToScreen() (monitor-aware
  resize/reposition) and SetWindowMinSize() to all three apps.
- fitWindowToScreen() itself was buggy: GetMonitorWidth/Height return the
  monitor's native pixel resolution while GetScreenWidth/Height and
  SetWindowSize/SetWindowPosition operate in logical points, a 2x mismatch
  on Retina displays that pushed the window mostly off-screen. Fixed by
  dividing by GetWindowScaleDPI(). Also added a PollInputEvents() call so
  raylib's cached mouse/window geometry is refreshed before rlImGuiSetup()
  reads it.
- Several ImGui widgets with trailing labels (InputInt, Combo, InputDouble)
  were wrapped in PushItemWidth(-1), which gives the widget box the entire
  row and clips the label off the right edge of the panel. Scoped a
  narrower width around each affected widget.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DiH2pr8ruiHu6k7Y2wSXS2
gps_time from the LAS/LAZ point record is in seconds, but Point3D::ts_ns
is treated as nanoseconds everywhere it's consumed (Trajectory::nearest(),
TrajectoryViewer's gps_time round-trip on export, nearest-image-by-timestamp
matching). Without the *1e9 conversion, loaded point timestamps were off by
~9 orders of magnitude from every value they get compared against, making
nearest-image/nearest-pose matching against point timestamps meaningless.

Fixes MapsHD#472

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DiH2pr8ruiHu6k7Y2wSXS2
@michalpelka michalpelka closed this Aug 2, 2026
@michalpelka
michalpelka deleted the mp/fix-pointcloud-ts-ns-units branch August 2, 2026 22:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

camera_lidar_* apps: point cloud ts_ns is in seconds, not nanoseconds — breaks image/trajectory timestamp matching

1 participant