diff --git a/crates/re_data_store/src/entity_properties.rs b/crates/re_data_store/src/entity_properties.rs index 9b4560aa953d..131bc85dfb65 100644 --- a/crates/re_data_store/src/entity_properties.rs +++ b/crates/re_data_store/src/entity_properties.rs @@ -1,5 +1,6 @@ #[cfg(feature = "serde")] use re_log_types::EntityPath; +use re_log_types::TimeInt; #[cfg(feature = "serde")] use crate::EditableAutoValue; @@ -172,25 +173,110 @@ impl EntityProperties { // ---------------------------------------------------------------------------- +/// One of the boundaries of the visible history. +/// +/// For [`VisibleHistoryBoundary::RelativeToTimeCursor`] and [`VisibleHistoryBoundary::Absolute`], +/// the value are either nanos or frames, depending on the type of timeline. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub enum VisibleHistoryBoundary { + /// Boundary is a value relative to the time cursor + RelativeToTimeCursor(i64), + + /// Boundary is an absolute value + Absolute(i64), + + /// The boundary extends to infinity. + Infinite, +} + +impl VisibleHistoryBoundary { + /// UI label to use when [`VisibleHistoryBoundary::RelativeToTimeCursor`] is selected. + pub const RELATIVE_LABEL: &'static str = "Relative"; + + /// UI label to use when [`VisibleHistoryBoundary::Absolute`] is selected. + pub const ABSOLUTE_LABEL: &'static str = "Absolute"; + + /// UI label to use when [`VisibleHistoryBoundary::Infinite`] is selected. + pub const INFINITE_LABEL: &'static str = "Infinite"; + + /// Value when the boundary is set to the current time cursor. + pub const AT_CURSOR: Self = Self::RelativeToTimeCursor(0); + + /// Label to use in the UI. + pub fn label(&self) -> &'static str { + match self { + Self::RelativeToTimeCursor(_) => Self::RELATIVE_LABEL, + Self::Absolute(_) => Self::ABSOLUTE_LABEL, + Self::Infinite => Self::INFINITE_LABEL, + } + } +} + +impl Default for VisibleHistoryBoundary { + fn default() -> Self { + Self::AT_CURSOR + } +} + +/// Visible history bounds. +#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct VisibleHistory { + /// Low time boundary. + pub from: VisibleHistoryBoundary, + + /// High time boundary. + pub to: VisibleHistoryBoundary, +} + +impl VisibleHistory { + /// Value with the visible history feature is disabled. + pub const OFF: Self = Self { + from: VisibleHistoryBoundary::AT_CURSOR, + to: VisibleHistoryBoundary::AT_CURSOR, + }; + + pub fn from(&self, cursor: TimeInt) -> TimeInt { + match self.from { + VisibleHistoryBoundary::Absolute(value) => TimeInt::from(value), + VisibleHistoryBoundary::RelativeToTimeCursor(value) => cursor + TimeInt::from(value), + VisibleHistoryBoundary::Infinite => TimeInt::MIN, + } + } + + pub fn to(&self, cursor: TimeInt) -> TimeInt { + match self.to { + VisibleHistoryBoundary::Absolute(value) => TimeInt::from(value), + VisibleHistoryBoundary::RelativeToTimeCursor(value) => cursor + TimeInt::from(value), + VisibleHistoryBoundary::Infinite => TimeInt::MAX, + } + } +} + /// When showing an entity in the history view, add this much history to it. #[derive(Clone, Copy, Default, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] pub struct ExtraQueryHistory { - /// Zero = off. - pub nanos: i64, + /// Is the feature enabled? + pub enabled: bool, + + /// Visible history settings for time timelines + pub nanos: VisibleHistory, - /// Zero = off. - pub sequences: i64, + /// Visible history settings for frame timelines + pub sequences: VisibleHistory, } impl ExtraQueryHistory { /// Multiply/and these together. #[allow(dead_code)] fn with_child(&self, child: &Self) -> Self { - Self { - nanos: self.nanos.max(child.nanos), - sequences: self.sequences.max(child.sequences), + if child.enabled { + *child + } else { + *self } } } diff --git a/crates/re_data_store/src/store_db.rs b/crates/re_data_store/src/store_db.rs index c61708b5bf30..09c0ed99eaaa 100644 --- a/crates/re_data_store/src/store_db.rs +++ b/crates/re_data_store/src/store_db.rs @@ -296,6 +296,10 @@ impl StoreDb { &self.entity_db.times_per_timeline } + pub fn time_histogram(&self, timeline: &Timeline) -> Option<&crate::TimeHistogram> { + self.entity_db().tree.prefix_times.get(timeline) + } + pub fn num_timeless_messages(&self) -> usize { self.entity_db.tree.num_timeless_messages() } diff --git a/crates/re_query/src/util.rs b/crates/re_query/src/util.rs index 1d1da7a755c8..3022e88ace72 100644 --- a/crates/re_query/src/util.rs +++ b/crates/re_query/src/util.rs @@ -1,5 +1,5 @@ use re_arrow_store::{DataStore, LatestAtQuery, RangeQuery, TimeInt, TimeRange, Timeline}; -use re_data_store::ExtraQueryHistory; +use re_data_store::{ExtraQueryHistory, VisibleHistory}; use re_log_types::EntityPath; use re_types_core::Archetype; @@ -17,14 +17,15 @@ pub fn query_archetype_with_history<'a, A: Archetype + 'a, const N: usize>( re_log_types::TimeType::Sequence => history.sequences, }; - if visible_history == 0 { + if !history.enabled || visible_history == VisibleHistory::OFF { let latest_query = LatestAtQuery::new(*timeline, *time); let latest = query_archetype::(store, &latest_query, ent_path)?; Ok(itertools::Either::Left(std::iter::once(latest))) } else { - let min_time = *time - TimeInt::from(visible_history); - let range_query = RangeQuery::new(*timeline, TimeRange::new(min_time, *time)); + let min_time = visible_history.from(*time); + let max_time = visible_history.to(*time); + let range_query = RangeQuery::new(*timeline, TimeRange::new(min_time, max_time)); let range = range_archetype::(store, &range_query, ent_path); diff --git a/crates/re_space_view_spatial/src/space_view_2d.rs b/crates/re_space_view_spatial/src/space_view_2d.rs index 1feaf4e9b38c..63e126accf7a 100644 --- a/crates/re_space_view_spatial/src/space_view_2d.rs +++ b/crates/re_space_view_spatial/src/space_view_2d.rs @@ -16,11 +16,15 @@ use crate::{ #[derive(Default)] pub struct SpatialSpaceView2D; +impl SpatialSpaceView2D { + pub const NAME: &'static str = "2D"; +} + impl SpaceViewClass for SpatialSpaceView2D { type State = SpatialSpaceViewState; fn name(&self) -> re_viewer_context::SpaceViewClassName { - "2D".into() + Self::NAME.into() } fn icon(&self) -> &'static re_ui::Icon { diff --git a/crates/re_space_view_spatial/src/space_view_3d.rs b/crates/re_space_view_spatial/src/space_view_3d.rs index 93da23aceaed..6873972f31ac 100644 --- a/crates/re_space_view_spatial/src/space_view_3d.rs +++ b/crates/re_space_view_spatial/src/space_view_3d.rs @@ -16,11 +16,15 @@ use crate::{ #[derive(Default)] pub struct SpatialSpaceView3D; +impl SpatialSpaceView3D { + pub const NAME: &'static str = "3D"; +} + impl SpaceViewClass for SpatialSpaceView3D { type State = SpatialSpaceViewState; fn name(&self) -> re_viewer_context::SpaceViewClassName { - "3D".into() + Self::NAME.into() } fn icon(&self) -> &'static re_ui::Icon { diff --git a/crates/re_time_panel/src/lib.rs b/crates/re_time_panel/src/lib.rs index 1f1f61c05a26..4a47ac9e2aed 100644 --- a/crates/re_time_panel/src/lib.rs +++ b/crates/re_time_panel/src/lib.rs @@ -822,10 +822,7 @@ fn initialize_time_ranges_ui( if let Some(times) = ctx .store_db - .entity_db() - .tree - .prefix_times - .get(ctx.rec_cfg.time_ctrl.timeline()) + .time_histogram(ctx.rec_cfg.time_ctrl.timeline()) { // NOTE: `times` can be empty if a GC wiped everything. if !times.is_empty() { diff --git a/crates/re_ui/src/lib.rs b/crates/re_ui/src/lib.rs index 4efd0f77b7ce..86271c055c67 100644 --- a/crates/re_ui/src/lib.rs +++ b/crates/re_ui/src/lib.rs @@ -15,6 +15,7 @@ pub use command_palette::CommandPalette; pub use design_tokens::DesignTokens; pub use icons::Icon; pub use layout_job_builder::LayoutJobBuilder; +use std::ops::RangeInclusive; pub use toggle_switch::toggle_switch; // --------------------------------------------------------------------------- @@ -445,6 +446,41 @@ impl ReUi { .inner } + /// Shows a `egui::DragValue` for a time in nanoseconds. + /// + /// The value and unit displayed adapts to the provided allowable time range. + #[allow(clippy::unused_self)] + pub fn time_drag_value( + &self, + ui: &mut egui::Ui, + value: &mut i64, + time_range: &RangeInclusive, + ) { + let span = time_range.end() - time_range.start(); + + let (unit, factor) = if span / 1_000_000_000 > 0 { + ("s", 1_000_000_000.) + } else if span / 1_000_000 > 0 { + ("ms", 1_000_000.) + } else if span / 1_000 > 0 { + ("μs", 1_000.) + } else { + ("ns", 1.) + }; + + let mut time_unit = *value as f32 / factor; + let time_range = *time_range.start() as f32 / factor..=*time_range.end() as f32 / factor; + let speed = (time_range.end() - time_range.start()) * 0.005; + + ui.add( + egui::DragValue::new(&mut time_unit) + .clamp_range(time_range) + .speed(speed) + .suffix(unit), + ); + *value = (time_unit * factor).round() as _; + } + pub fn large_button(&self, ui: &mut egui::Ui, icon: &Icon) -> egui::Response { self.large_button_impl(ui, icon, None, None) } diff --git a/crates/re_viewer/src/ui/selection_panel.rs b/crates/re_viewer/src/ui/selection_panel.rs index 6351ce4f4cd4..ecbf5c6b4779 100644 --- a/crates/re_viewer/src/ui/selection_panel.rs +++ b/crates/re_viewer/src/ui/selection_panel.rs @@ -1,14 +1,20 @@ -use egui::NumExt as _; +use egui::{NumExt as _, Ui}; +use std::ops::RangeInclusive; -use re_data_store::{ColorMapper, Colormap, EditableAutoValue, EntityPath, EntityProperties}; +use re_data_store::{ + ColorMapper, Colormap, EditableAutoValue, EntityPath, EntityProperties, VisibleHistoryBoundary, +}; use re_data_ui::{image_meaning_for_entity, item_ui, DataUi}; use re_log_types::TimeType; +use re_space_view_spatial::{SpatialSpaceView2D, SpatialSpaceView3D}; use re_types::{ components::{PinholeProjection, Transform3D}, tensor_data::TensorDataMeaning, }; +use re_types_core::ComponentName; use re_viewer_context::{ - gpu_bridge::colormap_dropdown_button_ui, Item, SpaceViewId, UiVerbosity, ViewerContext, + gpu_bridge::colormap_dropdown_button_ui, Item, SpaceViewClassName, SpaceViewId, UiVerbosity, + ViewerContext, }; use re_viewport::{Viewport, ViewportBlueprint}; @@ -303,9 +309,16 @@ fn blueprint_ui( // TODO(emilk): show the values of this specific instance (e.g. point in the point cloud)! } else { // splat - the whole entity + let space_view_class_name = *space_view.class_name(); let data_blueprint = space_view.contents.data_blueprints_individual(); let mut props = data_blueprint.get(&instance_path.entity_path); - entity_props_ui(ctx, ui, Some(&instance_path.entity_path), &mut props); + entity_props_ui( + ctx, + ui, + &space_view_class_name, + Some(&instance_path.entity_path), + &mut props, + ); data_blueprint.set(instance_path.entity_path.clone(), props); } } @@ -321,8 +334,15 @@ fn blueprint_ui( Item::DataBlueprintGroup(space_view_id, data_blueprint_group_handle) => { if let Some(space_view) = viewport.blueprint.space_view_mut(space_view_id) { + let space_view_class_name = *space_view.class_name(); if let Some(group) = space_view.contents.group_mut(*data_blueprint_group_handle) { - entity_props_ui(ctx, ui, None, &mut group.properties_individual); + entity_props_ui( + ctx, + ui, + &space_view_class_name, + None, + &mut group.properties_individual, + ); } else { ctx.selection_state_mut().clear_current(); } @@ -364,6 +384,7 @@ fn list_existing_data_blueprints( fn entity_props_ui( ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui, + space_view_class_name: &SpaceViewClassName, entity_path: Option<&EntityPath>, entity_props: &mut EntityProperties, ) { @@ -373,36 +394,11 @@ fn entity_props_ui( .checkbox(ui, &mut entity_props.interactive, "Interactive") .on_hover_text("If disabled, the entity will not react to any mouse interaction"); + visible_history_ui(ctx, ui, space_view_class_name, entity_path, entity_props); + egui::Grid::new("entity_properties") .num_columns(2) .show(ui, |ui| { - ui.label("Visible history"); - let visible_history = &mut entity_props.visible_history; - match ctx.rec_cfg.time_ctrl.timeline().typ() { - TimeType::Time => { - let mut time_sec = visible_history.nanos as f32 / 1e9; - let speed = (time_sec * 0.05).at_least(0.01); - ui.add( - egui::DragValue::new(&mut time_sec) - .clamp_range(0.0..=f32::INFINITY) - .speed(speed) - .suffix("s"), - ) - .on_hover_text("Include this much history of the Entity in the Space View"); - visible_history.nanos = (time_sec * 1e9).round() as _; - } - TimeType::Sequence => { - let speed = (visible_history.sequences as f32 * 0.05).at_least(1.0); - ui.add( - egui::DragValue::new(&mut visible_history.sequences) - .clamp_range(0.0..=f32::INFINITY) - .speed(speed), - ) - .on_hover_text("Include this much history of the Entity in the Space View"); - } - } - ui.end_row(); - // TODO(wumpf): It would be nice to only show pinhole & depth properties in the context of a 3D view. // if *view_state.state_spatial.nav_mode.get() == SpatialNavigationMode::ThreeD { if let Some(entity_path) = entity_path { @@ -413,6 +409,262 @@ fn entity_props_ui( }); } +fn visible_history_ui( + ctx: &mut ViewerContext<'_>, + ui: &mut Ui, + space_view_class_name: &SpaceViewClassName, + entity_path: Option<&EntityPath>, + entity_props: &mut EntityProperties, +) { + //TODO(#4107): support more space view types. + if space_view_class_name != SpatialSpaceView3D::NAME + && space_view_class_name != SpatialSpaceView2D::NAME + { + return; + } + + if !should_display_visible_history(ctx, entity_path) { + return; + } + + let re_ui = ctx.re_ui; + + re_ui + .checkbox( + ui, + &mut entity_props.visible_history.enabled, + "Visible history", + ) + .on_hover_text( + "Enable Visible History.\n\nBy default, only the last state before the \ + current time is shown. By activating Visible History, all data within a \ + time window is shown instead.", + ); + + let time_range = if let Some(times) = ctx + .store_db + .time_histogram(ctx.rec_cfg.time_ctrl.timeline()) + { + times.min_key().unwrap_or_default()..=times.max_key().unwrap_or_default() + } else { + 0..=0 + }; + + let current_time = ctx + .rec_cfg + .time_ctrl + .time_i64() + .unwrap_or_default() + .at_least(*time_range.start()); // accounts for timeless time (TimeInt::BEGINNING) + + let sequence_timeline = matches!(ctx.rec_cfg.time_ctrl.timeline().typ(), TimeType::Sequence); + + let visible_history = if sequence_timeline { + &mut entity_props.visible_history.sequences + } else { + &mut entity_props.visible_history.nanos + }; + + let visible_history_time_range = visible_history.from(current_time.into()).as_i64() + ..=visible_history.to(current_time.into()).as_i64(); + + ui.add_enabled_ui(entity_props.visible_history.enabled, |ui| { + egui::Grid::new("visible_history_boundaries") + .num_columns(4) + .show(ui, |ui| { + ui.label("From"); + visible_history_boundary_ui( + re_ui, + ui, + &mut visible_history.from, + sequence_timeline, + current_time, + time_range.clone(), + true, + *visible_history_time_range.end(), + ); + + ui.end_row(); + + ui.label("To"); + visible_history_boundary_ui( + re_ui, + ui, + &mut visible_history.to, + sequence_timeline, + current_time, + time_range, + false, + *visible_history_time_range.start(), + ); + + ui.end_row(); + }); + }); + ui.add( + egui::Label::new( + egui::RichText::new(if sequence_timeline { + "These settings apply to all sequence timelines." + } else { + "These settings apply to all temporal timelines." + }) + .italics() + .weak(), + ) + .wrap(true), + ); +} + +static VISIBLE_HISTORY_COMPONENT_NAMES: once_cell::sync::Lazy> = + once_cell::sync::Lazy::new(|| { + [ + ComponentName::from("rerun.components.Position2D"), + ComponentName::from("rerun.components.Position3D"), + ComponentName::from("rerun.components.LineStrip2D"), + ComponentName::from("rerun.components.LineStrip3D"), + ComponentName::from("rerun.components.TensorData"), + ComponentName::from("rerun.components.Vector3D"), + ComponentName::from("rerun.components.HalfSizes2D"), + ComponentName::from("rerun.components.HalfSizes3D"), + ] + .into() + }); + +// TODO(#4145): This method is obviously unfortunate. It's a temporary solution until the ViewPart +// system is able to report its ability to handle the visible history feature. +fn should_display_visible_history( + ctx: &mut ViewerContext<'_>, + entity_path: Option<&EntityPath>, +) -> bool { + if let Some(entity_path) = entity_path { + let store = ctx.store_db.store(); + let component_names = store.all_components(ctx.rec_cfg.time_ctrl.timeline(), entity_path); + if let Some(component_names) = component_names { + if !component_names + .iter() + .any(|name| VISIBLE_HISTORY_COMPONENT_NAMES.contains(name)) + { + return false; + } + } else { + return false; + } + } + + true +} + +#[allow(clippy::too_many_arguments)] +fn visible_history_boundary_ui( + re_ui: &re_ui::ReUi, + ui: &mut egui::Ui, + visible_history_boundary: &mut VisibleHistoryBoundary, + sequence_timeline: bool, + current_time: i64, + mut time_range: RangeInclusive, + low_bound: bool, + other_boundary_absolute: i64, +) { + let span = time_range.end() - time_range.start(); + + // Hot "usability" area! This achieves two things: + // 1) It makes sure the time range in relative mode has enough margin beyond the current + // timeline's span to avoid the boundary value to be modified by changing the current time + // cursor + // 2) It makes sure the two boundaries don't cross in time (i.e. low > high). It does so by + // prioritizing the low boundary. Moving the low boundary against the high boundary will + // displace the high boundary. On the other hand, the high boundary cannot be moved against + // the low boundary. This asymmetry is intentional, and avoids both boundaries fighting each + // other in some corner cases (when the user interacts with the current time cursor). + #[allow(clippy::collapsible_else_if)] // for readability + if matches!( + visible_history_boundary, + VisibleHistoryBoundary::RelativeToTimeCursor(_) + ) { + if low_bound { + time_range = -span..=2 * span; + } else { + time_range = + (other_boundary_absolute.saturating_sub(current_time)).at_least(-span)..=2 * span; + } + } else { + if !low_bound { + time_range = other_boundary_absolute.at_least(-span)..=*time_range.end(); + } + } + + match visible_history_boundary { + VisibleHistoryBoundary::RelativeToTimeCursor(value) + | VisibleHistoryBoundary::Absolute(value) => { + if sequence_timeline { + let speed = (span as f32 * 0.005).at_least(1.0); + + ui.add( + egui::DragValue::new(value) + .clamp_range(time_range) + .speed(speed), + ); + } else { + re_ui.time_drag_value(ui, value, &time_range); + } + } + VisibleHistoryBoundary::Infinite => { + let mut unused = 0.0; + ui.add_enabled( + false, + egui::DragValue::new(&mut unused).custom_formatter(|_, _| "∞".to_owned()), + ); + } + } + + let (abs_time, rel_time) = match visible_history_boundary { + VisibleHistoryBoundary::RelativeToTimeCursor(value) => (*value + current_time, *value), + VisibleHistoryBoundary::Absolute(value) => (*value, *value - current_time), + VisibleHistoryBoundary::Infinite => (current_time, 0), + }; + + egui::ComboBox::from_id_source(if low_bound { + "time_history_low_bound" + } else { + "time_history_high_bound" + }) + .selected_text(visible_history_boundary.label()) + .show_ui(ui, |ui| { + ui.set_min_width(64.0); + + ui.selectable_value( + visible_history_boundary, + VisibleHistoryBoundary::RelativeToTimeCursor(rel_time), + VisibleHistoryBoundary::RELATIVE_LABEL, + ) + .on_hover_text(if low_bound { + "Show data from a time point relative to the current time." + } else { + "Show data until a time point relative to the current time." + }); + ui.selectable_value( + visible_history_boundary, + VisibleHistoryBoundary::Absolute(abs_time), + VisibleHistoryBoundary::ABSOLUTE_LABEL, + ) + .on_hover_text(if low_bound { + "Show data from an absolute time point." + } else { + "Show data until an absolute time point." + }); + ui.selectable_value( + visible_history_boundary, + VisibleHistoryBoundary::Infinite, + VisibleHistoryBoundary::INFINITE_LABEL, + ) + .on_hover_text(if low_bound { + "Show data from the beginning of the timeline" + } else { + "Show data until the end of the timeline" + }); + }); +} + fn colormap_props_ui( ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui, diff --git a/tests/python/visible_history_playground/main.py b/tests/python/visible_history_playground/main.py new file mode 100644 index 000000000000..1c9b97a7c4c7 --- /dev/null +++ b/tests/python/visible_history_playground/main.py @@ -0,0 +1,61 @@ +"""Playground to test the visible history feature.""" +from __future__ import annotations + +import argparse +import math + +import numpy as np +import rerun as rr + +parser = argparse.ArgumentParser(description=__doc__) +rr.script_add_args(parser) +args = parser.parse_args() +rr.script_setup(args, "rerun_example_visible_history_playground") + + +rr.log("bbox", rr.Boxes2D(centers=[50, 3.5], half_sizes=[50, 4.5], colors=[255, 0, 0]), timeless=True) +rr.log("transform", rr.Transform3D(translation=[0, 0, 0])) +rr.log("some/nested/pinhole", rr.Pinhole(focal_length=3, width=3, height=3), timeless=True) + +rr.log("3dworld/depthimage/pinhole", rr.Pinhole(focal_length=20, width=100, height=10), timeless=True) +rr.log("3dworld/image", rr.Transform3D(translation=[0, 1, 0]), timeless=True) +rr.log("3dworld/image/pinhole", rr.Pinhole(focal_length=20, width=100, height=10), timeless=True) + +for i in range(1, 100): + rr.set_time_seconds("seconds", i) + rr.set_time_seconds("ms", i / 1000) + rr.set_time_seconds("us", i / 1000000) + rr.set_time_sequence("frames_offset", 10000 + i) + rr.set_time_sequence("frames", i) + + rr.log("world/data/nested/point", rr.Points2D([[i, 0], [i, 1]], radii=0.4)) + rr.log("world/data/nested/point2", rr.Points2D([i, 2], radii=0.4)) + rr.log("world/data/nested/box", rr.Boxes2D(centers=[i, 1], half_sizes=[0.5, 0.5])) + rr.log("world/data/nested/arrow", rr.Arrows3D(origins=[i, 4, 0], vectors=[0, 1.7, 0])) + rr.log( + "world/data/nested/linestrip", + rr.LineStrips2D([[[i - 0.4, 6], [i + 0.4, 6], [i - 0.4, 7], [i + 0.4, 7]], [[i - 0.2, 6.5], [i + 0.2, 6.5]]]), + ) + + rr.log("world/data/nested/transformed", rr.Transform3D(translation=[i, 0, 0])) + rr.log("world/data/nested/transformed/point", rr.Boxes2D(centers=[0, 3], half_sizes=[0.5, 0.5])) + + rr.log("text_log", rr.TextLog(f"hello {i}")) + rr.log("scalar", rr.TimeSeriesScalar(math.sin(i / 100 * 2 * math.pi))) + + depth_image = 100 * np.ones((10, 100), dtype=np.float32) + depth_image[:, i] = 50 + rr.log("3dworld/depthimage/pinhole/data", rr.DepthImage(depth_image, meter=100)) + + image = 100 * np.ones((10, 100, 3), dtype=np.uint8) + image[:, i, :] = [255, 0, 0] + rr.log("3dworld/image/pinhole/data", rr.Image(image)) + + x_coord = (i - 50) / 5 + rr.log( + "3dworld/mesh", + rr.Mesh3D( + vertex_positions=[[x_coord, 2, 0], [x_coord, 2, 1], [x_coord, 3, 0]], + vertex_colors=[[0, 0, 255], [0, 255, 0], [255, 0, 0]], + ), + )