Monado OpenXR Runtime
Loading...
Searching...
No Matches
sensor_fusion.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 Constellation tracker sensor fusion.
6 * @author Beyley Cardellio <ep1cm1n10n123@gmail.com>
7 * @ingroup tracking
8 */
9
10#pragma once
11
12#include "xrt/xrt_config_build.h"
13
14#include "os/os_threading.h"
15
17
19
21
23
24#include <array>
25#include <atomic>
26#include <ceres/context.h>
27#include <cstddef>
28#include <cstring>
29#include <memory>
30#include <optional>
31#include <string>
32#include <type_traits>
33#include <vector>
34
35
36namespace xrt::tracking::constellation {
37
38// Declared here rather than included, since the tracker's internal header is the one that includes this file.
39struct CameraSample;
40struct ConstellationTracker;
41
42}; // namespace xrt::tracking::constellation
43
44namespace xrt::tracking::constellation::optimizer::sensor_fusion {
45
46using namespace xrt::auxiliary;
47using namespace xrt::tracking;
48using namespace xrt::tracking::constellation::optimizer;
49
50//! How many keyframes to keep in our sliding window.
51static constexpr uint32_t kSlidingWindowSize = 3;
52
53//! The number of IMU samples to keep in our ring buffer
54static constexpr uint32_t kImuBufferSize = 5000;
55
56/*!
57 * The most IMU samples we ever expect to find between two keyframes. At a 1KHz IMU and a keyframe every few tens of
58 * milliseconds the real count is well under a hundred, so this is generous headroom rather than a tight bound. Sizing
59 * the staging buffer off this instead of `kImuBufferSize` keeps it small enough to sit on the stack.
60 */
61static constexpr uint32_t kMaxImuSamplesPerKeyframe = 512;
62
63/*!
64 * How many newer keyframes have to pile up before a keyframe gets sealed and marginalized.
65 */
66static constexpr uint32_t kKeyframeSealLagKeyframes = 2;
67
69 "the oldest keyframe in the window has to be sealed by the time it is evicted, so that marginalization "
70 "never has to fold an open keyframe into the prior");
71
72
73/*!
74 * How many camera samples may be queued for the fusion thread before new ones start being dropped.
75 *
76 * The fusion thread drains the whole queue every pass, so if this grows past even a handful, the thread has stalled a
77 * lot and we should just warn and start dropping them to not use an ever-increasing amount of RAM.
78 */
79static constexpr uint32_t kMaxQueuedCameraSamples = 32;
80
82{
83 //! The camera that this observation is tied to
84 uint32_t camera_idx;
85
86 //! The camera pose at the timestamp
88
89 //! The seed cam->device transform
91
92 //! 2D observations
93 std::array<xrt_vec2, XRT_CONSTELLATION_MAX_LEDS_PER_DEVICE> points2d;
94 //! 3D model correspondences
95 std::array<xrt_vec3, XRT_CONSTELLATION_MAX_LEDS_PER_DEVICE> points3d;
96
97 uint32_t num_points;
98};
99
101{
102 //! The mosaic this keyframe corresponds to
103 uint32_t mosaic_idx;
104 //! The timestamp of this observation
106
107 /*!
108 * Set once this keyframe can no longer accept observations due to marginalization or age.
109 */
110 bool sealed;
111
112 std::array<DeviceObservation, XRT_TRACKING_MAX_CAMS> observations;
113 uint32_t num_observations;
114};
115
116/*!
117 * One IMU sample as it sits in @ref Device::imu_samples.
118 *
119 * The fusion thread reads these while the IMU thread is writing them, so the words are loaded and stored one at a time
120 * through relaxed atomics. This can cause torn reads on the reader end, but it will not be UB.
121 *
122 * Relaxed is the right ordering for the words themselves. All the ordering that matters lives on
123 * @ref Device::imu_write_count, whose release/acquire pair is what publishes a finished slot, plus a fence pair around
124 * the words that lets a reader detect a slot being overwritten underneath it.
125 */
127{
128 static_assert(std::is_trivially_copyable_v<xrt_imu_sample>);
129 static_assert(sizeof(xrt_imu_sample) % sizeof(uint64_t) == 0,
130 "the sample has to decompose into whole words to be stored and loaded atomically");
131 static_assert(std::atomic<uint64_t>::is_always_lock_free,
132 "a locking atomic here would put a mutex back on the IMU push path");
133
134 static constexpr size_t kNumWords = sizeof(xrt_imu_sample) / sizeof(uint64_t);
135
136 //! The word `xrt_imu_sample::timestamp_ns` lives in, which is the only one the collector's bisection needs.
137 static constexpr size_t kTimestampWord = offsetof(xrt_imu_sample, timestamp_ns) / sizeof(uint64_t);
138
139 std::array<std::atomic<uint64_t>, kNumWords> words;
140
141 void
142 store(const xrt_imu_sample &sample) noexcept
143 {
144 std::array<uint64_t, kNumWords> raw;
145 std::memcpy(raw.data(), &sample, sizeof(sample));
146
147 for (size_t i = 0; i < kNumWords; i++) {
148 this->words[i].store(raw[i], std::memory_order_relaxed);
149 }
150 }
151
153 load() const noexcept
154 {
155 std::array<uint64_t, kNumWords> raw;
156 for (size_t i = 0; i < kNumWords; i++) {
157 raw[i] = this->words[i].load(std::memory_order_relaxed);
158 }
159
160 xrt_imu_sample sample;
161 std::memcpy(&sample, raw.data(), sizeof(sample));
162 return sample;
163 }
164
165 //! Just the timestamp, so the bisection does not pull in six words it is only going to throw away.
167 loadTimestamp() const noexcept
168 {
169 const uint64_t raw = this->words[kTimestampWord].load(std::memory_order_relaxed);
170
171 timepoint_ns timestamp;
172 std::memcpy(&timestamp, &raw, sizeof(timestamp));
173 return timestamp;
174 }
175};
176
178{
179 //! Needs to be recomputed
180 Dirty,
181 //! Does not refer to any real pre-integration. This term contributes nothing and can be skipped.
182 Empty,
183 //! This preintegration is complete and will never change.
185};
186
187/*!
188 * Stores state for a single preintegration residual for a device.
189 */
191{
192 PreintegratedImuSamples imu_preintegration;
193
195
196 bool
197 dirty() const
198 {
199 return this->state == DevicePreintegrationState::Dirty;
200 }
201
203 Identity(DevicePreintegrationState state)
204 {
205 return {
206 .imu_preintegration = PreintegratedImuSamples::Identity(),
207 .state = state,
208 };
209 }
210};
211
212/*!
213 * The IMU intrinsics the rig calibration happened to solve for, as the fusion takes them in as a seed.
214 *
215 * A copy of @ref rig_calibration::ImuCalibration rather than that type itself.
216 */
218{
219 //! Accelerometer bias, in meters per second squared.
221 //! Gyroscope bias, in radians per second.
223 //! Per-axis accelerometer scale, unitless.
225
226 //! The LED model -> IMU rotation.
228};
229
231{
232 //! The time of the solve
234 //! The world -> LED model pose at the solve
236 //! The world velocity at the solve
238 //! The imu calibration at the solve
240};
241
242struct Device
243{
244public: // Fields
245 const t_constellation_device_id_t id;
246
247 const t_constellation_tracker_led_model *const led_model;
248
249 //! The latest fused solve.
250 std::optional<DeviceSolveState> latest_solve;
251 //! The solved state for the latest IMU dead reckoned position.
252 std::optional<DeviceSolveState> latest_imu_reckon;
253
254 //! The relation history of the device, used as a fast way to retrieve the latest dead-reckoned poses.
256
257 //! Pre-calibrated accelerometer bias, in meters per second squared.
259
260 /*!
261 * The preintegrated IMU samples between keyframes, as a ring running parallel to @ref
262 * SensorFusion::keyframe_storage: slot `i` holds the interval that *ends* at the keyframe in slot `i`.
263 *
264 * Sharing the keyframe ring's indexing is what makes a window shift free for these too. The slot belonging
265 * to the oldest keyframe in the window has no predecessor to integrate from and is simply never read. Index
266 * this through @ref SensorFusion::preintegrationLocked rather than directly.
267 */
268 std::array<DevicePreintegration, kSlidingWindowSize> preintegrations;
269
270 /*!
271 * The total number of IMU samples ever pushed for this device. Absolute sample `n` lives in ring slot
272 * `n % kImuBufferSize`, so this is both the write pointer and the count of samples that have ever been
273 * written. Incrementing forever means there's no wraparound behaviour to worry about when the reader gets
274 * lapped by the writer.
275 *
276 * Only the IMU push thread writes this, and it publishes it with a release store once the slot it names has
277 * been filled. Readers must load it with acquire before touching `imu_samples`, and re-load it after an acquire
278 * fence to confirm the writer did not overwrite what they just read; the writer issues the matching release
279 * fence before it starts overwriting a slot.
280 */
281 std::atomic<uint64_t> imu_write_count;
282
283 std::atomic<timepoint_ns> latest_imu_ts_ns;
284
285 //! The buffered IMU samples, indexed by absolute sample index modulo `kImuBufferSize`.
286 std::array<RingImuSample, kImuBufferSize> imu_samples;
287
288public: // Methods
289 Device(t_constellation_device_id_t id, const t_constellation_tracker_led_model *led_model);
290 ~Device();
291
292 Device(const Device &) = delete;
293 Device &
294 operator=(const Device &) = delete;
295
297 getLatestImuCalibration() const;
298
299 /*!
300 * Resets the device's state to a fresh state.
301 *
302 * Can be used if all prior converged data can no longer
303 * be considered trusted (eg. large jump in clock sync).
304 */
305 void
306 resetLocked();
307};
308
310{
311public: // Fields
312 size_t mosaic_idx;
313 size_t camera_idx;
314
315 //! Intrinsics and distortion, which is what turns a LED in the device's frame into a pixel.
317};
318
319/*
320 * Solve snapshot
321 *
322 * A description of one solve that stands still while the next pass moves the window on underneath it. The fusion
323 * thread owns the window outright.
324 */
325
326//! A device's solved state at one keyframe.
328{
329 //! Solved world -> LED model pose.
331 //! Solved world-frame velocity, in meters per second.
333
334 //! Solved accelerometer bias, in meters per second squared.
336 //! Solved gyroscope bias, in radians per second.
338 //! Solved per-axis accelerometer scale, unitless.
340};
341
342/*!
343 * One camera's view of one device at one keyframe.
344 *
345 * Carries the camera intrinsics alongside the correspondences so that whatever consumes this can reproject the solved
346 * pose without having to reach back into @ref SensorFusion::cameras for them.
347 */
349{
350 uint32_t mosaic_idx;
351 uint32_t camera_idx;
352
353 xrt_pose Tcv_world_cam;
354 xrt_pose Tcv_cam_device_seed;
355
356 camera_model params;
357
358 uint32_t num_points;
359 std::array<xrt_vec2, XRT_CONSTELLATION_MAX_LEDS_PER_DEVICE> points2d;
360 std::array<xrt_vec3, XRT_CONSTELLATION_MAX_LEDS_PER_DEVICE> points3d;
361};
362
364{
365 timepoint_ns timestamp_ns;
366 uint32_t mosaic_idx;
367 bool sealed;
368
369 uint32_t num_observations;
370 std::array<SnapshotObservation, XRT_TRACKING_MAX_CAMS> observations;
371
373};
374
376{
377 t_constellation_device_id_t id;
378
379 //! A bit per keyframe age the solve held a state for.
381
382 //! Solved LED model -> IMU rotation.
384
385 //! The LED model -> IMU rotation from the rig calibration, for measuring how far the solve has walked from it.
387};
388
389/*!
390 * One solve, whole: what went in, what came out, and how the solver got there.
391 */
393{
394 //! Counts solves over the life of the fusion, and is what the logger uses as a sequence timeline.
395 uint64_t solve_index{0};
396
397 uint32_t num_keyframes;
398 //! Newest first, so index `i` is keyframe age `i`.
399 std::array<SnapshotKeyframe, kSlidingWindowSize> keyframes;
400
401 SnapshotDevice device;
402
403 //! Solved gravity magnitude, in meters per second squared.
404 double world_gravity_mag{0.0};
405
406 uint64_t num_late_samples_dropped{0};
407
408 int num_residuals{0};
409 int num_parameter_blocks{0};
410 uint32_t num_iterations{0};
411
412 //! Residual RMS in standard deviations, before and after the solve. See @ref kTargetResidualRmsSigma.
413 double initial_rms_sigma{0.0};
414 double final_rms_sigma{0.0};
415
416 double solve_time_s{0.0};
417
418 //! Ceres' termination type, already stringified, since the logger has no business including Ceres headers.
419 std::string termination;
420};
421
423{
424public: // Fields
425 ceres::Context *context;
426
427 u_logging_level log_level;
428
429 /*!
430 * The fusion thread, lock does not protect fusion operations, only threading operations like
431 * wakeup and inter-thread sample transfer.
432 *
433 * See @ref SensorFusion::lock for fusion operations.
434 */
436
437 //! Locks heavy fusion operations, but no threading operations (such as wakeup/sleep).
438 os::Mutex lock;
439
440 /*!
441 * Backing store for the keyframe ring. The keyframe with absolute index `n` lives in slot
442 * `n % kSlidingWindowSize`.
443 *
444 * Reach these through @ref keyframeLocked instead of indexing here, and never move the entries around.
445 */
446 std::array<Keyframe, kSlidingWindowSize> keyframe_storage{};
447
448 //! One past the absolute index of the newest keyframe, so the newest keyframe is `keyframe_head - 1`.
449 uint64_t keyframe_head{0};
450
451 //! How many slots of the ring hold real keyframes, saturating at `kSlidingWindowSize`.
452 uint32_t num_keyframes{0};
453
454 //! The cameras that are present in the system
455 std::array<std::array<CameraDescription, XRT_TRACKING_MAX_CAMS>, XRT_CONSTELLATION_MAX_TRACKING_MOSAICS>
457
458 Device device;
459
460 /*!
461 * Camera samples handed over by @ref pushCameraSample, in arrival order, waiting to be folded into the
462 * window by the fusion thread.
463 */
464 std::vector<constellation::CameraSample> sample_queue;
465
466 /*!
467 * How many samples have been thrown away for arriving with @ref sample_queue already at
468 * @ref kMaxQueuedCameraSamples.
469 *
470 * If this grows, it indicates the fusion thread has likely stalled or is running too slowly.
471 */
473
474 //! Whether the current state is dirty and we need to run an optimization
475 bool dirty{false};
476
477 //! How many camera samples have been dropped for turning up after their keyframe was already sealed.
479
480 //! The active Ceres problem.
481 struct SensorFusionProblem *problem{nullptr};
482
483#ifdef XRT_FEATURE_RERUN
484 /*!
485 * Rerun logging, null unless enabled.
486 */
487 std::unique_ptr<struct RerunContext> rerun_context{nullptr};
488#endif
489
490 /*!
491 * The window and its solved states as of the most recent solve, filled by @ref runFusionLocked once Ceres has
492 * returned. Kept between solves since dead reckoning integrates with the solved gravity it holds.
493 *
494 * This is owned in full by the fusion thread, don't touch it outside of that.
495 */
497
498private: // Methods
499 //! Marks the fusion dirty, call with the fusion lock held.
500 void
501 markDirtyFusionLocked();
502
503 //! Wakes the fusion thread, call with the thread lock held.
504 void
505 wakeThreadLocked();
506
507 /*!
508 * Retires the keyframe falling out of the window and opens a fresh one at the head of the ring.
509 *
510 * @return The new keyframe, which is the newest in the window and starts out open.
511 */
512 Keyframe &
513 pushKeyframeLocked(const constellation::CameraSample &sample);
514
515 //! Seals every keyframe that has aged past @ref kKeyframeSealLagKeyframes.
516 void
517 sealAgedKeyframesLocked();
518
519 //! @return `true` when the built problem has something to solve.
520 bool
521 buildFusionLocked();
522
523 /*!
524 * Rebuilds the problem, without solving it, so that an eviction can marginalize against the window as it
525 * stands.
526 *
527 * Needed when an earlier eviction in the same pass has already marginalized against the current problem: the
528 * prior it produced lives outside that problem, so a second eviction against it would drop the prior.
529 */
530 void
531 rebuildForEvictionLocked();
532
533 /*!
534 * Integrates every dirty IMU interval of the device.
535 *
536 * @param[in,out] work_remaining Set when an interval could not be integrated yet and has to be retried.
537 */
538 void
539 preintegrateDeviceLocked(bool &work_remaining);
540
541 //! Triggers the sensor fusion to reset it's state
542 void
543 resetLocked();
544
545public: // Methods
546 SensorFusion(ceres::Context *context,
547 t_constellation_device_id_t id,
548 const t_constellation_tracker_led_model *led_model);
549
551
552 //! Returns the index of the keyframe in the ring buffer.
553 uint32_t
554 keyframeIdxLocked(uint32_t age) const;
555
556 /*!
557 * The age of the keyframe currently living in ring slot @p idx, or `false` when no keyframe in the window
558 * does.
559 */
560 bool
561 keyframeAgeForIdxLocked(size_t idx, uint32_t &out_age) const;
562
563 /*!
564 * The keyframe `age` steps back from the newest, so age 0 is the newest keyframe in the window and
565 * `num_keyframes - 1` is the oldest.
566 */
567 Keyframe &
568 keyframeLocked(uint32_t age);
569
570 const Keyframe &
571 keyframeLocked(uint32_t age) const;
572
573 //! The preintegration covering the interval that *ends* at `keyframeLocked(age)`.
575 preintegrationLocked(uint32_t age);
576
577 //! Adds a camera to the sensor fusion
578 void
579 addCamera(const CameraDescription &camera_description);
580
581 /*!
582 * Hands a device the IMU intrinsics a rig calibration solved for. This is used as a better seed start than
583 * assuming an identity calibration.
584 *
585 * Blocks until the current solve is complete.
586 */
587 void
589
590 /*!
591 * Hands a complete constellation camera sample to the fusion thread. Must be called with
592 * `tracker->device_lock` held.
593 *
594 * Does no work beyond the copy onto @ref sample_queue: everything the sample goes on to do to the window
595 * happens on the fusion thread, so a camera thread never blocks on anything more than the queue push.
596 */
597 void
599
600 /*!
601 * Pushes an IMU sample for this particular device, IMU samples should be in OpenXR coordinate space. IMU biases
602 * found during rig calibration should not be applied to these samples.
603 *
604 * May wait on large data copies during inter-thread transfer.
605 * If your thread timing is thrown off calling this, put it behind a threaded queue,
606 * but this should be fine without, the copies are fast.
607 */
608 void
609 pushImuSample(const xrt_imu_sample &sample);
610
611 //! Provides the latest predicted device pose for the requested timestamp.
612 void
613 getTrackedPose(timepoint_ns requested_time_ns, xrt_space_relation &out_relation);
614
615 /*!
616 * Appends any camera samples into @p pending, before sorting pending by timestamp order.
617 *
618 * @remarks Call with @ref SensorFusion::thread lock held.
619 *
620 * @return Whether any camera samples have been passed by the IMU, and so are ready for integration.
621 */
622 bool
623 pullReadyCameraSamplesLocked(timepoint_ns now_ns, std::vector<constellation::CameraSample> &pending);
624
625 void
626 writeSampleToKeyframe(Keyframe &keyframe, const DeviceObservation &observation, uint32_t camera_index);
627
628 /*!
629 * Folds one queued camera sample into the window, or drops if it's no longer able to be merged in.
630 *
631 * This is the far side of @ref sample_queue, and the only thing that grows the window.
632 */
633 void
635
636 /*!
637 * Dead reckons @p inout_solve forward through every IMU sample that has arrived since it, pushing a pose for
638 * each into the device's relation history.
639 *
640 * The history only takes poses newer than the newest it already holds, so wherever it already covers the range,
641 * the poses integrated there are dropped, and only the state carried out the end of the range is kept.
642 *
643 * @return false, leaving @p inout_solve untouched, when the samples could not be collected.
644 */
645 bool
647
648 /*!
649 * Uses the latest IMU reckoned solve and reckons it forward to the latest IMU sample
650 */
651 void
653
654 /*!
655 * Runs preintegration, and when @p have_new_input says the window has gained something to solve over,
656 * builds the problem, then solves it if need-be.
657 *
658 * @param have_new_input Whether this pass drained any camera samples, rather than being woken by an IMU
659 * sample arriving to unpark a preintegration.
660 */
661 void
662 tickLocked(bool have_new_input);
663};
664
665}; // namespace xrt::tracking::constellation::optimizer::sensor_fusion
u_logging_level
Logging level enum.
Definition u_logging.h:45
int64_t timepoint_ns
Integer timestamp type.
Definition u_time.h:77
Generic math helpers for IMU preintegration.
Small utility for keeping track of the history of an xrt_space_relation, ie.
Wrapper around OS threading native functions.
static constexpr uint32_t kKeyframeSealLagKeyframes
How many newer keyframes have to pile up before a keyframe gets sealed and marginalized.
Definition sensor_fusion.hpp:66
static constexpr uint32_t kImuBufferSize
The number of IMU samples to keep in our ring buffer.
Definition sensor_fusion.hpp:54
static constexpr uint32_t kMaxImuSamplesPerKeyframe
The most IMU samples we ever expect to find between two keyframes.
Definition sensor_fusion.hpp:61
DevicePreintegrationState
Definition sensor_fusion.hpp:178
@ Integrated
This preintegration is complete and will never change.
@ Empty
Does not refer to any real pre-integration. This term contributes nothing and can be skipped.
static constexpr uint32_t kMaxQueuedCameraSamples
How many camera samples may be queued for the fusion thread before new ones start being dropped.
Definition sensor_fusion.hpp:79
static constexpr uint32_t kSlidingWindowSize
How many keyframes to keep in our sliding window.
Definition sensor_fusion.hpp:51
Definition camera_model.h:21
Definition m_relation_history.cpp:50
All in one helper that handles locking, waiting for change and starting a thread.
Definition os_threading.h:500
The LED model is a series of points which define the real-world positions of all LEDs.
Definition t_constellation.h:311
Definition u_worker.c:38
Definition t_constellation_tracker_internal.hpp:128
Definition t_constellation_tracker_internal.hpp:453
camera_model params
Intrinsics and distortion, which is what turns a LED in the device's frame into a pixel.
Definition sensor_fusion.hpp:316
void resetLocked()
Resets the device's state to a fresh state.
Definition sensor_fusion.cpp:2220
m_relation_history * relation_history
The relation history of the device, used as a fast way to retrieve the latest dead-reckoned poses.
Definition sensor_fusion.hpp:255
std::atomic< uint64_t > imu_write_count
The total number of IMU samples ever pushed for this device.
Definition sensor_fusion.hpp:281
std::optional< DeviceSolveState > latest_imu_reckon
The solved state for the latest IMU dead reckoned position.
Definition sensor_fusion.hpp:252
std::array< RingImuSample, kImuBufferSize > imu_samples
The buffered IMU samples, indexed by absolute sample index modulo kImuBufferSize.
Definition sensor_fusion.hpp:286
DeviceImuCalibration stored_imu_calibration
Pre-calibrated accelerometer bias, in meters per second squared.
Definition sensor_fusion.hpp:258
std::optional< DeviceSolveState > latest_solve
The latest fused solve.
Definition sensor_fusion.hpp:250
std::array< DevicePreintegration, kSlidingWindowSize > preintegrations
The preintegrated IMU samples between keyframes, as a ring running parallel to SensorFusion::keyframe...
Definition sensor_fusion.hpp:268
The IMU intrinsics the rig calibration happened to solve for, as the fusion takes them in as a seed.
Definition sensor_fusion.hpp:218
xrt_vec3_f64 accel_scale XRT_VEC3_ONE
Per-axis accelerometer scale, unitless.
Definition sensor_fusion.hpp:224
xrt_quat Qcv_model_imu XRT_QUAT_IDENTITY
The LED model -> IMU rotation.
Definition sensor_fusion.hpp:227
xrt_vec3_f64 gyro_bias XRT_VEC3_ZERO
Gyroscope bias, in radians per second.
Definition sensor_fusion.hpp:222
xrt_vec3_f64 accel_bias XRT_VEC3_ZERO
Accelerometer bias, in meters per second squared.
Definition sensor_fusion.hpp:220
xrt_pose Tcv_cam_device_seed
The seed cam->device transform.
Definition sensor_fusion.hpp:90
std::array< xrt_vec3, XRT_CONSTELLATION_MAX_LEDS_PER_DEVICE > points3d
3D model correspondences
Definition sensor_fusion.hpp:95
uint32_t camera_idx
The camera that this observation is tied to.
Definition sensor_fusion.hpp:84
std::array< xrt_vec2, XRT_CONSTELLATION_MAX_LEDS_PER_DEVICE > points2d
2D observations
Definition sensor_fusion.hpp:93
xrt_pose Tcv_world_cam
The camera pose at the timestamp.
Definition sensor_fusion.hpp:87
Stores state for a single preintegration residual for a device.
Definition sensor_fusion.hpp:191
timepoint_ns time_ns
The time of the solve.
Definition sensor_fusion.hpp:233
DeviceImuCalibration imu_calibration
The imu calibration at the solve.
Definition sensor_fusion.hpp:239
xrt_vec3_f64 world_velocity
The world velocity at the solve.
Definition sensor_fusion.hpp:237
xrt_pose Tcv_world_device
The world -> LED model pose at the solve.
Definition sensor_fusion.hpp:235
uint32_t mosaic_idx
The mosaic this keyframe corresponds to.
Definition sensor_fusion.hpp:103
bool sealed
Set once this keyframe can no longer accept observations due to marginalization or age.
Definition sensor_fusion.hpp:110
timepoint_ns timestamp_ns
The timestamp of this observation.
Definition sensor_fusion.hpp:105
One IMU sample as it sits in Device::imu_samples.
Definition sensor_fusion.hpp:127
timepoint_ns loadTimestamp() const noexcept
Just the timestamp, so the bisection does not pull in six words it is only going to throw away.
Definition sensor_fusion.hpp:167
static constexpr size_t kTimestampWord
The word xrt_imu_sample::timestamp_ns lives in, which is the only one the collector's bisection needs...
Definition sensor_fusion.hpp:137
os::Mutex lock
Locks heavy fusion operations, but no threading operations (such as wakeup/sleep).
Definition sensor_fusion.hpp:438
void pushImuSample(const xrt_imu_sample &sample)
Pushes an IMU sample for this particular device, IMU samples should be in OpenXR coordinate space.
Definition sensor_fusion.cpp:2693
uint64_t num_late_samples_dropped
How many camera samples have been dropped for turning up after their keyframe was already sealed.
Definition sensor_fusion.hpp:478
bool dirty
Whether the current state is dirty and we need to run an optimization.
Definition sensor_fusion.hpp:475
SolveSnapshot snapshot
The window and its solved states as of the most recent solve, filled by runFusionLocked once Ceres ha...
Definition sensor_fusion.hpp:496
bool pullReadyCameraSamplesLocked(timepoint_ns now_ns, std::vector< constellation::CameraSample > &pending)
Appends any camera samples into pending, before sorting pending by timestamp order.
Definition sensor_fusion.cpp:2395
void addCamera(const CameraDescription &camera_description)
Adds a camera to the sensor fusion.
Definition sensor_fusion.cpp:2475
void updateDeviceRelationHistoryLocked()
Uses the latest IMU reckoned solve and reckons it forward to the latest IMU sample.
Definition sensor_fusion.cpp:2943
std::vector< constellation::CameraSample > sample_queue
Camera samples handed over by pushCameraSample, in arrival order, waiting to be folded into the windo...
Definition sensor_fusion.hpp:464
uint32_t keyframeIdxLocked(uint32_t age) const
Returns the index of the keyframe in the ring buffer.
Definition sensor_fusion.cpp:2242
std::array< Keyframe, kSlidingWindowSize > keyframe_storage
Backing store for the keyframe ring.
Definition sensor_fusion.hpp:446
void setDeviceImuCalibration(const DeviceImuCalibration &calibration)
Hands a device the IMU intrinsics a rig calibration solved for.
Definition sensor_fusion.cpp:2484
uint32_t num_keyframes
How many slots of the ring hold real keyframes, saturating at kSlidingWindowSize.
Definition sensor_fusion.hpp:452
struct SensorFusionProblem * problem
The active Ceres problem.
Definition sensor_fusion.hpp:481
bool keyframeAgeForIdxLocked(size_t idx, uint32_t &out_age) const
The age of the keyframe currently living in ring slot idx, or false when no keyframe in the window do...
Definition sensor_fusion.cpp:2248
DevicePreintegration & preintegrationLocked(uint32_t age)
The preintegration covering the interval that ends at keyframeLocked(age).
Definition sensor_fusion.cpp:2278
void pushCameraSample(constellation::ConstellationTracker *tracker, const constellation::CameraSample &sample)
Hands a complete constellation camera sample to the fusion thread.
Definition sensor_fusion.cpp:2521
bool reckonToLatestImu(DeviceSolveState &inout_solve)
Dead reckons inout_solve forward through every IMU sample that has arrived since it,...
Definition sensor_fusion.cpp:2899
void getTrackedPose(timepoint_ns requested_time_ns, xrt_space_relation &out_relation)
Provides the latest predicted device pose for the requested timestamp.
Definition sensor_fusion.cpp:2736
std::array< std::array< CameraDescription, XRT_TRACKING_MAX_CAMS >, XRT_CONSTELLATION_MAX_TRACKING_MOSAICS > cameras
The cameras that are present in the system.
Definition sensor_fusion.hpp:456
uint64_t num_queued_samples_dropped
How many samples have been thrown away for arriving with sample_queue already at kMaxQueuedCameraSamp...
Definition sensor_fusion.hpp:472
void tickLocked(bool have_new_input)
Runs preintegration, and when have_new_input says the window has gained something to solve over,...
Definition sensor_fusion.cpp:2961
Keyframe & keyframeLocked(uint32_t age)
The keyframe age steps back from the newest, so age 0 is the newest keyframe in the window and num_ke...
Definition sensor_fusion.cpp:2262
void processCameraSampleLocked(const constellation::CameraSample &sample)
Folds one queued camera sample into the window, or drops if it's no longer able to be merged in.
Definition sensor_fusion.cpp:2584
uint64_t keyframe_head
One past the absolute index of the newest keyframe, so the newest keyframe is keyframe_head - 1.
Definition sensor_fusion.hpp:449
uint32_t keyframe_mask
A bit per keyframe age the solve held a state for.
Definition sensor_fusion.hpp:380
xrt_quat Qcv_model_imu
Solved LED model -> IMU rotation.
Definition sensor_fusion.hpp:383
xrt_quat calibrated_Qcv_model_imu
The LED model -> IMU rotation from the rig calibration, for measuring how far the solve has walked fr...
Definition sensor_fusion.hpp:386
One camera's view of one device at one keyframe.
Definition sensor_fusion.hpp:349
One solve, whole: what went in, what came out, and how the solver got there.
Definition sensor_fusion.hpp:393
double world_gravity_mag
Solved gravity magnitude, in meters per second squared.
Definition sensor_fusion.hpp:404
uint64_t solve_index
Counts solves over the life of the fusion, and is what the logger uses as a sequence timeline.
Definition sensor_fusion.hpp:395
std::string termination
Ceres' termination type, already stringified, since the logger has no business including Ceres header...
Definition sensor_fusion.hpp:419
double initial_rms_sigma
Residual RMS in standard deviations, before and after the solve. See kTargetResidualRmsSigma.
Definition sensor_fusion.hpp:413
std::array< SnapshotKeyframe, kSlidingWindowSize > keyframes
Newest first, so index i is keyframe age i.
Definition sensor_fusion.hpp:399
A device's solved state at one keyframe.
Definition sensor_fusion.hpp:328
xrt_vec3_f64 gyro_bias
Solved gyroscope bias, in radians per second.
Definition sensor_fusion.hpp:337
xrt_vec3_f64 accel_scale
Solved per-axis accelerometer scale, unitless.
Definition sensor_fusion.hpp:339
xrt_vec3_f64 velocity
Solved world-frame velocity, in meters per second.
Definition sensor_fusion.hpp:332
xrt_pose Tcv_world_device
Solved world -> LED model pose.
Definition sensor_fusion.hpp:330
xrt_vec3_f64 accel_bias
Solved accelerometer bias, in meters per second squared.
Definition sensor_fusion.hpp:335
IMU Sample.
Definition xrt_tracking.h:143
A pose composed of a position and orientation.
Definition xrt_defines.h:522
A quaternion with single floats.
Definition xrt_defines.h:246
A relation with two spaces, includes velocity and acceleration.
Definition xrt_defines.h:713
A 3 element vector with single doubles.
Definition xrt_defines.h:322
Header defining the tracking system integration in Monado.
Header defining the constellation tracker parameters and functions.