CARLA
Loading...
Searching...
No Matches
test_optical_flow_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 <algorithm>
12#include <array>
13#include <cmath>
14#include <vector>
15
16TEST(OpticalFlowEncoding, zero_flow_is_black_with_zero_alpha) {
17 // No motion -> magnitude 0 -> intensity clamped to 0 -> all RGB channels 0.
18 // The publisher convention sets the alpha byte to 0.
19 const auto bgra = carla::ros2::EncodeFlowPixelToBgra(0.0f, 0.0f);
20 EXPECT_EQ(bgra[0], 0u);
21 EXPECT_EQ(bgra[1], 0u);
22 EXPECT_EQ(bgra[2], 0u);
23 EXPECT_EQ(bgra[3], 0u);
24}
25
26TEST(OpticalFlowEncoding, alpha_is_always_zero) {
27 // The encoding deliberately sets the alpha byte to 0 regardless of input;
28 // ROS 2 subscribers rely on that to detect missing-data pixels.
29 for (float vx : {-1.0f, -0.1f, 0.0f, 0.1f, 1.0f, 10.0f}) {
30 for (float vy : {-1.0f, -0.1f, 0.0f, 0.1f, 1.0f, 10.0f}) {
31 const auto bgra = carla::ros2::EncodeFlowPixelToBgra(vx, vy);
32 EXPECT_EQ(bgra[3], 0u) << "vx=" << vx << " vy=" << vy;
33 }
34 }
35}
36
37TEST(OpticalFlowEncoding, large_magnitude_saturates_brightness) {
38 // A very large flow magnitude pushes log(norm + shift) above 1; the clamp
39 // pins the intensity at 1 so at least one channel saturates near 255.
40 const auto bgra = carla::ros2::EncodeFlowPixelToBgra(1000.0f, 1000.0f);
41 const int max_channel = std::max({static_cast<int>(bgra[0]),
42 static_cast<int>(bgra[1]),
43 static_cast<int>(bgra[2])});
44 EXPECT_GE(max_channel, 250);
45}
46
47TEST(OpticalFlowEncoding, positive_vx_lands_in_cyan_hemisphere) {
48 // vx > 0, vy = 0 -> atan2(0, vx) = 0 -> hue = 180 deg (cyan-ish). HSV's
49 // h_60 = 3 (case 3) puts the colour in the {0, x, c} channel triple, so
50 // the red channel must be the smallest of the three RGB bytes.
51 const auto bgra = carla::ros2::EncodeFlowPixelToBgra(2.0f, 0.0f);
52 // bgra order: B, G, R, A. Red is index 2.
53 EXPECT_LE(bgra[2], bgra[0]);
54 EXPECT_LE(bgra[2], bgra[1]);
55}
56
57TEST(OpticalFlowEncoding, negative_vx_lands_in_red_hemisphere) {
58 // vx < 0, vy = 0 -> atan2(0, -vx) = pi -> hue wraps to 0 (red-ish). Red is
59 // the dominant channel.
60 const auto bgra = carla::ros2::EncodeFlowPixelToBgra(-2.0f, 0.0f);
61 EXPECT_GE(bgra[2], bgra[0]);
62 EXPECT_GE(bgra[2], bgra[1]);
63}
64
65TEST(OpticalFlowEncoding, buffer_length_matches_height_times_width_times_four) {
66 // height * width * 4 BGRA bytes out, exactly.
67 constexpr uint32_t h = 4;
68 constexpr uint32_t w = 5;
69 std::vector<float> input(static_cast<size_t>(h) * w * 2, 0.5f);
70 const auto out = carla::ros2::EncodeFlowImageToBgra(h, w, input.data());
71 EXPECT_EQ(out.size(), static_cast<size_t>(h) * w * 4u);
72}
73
74TEST(OpticalFlowEncoding, buffer_per_pixel_matches_pixel_helper) {
75 // The buffer-level helper is just a loop over the per-pixel helper. Verify
76 // it: for a 2x2 image with distinct (vx, vy) per pixel, every output BGRA
77 // quad equals the per-pixel encoding.
78 std::array<float, 8> input = {
79 0.0f, 0.0f,
80 1.0f, 0.0f,
81 0.0f, 1.0f,
82 -1.0f, -1.0f,
83 };
84 const auto out = carla::ros2::EncodeFlowImageToBgra(2u, 2u, input.data());
85 for (size_t i = 0; i < 4; ++i) {
86 const auto expected = carla::ros2::EncodeFlowPixelToBgra(
87 input[i * 2], input[i * 2 + 1]);
88 EXPECT_EQ(out[i * 4 + 0], expected[0]) << "pixel " << i;
89 EXPECT_EQ(out[i * 4 + 1], expected[1]) << "pixel " << i;
90 EXPECT_EQ(out[i * 4 + 2], expected[2]) << "pixel " << i;
91 EXPECT_EQ(out[i * 4 + 3], expected[3]) << "pixel " << i;
92 }
93}
std::vector< uint8_t > EncodeFlowImageToBgra(uint32_t height, uint32_t width, const float *data)
std::array< uint8_t, 4 > EncodeFlowPixelToBgra(float vx, float vy) noexcept
TEST(OpticalFlowEncoding, zero_flow_is_black_with_zero_alpha)