Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

whl-scanner

Processing tools for 3DMakerPro JMK2 handheld scanner exports (Livox Mid-360 lidar + IMU, optional camera and GNSS).

The device exports raw sensor data only — no poses, no trajectory. These tools turn a recorded session into a single merged, gravity-levelled, deduplicated point cloud, and let you inspect the result in a browser.

bag  ──▶ bag2pcd.py     per-frame PCD + index (optional, for inspection)
     │
     ├─▶ odom.py        lidar odometry  ──▶ poses.npz
     │        │
     │        ├─▶ level_map.py    gravity levelling ──▶ R_level.npy
     │        ├─▶ georeference.py GPS → ENU (optional) ──▶ poses_geo.npz
     │        └─▶ merge_map.py    merge + filter ──▶ merged.pcd
     │
     └─▶ plot_trajectory.py   trajectory figure (any TUM-format trajectory)

viewer/index.html    browser point-cloud viewer

Example output

Indoor office session — merged cloud (left: top-down, floor-plan slice, vertical section) and the trajectory figure produced by plot_trajectory.py:

merged map trajectory

Install

git clone git@github.com:wheelos-tools/whl-scanner.git
cd whl-scanner
python3 -m venv .venv && ./.venv/bin/pip install -r requirements.txt

No ROS installation is needed — rosbags reads ROS 1 bags directly.

New to this? See QUICKSTART.md for a worked end-to-end run.

Tools

Script Purpose
whl_scanner/bag2pcd.py Export each lidar sweep to its own PCD, plus a frames.csv index. Lossless; useful for inspection.
whl_scanner/odom.py Lidar odometry. Scan-to-map VGICP with two-pass motion compensation → one 6-DOF pose per frame.
whl_scanner/level_map.py Gravity levelling. Accelerometer + RANSAC ground-plane fit → a rotation making +Z true up. The only stage that uses the IMU.
whl_scanner/georeference.py Optional. Rigidly aligns the trajectory to GNSS, producing an ENU frame with a documented lat/lon origin.
whl_scanner/gps_tools.py GNSS helpers: read fixes, project to ENU, score a trajectory, elastic correction.
whl_scanner/merge_map.py Merge all frames into one cloud: range/noise filtering, voxel dedup, isolated-speck removal.
whl_scanner/plot_trajectory.py Plot any TUM-format trajectory, optionally over a cloud and against a reference.
viewer/index.html Browser point-cloud viewer with height/axis filters and intensity colouring.

What the device actually exports

Full field-level reference, in English and Simplified Chinese:

Things that will bite you

Established by inspecting real sessions. Each one silently corrupts results if missed.

  • There are no poses in the bag. Only /livox/lidar and /livox/imu. Points are in the sensor frame of each sweep, not a world frame — consecutive frames differ by 4–12 cm while moving. Concatenating them without registration gives a smeared mess.
  • A "frame" is not a snapshot. Points within one message span ~100 ms (offset_time). At 50 °/s the scanner turns ~5° mid-sweep. Deskewing is not optional.
  • Deskew must use the current frame's motion. Driving it from the previous frame's motion, inside a loop that also feeds the map, has loop gain > 1 and diverges (~1.35×/frame). odom.py uses a two-pass scheme for this reason.
  • The lidar is mounted ~29–30° off vertical. Raw z is not height. Level with level_map.py before interpreting anything vertical.
  • IMU acceleration is in g, not m/s². Gyro carries ~1.5 °/s of static bias, so |gyro| never reads zero at rest.
  • Zero-range points are no-returns, not measurements: ~16% indoors, ~60% outdoors (beams into open sky). Filter range >= 0.2 m.
  • Indoor filter settings do not transfer outdoors. Outdoor scenes have far less overlap (1.2× dedup vs 3.7× indoors), so an aggressive neighbour threshold deletes real distant structure. See --nb-radius / --min-neighbours.
  • GNSS is single-point, metre-level, 2D only — no orientation, no altitude. Good for georeferencing and drift bounding; far too coarse to improve local accuracy. Measured here: elastically fitting the trajectory to GPS degraded map sharpness by 7–18%, so georeference.py deliberately applies a rigid transform only.

Validation on a reference session

Indoor office, 124 s, 1241 sweeps, 24.8M points:

Metric Result
Pose stability over 35 s stationary 1.1 / 3.4 / 0.8 mm
Rotation rate vs IMU gyro (independent sensor) correlation 0.94
Loop closure over a 53 m walk 28.3 cm (0.53%)
Ground-plane flatness 15.6 mm — at the Mid-360's own range noise
Agreement with the vendor's own SLAM trajectory mean 1.9 cm; path length within 0.2%

Credits

viewer/ is derived from Fabulani/pcd-viewer (MIT, © 2024 Fabiano Junior Maia Manschein), rewritten for multi-scene, Z-up, gravity-levelled clouds. Original licence retained in viewer/LICENSE.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages