Compare commits

..

6 Commits

Author SHA1 Message Date
Artur Mukhamadiev
d2146279c4 [tests] remote retrieving of available methods
:Release Notes:
check that get-available-methods correctly called and parsed

:Detailed Notes:
-

:Testing Performed:
weak coverage, basic tests

:QA Notes:
-

:Issues Addressed:
TG-4
2026-03-19 23:08:16 +03:00
Artur Mukhamadiev
4343dd13e7 [rpc] new methods in api
:Release Notes:
added new implicit rpc method:
"get-available-methods"

get_count
get_method_name_by_id
get_method_names

:Detailed Notes:
-

:Testing Performed:
weak coverage

:QA Notes:
-

:Issues Addressed:
TG-4 #done
2026-03-19 22:59:32 +03:00
e3b5be0f66 [docs] consistency across impl and documentation
fully generated by gemini+opencode
2026-03-12 23:23:36 +03:00
2b12a0be9b [opencv] rectification boilerplate
TG-8
2026-03-12 23:14:27 +03:00
f5f4bd4115 [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
2026-03-12 22:13:51 +03:00
9cb3f009ea [deps] added opencv and stdexec
opencv would be used to compile actual processing part
stdexec would be used for RPC module :)

TG-8 #in-progress
2026-03-12 20:47:25 +03:00
34 changed files with 501 additions and 78 deletions

2
.gitignore vendored
View File

@ -7,4 +7,6 @@ subprojects/nlohmann_json/
subprojects/packagecache/
subprojects/yaml-cpp-0.8.0
subprojects/base64-0.5.2/
subprojects/stdexec/
subprojects/.*
.venv/

View File

@ -66,12 +66,12 @@ Adhere strictly to **Modern C++20** standards.
### Naming Conventions
- **Files:** `snake_case.cpp`, `snake_case.hpp`.
- **Classes/Structs:** `PascalCase`.
- **Functions/Methods:** `snake_case` (or `camelCase` if adhering strictly to a specific external library style, but default to snake_case).
- **Functions/Methods:** `snake_case`.
- **Variables:** `snake_case`.
- **Private Members:** `snake_case_` (trailing underscore).
- **Constants:** `kPascalCase` or `ALL_CAPS` for macros (avoid macros).
- **Namespaces:** `snake_case`.
- **Interfaces:** `IPascalCase` (optional, but consistent if used).
- **Constants:** `kPascalCase` or `ALL_CAPS` for macros.
- **Namespaces:** `score` (primary project namespace).
- **Interfaces:** `IPascalCase`.
### Project Structure
- `include/cloud_point_rpc/`: Public header files.
@ -92,7 +92,7 @@ Adhere strictly to **Modern C++20** standards.
### Example Class
```cpp
namespace cloud_point_rpc {
namespace score {
/// @brief Manages camera parameters.
class CameraController {
@ -114,7 +114,7 @@ class CameraController {
std::vector<double> cached_intrinsics_;
};
} // namespace cloud_point_rpc
} // namespace score
```
### Implementation Details

View File

