···3030#include <opencv2/core/mat.hpp>
3131#include <opencv2/core/version.hpp>
32323333+#include <deque>
3334#include <filesystem>
3435#include <fstream>
3536#include <iomanip>
···6970DEBUG_GET_ONCE_BOOL_OPTION(slam_write_csvs, "SLAM_WRITE_CSVS", false)
7071DEBUG_GET_ONCE_OPTION(slam_csv_path, "SLAM_CSV_PATH", "evaluation/")
7172DEBUG_GET_ONCE_BOOL_OPTION(slam_timing_stat, "SLAM_TIMING_STAT", true)
7373+DEBUG_GET_ONCE_BOOL_OPTION(slam_features_stat, "SLAM_FEATURES_STAT", true)
72747375//! Namespace for the interface to the external SLAM tracking system
7476namespace xrt::auxiliary::tracking::slam {
7577constexpr int UI_TIMING_POSE_COUNT = 192;
7878+constexpr int UI_FEATURES_POSE_COUNT = 192;
7679constexpr int UI_GTDIFF_POSE_COUNT = 192;
7780constexpr int NUM_CAMS = 2; //!< This should be used as little as possible to allow setups that are not stereo
78818282+using std::deque;
7983using std::ifstream;
8084using std::make_shared;
8185using std::map;
8286using std::ofstream;
8787+using std::pair;
8388using std::shared_ptr;
8489using std::string;
8590using std::to_string;
···279284 }
280285};
281286287287+//! Writes feature information specific to a particular estimated pose
288288+class FeaturesWriter
289289+{
290290+public:
291291+ bool enabled; // Modified through UI
292292+293293+private:
294294+ string directory;
295295+ string filename;
296296+ ofstream file;
297297+ bool created = false;
298298+299299+ void
300300+ create()
301301+ {
302302+ create_directories(directory);
303303+ file = ofstream{directory + "/" + filename};
304304+ file << "#timestamp, cam0 feature count, cam1 feature count" CSV_EOL;
305305+ file << std::fixed << std::setprecision(CSV_PRECISION);
306306+ }
307307+308308+309309+public:
310310+ FeaturesWriter(const string &dir, const string &fn, bool e) : enabled(e), directory(dir), filename(fn) {}
311311+312312+ void
313313+ push(timepoint_ns ts, const vector<int> &counts)
314314+ {
315315+ if (!enabled) {
316316+ return;
317317+ }
318318+319319+ if (!created) {
320320+ created = true;
321321+ create();
322322+ }
323323+324324+ file << ts;
325325+ for (int count : counts) {
326326+ file << "," << count;
327327+ }
328328+ file << CSV_EOL;
329329+ }
330330+};
282331/*!
283332 * Main implementation of @ref xrt_tracked_slam. This is an adapter class for
284333 * SLAM tracking that wraps an external SLAM implementation.
···359408 // Stats and metrics
360409361410 // CSV writers for offline analysis (using pointers because of container_of)
362362- TimingWriter *slam_times_writer; //!< Timestamps of the pipeline for performance analysis
363363- TrajectoryWriter *slam_traj_writer; //!< Estimated poses from the SLAM system
364364- TrajectoryWriter *pred_traj_writer; //!< Predicted poses
365365- TrajectoryWriter *filt_traj_writer; //!< Predicted and filtered poses
411411+ TimingWriter *slam_times_writer; //!< Timestamps of the pipeline for performance analysis
412412+ FeaturesWriter *slam_features_writer; //!< Feature tracking information for analysis
413413+ TrajectoryWriter *slam_traj_writer; //!< Estimated poses from the SLAM system
414414+ TrajectoryWriter *pred_traj_writer; //!< Predicted poses
415415+ TrajectoryWriter *filt_traj_writer; //!< Predicted and filtered poses
366416367417 //! Tracker timing info for performance evaluation
368418 struct
···381431 struct u_var_button enable_btn; //!< Toggle tracker timing reports
382432 } timing;
383433434434+ //! Tracker feature tracking info
435435+ struct Features
436436+ {
437437+ struct FeatureCounter
438438+ {
439439+ //! Feature count for each frame timestamp for this camera.
440440+ //! @note Harmless race condition over this as the UI might read this while it's being written
441441+ deque<pair<timepoint_ns, int>> entries{};
442442+443443+ //! Persitently stored camera name for display in the UI
444444+ string cam_name;
445445+446446+ void
447447+ addFeatureCount(timepoint_ns ts, int count)
448448+ {
449449+ entries.emplace_back(ts, count);
450450+ if (entries.size() > UI_FEATURES_POSE_COUNT) {
451451+ entries.pop_front();
452452+ }
453453+ }
454454+ };
455455+456456+ vector<FeatureCounter> fcs; //!< Store feature count info for each camera
457457+ u_var_curves fcs_ui; //!< Display of `fcs` in UI
458458+459459+ bool ext_available = false; //!< Whether the SLAM system supports the features extension
460460+ bool ext_enabled = false; //!< Whether the features extension is enabled
461461+ struct u_var_button enable_btn; //!< Toggle extension
462462+ } features;
463463+384464 //! Ground truth related fields
385465 struct
386466 {
···403483static void
404484timing_ui_setup(TrackerSlam &t)
405485{
486486+ u_var_add_ro_ftext(&t, "\n%s", "Tracker timing");
487487+406488 // Setup toggle button
407489 static const char *msg[2] = {"[OFF] Enable timing", "[ON] Disable timing"};
408490 u_var_button_cb cb = [](void *t_ptr) {
···484566485567/*
486568 *
569569+ * Feature information functionality
570570+ *
571571+ */
572572+573573+static void
574574+features_ui_setup(TrackerSlam &t)
575575+{
576576+ // We can't do anything useful if the system doesn't implement the feature
577577+ if (!t.features.ext_available) {
578578+ return;
579579+ }
580580+581581+ u_var_add_ro_ftext(&t, "\n%s", "Tracker features");
582582+583583+ // Setup toggle button
584584+ static const char *msg[2] = {"[OFF] Enable features info", "[ON] Disable features info"};
585585+ u_var_button_cb cb = [](void *t_ptr) {
586586+ TrackerSlam *t = (TrackerSlam *)t_ptr;
587587+ u_var_button &btn = t->features.enable_btn;
588588+ bool &e = t->features.ext_enabled;
589589+ e = !e;
590590+ snprintf(btn.label, sizeof(btn.label), "%s", msg[e]);
591591+ const auto params = make_shared<FPARAMS_EPEF>(e);
592592+ shared_ptr<void> _;
593593+ t->slam->use_feature(F_ENABLE_POSE_EXT_FEATURES, params, _);
594594+ };
595595+ t.features.enable_btn.cb = cb;
596596+ t.features.enable_btn.disabled = !t.features.ext_available;
597597+ t.features.enable_btn.ptr = &t;
598598+ u_var_add_button(&t, &t.features.enable_btn, msg[t.features.ext_enabled]);
599599+600600+ // Setup graph
601601+602602+ u_var_curve_getter getter = [](void *fs_ptr, int i) -> u_var_curve_point {
603603+ auto *fs = (TrackerSlam::Features::FeatureCounter *)fs_ptr;
604604+ timepoint_ns now = os_monotonic_get_ns();
605605+606606+ size_t size = fs->entries.size();
607607+ if (size == 0) {
608608+ return {0, 0};
609609+ }
610610+611611+ int last_idx = size - 1;
612612+ if (i > last_idx) {
613613+ i = last_idx;
614614+ }
615615+616616+ auto [ts, count] = fs->entries.at(last_idx - i);
617617+ return {time_ns_to_s(now - ts), double(count)};
618618+ };
619619+620620+ t.features.fcs_ui.curve_count = NUM_CAMS;
621621+ t.features.fcs_ui.xlabel = "Last seconds";
622622+ t.features.fcs_ui.ylabel = "Number of features";
623623+624624+ t.features.fcs.resize(NUM_CAMS);
625625+ for (int i = 0; i < NUM_CAMS; i++) {
626626+ auto &fc = t.features.fcs[i];
627627+ fc.cam_name = "Cam" + to_string(i);
628628+629629+ auto &fc_ui = t.features.fcs_ui.curves[i];
630630+ fc_ui.count = UI_FEATURES_POSE_COUNT;
631631+ fc_ui.data = &fc;
632632+ fc_ui.getter = getter;
633633+ fc_ui.label = fc.cam_name.c_str();
634634+ }
635635+636636+ u_var_add_curves(&t, &t.features.fcs_ui, "Feature count");
637637+}
638638+639639+static vector<int>
640640+features_ui_push(TrackerSlam &t, const pose &ppp)
641641+{
642642+ if (!t.features.ext_available) {
643643+ return {};
644644+ }
645645+646646+ shared_ptr<pose_extension> ext = ppp.find_pose_extension(pose_ext_type::FEATURES);
647647+ if (!ext) {
648648+ return {};
649649+ }
650650+651651+ pose_ext_features pef = *std::static_pointer_cast<pose_ext_features>(ext);
652652+653653+ // Push to the UI graph
654654+ vector<int> fcs{};
655655+ for (size_t i = 0; i < pef.features_per_cam.size(); i++) {
656656+ int count = pef.features_per_cam.at(i).size();
657657+ t.features.fcs.at(i).addFeatureCount(ppp.timestamp, count);
658658+ fcs.push_back(count);
659659+ }
660660+661661+ return fcs;
662662+}
663663+664664+/*
665665+ *
487666 * Ground truth functionality
488667 *
489668 */
···562741static void
563742gt_ui_setup(TrackerSlam &t)
564743{
744744+ u_var_add_ro_ftext(&t, "\n%s", "Tracker groundtruth");
565745 t.gt.diff_ui.values.data = t.gt.diffs_mm;
566746 t.gt.diff_ui.values.length = UI_GTDIFF_POSE_COUNT;
567747 t.gt.diff_ui.values.index_ptr = &t.gt.diff_idx;
···638818 if (t.timing.ext_enabled) {
639819 auto tss = timing_ui_push(t, np);
640820 t.slam_times_writer->push(tss);
821821+ }
822822+823823+ if (t.features.ext_enabled) {
824824+ vector feat_count = features_ui_push(t, np);
825825+ t.slam_features_writer->push(nts, feat_count);
641826 }
642827643828 dequeued = t.slam->try_dequeue_pose(tracked_pose);
···797982 u_var_add_f32(&t, &t.gravity_correction.z, "Gravity Correction");
798983799984 u_var_add_gui_header(&t, NULL, "Stats");
985985+ u_var_add_ro_ftext(&t, "\n%s", "Record to CSV files");
800986 u_var_add_bool(&t, &t.slam_traj_writer->enabled, "Record tracked trajectory");
801987 u_var_add_bool(&t, &t.pred_traj_writer->enabled, "Record predicted trajectory");
802988 u_var_add_bool(&t, &t.filt_traj_writer->enabled, "Record filtered trajectory");
803989 u_var_add_bool(&t, &t.slam_times_writer->enabled, "Record tracker times");
990990+ u_var_add_bool(&t, &t.slam_features_writer->enabled, "Record feature count");
804991 timing_ui_setup(t);
992992+ features_ui_setup(t);
805993 // Later, gt_ui_setup will setup the tracking error UI if ground truth becomes available
806994}
807995···10291217 os_thread_helper_destroy(&t_ptr->oth);
10301218 delete t.gt.trajectory;
10311219 delete t.slam_times_writer;
12201220+ delete t.slam_features_writer;
10321221 delete t.slam_traj_writer;
10331222 delete t.pred_traj_writer;
10341223 delete t.filt_traj_writer;
···10731262 config->write_csvs = debug_get_bool_option_slam_write_csvs();
10741263 config->csv_path = debug_get_option_slam_csv_path();
10751264 config->timing_stat = debug_get_bool_option_slam_timing_stat();
12651265+ config->features_stat = debug_get_bool_option_slam_features_stat();
10761266 config->stereo_calib = NULL;
10771267 config->imu_calib = NULL;
10781268 config->extra_calib = NULL;
···11821372 t.timing.ext_enabled = enable_timing_extension;
11831373 }
1184137413751375+ // Setup features extension
13761376+ bool has_features_extension = t.slam->supports_feature(F_ENABLE_POSE_EXT_FEATURES);
13771377+ t.features.ext_available = has_features_extension;
13781378+ if (has_features_extension) {
13791379+ bool enable_features_extension = config->features_stat;
13801380+13811381+ const auto params = make_shared<FPARAMS_EPET>(enable_features_extension);
13821382+ shared_ptr<void> _;
13831383+ t.slam->use_feature(F_ENABLE_POSE_EXT_FEATURES, params, _);
13841384+13851385+ t.features.ext_enabled = enable_features_extension;
13861386+ }
13871387+11851388 // Setup CSV files
11861389 bool write_csvs = config->write_csvs;
11871390 string dir = config->csv_path;
11881391 t.slam_times_writer = new TimingWriter{dir, "timing.csv", write_csvs, t.timing.columns};
13921392+ t.slam_features_writer = new FeaturesWriter{dir, "features.csv", write_csvs};
11891393 t.slam_traj_writer = new TrajectoryWriter{dir, "tracking.csv", write_csvs};
11901394 t.pred_traj_writer = new TrajectoryWriter{dir, "prediction.csv", write_csvs};
11911395 t.filt_traj_writer = new TrajectoryWriter{dir, "filtering.csv", write_csvs};
+1
src/xrt/auxiliary/tracking/t_tracking.h
···470470 bool write_csvs; //!< Whether to enable CSV writers from the start for later analysis
471471 const char *csv_path; //!< Path to write CSVs to
472472 bool timing_stat; //!< Enable timing metric in external system
473473+ bool features_stat; //!< Enable feature metric in external system
473474474475 // Instead of a slam_config file you can set custom calibration data
475476 const struct t_stereo_camera_calibration *stereo_calib; //!< Camera calibration data