You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Incorrect LaserScan angular resolution for solid-state RTX LiDAR in Isaac Sim #896
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.
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
OgnROS2RtxLidarHelper.py L127. The same file exists under both nodes/ and ogn/python/nodes/.
iflen(azimuth_deg) <2:
carb.log_error("LaserScan SRTX: need at least two azimuth samples")
returnNoneh_res=h_fov/float(len(azimuth_deg) -1)
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.constbool fullCircle = horizontalFov >= 360.0f - 1e-3f;
constsize_t numOutputElements =
static_cast<size_t>(std::lround(horizontalFov / horizontalRes)) + (fullCircle ? 0 : 1);
...
constlong 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.
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.
@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!
Activity
sybaekrinfra commentedon Oct 21, 2026
Impact
With the current metadata,
slam_toolbox(Karto) rejects every scan from a solid-state RTX LiDAR (SICK_nanoScan3 profile, N = 1651):(angle_max - angle_min) / angle_increment + 1evaluates 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 withoutIdx = (size_t)((az_k - az_start) / h_res). Withh_res = fov / Nandaz_k = az_start + k * fov / (N - 1), this givesfloor(k * N / (N - 1)) = kfork < 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), andangle_min/angle_maxalready match the first and last beams. Onlyangle_incrementis wrong.Suggested fix
OgnROS2RtxLidarHelper.pyL127. The same file exists under bothnodes/andogn/python/nodes/.OgnROS2PublishLaserScan.cpp. This is needed together with (1). Otherwise L158 truncatesfov / h_resto N - 1 or fewer elements, and the last beam collides with its neighbour.lroundkeeps beams that sit exactly on a bin boundary from being floored into the previous slot. The currentstatic_cast<size_t>of a negative float is undefined behaviour before the clamp.When writing the message, derive
angle_maxfrom the element count so that the invariant always holds:angle_max = angle_min + (numOutputElements - 1) * angle_incrementRotating 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), keepingangle_minandangle_maxunchanged.vick-yu commentedon Oct 22, 2026
@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!