Fix the dewarping around one consistent reference frame: beam i should use scan_start + i * time_increment, scan readiness should wait only through the last real beam, each beam should be transformed with a proper 2D rigid-body transform into the scan-start frame, and the legacy quarter-turn cloud correction should be removed or isolated because it currently mixes reference frames after dewarping.
Steps
- Phase 1: lock down intended semantics. Treat
LaserScan.header.stampas the acquisition time of beam 0 and make the scan-start pose the sole reference pose forcloud_from_laser_pubandscan_2. Keep the existing start-time stamps atpoints.header.stamp = scan_msg.header.stampanddewarped_scan.header.stamp = scan_msg.header.stamp, but rewrite comments/docstrings so the timestamp, transform math, and published frame all describe the same scan-start reference. This blocks later math changes. - Phase 2: fix beam timing and readiness math in
/home/chris/src/lidar_dewarping/dewarp_scan_ros2.py. Beam indexishould usescan_start + i * time_increment, not(i + 1) * time_increment;scan_end_ns()should use the timestamp of the last real beam, which isscan_start + (len(ranges) - 1) * time_incrementfor non-empty scans. This depends on step 1. - Phase 3: replace the current point transform in
process_scan()with a proper 2D rigid-body transform. Compute the sampled robot pose relative to the scan-start pose in the scan-start frame, then add the beam vector rotated by the sampled relative heading. Do not add a world-frame translation vector directly to a scan-frame beam vector. This depends on step 2. - Phase 4: remove the legacy whole-cloud correction beginning at
robot_dth2 = robot_dth / 4.0once the primary transform is mathematically correct. If temporary comparison is useful during rollout, gate it behind a clearly named compatibility parameter and default it off; the recommended path is to delete it rather than preserve a quarter-angle heuristic. This depends on step 3. - Phase 5: re-check the downstream scan-domain filter at
removed_items = self.filter_scan(dewarped_scan, robot_dth). Keep it only if bag replay still shows a clear benefit after the math fix; otherwise convert it into an opt-in diagnostic filter soscan_2represents the raw corrected scan andscan_3stays a debugging stream. This depends on step 4. - Phase 6: make the math testable. Extract the beam timing and point projection math into small pure helpers inside
/home/chris/src/lidar_dewarping/dewarp_scan_ros2.pyso they can be exercised with deterministic inputs without spinning ROS 2. If the repo stays dependency-light, add a small stdlibunittestregression module; if not, add a repeatable script that checks known pose/beam cases. This can be prepared in parallel with step 1 but should be finalized after steps 2-4. - Phase 7: update
/home/chris/src/lidar_dewarping/README.mdto document the corrected scan timing semantics, the fact that scan dewarping now uses the scan-start pose consistently, and whether the legacy filter remains enabled by default. This depends on steps 1-5.
Relevant files
/home/chris/src/lidar_dewarping/dewarp_scan_ros2.py— fixscan_end_ns(),process_scan(), the legacy midpoint/quarter-turn block, and the post-dewarp filter wiring; reuseOdomHistory.get_interpolated_pose()and the existing worker/queue flow./home/chris/src/lidar_dewarping/README.md— document the corrected scan timing semantics and any behavior change inscan_2/scan_3./home/chris/src/lidar_dewarping/dewarp_scan.py— use only as a historical reference while removing legacy parity hacks; do not treat it as the correctness source.
Verification
- Add deterministic math checks for three cases before bag replay: stationary robot should preserve beam geometry exactly; pure translation with a non-zero start yaw should project correctly into the scan-start frame; pure in-place rotation should rotate beams without translation drift.
- Run the standalone node on the provided ROS 2 bag and confirm
/scan_2still publishes continuously while replaying2020-02-16-11-52-35_ros2, then compare the corrected scan againstcloud_from_laser_pubto verify the scan matches the pre-filter cloud rather than a quarter-turn-adjusted variant. - Visually inspect a hallway or corner segment in RViz before and after the change. The corrected output should no longer depend on the robot's absolute heading in the odometry frame, and straight structures should stay straight regardless of scan start orientation.
- If the sector filter remains, replay a turning segment and confirm the data removed to
scan_3is still justified; if it is not, disable it by default and verifyscan_2remains stable without it.
Decisions
- Included: correctness-driven changes that may change published scan geometry, timestamp semantics documentation, and regression coverage for the transform math.
- Excluded: broad ROS 1 backport work, performance tuning of the worker threads, and unrelated message/statistics cleanup unless a math fix exposes a direct dependency.
- Chosen direction: prioritize geometric correctness over legacy bag-tuned output parity.
The core timing and scan-start projection changes were added in commit d99dc6b (fix: dewarp scans in the scan-start frame). The implementation is not yet complete: in particular, the active node still combines odometry poses for base_link with beam vectors expressed in laser without applying the recorded base_link -> laser extrinsic transform (approximately pi yaw). That frame mismatch can still produce the scan/cloud alignment failures described in problems.md.
| Plan phase | Status | Current implementation / remaining work |
|---|---|---|
| 1. Scan-start semantics | Mostly implemented | Module comments and output header stamps consistently use the input scan timestamp as scan start. |
| 2. Beam timing and readiness | Implemented | beam_sample_time_ns() uses scan_start + i * time_increment; scan_end_ns() uses the final real beam at (len(ranges) - 1) * time_increment. |
| 3. Proper 2D point projection | Partially implemented | relative_pose_in_reference_frame() and project_beam_to_reference_frame() replace direct world-frame translation. They currently use base_link odometry axes while publishing in the scan/laser frame; apply the base_link -> laser transform before considering this complete. |
| 4. Remove quarter-turn cloud correction | Implemented | The legacy robot_dth / 4 whole-cloud correction was removed. |
| 5. Re-check downstream scan filter | Functionally disabled | BYPASS_LEGACY_SCAN_FILTER = True, so scan_2 remains the raw rebinned dewarped scan and scan_3 is empty. Convert this global debug flag into an explicit opt-in diagnostic control if it remains useful. |
| 6. Deterministic math checks | Not implemented | Pure helpers were extracted, but no stdlib unit tests or repeatable known-pose check script exists yet. |
| 7. README update | Not implemented | README.md does not yet document scan-start timing, scan-start reference geometry, or the disabled-by-default filter behavior. |
| Replay and RViz validation | Not evidenced | No committed validation demonstrates continuous /scan_2 publishing, cloud/scan alignment, or turning behavior on either the original or corrected ROS 2 bag. |