CARLA
Loading...
Searching...
No Matches
test_dvs_event_encoding.cpp
Go to the documentation of this file.
1// Copyright (c) 2026 Computer Vision Center (CVC) at the Universitat Autonoma
2// de Barcelona (UAB).
3//
4// This work is licensed under the terms of the MIT license.
5// For a copy, see <https://opensource.org/licenses/MIT>.
6
7#include "test.h"
8
10
11#include <array>
12#include <cstdint>
13#include <cstring>
14
15TEST(DvsEventEncoding, empty_event_stream_produces_all_zeros) {
16 const auto buffer = carla::ros2::EncodeDvsEventsToBgr(
17 4u, 4u, static_cast<const carla::ros2::DvsEvent *>(nullptr), 0u);
18 ASSERT_EQ(buffer.size(), 4u * 4u * 3u);
19 for (auto byte : buffer) {
20 EXPECT_EQ(byte, 0u);
21 }
22}
23
24TEST(DvsEventEncoding, buffer_length_is_height_times_width_times_three) {
25 const std::array<carla::ros2::DvsEvent, 0> events = {};
26 const auto buffer = carla::ros2::EncodeDvsEventsToBgr(
27 8u, 5u, events.data(), events.size());
28 EXPECT_EQ(buffer.size(), 8u * 5u * 3u);
29}
30
31TEST(DvsEventEncoding, positive_polarity_lights_red_channel) {
32 // Positive polarity: index offset 2 (red). At pixel (x=1, y=0) in a
33 // 4x2 image the red byte sits at (0*4 + 1)*3 + 2 = 5.
34 const std::array<carla::ros2::DvsEvent, 1> events = {
35 carla::ros2::DvsEvent{1, 0, 0, true},
36 };
37 const auto buffer = carla::ros2::EncodeDvsEventsToBgr(
38 2u, 4u, events.data(), events.size());
39 EXPECT_EQ(buffer[5], 255u);
40 EXPECT_EQ(buffer[3], 0u);
41 EXPECT_EQ(buffer[4], 0u);
42}
43
44TEST(DvsEventEncoding, negative_polarity_lights_blue_channel) {
45 // Negative polarity: index offset 0 (blue). At pixel (x=2, y=1) in a
46 // 4x2 image the blue byte sits at (1*4 + 2)*3 + 0 = 18.
47 const std::array<carla::ros2::DvsEvent, 1> events = {
48 carla::ros2::DvsEvent{2, 1, 0, false},
49 };
50 const auto buffer = carla::ros2::EncodeDvsEventsToBgr(
51 2u, 4u, events.data(), events.size());
52 EXPECT_EQ(buffer[18], 255u);
53 EXPECT_EQ(buffer[19], 0u);
54 EXPECT_EQ(buffer[20], 0u);
55}
56
57TEST(DvsEventEncoding, mixed_polarities_at_distinct_pixels_dont_collide) {
58 const std::array<carla::ros2::DvsEvent, 2> events = {
59 carla::ros2::DvsEvent{0, 0, 0, true},
60 carla::ros2::DvsEvent{1, 0, 0, false},
61 };
62 const auto buffer = carla::ros2::EncodeDvsEventsToBgr(
63 1u, 2u, events.data(), events.size());
64 // pixel (0,0): red byte = 2.
65 EXPECT_EQ(buffer[2], 255u);
66 // pixel (1,0): blue byte = 3.
67 EXPECT_EQ(buffer[3], 255u);
68}
69
70TEST(DvsEventEncoding, out_of_bounds_events_are_dropped) {
71 // x or y >= width / height must be silently skipped to mirror the legacy
72 // publisher behaviour (subscribers were tolerant of stray events on the
73 // pre-PR-4 wire format).
74 const std::array<carla::ros2::DvsEvent, 3> events = {
75 carla::ros2::DvsEvent{99, 0, 0, true},
76 carla::ros2::DvsEvent{0, 99, 0, true},
77 carla::ros2::DvsEvent{2, 2, 0, true},
78 };
79 const auto buffer = carla::ros2::EncodeDvsEventsToBgr(
80 4u, 4u, events.data(), events.size());
81 ASSERT_EQ(buffer.size(), 4u * 4u * 3u);
82 // Only the in-bounds event lit a pixel; sum equals 255.
83 std::uint32_t sum = 0u;
84 for (auto byte : buffer) {
85 sum += byte;
86 }
87 EXPECT_EQ(sum, 255u);
88}
89
90TEST(DvsEventEncoding, raw_byte_overload_matches_struct_overload) {
91 // Packed wire layout for sensor::data::DVSEvent: 13 bytes (2 + 2 + 8 + 1).
92 // Build a stride-13 byte stream and confirm both overloads agree.
93 // MSVC does not support GCC's `__attribute__((packed))`; use `#pragma pack`
94 // there (including clang-cl, which defines `_MSC_VER`).
95#ifdef _MSC_VER
96#pragma pack(push, 1)
97#endif
98 struct PackedEvent {
99 std::uint16_t x;
100 std::uint16_t y;
101 std::int64_t t;
102 bool pol;
103#ifdef _MSC_VER
104 };
105#pragma pack(pop)
106#else
107 } __attribute__((packed));
108#endif
109
110 PackedEvent raw_events[2] = {
111 {2u, 1u, 100, true},
112 {0u, 0u, 200, false},
113 };
114 const carla::ros2::DvsEvent typed_events[2] = {
115 {2u, 1u, 100, true},
116 {0u, 0u, 200, false},
117 };
118
119 const auto from_raw = carla::ros2::EncodeDvsEventsToBgr(
120 4u, 4u,
121 reinterpret_cast<const std::uint8_t *>(raw_events),
122 2u,
123 sizeof(PackedEvent));
124 const auto from_typed = carla::ros2::EncodeDvsEventsToBgr(
125 4u, 4u, typed_events, 2u);
126 EXPECT_EQ(from_raw, from_typed);
127}
128
129namespace {
130
131// Reads a field of type T back out of the packed PointCloud2 byte buffer at the
132// given byte offset within the per-event record (stride = sizeof(DvsEvent)).
133template <typename T>
134T ReadDvsPointCloudField(
135 const std::vector<std::uint8_t> &bytes, std::size_t event_index, std::size_t offset) {
136 T value{};
137 std::memcpy(
138 &value,
139 bytes.data() + event_index * sizeof(carla::ros2::DvsEvent) + offset,
140 sizeof(T));
141 return value;
142}
143
144} // namespace
145
146TEST(DvsEventEncoding, point_cloud_buffer_length_is_event_count_times_stride) {
147 const std::array<carla::ros2::DvsEvent, 3> events = {
148 carla::ros2::DvsEvent{0, 0, 0, true},
149 carla::ros2::DvsEvent{1, 1, 1, false},
150 carla::ros2::DvsEvent{2, 2, 2, true},
151 };
153 events.data(), events.size());
154 EXPECT_EQ(bytes.size(), events.size() * sizeof(carla::ros2::DvsEvent));
155 EXPECT_EQ(sizeof(carla::ros2::DvsEvent), 13u);
156}
157
158TEST(DvsEventEncoding, point_cloud_timestamp_is_a_real_double_not_reinterpreted_bytes) {
159 // The headline fix: the int64 timestamp must land in the FLOAT64 `t` slot as
160 // a converted double, not as raw int64 bits. 1234567 as a double reads back
161 // exactly; as reinterpreted int64 bits it would be a denormal near zero.
162 const std::array<carla::ros2::DvsEvent, 1> events = {
163 carla::ros2::DvsEvent{7, 9, 1234567, true},
164 };
166 events.data(), events.size());
167 const double t = ReadDvsPointCloudField<double>(bytes, 0u, 4u);
168 EXPECT_DOUBLE_EQ(t, 1234567.0);
169}
170
171TEST(DvsEventEncoding, point_cloud_packs_x_y_pol_at_declared_offsets) {
172 // kDvsFields layout: x UINT16 @0, y UINT16 @2, t FLOAT64 @4, pol INT8 @12.
173 const std::array<carla::ros2::DvsEvent, 2> events = {
174 carla::ros2::DvsEvent{11, 22, 100, true},
175 carla::ros2::DvsEvent{33, 44, 200, false},
176 };
178 events.data(), events.size());
179
180 EXPECT_EQ(ReadDvsPointCloudField<std::uint16_t>(bytes, 0u, 0u), 11u);
181 EXPECT_EQ(ReadDvsPointCloudField<std::uint16_t>(bytes, 0u, 2u), 22u);
182 EXPECT_DOUBLE_EQ(ReadDvsPointCloudField<double>(bytes, 0u, 4u), 100.0);
183 EXPECT_EQ(ReadDvsPointCloudField<std::int8_t>(bytes, 0u, 12u), 1);
184
185 EXPECT_EQ(ReadDvsPointCloudField<std::uint16_t>(bytes, 1u, 0u), 33u);
186 EXPECT_EQ(ReadDvsPointCloudField<std::uint16_t>(bytes, 1u, 2u), 44u);
187 EXPECT_DOUBLE_EQ(ReadDvsPointCloudField<double>(bytes, 1u, 4u), 200.0);
188 EXPECT_EQ(ReadDvsPointCloudField<std::int8_t>(bytes, 1u, 12u), 0);
189}
190
191TEST(DvsEventEncoding, point_cloud_raw_byte_overload_matches_struct_overload) {
192 const carla::ros2::DvsEvent typed_events[2] = {
193 {2u, 1u, 100, true},
194 {0u, 5u, -9, false},
195 };
196 // The seam's own DvsEvent is already #pragma pack(push, 1), so it doubles as
197 // a portable packed wire record for the raw-byte overload.
198 const auto from_raw = carla::ros2::EncodeDvsEventsToPointCloud(
199 reinterpret_cast<const std::uint8_t *>(typed_events),
200 2u,
201 sizeof(carla::ros2::DvsEvent));
202 const auto from_typed = carla::ros2::EncodeDvsEventsToPointCloud(
203 typed_events, 2u);
204 EXPECT_EQ(from_raw, from_typed);
205}
float y
float x
std::vector< std::uint8_t > EncodeDvsEventsToBgr(std::uint32_t height, std::uint32_t width, const DvsEvent *events, std::size_t event_count)
std::vector< std::uint8_t > EncodeDvsEventsToPointCloud(const DvsEvent *events, std::size_t event_count)
TEST(DvsEventEncoding, empty_event_stream_produces_all_zeros)