Refactor & test foxglove image converter
Add a simple test to confirm that it actually produces useful images.
Trying to get a start on better testing for the infrastructure
associated with various calibration stuff, and figured I'd actually add
tests for the code that I added...
Change-Id: I4ac485b1c199f5412c9c6f41a9dd93639ce8f1a1
Signed-off-by: James Kuszmaul <jabukuszmaul@gmail.com>
diff --git a/WORKSPACE b/WORKSPACE
index 3e1801c..aa92b7e 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -549,6 +549,18 @@
actual = "@com_google_googletest//:gtest_main",
)
+http_archive(
+ name = "april_tag_test_image",
+ build_file_content = """
+filegroup(
+ name = "april_tag_test_image",
+ srcs = ["test.bfbs", "expected.jpeg", "expected.png"],
+ visibility = ["//visibility:public"],
+)""",
+ sha256 = "5312c79b19e9883b3cebd9d65b4438a2bf05b41da0bcd8c35e19d22c3b2e1859",
+ urls = ["https://www.frc971.org/Build-Dependencies/test_image_frc971.vision.CameraImage_2023.01.28.tar.gz"],
+)
+
# Recompressed from libusb-1.0.21.7z.
http_file(
name = "libusb_1_0_windows",
diff --git a/frc971/vision/BUILD b/frc971/vision/BUILD
index 308792c..5d20aff 100644
--- a/frc971/vision/BUILD
+++ b/frc971/vision/BUILD
@@ -1,4 +1,5 @@
load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_cc_library", "flatbuffer_py_library")
+load("//aos:config.bzl", "aos_config")
load("@com_github_google_flatbuffers//:typescript.bzl", "flatbuffer_ts_library")
flatbuffer_cc_library(
@@ -262,9 +263,42 @@
hdrs = ["foxglove_image_converter.h"],
visibility = ["//visibility:public"],
deps = [
+ ":charuco_lib",
":vision_fbs",
"//aos/events:event_loop",
"//third_party:opencv",
"@com_github_foxglove_schemas//:schemas",
],
)
+
+aos_config(
+ name = "converter_config",
+ testonly = True,
+ src = "converter_test_config.json",
+ flatbuffers = [
+ "//frc971/vision:vision_fbs",
+ "//aos/events:event_loop_fbs",
+ "//aos/logging:log_message_fbs",
+ "//aos/network:message_bridge_client_fbs",
+ "//aos/network:message_bridge_server_fbs",
+ "//aos/network:timestamp_fbs",
+ "@com_github_foxglove_schemas//:schemas",
+ ],
+)
+
+cc_test(
+ name = "foxglove_image_converter_test",
+ srcs = ["foxglove_image_converter_test.cc"],
+ data = [
+ ":converter_config",
+ "@april_tag_test_image",
+ ],
+ deps = [
+ ":foxglove_image_converter",
+ "//aos:configuration",
+ "//aos/events:simulated_event_loop",
+ "//aos/testing:googletest",
+ "//aos/testing:path",
+ "//aos/testing:tmpdir",
+ ],
+)
diff --git a/frc971/vision/converter_test_config.json b/frc971/vision/converter_test_config.json
new file mode 100644
index 0000000..5d74dd1
--- /dev/null
+++ b/frc971/vision/converter_test_config.json
@@ -0,0 +1,46 @@
+{
+ "channels" : [
+ {
+ "name": "/aos",
+ "type": "aos.timing.Report",
+ "source_node": "test"
+ },
+ {
+ "name": "/aos",
+ "type": "aos.logging.LogMessageFbs",
+ "source_node": "test"
+ },
+ {
+ "name": "/aos",
+ "type": "aos.message_bridge.ClientStatistics",
+ "source_node": "test"
+ },
+ {
+ "name": "/aos",
+ "type": "aos.message_bridge.Timestamp",
+ "source_node": "test"
+ },
+ {
+ "name": "/aos",
+ "type": "aos.message_bridge.ServerStatistics",
+ "source_node": "test"
+ },
+ {
+ "name": "/camera",
+ "type": "frc971.vision.CameraImage",
+ "source_node": "test",
+ "max_size": 10000000
+ },
+ {
+ "name": "/visualize",
+ "type": "foxglove.CompressedImage",
+ "source_node": "test",
+ "max_size": 10000000
+ }
+ ],
+ "nodes": [
+ {
+ "name": "test"
+ }
+ ]
+}
diff --git a/frc971/vision/foxglove_image_converter.cc b/frc971/vision/foxglove_image_converter.cc
index 0c5c736..abde78b 100644
--- a/frc971/vision/foxglove_image_converter.cc
+++ b/frc971/vision/foxglove_image_converter.cc
@@ -3,7 +3,6 @@
#include <opencv2/imgproc.hpp>
namespace frc971::vision {
-namespace {
std::string_view ExtensionForCompression(ImageCompression compression) {
switch (compression) {
case ImageCompression::kJpeg:
@@ -12,25 +11,18 @@
return "png";
}
}
-} // namespace
+
flatbuffers::Offset<foxglove::CompressedImage> CompressImage(
- const CameraImage *raw_image, flatbuffers::FlatBufferBuilder *fbb,
- ImageCompression compression) {
+ const cv::Mat image, const aos::monotonic_clock::time_point eof,
+ flatbuffers::FlatBufferBuilder *fbb, ImageCompression compression) {
std::string_view format = ExtensionForCompression(compression);
// imencode doesn't let us pass in anything other than an std::vector, and
// performance isn't yet a big enough issue to try to avoid the copy.
std::vector<uint8_t> buffer;
- CHECK(raw_image->has_data());
- cv::Mat image_color_mat(cv::Size(raw_image->cols(), raw_image->rows()),
- CV_8UC2, (void *)raw_image->data()->data());
- cv::Mat bgr_image(cv::Size(raw_image->cols(), raw_image->rows()), CV_8UC3);
- cv::cvtColor(image_color_mat, bgr_image, cv::COLOR_YUV2BGR_YUYV);
- CHECK(cv::imencode(absl::StrCat(".", format), bgr_image, buffer));
+ CHECK(cv::imencode(absl::StrCat(".", format), image, buffer));
const flatbuffers::Offset<flatbuffers::Vector<uint8_t>> data_offset =
fbb->CreateVector(buffer);
- const struct timespec timestamp_t =
- aos::time::to_timespec(aos::monotonic_clock::time_point(
- std::chrono::nanoseconds(raw_image->monotonic_timestamp_ns())));
+ const struct timespec timestamp_t = aos::time::to_timespec(eof);
const foxglove::Time time{static_cast<uint32_t>(timestamp_t.tv_sec),
static_cast<uint32_t>(timestamp_t.tv_nsec)};
const flatbuffers::Offset<flatbuffers::String> format_offset =
@@ -47,13 +39,14 @@
std::string_view output_channel,
ImageCompression compression)
: event_loop_(event_loop),
+ image_callback_(
+ event_loop_, input_channel,
+ [this, compression](const cv::Mat image,
+ const aos::monotonic_clock::time_point eof) {
+ auto builder = sender_.MakeBuilder();
+ builder.CheckOk(builder.Send(
+ CompressImage(image, eof, builder.fbb(), compression)));
+ }),
sender_(
- event_loop_->MakeSender<foxglove::CompressedImage>(output_channel)) {
- event_loop_->MakeWatcher(input_channel, [this, compression](
- const CameraImage &image) {
- auto builder = sender_.MakeBuilder();
- builder.CheckOk(
- builder.Send(CompressImage(&image, builder.fbb(), compression)));
- });
-}
+ event_loop_->MakeSender<foxglove::CompressedImage>(output_channel)) {}
} // namespace frc971::vision
diff --git a/frc971/vision/foxglove_image_converter.h b/frc971/vision/foxglove_image_converter.h
index add83a6..872ac14 100644
--- a/frc971/vision/foxglove_image_converter.h
+++ b/frc971/vision/foxglove_image_converter.h
@@ -1,8 +1,9 @@
#ifndef FRC971_VISION_FOXGLOVE_IMAGE_CONVERTER_H_
#define FRC971_VISION_FOXGLOVE_IMAGE_CONVERTER_H_
-#include "external/com_github_foxglove_schemas/CompressedImage_generated.h"
-#include "frc971/vision/vision_generated.h"
#include "aos/events/event_loop.h"
+#include "external/com_github_foxglove_schemas/CompressedImage_generated.h"
+#include "frc971/vision/charuco_lib.h"
+#include "frc971/vision/vision_generated.h"
namespace frc971::vision {
// Empirically, from 2022 logs:
@@ -12,9 +13,11 @@
// conversion with a user-script in Foxglove Studio.
enum class ImageCompression { kJpeg, kPng };
+std::string_view ExtensionForCompression(ImageCompression compression);
+
flatbuffers::Offset<foxglove::CompressedImage> CompressImage(
- const CameraImage *raw_image, flatbuffers::FlatBufferBuilder *fbb,
- ImageCompression compression);
+ const cv::Mat image, const aos::monotonic_clock::time_point eof,
+ flatbuffers::FlatBufferBuilder *fbb, ImageCompression compression);
// This class provides a simple converter that will take an AOS CameraImage
// channel and output
@@ -30,6 +33,7 @@
private:
aos::EventLoop *event_loop_;
+ ImageCallback image_callback_;
aos::Sender<foxglove::CompressedImage> sender_;
};
} // namespace frc971::vision
diff --git a/frc971/vision/foxglove_image_converter_test.cc b/frc971/vision/foxglove_image_converter_test.cc
new file mode 100644
index 0000000..65b3b6b
--- /dev/null
+++ b/frc971/vision/foxglove_image_converter_test.cc
@@ -0,0 +1,68 @@
+#include "frc971/vision/foxglove_image_converter.h"
+
+#include "aos/events/simulated_event_loop.h"
+#include "aos/json_to_flatbuffer.h"
+#include "aos/testing/path.h"
+#include "aos/testing/tmpdir.h"
+#include "gtest/gtest.h"
+
+namespace frc971::vision {
+std::ostream &operator<<(std::ostream &os, ImageCompression compression) {
+ os << ExtensionForCompression(compression);
+ return os;
+}
+namespace testing {
+class ImageConverterTest : public ::testing::TestWithParam<ImageCompression> {
+ protected:
+ ImageConverterTest()
+ : config_(aos::configuration::ReadConfig(
+ aos::testing::ArtifactPath("frc971/vision/converter_config.json"))),
+ factory_(&config_.message()),
+ camera_image_(
+ aos::FileToFlatbuffer<CameraImage>(aos::testing::ArtifactPath(
+ "external/april_tag_test_image/test.bfbs"))),
+ node_(aos::configuration::GetNode(&config_.message(), "test")),
+ test_event_loop_(factory_.MakeEventLoop("test", node_)),
+ image_sender_(test_event_loop_->MakeSender<CameraImage>("/camera")),
+ converter_event_loop_(factory_.MakeEventLoop("converter", node_)),
+ converter_(converter_event_loop_.get(), "/camera", "/visualize",
+ GetParam()),
+ output_path_(absl::StrCat(aos::testing::TestTmpDir(), "/test.",
+ ExtensionForCompression(GetParam()))) {
+ test_event_loop_->OnRun(
+ [this]() { image_sender_.CheckOk(image_sender_.Send(camera_image_)); });
+ test_event_loop_->MakeWatcher(
+ "/visualize", [this](const foxglove::CompressedImage &image) {
+ ASSERT_TRUE(image.has_data());
+ std::string expected_contents =
+ aos::util::ReadFileToStringOrDie(aos::testing::ArtifactPath(
+ absl::StrCat("external/april_tag_test_image/expected.",
+ ExtensionForCompression(GetParam()))));
+ std::string_view data(
+ reinterpret_cast<const char *>(image.data()->data()),
+ image.data()->size());
+ EXPECT_EQ(expected_contents, data);
+ aos::util::WriteStringToFileOrDie(output_path_, data);
+ factory_.Exit();
+ });
+ }
+
+ aos::FlatbufferDetachedBuffer<aos::Configuration> config_;
+ aos::SimulatedEventLoopFactory factory_;
+ aos::FlatbufferVector<CameraImage> camera_image_;
+ const aos::Node *const node_;
+ std::unique_ptr<aos::EventLoop> test_event_loop_;
+ aos::Sender<CameraImage> image_sender_;
+ std::unique_ptr<aos::EventLoop> converter_event_loop_;
+ FoxgloveImageConverter converter_;
+ std::string output_path_;
+};
+
+TEST_P(ImageConverterTest, ImageToFoxglove) { factory_.Run(); }
+
+INSTANTIATE_TEST_SUITE_P(CompressionOptions, ImageConverterTest,
+ ::testing::Values(ImageCompression::kJpeg,
+ ImageCompression::kPng));
+
+} // namespace testing
+} // namespace frc971::vision