Skip to content

Step 1: classify moving objects as LAS class 7#448

Closed
piorkoo wants to merge 3 commits into
MapsHD:mainfrom
piorkoo:movableObject_class7
Closed

Step 1: classify moving objects as LAS class 7#448
piorkoo wants to merge 3 commits into
MapsHD:mainfrom
piorkoo:movableObject_class7

Conversation

@piorkoo

@piorkoo piorkoo commented Jun 29, 2026

Copy link
Copy Markdown

Summary

Step 1 (LIO) already detects moving/dynamic objects at the RGD-bucket level: in update_rgd() a ray is cast from each point to the viewport and number_of_hits is incremented for the buckets it passes through. Buckets with number_of_hits >= 20 are treated as "see-through" space and their points are excluded from the optimization Hessian (lidar_odometry_utils_optimizers.cpp) and hidden by "Show without filtered buckets" in the GUI.

Until now this signal was never persisted per point — every output point in scan_lio_*.laz was written with LAS classification 0. This PR exports points lying in moving buckets with LAS classification 7, so the resulting .laz files contain both class 0 (static) and class 7 (dynamic).

Changes

  • core/include/Core/export_laz.hLazWriter::writePoint() now sets point_->classification; exportLaz() takes an optional per-point classification vector. Both use default arguments, so existing callers (e.g. save_all_to_las) are unchanged.
  • apps/lidar_odometry_step_1/lidar_odometry.cpp — in save_result(), after points_to_vector() produces the global cloud, each point's indoor RGD bucket is looked up (get_rgd_index_3d with in_out_params_indoor resolution); number_of_hits >= threshold -> class 7, else 0. The grids (params.buckets_indoor) are still alive at save time.
  • LidarOdometryParams — new classify_moving_objects (default on) and moving_object_hits_threshold (default 20), persisted via a TOML [moving_objects] section and exposed in the GUI menu.

Scope / follow-up

This PR covers Step 1 only (the scan_lio_*.laz output). Carrying the classification through Step 2 / insightoBake to the final LAS export requires reading classification in PointCloud::load_pc and writing it in save_all_to_las; that is intentionally left for a separate branch.

Known limitation

number_of_hits is only accumulated for chunks with points_global.size() < 100000 (existing behaviour in update_rgd), so on very large scans the class-7 marking may be sparse or absent. Out of scope here.

🤖 Generated with Claude Code

Piotr Pioro and others added 3 commits June 29, 2026 15:02
Points whose indoor RGD bucket was raycast-through (number_of_hits >=
threshold, same signal that excludes them from LIO optimization) are now
exported in scan_lio_*.laz with LAS classification 7; static points stay
class 0.

- export_laz.h: writePoint sets point classification; exportLaz takes an
  optional per-point classification vector (defaults keep existing callers
  unchanged).
- lidar_odometry.cpp: save_result queries params.buckets_indoor per output
  point and builds the classification vector before exportLaz.
- LidarOdometryParams: classify_moving_objects (default on) +
  moving_object_hits_threshold (default 20), persisted via TOML
  [moving_objects] section and exposed in the GUI menu.

Scope is Step 1 only; preserving the class through Step 2 / insightoBake
to the final LAS export is handled on a separate branch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The untwine + libpdal-e57 conda env is created locally for COPC/E57
conversion and must not be tracked; without this, git add -A pulls in
thousands of env files.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The first version queried params.buckets_indoor in save_result(), but the
RGD grids are transient: process_worker_step_update_rgd_after() clears
buckets_indoor/outdoor on every sliding-window shift (~5 m), so at save
time only the last window survived — points were marked class 7 only at
the very end of the trajectory. It also ignored the outdoor grid and used
first-pose-relative coords for the bucket key (world coords were needed).

Now compute_step_2() accumulates the keys of every bucket that reached the
moving-object hit threshold into params.moving_buckets_indoor/outdoor,
snapshotting before each grid clear (segment peak) and once after the loop
(last window). save_result() classifies each output point by looking up
its world-coordinate bucket key (indoor OR outdoor) in those sets, mirroring
the points_to_vector output filter so the per-point vector stays aligned.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@piorkoo

piorkoo commented Jun 29, 2026

Copy link
Copy Markdown
Author

Closing: the classification reuses the LIO occlusion filter (number_of_hits >= 20), which is a robustness filter for optimization, not a moving-object detector. On real data it marks ~33-68% of points as class 7 (rising along the trajectory) and holds large per-window bucket sets in RAM. Withdrawing to rework the criterion.

@piorkoo piorkoo closed this Jun 29, 2026
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.

1 participant