@ -2,10 +2,19 @@
Communication JSON RPC protocol and implementation with Unity Scene.
## TODO
## Project Structure
- `include/`: Header files for the RPC server, TCP server, and C-API.
- `src/`: Implementation of the RPC logic, networking, and C-API.
- `src/cloud_point/`: OpenCV-based image processing and rectification logic.
- `docs/`: Documentation diagrams and models.
- `subprojects/`: Dependencies managed by Meson.
## Status
- [x] Server implementation with C-API for Unity
- [ ] Client correct implementation with OpenCV
- [x] Basic OpenCV image processing (Rectification, Image wrapper)
- [ ] Full OpenCV client implementation
## API Documentation
@ -17,20 +26,29 @@ The project uses **Meson** build system and **C++20**.
### Dependencies
- Meson, Ninja
- Meson (>= 1.1.0), Ninja
- GCC/Clang (C++20 support)
- Git (for subprojects)
- OpenCV (for cloud point compute)
The following dependencies are managed via Meson subprojects:
- [ASIO](https://think-async.com/Asio/) (Networking)
- [nlohmann/json](https://github.com/nlohmann/json) (JSON serialization)
- [yaml-cpp](https://github.com/jbeder/yaml-cpp) (Configuration loading)
- [glog](https://github.com/google/glog) (Logging)
- [jsonrpccxx](https://github.com/uS-S/jsonrpccxx) (JSON-RPC 2.0 implementation)
- [stdexec](https://github.com/NVIDIA/stdexec) (P2300 Senders/Receivers)
### Build & Run
```bash
git submodule init
git submodule update
meson setup build
meson compile -C build
./build/src/cloud_point_rpc_server config.yaml
```
*Note: You need a `config.yaml` file. See `config.yaml.example` for the required format.*
#### Build on windows
It's assumed that you have `GCC` and `make`/`ninja` installed on your system (and available in `PATH`)

11
config.yaml.example Normal file
View File

@ -0,0 +1,11 @@
server:
ip: "127.0.0.1"
port: 8080
test_data:
intrinsic_params: [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
extrinsic_params: [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]
cloud_point:
- [0.1, 0.2, 0.3]
- [1.1, 1.2, 1.3]
- [5.5, 6.6, 7.7]

View File

@ -0,0 +1,21 @@
//
// Created by vptyp on 11.03.2026.
//
#pragma once
#include <opencv4/opencv2/opencv.hpp>
namespace score {
class Image {
public:
Image();
explicit Image(const cv::Mat &image);
~Image();
/// @note data_ could be changed through this
[[nodiscard]] cv::Mat get();
protected:
cv::Mat data_;
};
} // namespace score

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,22 @@
#pragma once
#include "opencv2/core/hal/interface.h"
#include "opencv2/core/mat.hpp"
#include <stdexcept>
namespace score {
class CameraMatrixFactory {
public:
/**
* @param rpc < vector of size Width*Height
* @throw runtime_error if size is not Width*Height
*/
template <size_t Width, size_t Height>
static cv::Mat create(std::vector<double> rpc) {
if (rpc.size() != Width * Height)
throw std::runtime_error("Vector size is not Width*Height");
return {Width, Height, CV_64F, rpc.data()};
}
};
} // namespace score

View File

@ -0,0 +1,16 @@
#pragma once
#include "cloud_point/image.h"
#include <opencv2/calib3d.hpp>
namespace score {
class Rectify {
public:
Rectify();
~Rectify();
void perform(Image &image, cv::Mat cameraMatrix,
cv::Mat distCoeffs = cv::Mat::zeros(1, 5, CV_64F));
};
} // namespace score

View File

@ -1,8 +1,8 @@
#pragma once
#include "export.h"
#include <iostream>
#include <string>
#include "export.h"
namespace score {
/**
@ -14,7 +14,7 @@ namespace score {
* @param port Server Port
* @return int exit code
*/
int CRPC_EXPORT run_cli(std::istream &input, std::ostream &output, const std::string &ip,
int port);
int CRPC_EXPORT run_cli(std::istream &input, std::ostream &output,
const std::string &ip, int port);
} // namespace cloud_point_rpc
} // namespace score

View File

@ -69,4 +69,4 @@ class ConfigLoader {
}
};
} // namespace cloud_point_rpc
} // 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

@ -36,4 +36,4 @@ class RpcClient : public jsonrpccxx::JsonRpcClient {
int id{0};
};
} // namespace cloud_point_rpc
} // namespace score

View File

@ -1,15 +1,15 @@
#pragma once
#include "export.h"
#include <functional>
#include <jsonrpccxx/server.hpp>
#include <map>
#include <nlohmann/json.hpp>
#include <string>
#include "export.h"
extern "C" {
struct rpc_string {
rpc_string(const char* data, uint64_t size) : s(data,size) {}
rpc_string(const char *data, uint64_t size) : s(data, size) {}
rpc_string() = default;
std::string s;
@ -20,16 +20,26 @@ namespace score {
class CRPC_EXPORT RpcServer {
public:
using Handler = std::function<nlohmann::json(const nlohmann::json &)>;
using callback_t = rpc_string*(*)(rpc_string*);
using Handler = std::function<nlohmann::json(const nlohmann::json &)>;
using callback_t = rpc_string *(*)(rpc_string *);
public:
/// @note +1 method implicitly added: get-available-methods
RpcServer();
void register_method(const std::string &name, Handler handler);
void register_method(const std::string &name, callback_t handler);
uint64_t get_count() noexcept;
std::span<std::string_view> get_method_names() noexcept;
std::string_view get_method_name_by_id(uint64_t id) noexcept;
///@param request_str json rpc 2.0 formatted string
[[nodiscard]] std::string process(const std::string &request_str);
private:
std::vector<std::string_view> handler_names_;
std::map<std::string, Handler> handlers_;
};
} // namespace cloud_point_rpc
} // namespace score

View File

@ -34,4 +34,4 @@ template <NumericType T> T deserialize(const std::vector<uint8_t> &buffer) {
return *reinterpret_cast<const T *>(buffer.data());
}
} // namespace cloud_point_rpc
} // namespace score

