├── .gitignore ├── .typos.toml ├── rerun-logo.png ├── pixi ├── env.sh └── env.bat ├── cspell.json ├── .github └── workflows │ ├── cpp.yml │ └── spelling_and_links.yml ├── CMakeLists.txt ├── LICENSE ├── .clang-format ├── src ├── main.cpp └── collection_adapters.hpp ├── README.md └── pixi.toml /.gitignore: -------------------------------------------------------------------------------- 1 | # Build directories 2 | build_* 3 | build 4 | 5 | # Pixi environment 6 | .pixi 7 | -------------------------------------------------------------------------------- /.typos.toml: -------------------------------------------------------------------------------- 1 | [files] 2 | extend-exclude = [ 3 | ".typos.toml", 4 | "cspell.json", 5 | ] 6 | -------------------------------------------------------------------------------- /rerun-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rerun-io/cpp-example-opencv-eigen/HEAD/rerun-logo.png -------------------------------------------------------------------------------- /pixi/env.sh: -------------------------------------------------------------------------------- 1 | export CMAKE_GENERATOR=Ninja 2 | export BUILD_FOLDER=build_${PIXI_ENVIRONMENT_NAME} 3 | export EXE_PATH=$BUILD_FOLDER/rerun_ext_example -------------------------------------------------------------------------------- /pixi/env.bat: -------------------------------------------------------------------------------- 1 | set CMAKE_GENERATOR="Visual Studio 17 2022" 2 | set BUILD_FOLDER=build_%PIXI_ENVIRONMENT_NAME% 3 | set EXE_PATH=%BUILD_FOLDER%/RelWithDebInfo/rerun_ext_example -------------------------------------------------------------------------------- /cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", 3 | "version": "0.2", 4 | "usePnP": true, 5 | "ignoreWords": [ 6 | "eigen", 7 | "Eigen", 8 | "findpackage", 9 | "pixi", 10 | "srcset" 11 | ] 12 | } -------------------------------------------------------------------------------- /.github/workflows/cpp.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: C++ 4 | 5 | jobs: 6 | pixi-build: 7 | name: "pixi build" 8 | runs-on: ubuntu-22.04 9 | steps: 10 | - uses: actions/checkout@v4 11 | 12 | - uses: prefix-dev/setup-pixi@v0.8.1 13 | with: 14 | pixi-version: v0.40.3 15 | cache: true 16 | 17 | - name: run build task on all environments 18 | run: | 19 | pixi run build-fetchcontent 20 | pixi run build-findpackage 21 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16...3.27) 2 | 3 | project(rerun_external_cpp_proj LANGUAGES CXX) 4 | 5 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 6 | 7 | if(NOT DEFINED CMAKE_CXX_STANDARD) 8 | set(CMAKE_CXX_STANDARD 17) 9 | endif() 10 | 11 | set(RERUN_CPP_URL "https://github.com/rerun-io/rerun/releases/download/0.21.0/rerun_cpp_sdk.zip" CACHE STRING "URL to the rerun_cpp zip.") 12 | option(RERUN_FIND_PACKAGE "Whether to use find_package to find a preinstalled rerun package (instead of using FetchContent)." OFF) 13 | 14 | if(RERUN_FIND_PACKAGE) 15 | find_package(rerun_sdk REQUIRED) 16 | else() 17 | # Download the rerun_sdk 18 | include(FetchContent) 19 | FetchContent_Declare(rerun_sdk URL ${RERUN_CPP_URL}) 20 | FetchContent_MakeAvailable(rerun_sdk) 21 | endif() 22 | 23 | find_package(Eigen3 REQUIRED) 24 | find_package(OpenCV REQUIRED) 25 | 26 | add_executable(rerun_ext_example src/main.cpp) 27 | 28 | target_link_libraries(rerun_ext_example Eigen3::Eigen ${OpenCV_LIBS} rerun_sdk) 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 rerun.io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | 3 | # Make it slightly more similar to Rust. 4 | # Based loosely on https://gist.github.com/YodaEmbedding/c2c77dc693d11f3734d78489f9a6eea4 5 | AccessModifierOffset: -2 6 | AlignAfterOpenBracket: BlockIndent 7 | AllowAllArgumentsOnNextLine: false 8 | AllowShortBlocksOnASingleLine: false 9 | AllowShortCaseLabelsOnASingleLine: false 10 | AllowShortFunctionsOnASingleLine: Empty 11 | AllowShortIfStatementsOnASingleLine: Never 12 | AlwaysBreakAfterReturnType: None 13 | AlwaysBreakBeforeMultilineStrings: true 14 | BinPackArguments: false 15 | BreakStringLiterals: false 16 | ColumnLimit: 100 17 | ContinuationIndentWidth: 4 18 | DerivePointerAlignment: false 19 | EmptyLineBeforeAccessModifier: LogicalBlock 20 | IndentWidth: 4 21 | IndentWrappedFunctionNames: true 22 | InsertBraces: true 23 | InsertTrailingCommas: Wrapped 24 | MaxEmptyLinesToKeep: 1 25 | NamespaceIndentation: All 26 | PointerAlignment: Left 27 | ReflowComments: false 28 | SeparateDefinitionBlocks: Always 29 | SpacesBeforeTrailingComments: 1 30 | 31 | # Don't change include blocks, we want to control this manually. 32 | # Sorting headers however is allowed as all our headers should be standalone. 33 | IncludeBlocks: Preserve 34 | SortIncludes: CaseInsensitive 35 | -------------------------------------------------------------------------------- /.github/workflows/spelling_and_links.yml: -------------------------------------------------------------------------------- 1 | name: Check spelling and links 2 | on: [pull_request] 3 | 4 | jobs: 5 | typos: 6 | # https://github.com/crate-ci/typos 7 | # Add exceptions to .typos.toml 8 | # install and run locally: cargo install typos-cli && typos 9 | name: typos 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout Actions Repository 13 | uses: actions/checkout@v4 14 | 15 | - name: Check spelling of entire workspace 16 | uses: crate-ci/typos@master 17 | 18 | spellcheck: 19 | name: Spellcheck 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v4 23 | - uses: streetsidesoftware/cspell-action@v2 24 | with: 25 | files: "**/*.md" 26 | 27 | linkinator: 28 | name: linkinator 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v4 32 | - uses: jprochazk/linkinator-action@main 33 | with: 34 | linksToSkip: "https://crates.io/crates/.*, http://localhost:.*" # Avoid crates.io rate-limiting 35 | paths: "**/*.cpp, **/*.hpp, **/*.md, **/*.toml" 36 | retry: true 37 | retryErrors: true 38 | retryErrorsCount: 5 39 | retryErrorsJitter: 2000 40 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "collection_adapters.hpp" 12 | 13 | std::vector generate_random_points_vector(int num_points) { 14 | std::vector points(num_points); 15 | for (auto& point : points) { 16 | point.setRandom(); 17 | } 18 | return points; 19 | } 20 | 21 | int main() { 22 | std::cout << "Rerun SDK Version: " << rerun::version_string() << std::endl; 23 | 24 | const auto rec = rerun::RecordingStream("rerun_example_cpp"); 25 | rec.spawn().exit_on_failure(); 26 | 27 | rec.log_static("world", rerun::ViewCoordinates::RIGHT_HAND_Z_UP); // Set an up-axis 28 | 29 | const int num_points = 1000; 30 | 31 | // Points represented by std::vector 32 | const auto points3d_vector = generate_random_points_vector(1000); 33 | rec.log("world/points_from_vector", rerun::Points3D(points3d_vector)); 34 | 35 | // Points represented by Eigen::Mat3Xf (3xN matrix) 36 | const Eigen::Matrix3Xf points3d_matrix = Eigen::Matrix3Xf::Random(3, num_points); 37 | rec.log("world/points_from_matrix", rerun::Points3D(points3d_matrix)); 38 | 39 | // Posed pinhole camera: 40 | rec.log( 41 | "world/camera", 42 | rerun::Pinhole::from_focal_length_and_resolution({500.0, 500.0}, {640.0, 480.0}) 43 | ); 44 | 45 | const Eigen::Vector3f camera_position{0.0, -1.0, 0.0}; 46 | Eigen::Matrix3f camera_orientation; 47 | // clang-format off 48 | camera_orientation << 49 | +1.0, +0.0, +0.0, 50 | +0.0, +0.0, +1.0, 51 | +0.0, -1.0, +0.0; 52 | // clang-format on 53 | rec.log( 54 | "world/camera", 55 | rerun::Transform3D( 56 | rerun::Vec3D(camera_position.data()), 57 | rerun::Mat3x3(camera_orientation.data()) 58 | ) 59 | ); 60 | 61 | // Read image 62 | const auto image_path = "rerun-logo.png"; 63 | cv::Mat img = imread(image_path, cv::IMREAD_COLOR); 64 | if (img.empty()) { 65 | std::cout << "Could not read the image: " << image_path << std::endl; 66 | return 1; 67 | } 68 | 69 | uint32_t width = img.cols; 70 | uint32_t height = img.rows; 71 | 72 | // Rerun expects RGB format 73 | cv::cvtColor(img, img, cv::COLOR_BGR2RGB); 74 | 75 | // Log image to rerun using the tensor buffer adapter defined in `collection_adapters.hpp`. 76 | rec.log("image0", rerun::Image::from_rgb24(img, {width, height})); 77 | 78 | // Or by passing a pointer to the image data. 79 | // The pointer cast here is redundant since `data` is already uint8_t in this case, but if you have e.g. a float image it may be necessary to cast to float*. 80 | rec.log( 81 | "image1", 82 | rerun::Image::from_rgb24( 83 | rerun::borrow(img.data, img.total() * img.elemSize()), 84 | {width, height} 85 | ) 86 | ); 87 | 88 | return 0; 89 | } 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C++ Example with OpenCV and Eigen 2 | 3 | This is a minimal CMake project that shows how to use [Rerun](https://github.com/rerun-io/rerun) in your code in conjunction with [Eigen](https://eigen.tuxfamily.org/) and [OpenCV](https://opencv.org/). 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | ## Using `pixi` 16 | The easiest way to get started is to install [pixi](https://prefix.dev/docs/pixi/overview). 17 | 18 | The pixi environment described in `pixi.toml` contains all of the dependencies, including the rerun viewer, 19 | allowing you to run the example with a single command, while the rerun C++ SDK is downloaded via `FetchContent` 20 | * `pixi run example` 21 | 22 | If you want to also download rerun C++ SDK via pixi, you can run: 23 | * `pixi run example-findpackage` 24 | 25 | ## Without `pixi` 26 | If you choose not to use pixi, you will need to install a few things yourself before you get started. 27 | 28 | ### Installing the Rerun Viewer 29 | The Rerun C++ SDK works by connecting to an awaiting Rerun Viewer over TCP. 30 | 31 | If you need to install the viewer, follow the [installation guide](https://www.rerun.io/docs/getting-started/installing-viewer). Two of the more common ways to install the Rerun are: 32 | * Via cargo: `cargo install rerun-cli@0.14.1` 33 | * Via pip: `pip install rerun-sdk==0.14.1` 34 | 35 | After you have installed it, you should be able to type `rerun` in your terminal to start the viewer. 36 | 37 | ### Installing dependencies: 38 | This project depends on a few libraries and toolchains. Installing these is outside the scope of this README, 39 | but your OS should have these available though a common package manager: 40 | * `eigen` and `opencv` (required by this example) 41 | * `cmake` (for building) 42 | 43 | ### Build and run on Linux & Mac 44 | 45 | Build: 46 | ```bash 47 | cmake -B build 48 | cmake --build build -j 49 | ``` 50 | 51 | Then run the binary with: 52 | `build/rerun_ext_example` 53 | 54 | 55 | ### Build and run on Windows using Visual Studio 2022 56 | 57 | Build 58 | ```cmd 59 | cmake -B build -G 'Visual Studio 17 2022' 60 | cmake --build build 61 | ``` 62 | Instead of building via CMake you can also open `build/rerun_external_cpp_proj.sln` in Visual Studio and build & run from there. 63 | 64 | Then run the binary with 65 | ```cmd 66 | build\RelWithDebInfo\rerun_ext_example.exe 67 | ``` 68 | Make sure that all dependent dlls (OpenCV & Eigen) are in PATH or next to the executable. 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/collection_adapters.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | // Adapters so we can log eigen vectors as rerun positions: 11 | template <> 12 | struct rerun::CollectionAdapter> { 13 | /// Borrow for non-temporary. 14 | Collection operator()(const std::vector& container) { 15 | return Collection::borrow(container.data(), container.size()); 16 | } 17 | 18 | // Do a full copy for temporaries (otherwise the data might be deleted when the temporary is destroyed). 19 | Collection operator()(std::vector&& container) { 20 | std::vector positions(container.size()); 21 | memcpy(positions.data(), container.data(), container.size() * sizeof(Eigen::Vector3f)); 22 | return Collection::take_ownership(std::move(positions)); 23 | } 24 | }; 25 | 26 | // Adapters so we can log an eigen matrix as rerun positions: 27 | template <> 28 | struct rerun::CollectionAdapter { 29 | // Sanity check that this is binary compatible. 30 | static_assert( 31 | sizeof(rerun::Position3D) == 32 | sizeof(Eigen::Matrix3Xf::Scalar) * Eigen::Matrix3Xf::RowsAtCompileTime 33 | ); 34 | 35 | /// Borrow for non-temporary. 36 | Collection operator()(const Eigen::Matrix3Xf& matrix) { 37 | static_assert(alignof(rerun::Position3D) <= alignof(Eigen::Matrix3Xf::Scalar)); 38 | return Collection::borrow( 39 | // Cast to void because otherwise Rerun will try to do above sanity checks with the wrong type (scalar). 40 | reinterpret_cast(matrix.data()), 41 | matrix.cols() 42 | ); 43 | } 44 | 45 | // Do a full copy for temporaries (otherwise the data might be deleted when the temporary is destroyed). 46 | Collection operator()(Eigen::Matrix3Xf&& matrix) { 47 | std::vector positions(matrix.cols()); 48 | memcpy(positions.data(), matrix.data(), matrix.size() * sizeof(rerun::Position3D)); 49 | return Collection::take_ownership(std::move(positions)); 50 | } 51 | }; 52 | 53 | // Adapters so we can borrow an OpenCV image easily into Rerun images without copying: 54 | template <> 55 | struct rerun::CollectionAdapter { 56 | /// Borrow for non-temporary. 57 | Collection operator()(const cv::Mat& img) { 58 | assert( 59 | "OpenCV matrix was expected have bit depth CV_U8" && CV_MAT_DEPTH(img.type()) == CV_8U 60 | ); 61 | 62 | return Collection::borrow(img.data, img.total() * img.channels()); 63 | } 64 | 65 | // Do a full copy for temporaries (otherwise the data might be deleted when the temporary is destroyed). 66 | Collection operator()(cv::Mat&& img) { 67 | assert( 68 | "OpenCV matrix was expected have bit depth CV_U8" && CV_MAT_DEPTH(img.type()) == CV_8U 69 | ); 70 | 71 | std::vector img_vec(img.total() * img.channels()); 72 | img_vec.assign(img.data, img.data + img.total() * img.channels()); 73 | return Collection::take_ownership(std::move(img_vec)); 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /pixi.toml: -------------------------------------------------------------------------------- 1 | # Pixi is a package management tool for developers. 2 | # Before running a task, pixi ensures that all listed dependencies are installed first. 3 | # 4 | # Pixi is not required for rerun, but it is a convenient way to install the 5 | # dependencies required for this example. 6 | # 7 | # https://prefix.dev/docs/pixi/overview 8 | # 9 | # Use `pixi task list` to list the available tasks, 10 | # and `pixi run TASK` to run it (e.g. `pixi run example`). 11 | 12 | [project] 13 | name = "rerun_cpp_example_opencv_eigen" 14 | authors = ["rerun.io "] 15 | channels = ["conda-forge"] 16 | description = "Use the Rerun C++ SDK together with Eigen and OpenCV" 17 | homepage = "https://rerun.io" 18 | license = "MIT OR Apache-2.0" 19 | platforms = ["linux-64", "linux-aarch64", "osx-arm64", "osx-64", "win-64"] 20 | readme = "README.md" 21 | repository = "https://github.com/rerun-io/cpp-example-opencv-eigen" 22 | version = "0.1.0" 23 | 24 | # The following activation scripts make the tasks portable cross-platform. 25 | # 26 | # They define: 27 | # - CMAKE_GENERATOR 28 | # - BUILD_FOLDER 29 | # - EXE_PATH 30 | # 31 | # In particular, BUILD_FOLDER will be set to either: 32 | # - build_fetchcontent 33 | # - build_findpackage 34 | # depending on the feature being used. 35 | [activation] 36 | scripts = ["pixi/env.sh"] 37 | 38 | [target.win-64.activation] 39 | scripts = ["pixi/env.bat"] 40 | 41 | ################################################################################ 42 | # Common Tasks 43 | ################################################################################ 44 | 45 | [tasks] 46 | 47 | print-env = { cmd = "echo $PATH" } 48 | 49 | [target.unix.tasks] 50 | # This task only runs properly Unix systems. 51 | format = { cmd = "clang-format -i src/*" } 52 | 53 | ################################################################################ 54 | # Building against Rerun using the default of FetchContent. 55 | # 56 | # The rerun-cpp source will be downloaded and built as part of the build. 57 | ################################################################################ 58 | [feature.fetchcontent.tasks] 59 | 60 | prepare-fetchcontent = "cmake -B $BUILD_FOLDER -S . -DCMAKE_BUILD_TYPE=RelWithDebInfo" 61 | 62 | build-fetchcontent = { cmd = "cmake --build $BUILD_FOLDER --config RelWithDebInfo", depends_on = [ 63 | "prepare-fetchcontent", 64 | ] } 65 | 66 | example = { cmd = "$EXE_PATH", depends_on = ["build-fetchcontent"] } 67 | 68 | clean = { cmd = "rm -rf $BUILD_FOLDER bin CMakeFiles/" } 69 | 70 | ################################################################################ 71 | # Alternatively, build by locating Rerun via find_package 72 | # 73 | # In this case rerun is provided by the `librerun-sdk` package in the 74 | # conda environment. 75 | ################################################################################ 76 | [feature.findpackage.tasks] 77 | 78 | prepare-findpackage = "cmake -B $BUILD_FOLDER -S . -DCMAKE_BUILD_TYPE=RelWithDebInfo -DRERUN_FIND_PACKAGE:BOOL=ON" 79 | 80 | build-findpackage = { cmd = "cmake --build $BUILD_FOLDER --config RelWithDebInfo", depends_on = [ 81 | "prepare-findpackage", 82 | ] } 83 | 84 | example-findpackage = { cmd = "$EXE_PATH", depends_on = ["build-findpackage"] } 85 | 86 | clean = { cmd = "rm -rf $BUILD_FOLDER bin CMakeFiles/" } 87 | 88 | ################################################################################ 89 | # Dependencies 90 | ################################################################################ 91 | [dependencies] 92 | # Build tools: 93 | clang-tools = ">=15,<16" # clang-format 94 | cmake = "3.27.6" 95 | # Required by this example: 96 | eigen = "3.4.0.*" 97 | opencv = "4.10.0.*" 98 | # Installs the Rerun Python SDK so the user doesn't have to download & install rerun-cli manually. 99 | # TODO(jleibs): In the future we'll want to use a rerun-cli package that only contains the CLI. 100 | rerun-sdk = "0.21.0" 101 | 102 | [target.linux-64.dependencies] 103 | sysroot_linux-64 = ">=2.17,<3" # rustc 1.64+ requires glibc 2.17+, see https://blog.rust-lang.org/2022/08/01/Increasing-glibc-kernel-requirements.html 104 | 105 | [target.linux-aarch64.dependencies] 106 | sysroot_linux-aarch64 = ">=2.17,<3" # rustc 1.64+ requires glibc 2.17+, see https://blog.rust-lang.org/2022/08/01/Increasing-glibc-kernel-requirements.html 107 | 108 | [target.unix.dependencies] 109 | # Build tools: 110 | cxx-compiler = "1.6.0.*" 111 | ninja = "1.11.1" 112 | 113 | [target.win-64.dependencies] 114 | # Build tools: 115 | vs2022_win-64 = "19.37.32822" 116 | 117 | [feature.findpackage.dependencies] 118 | librerun-sdk = "0.21.0" 119 | 120 | [environments] 121 | default = { solve-group = "defaultgroup" } 122 | fetchcontent = { features = ["fetchcontent"], solve-group = "defaultgroup" } 123 | findpackage = { features = ["findpackage"], solve-group = "defaultgroup" } 124 | --------------------------------------------------------------------------------