Skip to content

Commit

Permalink
end-to-end arrow-based scalars / time-series (#666)
Browse files Browse the repository at this point in the history
* add scalar component

* add scalarplotprops component

* re_query: visit4 & visit5

* building the scene

* dispatching to plot view

* complete rust example

* python bindings

* doesnt look like it's used but still
  • Loading branch information
teh-cmc authored Jan 4, 2023
1 parent 996012f commit 034f103
Show file tree
Hide file tree
Showing 15 changed files with 605 additions and 14 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 11 additions & 3 deletions crates/re_data_store/src/log_db.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use itertools::Itertools as _;
use nohash_hasher::IntMap;
use nohash_hasher::{IntMap, IntSet};

use re_log_types::{
field_types::{Instance, TextEntry},
field_types::{Instance, Scalar, TextEntry},
msg_bundle::{Component as _, MsgBundle},
objects, ArrowMsg, BatchIndex, BeginRecordingMsg, DataMsg, DataPath, DataVec, LogMsg,
LoggedData, MsgId, ObjPath, ObjPathHash, ObjTypePath, ObjectType, PathOp, PathOpMsg,
Expand Down Expand Up @@ -134,8 +134,16 @@ impl ObjDb {
// TODO(cmc): That's an extension of the hack below, and will disappear at the same time
// and for the same reasons.
{
let obj_type = if msg_bundle.find_component(&TextEntry::name()).is_some() {
let components = msg_bundle
.components
.iter()
.map(|bundle| bundle.name)
.collect::<IntSet<_>>();

let obj_type = if components.contains(&TextEntry::name()) {
ObjectType::TextEntry
} else if components.contains(&Scalar::name()) {
ObjectType::Scalar
} else {
// TODO(jleibs): Hack in a type so the UI treats these objects as visible
// This can go away once we determine object categories directly from the arrow
Expand Down
2 changes: 1 addition & 1 deletion crates/re_log_types/src/field_types/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::msg_bundle::Component;
///
/// assert_eq!(ColorRGBA::data_type(), DataType::UInt32);
/// ```
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, derive_more::From, derive_more::Into)]
pub struct ColorRGBA(pub u32);

impl ColorRGBA {
Expand Down
2 changes: 1 addition & 1 deletion crates/re_log_types/src/field_types/label.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::msg_bundle::Component;
///
/// assert_eq!(Label::data_type(), DataType::Utf8);
/// ```
#[derive(Debug)]
#[derive(Debug, Clone, derive_more::From, derive_more::Into)]
pub struct Label(pub String);

arrow_enable_vec_for_type!(Label);
Expand Down
6 changes: 5 additions & 1 deletion crates/re_log_types/src/field_types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mod point;
mod quaternion;
mod radius;
mod rect;
mod scalar;
mod size;
mod text_entry;

Expand All @@ -31,12 +32,13 @@ pub use point::{Point2D, Point3D};
pub use quaternion::Quaternion;
pub use radius::Radius;
pub use rect::Rect2D;
pub use scalar::{Scalar, ScalarPlotProps};
pub use size::Size3D;
pub use text_entry::TextEntry;

lazy_static! {
//TODO(john) actully use a run-time type registry
static ref FIELDS: [Field; 12] = [
static ref FIELDS: [Field; 14] = [
<ColorRGBA as Component>::field(),
<Instance as Component>::field(),
<KeypointId as Component>::field(),
Expand All @@ -47,6 +49,8 @@ lazy_static! {
<Quaternion as Component>::field(),
<Radius as Component>::field(),
<Rect2D as Component>::field(),
<Scalar as Component>::field(),
<ScalarPlotProps as Component>::field(),
<Size3D as Component>::field(),
<TextEntry as Component>::field(),
];
Expand Down
2 changes: 1 addition & 1 deletion crates/re_log_types/src/field_types/radius.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::msg_bundle::Component;
///
/// assert_eq!(Radius::data_type(), DataType::Float32);
/// ```
#[derive(Debug)]
#[derive(Debug, Clone, Copy, derive_more::From, derive_more::Into)]
pub struct Radius(pub f32);

arrow_enable_vec_for_type!(Radius);
Expand Down
87 changes: 87 additions & 0 deletions crates/re_log_types/src/field_types/scalar.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use arrow2_convert::{
arrow_enable_vec_for_type, deserialize::ArrowDeserialize, field::ArrowField,
serialize::ArrowSerialize,
};

use crate::msg_bundle::Component;

// ---

/// A double-precision scalar.
///
/// ```
/// use re_log_types::field_types::Scalar;
/// use arrow2_convert::field::ArrowField;
/// use arrow2::datatypes::{DataType, Field};
///
/// assert_eq!(Scalar::data_type(), DataType::Float64);
/// ```
#[derive(Debug, Clone, Copy, derive_more::From, derive_more::Into)]
pub struct Scalar(pub f64);

arrow_enable_vec_for_type!(Scalar);

impl ArrowField for Scalar {
type Type = Self;
fn data_type() -> arrow2::datatypes::DataType {
<f64 as ArrowField>::data_type()
}
}

impl ArrowSerialize for Scalar {
type MutableArrayType = <f64 as ArrowSerialize>::MutableArrayType;

#[inline]
fn new_array() -> Self::MutableArrayType {
<f64 as ArrowSerialize>::new_array()
}

#[inline]
fn arrow_serialize(v: &Self, array: &mut Self::MutableArrayType) -> arrow2::error::Result<()> {
<f64 as ArrowSerialize>::arrow_serialize(&v.0, array)
}
}

impl ArrowDeserialize for Scalar {
type ArrayType = <f64 as ArrowDeserialize>::ArrayType;

#[inline]
fn arrow_deserialize(
v: <&Self::ArrayType as IntoIterator>::Item,
) -> Option<<Self as ArrowField>::Type> {
<f64 as ArrowDeserialize>::arrow_deserialize(v).map(Scalar)
}
}

impl Component for Scalar {
fn name() -> crate::ComponentName {
"rerun.scalar".into()
}
}

// ---

/// Additional properties of a scalar when rendered as a plot.
///
/// ```
/// use re_log_types::field_types::ScalarPlotProps;
/// use arrow2_convert::field::ArrowField;
/// use arrow2::datatypes::{DataType, Field};
///
/// assert_eq!(
/// ScalarPlotProps::data_type(),
/// DataType::Struct(vec![
/// Field::new("scattered", DataType::Boolean, false),
/// ])
/// );
/// ```
#[derive(Debug, Clone, Copy, arrow2_convert::ArrowField)]
pub struct ScalarPlotProps {
pub scattered: bool,
}

impl Component for ScalarPlotProps {
fn name() -> crate::ComponentName {
"rerun.scalar_plot_props".into()
}
}
72 changes: 72 additions & 0 deletions crates/re_query/src/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,76 @@ where

Ok(())
}

/// Visit the primary and joined components of an [`EntityView`]
/// See [`Self::visit2`]
pub fn visit4<C1: Component, C2: Component, C3: Component>(
&self,
mut visit: impl FnMut(Instance, Primary, Option<C1>, Option<C2>, Option<C3>),
) -> crate::Result<()>
where
C1: ArrowDeserialize + ArrowField<Type = C1> + 'static,
C1::ArrayType: ArrowArray,
for<'a> &'a C1::ArrayType: IntoIterator,
C2: ArrowDeserialize + ArrowField<Type = C2> + 'static,
C2::ArrayType: ArrowArray,
for<'a> &'a C2::ArrayType: IntoIterator,
C3: ArrowDeserialize + ArrowField<Type = C3> + 'static,
C3::ArrayType: ArrowArray,
for<'a> &'a C3::ArrayType: IntoIterator,
{
let instance_iter = self.primary.iter_instance_keys()?;
let prim_iter = self.primary.iter_values::<Primary>()?;
let c1_iter = self.iter_component::<C1>()?;
let c2_iter = self.iter_component::<C2>()?;
let c3_iter = self.iter_component::<C3>()?;

itertools::izip!(instance_iter, prim_iter, c1_iter, c2_iter, c3_iter).for_each(
|(instance, primary, c1_data, c2_data, c3_iter)| {
if let Some(primary) = primary {
visit(instance, primary, c1_data, c2_data, c3_iter);
}
},
);

Ok(())
}

/// Visit the primary and joined components of an [`EntityView`]
/// See [`Self::visit2`]
pub fn visit5<C1: Component, C2: Component, C3: Component, C4: Component>(
&self,
mut visit: impl FnMut(Instance, Primary, Option<C1>, Option<C2>, Option<C3>, Option<C4>),
) -> crate::Result<()>
where
C1: ArrowDeserialize + ArrowField<Type = C1> + 'static,
C1::ArrayType: ArrowArray,
for<'a> &'a C1::ArrayType: IntoIterator,
C2: ArrowDeserialize + ArrowField<Type = C2> + 'static,
C2::ArrayType: ArrowArray,
for<'a> &'a C2::ArrayType: IntoIterator,
C3: ArrowDeserialize + ArrowField<Type = C3> + 'static,
C3::ArrayType: ArrowArray,
for<'a> &'a C3::ArrayType: IntoIterator,
C4: ArrowDeserialize + ArrowField<Type = C4> + 'static,
C4::ArrayType: ArrowArray,
for<'a> &'a C4::ArrayType: IntoIterator,
{
let instance_iter = self.primary.iter_instance_keys()?;
let prim_iter = self.primary.iter_values::<Primary>()?;
let c1_iter = self.iter_component::<C1>()?;
let c2_iter = self.iter_component::<C2>()?;
let c3_iter = self.iter_component::<C3>()?;
let c4_iter = self.iter_component::<C4>()?;

itertools::izip!(instance_iter, prim_iter, c1_iter, c2_iter, c3_iter, c4_iter).for_each(
|(instance, primary, c1_data, c2_data, c3_iter, c4_iter)| {
if let Some(primary) = primary {
visit(instance, primary, c1_data, c2_data, c3_iter, c4_iter);
}
},
);

Ok(())
}
}
92 changes: 91 additions & 1 deletion crates/re_viewer/src/ui/view_time_series/scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@ use crate::{
ui::{annotations::AnnotationMap, DefaultColor, SceneQuery},
ViewerContext,
};
use re_arrow_store::TimeRange;
use re_data_store::{query::visit_type_data_4, FieldName, TimeQuery};
use re_log_types::{IndexHash, MsgId, ObjectType};
use re_log_types::{
field_types::{self, Instance},
msg_bundle::Component,
IndexHash, MsgId, ObjectType,
};
use re_query::{range_entity_with_primary, QueryError};

// ---

Expand Down Expand Up @@ -67,6 +73,8 @@ impl SceneTimeSeries {
self.annotation_map.load(ctx, query);

self.load_scalars(ctx, query);

self.load_scalars_arrow(ctx, query);
}

fn load_scalars(&mut self, ctx: &mut ViewerContext<'_>, query: &SceneQuery<'_>) {
Expand Down Expand Up @@ -129,6 +137,88 @@ impl SceneTimeSeries {
}
}

fn load_scalars_arrow(&mut self, ctx: &mut ViewerContext<'_>, query: &SceneQuery<'_>) {
crate::profile_function!();

let store = &ctx.log_db.obj_db.arrow_store;

for obj_path in query.obj_paths {
let ent_path = obj_path;

let mut points = Vec::new();
let annotations = self.annotation_map.find(ent_path);
let default_color = DefaultColor::ObjPath(ent_path);

let query = re_arrow_store::RangeQuery::new(
query.timeline,
TimeRange::new(i64::MIN.into(), i64::MAX.into()),
);

let components = [
Instance::name(),
field_types::Scalar::name(),
field_types::ScalarPlotProps::name(),
field_types::ColorRGBA::name(),
field_types::Radius::name(),
field_types::Label::name(),
];
let ent_views = range_entity_with_primary::<field_types::Scalar, 6>(
store, &query, ent_path, components,
);

for (time, ent_view) in ent_views {
match ent_view.visit5(
|_instance,
scalar: field_types::Scalar,
props: Option<field_types::ScalarPlotProps>,
color: Option<field_types::ColorRGBA>,
radius: Option<field_types::Radius>,
label: Option<field_types::Label>| {
// TODO(andreas): Support object path
let annotation_info = annotations.class_description(None).annotation_info();
let color = annotation_info
.color(color.map(|c| c.to_array()).as_ref(), default_color);
let label = annotation_info.label(label.map(|l| l.into()).as_ref());

points.push(PlotPoint {
time: time.as_i64(),
value: scalar.into(),
attrs: PlotPointAttrs {
label,
color,
radius: radius.map_or(1.0, |r| r.into()),
scattered: props.map_or(false, |props| props.scattered),
},
});
},
) {
Ok(_) | Err(QueryError::PrimaryNotFound) => {}
Err(err) => {
re_log::error_once!("Unexpected error querying '{ent_path:?}': {err:?}");
}
}
}

points.sort_by_key(|s| s.time);

if points.is_empty() {
continue;
}

// If all points within a line share the label (and it isn't `None`), then we use it
// as the whole line label for the plot legend.
// Otherwise, we just use the object path as-is.
let same_label = |points: &[PlotPoint]| {
let label = points[0].attrs.label.as_ref();
(label.is_some() && points.iter().all(|p| p.attrs.label.as_ref() == label))
.then(|| label.cloned().unwrap())
};
let line_label = same_label(&points).unwrap_or_else(|| obj_path.to_string());

self.add_line_segments(&line_label, points);
}
}

// We have a bunch of raw points, and now we need to group them into actual line
// segments.
// A line segment is a continuous run of points with identical attributes: each time
Expand Down
1 change: 1 addition & 0 deletions crates/rerun_sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ tokio = { version = "1.16", features = ["macros", "rt-multi-thread"] }

[dev-dependencies]
arrow2_convert.workspace = true
rand = "0.8"

[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
clap = { version = "4.0", features = ["derive"] }
Expand Down
Loading

1 comment on commit 034f103

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rust Benchmark

Benchmark suite Current: 034f103 Previous: 996012f Ratio
datastore/insert/batch/rects/insert 281468 ns/iter (± 2667) 275899 ns/iter (± 3696) 1.02
datastore/latest_at/batch/rects/query 712 ns/iter (± 2) 722 ns/iter (± 1) 0.99
datastore/latest_at/missing_components/primary 306 ns/iter (± 0) 305 ns/iter (± 0) 1.00
datastore/latest_at/missing_components/secondaries 372 ns/iter (± 0) 372 ns/iter (± 0) 1
datastore/range/batch/rects/query 45102 ns/iter (± 33) 45278 ns/iter (± 68) 1.00
obj_mono_points/insert 1241247556 ns/iter (± 15165627) 941043825 ns/iter (± 6919329) 1.32
obj_mono_points/query 375319 ns/iter (± 7144) 372120 ns/iter (± 3787) 1.01
obj_batch_points/insert 116090396 ns/iter (± 731483) 95493615 ns/iter (± 531217) 1.22
obj_batch_points/query 11495 ns/iter (± 33) 11478 ns/iter (± 22) 1.00
obj_batch_points_sequential/insert 26422364 ns/iter (± 306849) 23881484 ns/iter (± 30674) 1.11
obj_batch_points_sequential/query 7972 ns/iter (± 11) 7956 ns/iter (± 22) 1.00
mono_points_classic/generate_messages 6664583 ns/iter (± 1166702) 4968611 ns/iter (± 278247) 1.34
mono_points_classic/encode_log_msg 17089648 ns/iter (± 576862) 13838820 ns/iter (± 385900) 1.23
mono_points_classic/encode_total 22787506 ns/iter (± 2643036) 20554480 ns/iter (± 922108) 1.11
mono_points_classic/decode_total 43060939 ns/iter (± 1065491) 40139140 ns/iter (± 272661) 1.07
mono_points_arrow/generate_message_bundles 66739347 ns/iter (± 1081415) 53763709 ns/iter (± 623381) 1.24
mono_points_arrow/generate_messages 174560698 ns/iter (± 1886650) 138735682 ns/iter (± 1345995) 1.26
mono_points_arrow/encode_log_msg 207816459 ns/iter (± 6067174) 169444469 ns/iter (± 916652) 1.23
mono_points_arrow/encode_total 449207605 ns/iter (± 5154956) 363533270 ns/iter (± 2011150) 1.24
mono_points_arrow/decode_log_msg 221086532 ns/iter (± 1644215) 190675666 ns/iter (± 927609) 1.16
mono_points_arrow/decode_message_bundles 114545412 ns/iter (± 1612205) 79889872 ns/iter (± 1072545) 1.43
mono_points_arrow/decode_total 325496189 ns/iter (± 2707486) 267454225 ns/iter (± 1945184) 1.22
batch_points_classic/generate_messages 3446 ns/iter (± 136) 3380 ns/iter (± 47) 1.02
batch_points_classic/encode_log_msg 389407 ns/iter (± 3235) 386571 ns/iter (± 568) 1.01
batch_points_classic/encode_total 395509 ns/iter (± 10526) 391285 ns/iter (± 733) 1.01
batch_points_classic/decode_total 748744 ns/iter (± 7392) 742352 ns/iter (± 3231) 1.01
batch_points_arrow/generate_message_bundles 333959 ns/iter (± 1130) 325110 ns/iter (± 578) 1.03
batch_points_arrow/generate_messages 6230 ns/iter (± 17) 6308 ns/iter (± 16) 0.99
batch_points_arrow/encode_log_msg 372267 ns/iter (± 6602) 352650 ns/iter (± 1469) 1.06
batch_points_arrow/encode_total 749704 ns/iter (± 17782) 717522 ns/iter (± 3683) 1.04
batch_points_arrow/decode_log_msg 364502 ns/iter (± 5529) 346710 ns/iter (± 1303) 1.05
batch_points_arrow/decode_message_bundles 2116 ns/iter (± 6) 2128 ns/iter (± 5) 0.99
batch_points_arrow/decode_total 364375 ns/iter (± 11180) 357838 ns/iter (± 1430) 1.02
arrow_mono_points/insert 9976262179 ns/iter (± 111747850) 6863558650 ns/iter (± 38423548) 1.45
arrow_mono_points/query 1740537 ns/iter (± 151648) 1690461 ns/iter (± 25183) 1.03
arrow_batch_points/insert 2719421 ns/iter (± 63068) 2692122 ns/iter (± 34525) 1.01
arrow_batch_points/query 12919 ns/iter (± 14) 13016 ns/iter (± 29) 0.99
obj_batch_points_sequential/Tuid::random 38 ns/iter (± 0) 37 ns/iter (± 0) 1.03

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.