View File

@ -1,8 +1,8 @@
#pragma once
#include "cloud_point_rpc/config.hpp"
#include <vector>
#include "export.h"
#include <vector>
namespace score {
class CRPC_EXPORT Service {
@ -17,4 +17,4 @@ class CRPC_EXPORT Service {
TestData data_;
};
} // namespace cloud_point_rpc
} // namespace score

View File

@ -1,11 +1,11 @@
#pragma once
#include "cloud_point_rpc/serialize.hpp"
#include "export.h"
#include "jsonrpccxx/iclientconnector.hpp"
#include <asio.hpp>
#include <cloud_point_rpc/tcp_read.hpp>
#include <glog/logging.h>
#include <string>
#include "export.h"
namespace score {
/**
* TCPConnector main purpose is to implement jsonrpccxx::IClientConnector Send
@ -43,4 +43,4 @@ class CRPC_EXPORT TCPConnector : public jsonrpccxx::IClientConnector {
asio::ip::tcp::socket socket_;
};
} // namespace cloud_point_rpc
} // namespace score

View File

@ -40,4 +40,4 @@ static inline std::string tcp_read(asio::ip::tcp::socket &socket,
return result;
}
} // namespace cloud_point_rpc
} // namespace score

View File

@ -1,15 +1,15 @@
#pragma once
#include "export.h"
#include <asio.hpp>
#include <atomic>
#include <cloud_point_rpc/tcp_read.hpp>
#include <functional>
#include <glog/logging.h>
#include <string>
#include <thread>
#include "export.h"
#include <list>
#include <ranges>
#include <string>
#include <thread>
namespace score {
class CRPC_EXPORT TcpServer {
@ -43,14 +43,17 @@ class CRPC_EXPORT TcpServer {
accept_thread_ = std::jthread([this]() {
LOG(INFO) << "Accept thread started";
while (running_) {
std::ranges::remove_if(client_threads.begin(), client_threads.end(), [](auto& client_info) {
bool result = false;
if (client_info.second.wait_for(0ms) == std::future_status::ready) {
client_info.first.join();
result = true;
}
return result;
});
std::ranges::remove_if(
client_threads.begin(), client_threads.end(),
[](auto &client_info) {
bool result = false;
if (client_info.second.wait_for(0ms) ==
std::future_status::ready) {
client_info.first.join();
result = true;
}
return result;
});
try {
auto socket = std::make_shared<asio::ip::tcp::socket>(
io_context_);
@ -60,10 +63,12 @@ class CRPC_EXPORT TcpServer {
<< "New connection from "
<< socket->remote_endpoint().address().to_string();
auto done = std::make_shared<std::promise<bool>>();
client_threads.push_back(std::make_pair(std::jthread([this, socket, done]() {
handle_client(socket);
done->set_value(true);
}),done->get_future()));
client_threads.push_back(
std::make_pair(std::jthread([this, socket, done]() {
handle_client(socket);
done->set_value(true);
}),
done->get_future()));
} catch (const std::system_error &e) {
LOG(INFO) << "Accept exception: " << e.what();
if (running_) {
@ -144,4 +149,4 @@ class CRPC_EXPORT TcpServer {
std::jthread accept_thread_;
};
} // namespace cloud_point_rpc
} // namespace score

View File

@ -29,6 +29,8 @@ typedef rpc_string*(*callback_t)(rpc_string*);
CRPC_EXPORT void crpc_init(const char* config_path);
CRPC_EXPORT void crpc_deinit();
CRPC_EXPORT rpc_string* crpc_get_method_name_by_id(uint64_t id);
CRPC_EXPORT uint64_t crpc_get_methods_count();
CRPC_EXPORT void crpc_add_method(callback_t cb, rpc_string* name);
#ifdef __cplusplus

View File

@ -3,6 +3,7 @@ project('cloud_point_rpc', 'cpp',
default_options : ['warning_level=3', 'cpp_std=c++20'])
# Dependencies
stdexec_dep = dependency('stdexec', fallback: ['stdexec', 'stdexec_dep'])
json_dep = dependency('nlohmann_json', fallback : ['nlohmann_json', 'nlohmann_json_dep'])
thread_dep = dependency('threads')
asio_dep = dependency('asio', fallback : ['asio', 'asio_dep'])

View File

@ -86,4 +86,4 @@ int run_cli(std::istream &input, std::ostream &output, const std::string &ip,
return 0;
}
} // namespace cloud_point_rpc
} // namespace score

18
src/cloud_point/image.cpp Normal file
View File

@ -0,0 +1,18 @@
//
// Created by vptyp on 12.03.2026.
//
#include "cloud_point/image.h"
namespace score {
Image::Image() {
// no work
}
Image::Image(const cv::Mat &image) { this->data_ = image; }
Image::~Image() = default;
cv::Mat Image::get() { return this->data_; }
} // namespace score

View File

@ -0,0 +1,28 @@
opencv_dep = dependency('opencv4',
fallback: ['libopencv', 'libopencv4'],
required: false)
if not opencv_dep.found()
message('\'opencv\' was not found. Try install libopencv-dev or similar package on your system')
message('cloud_point_compute library removed from compilation')
subdir_done()
endif
cloud_point_sources = files(
'image.cpp',
'rectify.cpp'
)
cpc_deps = [ cloud_point_rpc_dep, opencv_dep ]
cloud_point_compute_lib = shared_library('cloud_point_compute',
sources: cloud_point_sources,
include_directories: inc,
dependencies: cpc_deps,
)
cloud_point_compute_dep = declare_dependency(
include_directories: inc,
link_with: cloud_point_compute_lib,
dependencies: cpc_deps
)

View File

@ -0,0 +1,23 @@
#include "opencv2/calib3d.hpp"
#include <cloud_point/rectify.h>
namespace score {
Rectify::Rectify() = default;
Rectify::~Rectify() = default;
/**
* @brief perform rectification operation on providen image
* @param cameraMatrix matrix of intrinsic params of size 3x3
* @param distCoeffs distortion matrix
* @param image reference to image object (changed in-place)
*/
void Rectify::perform(Image &image, cv::Mat cameraMatrix, cv::Mat distCoeffs) {
cv::Mat newmatrix = cv::getOptimalNewCameraMatrix(
cameraMatrix, distCoeffs, {image.get().size[0], image.get().size[1]},
1);
cv::Mat output;
cv::undistort(image.get(), output, cameraMatrix, distCoeffs, newmatrix);
output.copyTo(image.get());
}
} // namespace score

