[image] connection block between opencv and rpc

Details:
  imageRPC for now assumed to have 4 fields:
  width,height of type int (4 signed bytes)
  type (enum BGR,RGBA,DEPTH)
  and raw vector of data

tests written by opencode + gemini flash

TG-7
This commit is contained in:
Artur Mukhamadiev 2026-03-12 22:04:26 +03:00
parent 9cb3f009ea
commit f5f4bd4115
6 changed files with 172 additions and 5 deletions

View File

@ -8,10 +8,10 @@ namespace score {
class Image { class Image {
public: public:
Image(); Image();
explicit Image(cv::Mat&& image); explicit Image(const cv::Mat& image);
~Image(); ~Image();
cv::Mat get() const; [[nodiscard]] cv::Mat get() const;
protected: protected:
cv::Mat data_; cv::Mat data_;

View File

@ -0,0 +1,40 @@
//
// Created by vptyp on 12.03.2026.
//
#pragma once
#include <cloud_point/image.h>
#include <cloud_point_rpc/imageRpc.h>
namespace score {
class ImageFactory {
public:
/**
* @brief tries to decode available RPC image type to opencv compliant
* @return opencv compliant image type
* @throw runtime_error if type is unknown
*/
static int pixelType(const ImageRPC::Type &type) {
switch (type) {
case ImageRPC::Type::BGR:
return CV_8UC3;
case ImageRPC::Type::RGBA:
return CV_8UC4;
case ImageRPC::Type::DEPTH:
return CV_64FC1;
default:
throw std::runtime_error("Unknown image type");
}
}
/**
* @brief tries to create Image object from ImageRPC
* @throw runtime_error if type is unknown
*/
static Image create(const ImageRPC &image) {
cv::Mat imageMat(image.width, image.height, pixelType(image.type),
const_cast<unsigned char *>(image.data.data()));
return Image{imageMat};
}
};
} // namespace score

View File

@ -0,0 +1,21 @@
//
// Created by vptyp on 12.03.2026.
//
#pragma once
#include <vector>
namespace score {
struct ImageRPC {
int width{0};
int height{0};
enum class Type {
UNKNOWN,
BGR,
RGBA,
DEPTH,
} type{Type::UNKNOWN};
std::vector<unsigned char> data;
};
} // namespace score

View File

@ -9,8 +9,8 @@ namespace score {
//no work //no work
} }
Image::Image(cv::Mat&& image) { Image::Image(const cv::Mat& image) {
this->data_ = std::move(image); this->data_ = image;
} }
Image::~Image() = default; Image::~Image() = default;

View File

@ -6,9 +6,24 @@ test_sources = files(
'test_c_api.cpp', 'test_c_api.cpp',
'test_base64.cpp' 'test_base64.cpp'
) )
test_deps = [cloud_point_rpc_dep, cloud_point_rpc_cli_dep,
cloud_point_rpc_test_dep, json_dep, gtest_dep,
gtest_main_dep, gmock_dep]
if opencv_dep.found()
message('found cloud_point_compute dependency')
test_sources += files(
'test_image.cpp'
)
test_deps += [cloud_point_compute_dep]
else
message('cpc_dep was not found')
endif
test_exe = executable('unit_tests', test_exe = executable('unit_tests',
test_sources, test_sources,
dependencies : [cloud_point_rpc_dep, cloud_point_rpc_cli_dep, cloud_point_rpc_test_dep, json_dep, gtest_dep, gtest_main_dep, gmock_dep]) dependencies : test_deps)
test('unit_tests', test_exe) test('unit_tests', test_exe)

91
tests/test_image.cpp Normal file
View File

@ -0,0 +1,91 @@
//
// Created by vptyp on 12.03.2026.
//
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <cloud_point/imageFactory.h>
class ImageTest : public ::testing::Test {
protected:
void SetUp() override {}
void TearDown() override {}
};
TEST_F(ImageTest, DefaultConstructor) {
score::Image image;
cv::Mat mat = image.get();
EXPECT_TRUE(mat.empty());
}
TEST_F(ImageTest, ConstructorWithMat) {
cv::Mat input = cv::Mat::zeros(5, 5, CV_8UC1);
score::Image image(input);
cv::Mat output = image.get();
EXPECT_EQ(output.rows, 5);
EXPECT_EQ(output.cols, 5);
EXPECT_EQ(output.type(), CV_8UC1);
}
TEST_F(ImageTest, PixelTypeMapping) {
EXPECT_EQ(score::ImageFactory::pixelType(score::ImageRPC::Type::BGR),
CV_8UC3);
EXPECT_EQ(score::ImageFactory::pixelType(score::ImageRPC::Type::RGBA),
CV_8UC4);
EXPECT_EQ(score::ImageFactory::pixelType(score::ImageRPC::Type::DEPTH),
CV_64FC1);
EXPECT_THROW(score::ImageFactory::pixelType(score::ImageRPC::Type::UNKNOWN),
std::runtime_error);
}
TEST_F(ImageTest, CreateBGR) {
score::ImageRPC rpc;
rpc.width = 10;
rpc.height = 20;
rpc.type = score::ImageRPC::Type::BGR;
rpc.data.resize(rpc.width * rpc.height * 3, 128);
score::Image image = score::ImageFactory::create(rpc);
cv::Mat mat = image.get();
EXPECT_EQ(mat.rows, 10);
EXPECT_EQ(mat.cols, 20);
EXPECT_EQ(mat.type(), CV_8UC3);
EXPECT_EQ(mat.at<cv::Vec3b>(0, 0)[0], 128);
}
TEST_F(ImageTest, CreateRGBA) {
score::ImageRPC rpc;
rpc.width = 15;
rpc.height = 25;
rpc.type = score::ImageRPC::Type::RGBA;
rpc.data.resize(rpc.width * rpc.height * 4, 255);
score::Image image = score::ImageFactory::create(rpc);
cv::Mat mat = image.get();
EXPECT_EQ(mat.rows, 15);
EXPECT_EQ(mat.cols, 25);
EXPECT_EQ(mat.type(), CV_8UC4);
EXPECT_EQ(mat.at<cv::Vec4b>(0, 0)[0], 255);
}
TEST_F(ImageTest, CreateDepth) {
score::ImageRPC rpc;
rpc.width = 5;
rpc.height = 10;
rpc.type = score::ImageRPC::Type::DEPTH;
rpc.data.resize(rpc.width * rpc.height * sizeof(double));
auto *dataPtr = reinterpret_cast<double *>(rpc.data.data());
for (int i = 0; i < 50; ++i)
dataPtr[i] = static_cast<double>(i);
score::Image image = score::ImageFactory::create(rpc);
cv::Mat mat = image.get();
EXPECT_EQ(mat.rows, 5);
EXPECT_EQ(mat.cols, 10);
EXPECT_EQ(mat.type(), CV_64FC1);
EXPECT_DOUBLE_EQ(mat.at<double>(0, 0), 0.0);
EXPECT_DOUBLE_EQ(mat.at<double>(4, 9), 49.0);
}