Skip to content

Commit 07aee65

Browse files
hakuturu583claude
andcommitted
fix: apply per-channel azimuth corrections for OT128 decoder
Add per-channel azimuth offset from the OT128 Angle Correction CSV. Channels 25-88 have offsets up to ±4.6° which caused visible projection misalignment without correction. XT32 azimuth offsets are all 0 per the official CSV. Also verified XT32 elevation angles match the CSV exactly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a9b460d commit 07aee65

1 file changed

Lines changed: 38 additions & 5 deletions

File tree

t4_devkit/rosbag/pandar_decoder.py

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,18 +72,25 @@ class _HesaiModelConfig:
7272

7373
name: str
7474
elevation_deg: list[float] = field(repr=False)
75+
azimuth_offset_deg: list[float] = field(repr=False)
7576
cos_el: np.ndarray = field(init=False, repr=False, compare=False)
7677
sin_el: np.ndarray = field(init=False, repr=False, compare=False)
78+
azimuth_offset_rad: np.ndarray = field(init=False, repr=False, compare=False)
7779

7880
def __post_init__(self) -> None:
7981
el_rad = np.radians(np.array(self.elevation_deg, dtype=np.float32))
8082
object.__setattr__(self, "cos_el", np.cos(el_rad))
8183
object.__setattr__(self, "sin_el", np.sin(el_rad))
84+
object.__setattr__(
85+
self, "azimuth_offset_rad",
86+
np.radians(np.array(self.azimuth_offset_deg, dtype=np.float32)),
87+
)
8288

8389

8490
# XT32: 32 channels, 1° spacing from +15° to -16°
8591
# https://www.hesaitech.com/product/xt16-32-32m/
8692
_XT32_ELEVATION_DEG: list[float] = [float(15 - i) for i in range(32)]
93+
_XT32_AZIMUTH_OFFSET_DEG: list[float] = [0.0] * 32
8794

8895
# OT128: 128 channels, non-uniform spacing from +14.985° to -24.765°
8996
# Per Hesai OT128 User Manual (O01-en-260410), Appendix A / Angle Correction File:
@@ -107,12 +114,36 @@ def __post_init__(self) -> None:
107114
-14.879, -15.237, -15.593, -15.948, -16.299, -16.651, -17.0, -17.347,
108115
-17.701, -18.386, -19.063, -19.73, -20.376, -21.653, -23.044, -24.765,
109116
]
117+
_OT128_AZIMUTH_OFFSET_DEG: list[float] = [
118+
0.186, 0.185, 1.335, 1.343, 0.148, 0.147, 0.146, 0.146,
119+
1.335, 1.336, 1.337, 1.338, 1.339, 1.34, 1.341, 1.342,
120+
0.128, 0.128, 0.127, 0.127, 0.107, 0.106, 0.105, 0.105,
121+
-3.118, 1.315, 4.529, -3.121, 1.316, 4.532, -3.124, 1.317,
122+
4.536, -3.127, 1.317, 4.539, -3.13, 1.318, 4.542, -3.133,
123+
0.103, 2.935, -1.517, 0.103, 2.937, -1.519, 0.103, 2.939,
124+
-1.52, 0.103, 2.941, -1.521, 0.102, 2.943, -1.523, 0.102,
125+
2.945, -1.524, 0.102, 2.946, -1.526, 0.102, 2.948, -1.526,
126+
1.324, 4.57, -3.155, 1.325, 4.573, -3.157, 1.326, 4.575,
127+
-3.159, 1.326, 4.578, -3.161, 1.327, 4.581, -3.163, 1.328,
128+
4.583, -3.165, 1.329, 4.586, -3.167, 1.329, 4.588, -3.168,
129+
0.102, 0.103, 0.103, 0.103, 0.104, 0.104, 0.104, 0.104,
130+
1.337, 1.337, 1.338, 1.339, 1.34, 1.341, 1.341, 1.342,
131+
0.108, 0.108, 0.109, 0.109, 0.13, 0.131, 0.131, 0.132,
132+
1.384, 1.384, 1.385, 1.385, 1.386, 1.386, 1.387, 1.387,
133+
0.151, 0.153, 0.154, 0.156, 1.388, 1.408, 0.196, 0.286,
134+
]
110135
# fmt: on
111136

112137
# Model lookup by sensor type name.
113138
HESAI_MODELS: dict[str, _HesaiModelConfig] = {
114-
"XT32": _HesaiModelConfig(name="XT32", elevation_deg=_XT32_ELEVATION_DEG),
115-
"OT128": _HesaiModelConfig(name="OT128", elevation_deg=_OT128_ELEVATION_DEG),
139+
"XT32": _HesaiModelConfig(
140+
name="XT32", elevation_deg=_XT32_ELEVATION_DEG,
141+
azimuth_offset_deg=_XT32_AZIMUTH_OFFSET_DEG,
142+
),
143+
"OT128": _HesaiModelConfig(
144+
name="OT128", elevation_deg=_OT128_ELEVATION_DEG,
145+
azimuth_offset_deg=_OT128_AZIMUTH_OFFSET_DEG,
146+
),
116147
}
117148

118149

@@ -262,13 +293,15 @@ def _decode_packet(
262293
if not np.any(valid):
263294
return _EMPTY_POINTS
264295

265-
azimuths_rad = np.radians(azimuths_raw.astype(np.float32) / 100.0)
296+
# Per-channel azimuth: block azimuth + channel-specific offset
297+
block_az_rad = np.radians(azimuths_raw.astype(np.float32) / 100.0)
298+
azimuths_rad = block_az_rad[:, np.newaxis] + config.azimuth_offset_rad
266299

267300
# Hesai native frame: x = d*cos(el)*sin(az), y = d*cos(el)*cos(az), z = d*sin(el)
268301
# The TF tree (hesai_top -> base_link) handles the frame conversion.
269302
xy_dist = distances * cos_el
270-
x = xy_dist * np.sin(azimuths_rad[:, np.newaxis])
271-
y = xy_dist * np.cos(azimuths_rad[:, np.newaxis])
303+
x = xy_dist * np.sin(azimuths_rad)
304+
y = xy_dist * np.cos(azimuths_rad)
272305
z = distances * sin_el
273306

274307
return np.stack([x[valid], y[valid], z[valid], reflectivities[valid]], axis=0)

0 commit comments

Comments
 (0)