View File

@ -7,16 +7,18 @@ cloud_point_rpc_sources = files(
'rpc_coder.cpp'
)
deps = [json_dep, thread_dep, glog_dep, yaml_dep, asio_dep, base64_dep]
libcloud_point_rpc = shared_library('cloud_point_rpc',
cloud_point_rpc_sources,
include_directories : inc,
dependencies : [json_dep, thread_dep, glog_dep, yaml_dep, asio_dep, base64_dep],
dependencies : deps,
install : true)
cloud_point_rpc_dep = declare_dependency(
include_directories : inc,
link_with : libcloud_point_rpc,
dependencies : [json_dep, glog_dep, yaml_dep, asio_dep, base64_dep])
dependencies : deps)
# Test lib
libcloud_point_rpc_test = shared_library('test_cloud_point',
@ -54,3 +56,5 @@ executable('cloud_point_rpc_server',
dependencies : cloud_point_rpc_dep,
link_args : '-pthread',
install : true)
subdir('cloud_point')

View File

@ -11,7 +11,7 @@ Base64RPCCoder::Base64RPCCoder() = default;
Base64RPCCoder::~Base64RPCCoder() = default;
/**
* Tries to decode ASCII complained string to the
* Tries to decode ASCII complained string to the raw bytes
* @param encoded ASCII complained base64 encoded string
* @return vector of raw bytes << allocated on encoded.size() / 4 * 3 + 1 size
*/

View File

@ -1,4 +1,5 @@
#include "cloud_point_rpc/rpc_server.hpp"
#include <cstdint>
#include <glog/logging.h>
using json = nlohmann::json;
@ -17,17 +18,41 @@ json create_success(const json &result, const json &id) {
}
} // namespace
void RpcServer::register_method(const std::string &name, Handler handler) {
handlers_[name] = std::move(handler);
RpcServer::RpcServer() {
register_method("get-available-methods", [&](const json&) {
return get_method_names();
});
}
void RpcServer::register_method(const std::string& name, callback_t handler) {
handlers_[name] = [handler](const nlohmann::json& j) -> nlohmann::json {
void RpcServer::register_method(const std::string &name, Handler handler) {
handlers_[name] = std::move(handler);
handler_names_.push_back(handlers_.find(name)->first);
}
void RpcServer::register_method(const std::string &name, callback_t handler) {
handlers_[name] = [handler](const nlohmann::json &j) -> nlohmann::json {
rpc_string tmp;
tmp.s = j.dump();
rpc_string* res = handler(&tmp);
tmp.s = j.dump();
rpc_string *res = handler(&tmp);
return {res->s};
};
handler_names_.push_back(handlers_.find(name)->first);
}
std::span<std::string_view> RpcServer::get_method_names() noexcept {
return this->handler_names_;
}
uint64_t RpcServer::get_count() noexcept {
return this->handler_names_.size();
}
std::string_view RpcServer::get_method_name_by_id(uint64_t id) noexcept {
if(id >= handler_names_.size()) {
LOG(ERROR) << __func__ << std::format(": called with id = {} which is bigger, than size={}", id, handler_names_.size());
return {};
}
return handler_names_.at(id);
}
std::string RpcServer::process(const std::string &request_str) {
@ -66,4 +91,4 @@ std::string RpcServer::process(const std::string &request_str) {
}
}
} // namespace cloud_point_rpc
} // namespace score

View File

@ -1,53 +1,50 @@
#include "server_api.h"
#include "cloud_point_rpc/config.hpp"
#include "cloud_point_rpc/rpc_server.hpp"
#include "cloud_point_rpc/tcp_server.hpp"
#include <glog/logging.h>
#include "server_api.h"
#include <algorithm>
#include <glog/logging.h>
#include <list>
#include <memory>
#include <string>
#include <list>
static std::list<std::unique_ptr<rpc_string>> gc;
static std::list<std::unique_ptr<rpc_string>> gc;
score::RpcServer rpc_server;
std::unique_ptr<score::TcpServer> server = nullptr;
extern "C" {
const char* crpc_str_get_data(const rpc_string* that) {
const char *crpc_str_get_data(const rpc_string *that) {
return that->s.c_str();
}
uint64_t crpc_str_get_size(const rpc_string* that){
return that->s.size();
}
uint64_t crpc_str_get_size(const rpc_string *that) { return that->s.size(); }
rpc_string* crpc_str_create(const char* data, uint64_t size){
rpc_string *crpc_str_create(const char *data, uint64_t size) {
gc.push_back(std::make_unique<rpc_string>(data, size));
return gc.back().get();
}
void crpc_str_destroy(rpc_string* that){
void crpc_str_destroy(rpc_string *that) {
auto it = std::ranges::find(gc, that, &std::unique_ptr<rpc_string>::get);
if(it != gc.end())
if (it != gc.end())
gc.erase(it);
}
void crpc_init(const char* config_path) {
void crpc_init(const char *config_path) {
google::InitGoogleLogging("CloudPointRPC");
if(config_path == nullptr) {
if (config_path == nullptr) {
LOG(INFO) << "config_path was not provided";
}
try {
auto config = score::ConfigLoader::load(config_path);
LOG(INFO) << "Loaded config from " << config_path;
server = std::make_unique<score::TcpServer>(config.server.ip, config.server.port,
[&](const std::string &request) {
return rpc_server.process(
request);
});
server = std::make_unique<score::TcpServer>(
config.server.ip, config.server.port,
[&](const std::string &request) {
return rpc_server.process(request);
});
server->start();
} catch (const std::exception &e) {
LOG(ERROR) << "Fatal error: " << e.what();
@ -55,14 +52,23 @@ void crpc_init(const char* config_path) {
}
void crpc_deinit() {
if(server)
if (server)
server->join();
server.reset();
gc.clear();
}
void crpc_add_method(callback_t cb, rpc_string* name) {
void crpc_add_method(callback_t cb, rpc_string *name) {
rpc_server.register_method(name->s, cb);
}
rpc_string *crpc_get_method_name_by_id(uint64_t id) {
auto value = rpc_server.get_method_name_by_id(id);
return crpc_str_create(value.data(), value.size());
}
uint64_t crpc_get_methods_count() {
return rpc_server.get_count();
}
}

View File

@ -27,4 +27,4 @@ std::vector<std::vector<double>> Service::get_cloud_point() const {
return data_.cloud_point;
}
} // namespace cloud_point_rpc
} // namespace score

7
subprojects/stdexec.wrap Normal file
View File

@ -0,0 +1,7 @@
[wrap-git]
url = https://github.com/NVIDIA/stdexec.git
revision = gtc-2026
depth = 1
[provide]
stdexec = stdexec_dep

View File

@ -6,9 +6,24 @@ test_sources = files(
'test_c_api.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_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)

107
tests/test_image.cpp Normal file
View File

@ -0,0 +1,107 @@
//
// Created by vptyp on 12.03.2026.
//
#include "cloud_point/rectify.h"
#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);
}
TEST_F(ImageTest, RectificationNoThrow) {
score::Rectify rectify;
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);
double fx = 10.1, fy = 20.2, cx = 30.3, cy = 40.4;
cv::Mat cameraMatrix =
(cv::Mat_<double>(3, 3) << fx, 0, cx, 0, fy, cy, 0, 0, 1);
EXPECT_NO_THROW(rectify.perform(image, cameraMatrix));
}

View File

@ -95,3 +95,11 @@ TEST_F(IntegrationTest, ClientCanConnectAndRetrieveValues) {
TEST_F(IntegrationTest, ClientHandlesConnectionError) {
EXPECT_THROW(TCPConnector connector("127.0.0.1", 9999), std::runtime_error);
}
TEST_F(IntegrationTest, ClientRetrieveRemoteMethods) {
TCPConnector connector(config_.server.ip, config_.server.port);
RpcClient client(connector);
auto res = client.call<std::vector<std::string>>("get-available-methods");
EXPECT_EQ(res.size(), 2);
}

View File

@ -61,3 +61,16 @@ TEST_F(RpcServerTest, InvalidJsonReturnsParseError) {
ASSERT_TRUE(response.contains("error"));
EXPECT_EQ(response["error"]["code"], -32700);
}
TEST_F(RpcServerTest, GetMethod) {
EXPECT_EQ(server.get_count(), 2);
EXPECT_EQ(server.get_method_name_by_id(1), "get-intrinsic-params");
EXPECT_EQ(server.get_method_names()[1], "get-intrinsic-params");
server.register_method("get-test-2", [&](const json&){
return "test";
});
EXPECT_EQ(server.get_count(), 3);
EXPECT_EQ(server.get_method_name_by_id(2), "get-test-2");
EXPECT_EQ(server.get_method_names()[2], "get-test-2");
}