Monado OpenXR Runtime
Loading...
Searching...
No Matches
constellation_rerun_common.hpp
Go to the documentation of this file.
1// Copyright 2026, Beyley Cardellio
2// SPDX-License-Identifier: BSL-1.0
3/*!
4 * @file
5 * @brief Shared internal helpers for every constellation rerun recorder.
6 * @author Beyley Cardellio <ep1cm1n10n123@gmail.com>
7 * @ingroup tracking
8 */
9
10#pragma once
11
12#include "xrt/xrt_defines.h"
13#include "xrt/xrt_frame.h"
14
15#include "math/m_api.h"
16#include "math/m_vec3.h"
17
18#include "tracking/t_tracking.h"
21
22#include <rerun.hpp>
23
24#include <algorithm>
25#include <array>
26#include <cassert>
27#include <cmath>
28#include <cstdint>
29#include <cstring>
30#include <format>
31#include <memory>
32#include <string>
33#include <vector>
34
35
36namespace xrt::tracking::constellation {
37
38using namespace xrt::auxiliary::tracking;
39
40/*
41 *
42 * Shared constants
43 *
44 */
45
46//! How large a LED is drawn in the 3D views, in meters.
47constexpr float kLedPointRadiusM = 0.003f;
48constexpr float kAxisLength = 0.03f;
49constexpr float kBlobRadiusPixels = 3.0f;
50constexpr float kTrajectoryPointRadiusM = 0.005f;
51constexpr float kTrajectoryLineRadiusM = 0.002f;
52
53/*
54 *
55 * Helper functions
56 *
57 */
58
59//! The pose the solved poses predict this camera would have observed the device at.
60inline xrt_pose
61predictedCameraDevicePose(const xrt_pose &Tcv_world_cam, const xrt_pose &Tcv_world_device)
62{
63 xrt_pose Tcv_cam_world;
64 math_pose_invert(&Tcv_world_cam, &Tcv_cam_world);
65
66 xrt_pose Tcv_cam_device;
67 math_pose_transform(&Tcv_cam_world, &Tcv_world_device, &Tcv_cam_device);
68
69 return Tcv_cam_device;
70}
71
72//! The angle between two orientations, in degrees.
73inline float
75{
76 xrt_quat a_inverse;
77 math_quat_invert(&a, &a_inverse);
78
79 xrt_quat error;
80 math_quat_rotate(&a_inverse, &b, &error);
81
82 /*
83 * Clamped because numerical drift, or a quaternion which is very slightly denormalized, would otherwise hand
84 * std::acos a value outside of its domain.
85 */
86 float w = std::min(std::abs(error.w), 1.0f);
87
88 return static_cast<float>(2.0 * std::acos(w) * (180.0 / M_PI));
89}
90
91inline float
92reprojectionErrorPixels(const t_camera_model_params &dist,
93 const xrt_pose &Tcv_world_cam,
94 const xrt_pose &Tcv_world_device,
95 uint32_t num_points,
96 std::span<const xrt_vec2> points2d,
97 std::span<const xrt_vec3> points3d)
98{
99 const xrt_pose Tcv_cam_device = predictedCameraDevicePose(Tcv_world_cam, Tcv_world_device);
100
101 double sum_squared_error = 0.0;
102 size_t num_projected = 0;
103
104 for (size_t i = 0; i < num_points; i++) {
105 xrt_vec3 T_cam_led;
106 math_quat_rotate_vec3(&Tcv_cam_device.orientation, &points3d[i], &T_cam_led);
107 T_cam_led = T_cam_led + Tcv_cam_device.position;
108
109 if (T_cam_led.z <= 0.0f) {
110 // Behind the camera, so there is no pixel to compare against.
111 continue;
112 }
113
114 float projected_x = 0.0f;
115 float projected_y = 0.0f;
116 if (!camera_models::project<float>(dist, T_cam_led.x, T_cam_led.y, T_cam_led.z, projected_x,
117 projected_y)) {
118 continue;
119 }
120
121 const float error_x = projected_x - points2d[i].x;
122 const float error_y = projected_y - points2d[i].y;
123
124 sum_squared_error += (error_x * error_x) + (error_y * error_y);
125 num_projected++;
126 }
127
128 if (num_projected == 0) {
129 return 0.0f;
130 }
131
132 return static_cast<float>(std::sqrt(sum_squared_error / static_cast<double>(num_projected)));
133}
134
135/*
136 *
137 * Recording streams
138 *
139 */
140
141/*!
142 * The recording stream a rerun recorder logs through.
143 *
144 * Every recorder owns exactly one stream and reaches it the same way, so they inherit this rather than each repeating
145 * the pointer and its construction. The recording id stays a per-recorder choice, since it is what separates one
146 * recorder's data from another's in the viewer.
147 */
149{
150public: // Fields
151 std::unique_ptr<rerun::RecordingStream> stream{};
152
153public: // Methods
154 explicit RerunStream(const std::string_view &recording_id)
155 : stream(std::make_unique<rerun::RecordingStream>(recording_id))
156 {}
157
158 /*!
159 * Starts a viewer for this stream and points the stream at it.
160 *
161 * Fatal on failure, because a recorder which was explicitly asked for and cannot be reached is a debugging
162 * session that would otherwise run to completion and silently record nothing.
163 */
164 void
166 {
167 this->stream->spawn().exit_on_failure();
168 }
169};
170
171/*
172 *
173 * Conversions into rerun's types
174 *
175 */
176
177inline rerun::components::Translation3D
178toRerunTranslation(const xrt_vec3 &position)
179{
180 return rerun::components::Translation3D(position.x, position.y, position.z);
181}
182
183inline rerun::Rotation3D
184toRerunRotation(const xrt_quat &orientation)
185{
186 return rerun::Rotation3D(
187 rerun::datatypes::Quaternion::from_xyzw(orientation.x, orientation.y, orientation.z, orientation.w));
188}
189
190inline rerun::Transform3D
191toRerunTransform(const xrt_pose &pose, bool from_parent = true)
192{
193 auto transform = rerun::Transform3D()
194 .with_translation(toRerunTranslation(pose.position))
195 .with_rotation(toRerunRotation(pose.orientation));
196
197 if (from_parent) {
198 transform = std::move(transform).with_relation(rerun::components::TransformRelation::ParentFromChild);
199 }
200
201 return transform;
202}
203
204//! The camera's intrinsics as the projection rerun needs to place an image in the 3D scene.
205inline rerun::Pinhole
207{
208 // Construct the 3x3 intrinsic matrix in column-major order.
209 std::array<float, 9> image_from_camera = {
210 static_cast<float>(calibration.intrinsics[0][0]),
211 static_cast<float>(calibration.intrinsics[1][0]),
212 static_cast<float>(calibration.intrinsics[2][0]),
213 //
214 static_cast<float>(calibration.intrinsics[0][1]),
215 static_cast<float>(calibration.intrinsics[1][1]),
216 static_cast<float>(calibration.intrinsics[2][1]),
217 //
218 static_cast<float>(calibration.intrinsics[0][2]),
219 static_cast<float>(calibration.intrinsics[1][2]),
220 static_cast<float>(calibration.intrinsics[2][2]),
221 };
222
223 return rerun::Pinhole(rerun::components::PinholeProjection(image_from_camera))
224 .with_resolution(calibration.image_size_pixels.w, calibration.image_size_pixels.h)
225 .with_image_plane_distance(0.2f);
226}
227
228inline rerun::Image
229makeImage(const xrt_frame &frame)
230{
231 // We only support L8 format for now
232 assert(frame.format == XRT_FORMAT_L8);
233
234 std::vector<uint8_t> image_data(frame.width * frame.height);
235 // Copy the frame data into the image_data vector, accounting for stride
236 for (uint32_t y = 0; y < frame.height; ++y) {
237 std::memcpy(&image_data[y * frame.width], &frame.data[y * frame.stride], frame.width);
238 }
239
240 // Create a rerun image from the xrt_frame data.
241 return rerun::Image(
242 rerun::archetypes::Image::from_grayscale8(std::move(image_data), {frame.width, frame.height})
243 .with_opacity(0.5f));
244}
245
246/*
247 *
248 * Colours
249 *
250 */
251
252//! A stable colour per device, so one device keeps its colour across every recorder and every view.
253inline rerun::components::Color
254deviceColor(t_constellation_device_id_t device_id, float brightness)
255{
256 // Simple deterministic hash for device ID, ensuring device ID is non-zero
257 uint8_t r = (((device_id + 1) * 37) % 127) + 128;
258 uint8_t g = (((device_id + 1) * 57) % 127) + 128;
259 uint8_t b = (((device_id + 1) * 97) % 127) + 128;
260
261 return rerun::components::Color(r, g, b, static_cast<uint8_t>(255 * brightness));
262}
263
264//! A stable colour per camera, chosen the same way as @ref deviceColor and for the same reason.
265inline rerun::components::Color
266cameraColor(size_t mosaic_idx, size_t camera_idx, float brightness)
267{
268 // Simple deterministic hash for the camera, ensuring the seed is non-zero
269 size_t seed = (mosaic_idx * 16) + camera_idx + 1;
270
271 uint8_t r = ((seed * 71) % 127) + 128;
272 uint8_t g = ((seed * 113) % 127) + 128;
273 uint8_t b = ((seed * 29) % 127) + 128;
274
275 return rerun::components::Color(r, g, b, static_cast<uint8_t>(255 * brightness));
276}
277
278/*
279 *
280 * Entity names shared between recorders
281 *
282 */
283
284//! The root every 3D entity hangs off, so the recorders agree on where the world is.
285inline std::string_view
287{
288 return "world";
289}
290
291//! Where a camera sits in the world. Its image, observations and metrics are logged under this.
292inline std::string
293getWorldCameraEntityName(size_t mosaic_idx, size_t camera_idx)
294{
295 return std::format("{}/cameras/{}/{}", getWorldEntityName(), mosaic_idx, camera_idx);
296}
297
298}; // namespace xrt::tracking::constellation
float orientationErrorDegrees(const xrt_quat &a, const xrt_quat &b)
The angle between two orientations, in degrees.
Definition constellation_rerun_common.hpp:74
xrt_pose predictedCameraDevicePose(const xrt_pose &Tcv_world_cam, const xrt_pose &Tcv_world_device)
The pose the solved poses predict this camera would have observed the device at.
Definition constellation_rerun_common.hpp:61
std::string_view getWorldEntityName()
The root every 3D entity hangs off, so the recorders agree on where the world is.
Definition constellation_rerun_common.hpp:286
rerun::components::Color deviceColor(t_constellation_device_id_t device_id, float brightness)
A stable colour per device, so one device keeps its colour across every recorder and every view.
Definition constellation_rerun_common.hpp:254
std::string getWorldCameraEntityName(size_t mosaic_idx, size_t camera_idx)
Where a camera sits in the world. Its image, observations and metrics are logged under this.
Definition constellation_rerun_common.hpp:293
rerun::components::Color cameraColor(size_t mosaic_idx, size_t camera_idx, float brightness)
A stable colour per camera, chosen the same way as deviceColor and for the same reason.
Definition constellation_rerun_common.hpp:266
rerun::Pinhole makePinhole(const t_camera_calibration &calibration)
The camera's intrinsics as the projection rerun needs to place an image in the 3D scene.
Definition constellation_rerun_common.hpp:206
constexpr float kLedPointRadiusM
How large a LED is drawn in the 3D views, in meters.
Definition constellation_rerun_common.hpp:47
C interface to math library.
C vec3 math library.
static Eigen::Map< const Eigen::Vector3f > position(const struct xrt_pose &pose)
Return a Eigen type wrapping a pose's position (const).
Definition m_eigen_interop.hpp:250
static Eigen::Map< const Eigen::Quaternionf > orientation(const struct xrt_pose &pose)
Return a Eigen type wrapping a pose's orientation (const).
Definition m_eigen_interop.hpp:232
Definition u_pacing_compositor.c:58
Essential calibration data for a single camera, or single lens/sensor of a stereo camera.
Definition t_tracking.h:269
double intrinsics[3][3]
Camera intrinsics matrix.
Definition t_tracking.h:274
struct xrt_size image_size_pixels
Source image size.
Definition t_tracking.h:271
Floating point calibration data for a single calibrated camera.
Definition t_camera_models.h:62
The recording stream a rerun recorder logs through.
Definition constellation_rerun_common.hpp:149
void spawnViewer() const
Starts a viewer for this stream and points the stream at it.
Definition constellation_rerun_common.hpp:165
Basic frame data structure - holds a pointer to buffer.
Definition xrt_frame.h:25
A pose composed of a position and orientation.
Definition xrt_defines.h:522
A quaternion with single floats.
Definition xrt_defines.h:246
A 3 element vector with single floats.
Definition xrt_defines.h:310
Camera (un)projection C++ API for various camera models.
Header defining the tracking system integration in Monado.
Tracking API interface.
Common defines and enums for XRT.
Data frame header.