Skip to content

Incorrect LaserScan angular resolution for solid-state RTX LiDAR in Isaac Sim #896

Open

Description

@sybaekrinfra

Description

OgnROS2RtxLidarHelper.py calculates the horizontal angular resolution for solid-state RTX LiDAR using:

h_res = h_fov / float(len(azimuth_deg))

For N uniformly spaced samples spanning an inclusive angular interval, the expected resolution is:

h_res = h_fov / float(len(azimuth_deg) - 1)

This appears inconsistent with ROS 2 sensor_msgs/LaserScan semantics, where angle_increment represents the angular separation between consecutive measurements.

Expected behavior: The published angular metadata should be consistent with the actual sample bearings, including both endpoints when applicable.

Actual behavior: The metadata calculation divides the FOV by the number of emitter angles rather than the number of intervals.

Request: Please confirm whether this is intentional and review the angular resolution calculation for solid-state RTX LiDAR.

Isaac Sim version

6.2.0

Operating System (OS)

Ubuntu 24.04

GPU Name

RTX 6000 Ada

GPU Driver and CUDA versions

595, CUDA 13.2

Logs

No response

Additional information

No response

Activity

  1. sybaekrinfra commented on Oct 21, 2026

    @sybaekrinfra
    Author

    Impact

    With the current metadata, slam_toolbox (Karto) rejects every scan from a solid-state RTX LiDAR (SICK_nanoScan3 profile, N = 1651):

    LaserRangeScan contains 1651 range readings, expected 1652
    

    (angle_max - angle_min) / angle_increment + 1 evaluates to N + 1. In our test, SLAM never updated the map until the metadata was corrected. After the correction, the pose error against ground truth was 3–6 cm.

    Where the data actually lands

    In OgnROS2PublishLaserScan.cpp (L194), beam k is binned with outIdx = (size_t)((az_k - az_start) / h_res). With h_res = fov / N and az_k = az_start + k * fov / (N - 1), this gives floor(k * N / (N - 1)) = k for k < N - 1. The last beam lands on index N, which the clamp at L197 moves back to N - 1.

    So slot i already holds the beam at az_start + i * fov / (N - 1), and angle_min / angle_max already match the first and last beams. Only angle_increment is wrong.

    Suggested fix

    1. OgnROS2RtxLidarHelper.py L127. The same file exists under both nodes/ and ogn/python/nodes/.

      if len(azimuth_deg) < 2:
          carb.log_error("LaserScan SRTX: need at least two azimuth samples")
          return None
      h_res = h_fov / float(len(azimuth_deg) - 1)
    2. OgnROS2PublishLaserScan.cpp. This is needed together with (1). Otherwise L158 truncates fov / h_res to N - 1 or fewer elements, and the last beam collides with its neighbour.

      // N samples span the FOV inclusively (solid-state); a full 360° scan must not duplicate the seam.
      const bool fullCircle = horizontalFov >= 360.0f - 1e-3f;
      const size_t numOutputElements =
          static_cast<size_t>(std::lround(horizontalFov / horizontalRes)) + (fullCircle ? 0 : 1);
      ...
      const long idx = std::lround((azimuth - azimuthRangeStart) / horizontalRes);
      size_t outIdx = idx < 0 ? 0 : static_cast<size_t>(idx);
      if (outIdx >= numOutputElements)
      {
          outIdx = fullCircle ? outIdx % numOutputElements : numOutputElements - 1;
      }

      lround keeps beams that sit exactly on a bin boundary from being floored into the previous slot. The current static_cast<size_t> of a negative float is undefined behaviour before the clamp.

    3. When writing the message, derive angle_max from the element count so that the invariant always holds:
      angle_max = angle_min + (numOutputElements - 1) * angle_increment

    Rotating LiDARs

    The rotating branch (L135, fov = 360, angle_min = -180°, angle_max = 180°, N = 360 / h_res) seems to imply N + 1 ranges as well. Could you check whether it is affected too?

    Workaround we use

    We republish each scan with angle_increment = (angle_max - angle_min) / (len(ranges) - 1), keeping angle_min and angle_max unchanged.

  2. vick-yu commented on Oct 22, 2026

    @vick-yu

    @sybaekrinfra Thanks for the detailed breakdown—this is indeed a bug, and your analysis regarding the solid-state RTX LiDAR horizontal angular resolution calculation in OgnROS2RtxLidarHelper.py is spot on. We appreciate you highlighting the impact on packages like slam_toolbox.

    We have logged an internal tracking ticket (6978593) to address this fix in an upcoming release and will keep this thread updated with our progress. Thanks again for contributing such a thorough root-cause analysis!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions