Compare commits
6 Commits
master
...
feature/op
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2146279c4 | ||
|
|
4343dd13e7 | ||
| e3b5be0f66 | |||
| 2b12a0be9b | |||
| f5f4bd4115 | |||
| 9cb3f009ea |
2
.gitignore
vendored
2
.gitignore
vendored
@ -7,4 +7,6 @@ subprojects/nlohmann_json/
|
||||
subprojects/packagecache/
|
||||
subprojects/yaml-cpp-0.8.0
|
||||
subprojects/base64-0.5.2/
|
||||
subprojects/stdexec/
|
||||
subprojects/.*
|
||||
.venv/
|
||||
|
||||
12
AGENTS.md
12
AGENTS.md
@ -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
|
||||
|
||||
28
README.md
28
README.md
@ -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
11
config.yaml.example
Normal 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]
|
||||
21
include/cloud_point/image.h
Normal file
21
include/cloud_point/image.h
Normal 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
|
||||
40
include/cloud_point/imageFactory.h
Normal file
40
include/cloud_point/imageFactory.h
Normal 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
|
||||
22
include/cloud_point/matrixFactory.h
Normal file
22
include/cloud_point/matrixFactory.h
Normal 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
|
||||
16
include/cloud_point/rectify.h
Normal file
16
include/cloud_point/rectify.h
Normal 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
|
||||
@ -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
|
||||
|
||||
@ -69,4 +69,4 @@ class ConfigLoader {
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace cloud_point_rpc
|
||||
} // namespace score
|
||||
|
||||
21
include/cloud_point_rpc/imageRpc.h
Normal file
21
include/cloud_point_rpc/imageRpc.h
Normal 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
|
||||
@ -36,4 +36,4 @@ class RpcClient : public jsonrpccxx::JsonRpcClient {
|
||||
int id{0};
|
||||
};
|
||||
|
||||
} // namespace cloud_point_rpc
|
||||
} // namespace score
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
@ -40,4 +40,4 @@ static inline std::string tcp_read(asio::ip::tcp::socket &socket,
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace cloud_point_rpc
|
||||
} // namespace score
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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'])
|
||||
|
||||
@ -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
18
src/cloud_point/image.cpp
Normal 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
|
||||
28
src/cloud_point/meson.build
Normal file
28
src/cloud_point/meson.build
Normal 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
|
||||
)
|
||||
23
src/cloud_point/rectify.cpp
Normal file
23
src/cloud_point/rectify.cpp
Normal 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
|
||||
@ -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')
|
||||
@ -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
|
||||
*/
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
7
subprojects/stdexec.wrap
Normal file
@ -0,0 +1,7 @@
|
||||
[wrap-git]
|
||||
url = https://github.com/NVIDIA/stdexec.git
|
||||
revision = gtc-2026
|
||||
depth = 1
|
||||
|
||||
[provide]
|
||||
stdexec = stdexec_dep
|
||||
@ -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
107
tests/test_image.cpp
Normal 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));
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user