From 868036b4d3578421a2c56195a2eaebedef01167a Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Sat, 20 May 2023 21:18:07 +0200 Subject: [PATCH 01/63] scripts: add PostHog crash fetcher/reporter script (#2168) * add crash fetcher script * newline * fmt * typo * uniques * fmt * format again........ * woops -- missing signals * shebang, and chmod +x * better header template --------- Co-authored-by: Emil Ernerfeldt --- scripts/fetch_crashes.py | 164 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100755 scripts/fetch_crashes.py diff --git a/scripts/fetch_crashes.py b/scripts/fetch_crashes.py new file mode 100755 index 000000000000..66b608056b7c --- /dev/null +++ b/scripts/fetch_crashes.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 + +""" +Fetch user crashes from PostHog database, deduplicates them, and prefill issue reports for them. + +Usage: +``` +export POSTHOG_PERSONAL_API_KEY= + +python scripts/fetch_crashes.py -v 0.5.0 -v 0.5.1 > crashes.md +``` + +Optionally, you can filter out data older than a given timestamp: +``` +python scripts/fetch_crashes.py -v 0.5.0 -v 0.5.1 --after 2023-05-02T20:17:52 > crashes.md +``` + +See Also +-------- +``` +python scripts/fetch_crashes.py --help +``` +""" + +import argparse +import json +import os +from collections import defaultdict + +import requests + +## CLI + +parser = argparse.ArgumentParser(description="Fetch user crashes from PostHog database") +parser.add_argument( + "-v", + "--version", + action="append", + dest="versions", + metavar="VERSION", + help="Specify one or more Rerun version", + required=True, +) +parser.add_argument( + "-a", + "--after", + action="append", + dest="date_after_included", + metavar="TIMESTAMP", + help="Filter out data older than this ISO8061 timestamp", +) +args = parser.parse_args() + +## Set up query, auth, etc + +personal_api_key = os.environ.get("POSTHOG_PERSONAL_API_KEY") +project_id = os.environ.get("POSTHOG_PROJECT_ID", "1954") + +url = f"https://eu.posthog.com/api/projects/{project_id}/events" +properties = [ + {"key": "email", "value": "is_not_set", "operator": "is_not_set", "type": "person"}, + {"key": "rerun_version", "value": args.versions, "operator": "exact", "type": "event"}, +] + +## Fetch results + + +# NOTE: For reference, here's the complete event payload: +# +# { +# "id": "01880cc1-10bd-0000-8338-2cc3ffed784b", +# "distinct_id": "7981eecd-f1b9-4446-8824-b854d5474787", +# "properties": { +# "llvm_version": "15.0.6", +# "target": "x86_64-pc-windows-msvc", +# "callstack": "", +# "session_id": "f67f53b8-da72-4564-b849-05b048a5b6be", +# "git_hash": "968bf7355ef146c6fad3283835f2d87e7757abc6", +# "rerun_workspace": false, +# "file_line": "wgpu-0.15.1/src/backend/direct.rs:3024", +# "event_id": 1, +# "rerun_version": "0.5.1", +# "rust_version": "1.67.1 (d5a82bbd2 2023-02-07)", +# "debug": false, +# "build_date": "2023-05-02T21:24:20Z" +# }, +# "event": "crash-panic", +# "timestamp": "2023-05-11T20:17:52.479000+00:00", +# "person": null, +# "elements": [], +# "elements_chain": "" +# } + + +results = [] + +for event in ["crash-panic", "crash-signal"]: + params = { + "properties": json.dumps(properties), + "event": event, + "orderBy": '["-timestamp"]', + } + if args.date_after_included: + params["after"] = args.date_after_included + headers = {"Authorization": f"Bearer {personal_api_key}"} + + response = requests.get(url, headers=headers, params=params) + + if response.status_code != 200: + print("Request failed with status code:", response.status_code) + exit(1) + + results += response.json()["results"] + +## Deduplicate results and massage output + +backtraces = defaultdict(list) + +for res in results: + res["properties"]["timestamp"] = res["timestamp"] + res["properties"]["event"] = res["event"] + res["properties"]["user_id"] = res["distinct_id"] + backtrace = res["properties"].pop("callstack").encode("utf-8").strip() + backtraces[backtrace].append(res.pop("properties")) + + +def count_uniques(backtrace): + return len(set([prop["user_id"] for prop in backtrace[1]])) + + +backtraces = list(backtraces.items()) +backtraces.sort(key=count_uniques, reverse=True) + +## Generate reports + +for backtrace, props in backtraces: + n = count_uniques((backtrace, props)) + event = "panic" if props[0]["event"] == "crash-panic" else "signal" + file_line = props[0].get("file_line") + signal = props[0].get("signal") + title = file_line if file_line is not None else signal + + timestamps = sorted(list(set([prop["timestamp"] for prop in props]))) + first_occurrence = timestamps[0] + last_occurrence = timestamps[-1] + + targets = sorted(list(set([prop["target"] for prop in props]))) + rust_versions = sorted(list(set([prop["rust_version"] for prop in props]))) + rerun_versions = sorted(list(set([prop["rerun_version"] for prop in props]))) + + print( + f"## {n} distinct user(s) affected by {event} crash @ `{title}`\n" + "\n" + f"- First occurrence: `{first_occurrence}`\n" + f"- Last occurrence: `{last_occurrence}`\n" + f"- Affected Rust versions: `{rust_versions}`\n" + f"- Affected Rerun versions: `{rerun_versions}`\n" + f"- Affected Targets: `{targets}`\n" + "\n" + "Backtrace:\n" + "```\n" + f' {backtrace.decode("utf-8")}\n' + "```\n" + ) From 631c268f35714a3c71297ea620bc2e2539fc277a Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Mon, 22 May 2023 23:01:52 +0200 Subject: [PATCH 02/63] Centralize freestanding store helpers (#2153) * centralize freestanding store helpers * port everything to new helpers * note to self * missing feature flag * docs --- crates/re_arrow_store/src/lib.rs | 1 + crates/re_arrow_store/src/store_helpers.rs | 158 ++++++++++++++++++ crates/re_data_store/src/log_db.rs | 5 + crates/re_data_ui/src/annotation_context.rs | 9 +- crates/re_query/src/query.rs | 2 +- crates/re_viewer/src/misc/queries.rs | 7 +- crates/re_viewer/src/misc/space_info.rs | 11 +- crates/re_viewer/src/misc/transform_cache.rs | 8 +- crates/re_viewer/src/ui/selection_panel.rs | 11 +- .../re_viewer/src/ui/space_view_heuristics.rs | 28 ++-- .../re_viewer/src/ui/view_bar_chart/scene.rs | 7 +- crates/re_viewer/src/ui/view_category.rs | 9 +- .../src/ui/view_spatial/scene/mod.rs | 9 +- .../view_spatial/scene/scene_part/cameras.rs | 11 +- .../view_spatial/scene/scene_part/images.rs | 6 +- crates/re_viewer/src/ui/view_spatial/ui.rs | 64 ++++--- crates/re_viewer/src/ui/view_spatial/ui_2d.rs | 19 +-- crates/re_viewer/src/ui/view_tensor/scene.rs | 8 +- 18 files changed, 261 insertions(+), 112 deletions(-) create mode 100644 crates/re_arrow_store/src/store_helpers.rs diff --git a/crates/re_arrow_store/src/lib.rs b/crates/re_arrow_store/src/lib.rs index 0ac2be79a28e..b61f18c2768a 100644 --- a/crates/re_arrow_store/src/lib.rs +++ b/crates/re_arrow_store/src/lib.rs @@ -20,6 +20,7 @@ mod store_arrow; mod store_dump; mod store_format; mod store_gc; +mod store_helpers; mod store_read; mod store_sanity; mod store_stats; diff --git a/crates/re_arrow_store/src/store_helpers.rs b/crates/re_arrow_store/src/store_helpers.rs new file mode 100644 index 000000000000..1cf79b6ac05a --- /dev/null +++ b/crates/re_arrow_store/src/store_helpers.rs @@ -0,0 +1,158 @@ +use re_log_types::{ + ComponentName, DataCell, DataRow, DeserializableComponent, EntityPath, RowId, + SerializableComponent, TimeInt, TimePoint, Timeline, +}; + +use crate::{DataStore, LatestAtQuery}; + +// --- Read --- + +impl DataStore { + /// Get the latest value for a given [`re_log_types::Component`]. + /// + /// This assumes that the row we get from the store only contains a single instance for this + /// component; it will log a warning otherwise. + /// + /// This should only be used for "mono-components" such as `Transform` and `Tensor`. + /// + /// This is a best-effort helper, it will merely log errors on failure. + pub fn query_latest_component( + &self, + entity_path: &EntityPath, + query: &LatestAtQuery, + ) -> Option + where + for<'b> &'b C::ArrayType: IntoIterator, + { + crate::profile_function!(); + + let (_, cells) = self.latest_at(query, entity_path, C::name(), &[C::name()])?; + let cell = cells.get(0)?.as_ref()?; + + let mut iter = cell + .try_to_native::() + .map_err(|err| { + re_log::error_once!( + "Couldn't deserialize component at {entity_path}.{}: {err}", + C::name() + ); + }) + .ok()?; + + let component = iter.next(); + + if iter.next().is_some() { + re_log::warn_once!("Unexpected batch for {} at: {}", C::name(), entity_path); + } + + component + } + + /// Get the latest value for a given [`re_log_types::Component`], assuming it is timeless. + /// + /// This assumes that the row we get from the store only contains a single instance for this + /// component; it will log a warning otherwise. + /// + /// This should only be used for "mono-components" such as `Transform` and `Tensor`. + /// + /// This is a best-effort helper, it will merely log errors on failure. + pub fn query_timeless_component( + &self, + entity_path: &EntityPath, + ) -> Option + where + for<'b> &'b C::ArrayType: IntoIterator, + { + crate::profile_function!(); + + let query = LatestAtQuery::new(Timeline::default(), TimeInt::MAX); + self.query_latest_component(entity_path, &query) + } +} + +// --- Write --- + +impl DataStore { + /// Stores a single value for a given [`re_log_types::Component`]. + /// + /// This is a best-effort helper, it will merely log errors on failure. + pub fn insert_component( + &mut self, + entity_path: &EntityPath, + timepoint: &TimePoint, + component: C, + ) { + crate::profile_function!(); + + let mut row = match DataRow::try_from_cells1( + RowId::random(), + entity_path.clone(), + timepoint.clone(), + 1, + [component].as_slice(), + ) { + Ok(row) => row, + Err(err) => { + re_log::error_once!( + "Couldn't serialize component at {entity_path}.{}: {err}", + C::name() + ); + return; + } + }; + row.compute_all_size_bytes(); + + if let Err(err) = self.insert_row(&row) { + re_log::error_once!( + "Couldn't insert component at {entity_path}.{}: {err}", + C::name() + ); + } + } + + /// Stores a single empty value for a given [`re_log_types::ComponentName`]. + /// + /// This is a best-effort helper, it will merely log errors on failure. + pub fn insert_empty_component( + &mut self, + entity_path: &EntityPath, + timepoint: &TimePoint, + component: ComponentName, + ) { + crate::profile_function!(); + + if let Some(datatype) = self.lookup_datatype(&component) { + let cell = DataCell::from_arrow_empty(component, datatype.clone()); + + let mut row = match DataRow::try_from_cells1( + RowId::random(), + entity_path.clone(), + timepoint.clone(), + cell.num_instances(), + cell, + ) { + Ok(row) => row, + Err(err) => { + re_log::error_once!( + "Couldn't serialize component at {entity_path}.{}: {err}", + component + ); + return; + } + }; + row.compute_all_size_bytes(); + + if let Err(err) = self.insert_row(&row) { + re_log::error_once!( + "Couldn't insert component at {entity_path}.{}: {err}", + component + ); + } + } else { + re_log::error_once!( + "Couldn't find appropriate datatype at {entity_path}.{}", + component + ); + } + } +} diff --git a/crates/re_data_store/src/log_db.rs b/crates/re_data_store/src/log_db.rs index 21ed57f7040c..0a1b3f040507 100644 --- a/crates/re_data_store/src/log_db.rs +++ b/crates/re_data_store/src/log_db.rs @@ -87,6 +87,8 @@ impl EntityDb { let cell = DataCell::from_arrow_empty(cell.component_name(), cell.datatype().clone()); + // NOTE(cmc): The fact that this inserts data to multiple entity paths using a + // single `RowId` is... interesting. Keep it in mind. let row = DataRow::from_cells1( row_id, row.entity_path.clone(), @@ -116,6 +118,9 @@ impl EntityDb { // TODO(jleibs): Faster empty-array creation let cell = DataCell::from_arrow_empty(component_path.component_name, data_type.clone()); + + // NOTE(cmc): The fact that this inserts data to multiple entity paths using a + // single `RowId` is... interesting. Keep it in mind. let row = DataRow::from_cells1( row_id, component_path.entity_path.clone(), diff --git a/crates/re_data_ui/src/annotation_context.rs b/crates/re_data_ui/src/annotation_context.rs index 7bf8eed1c013..72c67eed5367 100644 --- a/crates/re_data_ui/src/annotation_context.rs +++ b/crates/re_data_ui/src/annotation_context.rs @@ -1,7 +1,7 @@ use egui::{color_picker, Vec2}; use itertools::Itertools; -use re_log_types::{context::AnnotationInfo, AnnotationContext}; +use re_log_types::{component_types::ClassId, context::AnnotationInfo, AnnotationContext}; use re_viewer_context::{auto_color, UiVerbosity, ViewerContext}; use super::DataUi; @@ -77,8 +77,11 @@ fn annotation_info( query: &re_arrow_store::LatestAtQuery, keypoint_id: &re_log_types::component_types::KeypointId, ) -> Option { - let class_id = - re_data_store::query_latest_single(&ctx.log_db.entity_db.data_store, entity_path, query)?; + let class_id = ctx + .log_db + .entity_db + .data_store + .query_latest_component::(entity_path, query)?; let annotations = crate::annotations(ctx, query, entity_path); let class = annotations .class_description(Some(class_id)) diff --git a/crates/re_query/src/query.rs b/crates/re_query/src/query.rs index 591d19df2347..d7826679eaef 100644 --- a/crates/re_query/src/query.rs +++ b/crates/re_query/src/query.rs @@ -79,7 +79,7 @@ pub fn get_component_with_instances( /// length. /// /// If you expect only one instance (e.g. for mono-components like `Transform` `Tensor`] -/// and have no additional components you can use [`re_data_store::query_latest_single`] instead. +/// and have no additional components you can use [`DataStore::query_latest_component`] instead. /// /// ``` /// # use re_arrow_store::LatestAtQuery; diff --git a/crates/re_viewer/src/misc/queries.rs b/crates/re_viewer/src/misc/queries.rs index e02e8b96dace..a5124255a0f5 100644 --- a/crates/re_viewer/src/misc/queries.rs +++ b/crates/re_viewer/src/misc/queries.rs @@ -1,5 +1,5 @@ use re_arrow_store::LatestAtQuery; -use re_data_store::{query_latest_single, EntityPath}; +use re_data_store::EntityPath; use re_log_types::Transform; use re_viewer_context::ViewerContext; @@ -11,13 +11,12 @@ pub fn closest_pinhole_transform( ) -> Option { crate::profile_function!(); - let data_store = &ctx.log_db.entity_db.data_store; + let store = &ctx.log_db.entity_db.data_store; let mut pinhole_ent_path = None; let mut cur_path = Some(entity_path.clone()); while let Some(path) = cur_path { - if let Some(Transform::Pinhole(_)) = - query_latest_single::(data_store, &path, query) + if let Some(Transform::Pinhole(_)) = store.query_latest_component::(&path, query) { pinhole_ent_path = Some(path); break; diff --git a/crates/re_viewer/src/misc/space_info.rs b/crates/re_viewer/src/misc/space_info.rs index 57b567d8cd41..2ed7476b86fb 100644 --- a/crates/re_viewer/src/misc/space_info.rs +++ b/crates/re_viewer/src/misc/space_info.rs @@ -3,7 +3,7 @@ use std::collections::BTreeMap; use nohash_hasher::IntSet; use re_arrow_store::{LatestAtQuery, TimeInt, Timeline}; -use re_data_store::{log_db::EntityDb, query_latest_single, EntityPath, EntityTree}; +use re_data_store::{log_db::EntityDb, EntityPath, EntityTree}; use re_log_types::Transform; use super::UnreachableTransform; @@ -111,8 +111,9 @@ impl SpaceInfoCollection { tree: &EntityTree, query: &LatestAtQuery, ) { - if let Some(transform) = - query_latest_single::(&entity_db.data_store, &tree.path, query) + if let Some(transform) = entity_db + .data_store + .query_latest_component::(&tree.path, query) { // A set transform (likely non-identity) - create a new space. parent_space @@ -157,7 +158,9 @@ impl SpaceInfoCollection { let mut spaces_info = Self::default(); // Start at the root. The root is always part of the collection! - if query_latest_single::(&entity_db.data_store, &EntityPath::root(), &query) + if entity_db + .data_store + .query_latest_component::(&EntityPath::root(), &query) .is_some() { re_log::warn_once!("The root entity has a 'transform' component! This will have no effect. Did you mean to apply the transform elsewhere?"); diff --git a/crates/re_viewer/src/misc/transform_cache.rs b/crates/re_viewer/src/misc/transform_cache.rs index a7f6fc1b04a5..9474fb7fadad 100644 --- a/crates/re_viewer/src/misc/transform_cache.rs +++ b/crates/re_viewer/src/misc/transform_cache.rs @@ -1,8 +1,6 @@ use nohash_hasher::IntMap; use re_arrow_store::LatestAtQuery; -use re_data_store::{ - log_db::EntityDb, query_latest_single, EntityPath, EntityPropertyMap, EntityTree, -}; +use re_data_store::{log_db::EntityDb, EntityPath, EntityPropertyMap, EntityTree}; use re_viewer_context::TimeControl; /// Provides transforms from an entity to a chosen reference space for all elements in the scene @@ -224,12 +222,12 @@ impl TransformCache { fn transform_at( entity_path: &EntityPath, - data_store: &re_arrow_store::DataStore, + store: &re_arrow_store::DataStore, query: &LatestAtQuery, pinhole_image_plane_distance: impl Fn(&EntityPath) -> f32, encountered_pinhole: &mut bool, ) -> Result, UnreachableTransform> { - if let Some(transform) = query_latest_single(data_store, entity_path, query) { + if let Some(transform) = store.query_latest_component(entity_path, query) { match transform { re_log_types::Transform::Rigid3(rigid) => Ok(Some(rigid.parent_from_child().into())), // If we're connected via 'unknown' it's not reachable diff --git a/crates/re_viewer/src/ui/selection_panel.rs b/crates/re_viewer/src/ui/selection_panel.rs index 161875fff4c8..534fdfb45f54 100644 --- a/crates/re_viewer/src/ui/selection_panel.rs +++ b/crates/re_viewer/src/ui/selection_panel.rs @@ -1,8 +1,6 @@ use egui::NumExt as _; -use re_data_store::{ - query_latest_single, ColorMapper, Colormap, EditableAutoValue, EntityPath, EntityProperties, -}; +use re_data_store::{ColorMapper, Colormap, EditableAutoValue, EntityPath, EntityProperties}; use re_data_ui::{item_ui, DataUi}; use re_log_types::{ component_types::{Tensor, TensorDataMeaning}, @@ -437,8 +435,9 @@ fn pinhole_props_ui( entity_props: &mut EntityProperties, ) { let query = ctx.current_query(); + let store = &ctx.log_db.entity_db.data_store; if let Some(re_log_types::Transform::Pinhole(_)) = - query_latest_single::(&ctx.log_db.entity_db.data_store, entity_path, &query) + store.query_latest_component::(entity_path, &query) { ui.label("Image plane distance"); let mut distance = *entity_props.pinhole_image_plane_distance.get(); @@ -467,8 +466,8 @@ fn depth_props_ui( crate::profile_function!(); let query = ctx.current_query(); - let tensor = - query_latest_single::(&ctx.log_db.entity_db.data_store, entity_path, &query)?; + let store = &ctx.log_db.entity_db.data_store; + let tensor = store.query_latest_component::(entity_path, &query)?; if tensor.meaning != TensorDataMeaning::Depth { return Some(()); } diff --git a/crates/re_viewer/src/ui/space_view_heuristics.rs b/crates/re_viewer/src/ui/space_view_heuristics.rs index ddc8ee3dcdc3..6a44fff197b6 100644 --- a/crates/re_viewer/src/ui/space_view_heuristics.rs +++ b/crates/re_viewer/src/ui/space_view_heuristics.rs @@ -4,7 +4,7 @@ use ahash::HashMap; use itertools::Itertools; use nohash_hasher::IntSet; use re_arrow_store::{DataStore, LatestAtQuery, Timeline}; -use re_data_store::{query_latest_single, ComponentName, EntityPath}; +use re_data_store::{ComponentName, EntityPath}; use re_log_types::{component_types::Tensor, Component}; use re_viewer_context::ViewerContext; @@ -46,10 +46,10 @@ pub fn all_possible_space_views( fn contains_any_image( entity_path: &EntityPath, - data_store: &re_arrow_store::DataStore, + store: &re_arrow_store::DataStore, query: &LatestAtQuery, ) -> bool { - if let Some(tensor) = query_latest_single::(data_store, entity_path, query) { + if let Some(tensor) = store.query_latest_component::(entity_path, query) { tensor.is_shaped_like_an_image() } else { false @@ -79,7 +79,7 @@ fn is_interesting_space_view_at_root( } fn is_interesting_space_view_not_at_root( - data_store: &re_arrow_store::DataStore, + store: &re_arrow_store::DataStore, candidate: &SpaceView, categories_with_interesting_roots: &ViewCategorySet, query: &LatestAtQuery, @@ -97,7 +97,7 @@ fn is_interesting_space_view_not_at_root( // .. an unknown transform, the children can't be shown otherwise // .. an pinhole transform, we'd like to see the world from this camera's pov as well! if candidate.category == ViewCategory::Spatial { - if let Some(transform) = query_latest_single(data_store, &candidate.space_path, query) { + if let Some(transform) = store.query_latest_component(&candidate.space_path, query) { match transform { re_log_types::Transform::Rigid3(_) => {} re_log_types::Transform::Pinhole(_) | re_log_types::Transform::Unknown => { @@ -121,7 +121,7 @@ pub fn default_created_space_views( } fn default_created_space_views_from_candidates( - data_store: &re_arrow_store::DataStore, + store: &re_arrow_store::DataStore, candidates: Vec, ) -> Vec { crate::profile_function!(); @@ -134,7 +134,7 @@ fn default_created_space_views_from_candidates( .iter() .filter_map(|space_view_candidate| { (space_view_candidate.space_path.is_root() - && is_interesting_space_view_at_root(data_store, space_view_candidate, &query)) + && is_interesting_space_view_at_root(store, space_view_candidate, &query)) .then_some(space_view_candidate.category) }) .collect::(); @@ -149,7 +149,7 @@ fn default_created_space_views_from_candidates( continue; } } else if !is_interesting_space_view_not_at_root( - data_store, + store, &candidate, &categories_with_interesting_roots, &query, @@ -180,15 +180,11 @@ fn default_created_space_views_from_candidates( // For this we're only interested in the direct children. for entity_path in &candidate.data_blueprint.root_group().entities { - if let Some(tensor) = query_latest_single::(data_store, entity_path, &query) - { + if let Some(tensor) = store.query_latest_component::(entity_path, &query) { if let Some([height, width, _]) = tensor.image_height_width_channels() { - if query_latest_single::( - data_store, - entity_path, - &query, - ) - .is_some() + if store + .query_latest_component::(entity_path, &query) + .is_some() { // Put everything in the same bucket if it has a draw order. images_by_bucket diff --git a/crates/re_viewer/src/ui/view_bar_chart/scene.rs b/crates/re_viewer/src/ui/view_bar_chart/scene.rs index 25098ab57fbe..df42787737d4 100644 --- a/crates/re_viewer/src/ui/view_bar_chart/scene.rs +++ b/crates/re_viewer/src/ui/view_bar_chart/scene.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use re_arrow_store::LatestAtQuery; -use re_data_store::{query_latest_single, EntityPath}; +use re_data_store::EntityPath; use re_log_types::component_types::{self, Tensor}; use re_viewer_context::{SceneQuery, ViewerContext}; @@ -21,7 +21,7 @@ impl SceneBarChart { fn load_tensors(&mut self, ctx: &mut ViewerContext<'_>, query: &SceneQuery<'_>) { crate::profile_function!(); - let data_store = &ctx.log_db.entity_db.data_store; + let store = &ctx.log_db.entity_db.data_store; for (ent_path, props) in query.iter_entities() { if !props.visible { @@ -29,8 +29,7 @@ impl SceneBarChart { } let query = LatestAtQuery::new(query.timeline, query.latest_at); - let tensor = - query_latest_single::(data_store, ent_path, &query); + let tensor = store.query_latest_component::(ent_path, &query); if let Some(tensor) = tensor { if tensor.is_vector() { diff --git a/crates/re_viewer/src/ui/view_category.rs b/crates/re_viewer/src/ui/view_category.rs index b882d3e892fa..8e28014d9014 100644 --- a/crates/re_viewer/src/ui/view_category.rs +++ b/crates/re_viewer/src/ui/view_category.rs @@ -100,11 +100,10 @@ pub fn categorize_entity_path( } else if component == Tensor::name() { let timeline_query = LatestAtQuery::new(timeline, TimeInt::MAX); - if let Some(tensor) = re_data_store::query_latest_single::( - &log_db.entity_db.data_store, - entity_path, - &timeline_query, - ) { + let store = &log_db.entity_db.data_store; + if let Some(tensor) = + store.query_latest_component::(entity_path, &timeline_query) + { if tensor.is_vector() { set.insert(ViewCategory::BarChart); } else if tensor.is_shaped_like_an_image() { diff --git a/crates/re_viewer/src/ui/view_spatial/scene/mod.rs b/crates/re_viewer/src/ui/view_spatial/scene/mod.rs index 4dfc9bd2bc4d..ed9acd715fef 100644 --- a/crates/re_viewer/src/ui/view_spatial/scene/mod.rs +++ b/crates/re_viewer/src/ui/view_spatial/scene/mod.rs @@ -6,7 +6,7 @@ use std::{ use ahash::HashMap; use nohash_hasher::IntMap; -use re_data_store::{query_latest_single, EntityPath, InstancePathHash}; +use re_data_store::{EntityPath, InstancePathHash}; use re_log_types::{ component_types::{ClassId, InstanceKey, KeypointId}, DecodedTensor, DrawOrder, EntityPathHash, @@ -142,9 +142,12 @@ impl SceneSpatial { // Use a BTreeSet for entity hashes to get a stable order. let mut entities_per_draw_order = BTreeMap::>::new(); + // let mut entities_per_draw_order = BTreeMap::>::new(); + + let store = &ctx.log_db.entity_db.data_store; + for (ent_path, _) in query.iter_entities() { - if let Some(draw_order) = query_latest_single::( - &ctx.log_db.entity_db.data_store, + if let Some(draw_order) = store.query_latest_component::( ent_path, &ctx.rec_cfg.time_ctrl.current_query(), ) { diff --git a/crates/re_viewer/src/ui/view_spatial/scene/scene_part/cameras.rs b/crates/re_viewer/src/ui/view_spatial/scene/scene_part/cameras.rs index b51484b1eb94..3fb94badb286 100644 --- a/crates/re_viewer/src/ui/view_spatial/scene/scene_part/cameras.rs +++ b/crates/re_viewer/src/ui/view_spatial/scene/scene_part/cameras.rs @@ -26,13 +26,13 @@ use super::{instance_path_hash_for_picking, ScenePart}; /// /// TODO(andreas): Doing a search upwards here isn't great. Maybe this can be part of the transform cache or similar? fn determine_view_coordinates( - data_store: &re_arrow_store::DataStore, + store: &re_arrow_store::DataStore, time_ctrl: &TimeControl, mut entity_path: EntityPath, ) -> ViewCoordinates { loop { if let Some(view_coordinates) = - re_data_store::query_latest_single(data_store, &entity_path, &time_ctrl.current_query()) + store.query_latest_component(&entity_path, &time_ctrl.current_query()) { return view_coordinates; } @@ -192,14 +192,11 @@ impl ScenePart for CamerasPart { ) { crate::profile_scope!("CamerasPart"); + let store = &ctx.log_db.entity_db.data_store; for (ent_path, props) in query.iter_entities() { let query = re_arrow_store::LatestAtQuery::new(query.timeline, query.latest_at); - if let Some(transform) = re_data_store::query_latest_single::( - &ctx.log_db.entity_db.data_store, - ent_path, - &query, - ) { + if let Some(transform) = store.query_latest_component::(ent_path, &query) { let Transform::Pinhole(pinhole) = transform else { continue; }; diff --git a/crates/re_viewer/src/ui/view_spatial/scene/scene_part/images.rs b/crates/re_viewer/src/ui/view_spatial/scene/scene_part/images.rs index 30ad95bedcbb..e775660ec268 100644 --- a/crates/re_viewer/src/ui/view_spatial/scene/scene_part/images.rs +++ b/crates/re_viewer/src/ui/view_spatial/scene/scene_part/images.rs @@ -2,7 +2,7 @@ use egui::NumExt; use glam::Vec3; use itertools::Itertools; -use re_data_store::{query_latest_single, EntityPath, EntityProperties}; +use re_data_store::{EntityPath, EntityProperties}; use re_log_types::{ component_types::{ColorRGBA, InstanceKey, Tensor, TensorData, TensorDataMeaning}, Component, DecodedTensor, DrawOrder, Transform, @@ -274,8 +274,8 @@ impl ImagesPart { ) -> Result<(), String> { crate::profile_function!(); - let Some(re_log_types::Transform::Pinhole(intrinsics)) = query_latest_single::( - &ctx.log_db.entity_db.data_store, + let store = &ctx.log_db.entity_db.data_store; + let Some(re_log_types::Transform::Pinhole(intrinsics)) = store.query_latest_component::( pinhole_ent_path, &ctx.current_query(), ) else { diff --git a/crates/re_viewer/src/ui/view_spatial/ui.rs b/crates/re_viewer/src/ui/view_spatial/ui.rs index 0de4e8ef789d..c52c9e1eca72 100644 --- a/crates/re_viewer/src/ui/view_spatial/ui.rs +++ b/crates/re_viewer/src/ui/view_spatial/ui.rs @@ -2,7 +2,7 @@ use eframe::epaint::text::TextWrapping; use egui::{NumExt, WidgetText}; use macaw::BoundingBox; -use re_data_store::{query_latest_single, EditableAutoValue, EntityPath, EntityPropertyMap}; +use re_data_store::{EditableAutoValue, EntityPath, EntityPropertyMap}; use re_data_ui::{item_ui, DataUi}; use re_data_ui::{show_zoomed_image_region, show_zoomed_image_region_area_outline}; use re_format::format_f32; @@ -167,12 +167,9 @@ impl ViewSpatialState { query: &re_arrow_store::LatestAtQuery, entity_path: &EntityPath, ) { + let store = &ctx.log_db.entity_db.data_store; if let Some(re_log_types::Transform::Pinhole(_)) = - query_latest_single::( - &ctx.log_db.entity_db.data_store, - entity_path, - query, - ) + store.query_latest_component::(entity_path, query) { let mut properties = data_blueprint.data_blueprints_individual().get(entity_path); if properties.pinhole_image_plane_distance.is_auto() { @@ -198,8 +195,8 @@ impl ViewSpatialState { query: &re_arrow_store::LatestAtQuery, entity_path: &EntityPath, ) -> Option<()> { - let tensor = - query_latest_single::(&ctx.log_db.entity_db.data_store, entity_path, query)?; + let store = &ctx.log_db.entity_db.data_store; + let tensor = store.query_latest_component::(entity_path, query)?; let mut properties = data_blueprint.data_blueprints_individual().get(entity_path); if properties.backproject_depth.is_auto() { @@ -396,13 +393,10 @@ impl ViewSpatialState { } self.scene_num_primitives = scene.primitives.num_primitives(); + let store = &ctx.log_db.entity_db.data_store; match *self.nav_mode.get() { SpatialNavigationMode::ThreeD => { - let coordinates = query_latest_single( - &ctx.log_db.entity_db.data_store, - space, - &ctx.current_query(), - ); + let coordinates = store.query_latest_component(space, &ctx.current_query()); self.state_3d.space_specs = SpaceSpecs::from_view_coordinates(coordinates); super::view_3d( ctx, @@ -750,29 +744,27 @@ pub fn picking( let picked_image_with_coords = if hit.hit_type == PickingHitType::TexturedRect || *ent_properties.backproject_depth.get() { - query_latest_single::( - &ctx.log_db.entity_db.data_store, - &instance_path.entity_path, - &ctx.current_query(), - ) - .and_then(|tensor| { - // If we're here because of back-projection, but this wasn't actually a depth image, drop out. - // (the back-projection property may be true despite this not being a depth image!) - if hit.hit_type != PickingHitType::TexturedRect - && *ent_properties.backproject_depth.get() - && tensor.meaning != TensorDataMeaning::Depth - { - None - } else { - tensor.image_height_width_channels().map(|[_, w, _]| { - let coordinates = hit - .instance_path_hash - .instance_key - .to_2d_image_coordinate(w); - (tensor, coordinates) - }) - } - }) + let store = &ctx.log_db.entity_db.data_store; + store + .query_latest_component::(&instance_path.entity_path, &ctx.current_query()) + .and_then(|tensor| { + // If we're here because of back-projection, but this wasn't actually a depth image, drop out. + // (the back-projection property may be true despite this not being a depth image!) + if hit.hit_type != PickingHitType::TexturedRect + && *ent_properties.backproject_depth.get() + && tensor.meaning != TensorDataMeaning::Depth + { + None + } else { + tensor.image_height_width_channels().map(|[_, w, _]| { + let coordinates = hit + .instance_path_hash + .instance_key + .to_2d_image_coordinate(w); + (tensor, coordinates) + }) + } + }) } else { None }; diff --git a/crates/re_viewer/src/ui/view_spatial/ui_2d.rs b/crates/re_viewer/src/ui/view_spatial/ui_2d.rs index 9c9bf30545bd..34cbbeea4c8a 100644 --- a/crates/re_viewer/src/ui/view_spatial/ui_2d.rs +++ b/crates/re_viewer/src/ui/view_spatial/ui_2d.rs @@ -1,7 +1,7 @@ use eframe::emath::RectTransform; use egui::{pos2, vec2, Align2, Color32, NumExt as _, Pos2, Rect, ScrollArea, Shape, Vec2}; use macaw::IsoTransform; -use re_data_store::{query_latest_single, EntityPath, EntityPropertyMap}; +use re_data_store::{EntityPath, EntityPropertyMap}; use re_log_types::Pinhole; use re_renderer::view_builder::{TargetConfiguration, ViewBuilder}; use re_viewer_context::{gpu_bridge, HoveredSpace, SpaceViewId, ViewerContext}; @@ -241,6 +241,8 @@ pub fn view_2d( // Save off the available_size since this is used for some of the layout updates later let available_size = ui.available_size(); + let store = &ctx.log_db.entity_db.data_store; + // Determine the canvas which determines the extent of the explorable scene coordinates, // and thus the size of the scroll area. // @@ -251,15 +253,12 @@ pub fn view_2d( // For that we need to check if this is defined by a pinhole camera. // Note that we can't rely on the camera being part of scene.space_cameras since that requires // the camera to be added to the scene! - let pinhole = query_latest_single( - &ctx.log_db.entity_db.data_store, - space, - &ctx.rec_cfg.time_ctrl.current_query(), - ) - .and_then(|transform| match transform { - re_log_types::Transform::Pinhole(pinhole) => Some(pinhole), - _ => None, - }); + let pinhole = store + .query_latest_component(space, &ctx.rec_cfg.time_ctrl.current_query()) + .and_then(|transform| match transform { + re_log_types::Transform::Pinhole(pinhole) => Some(pinhole), + _ => None, + }); let canvas_rect = pinhole .and_then(|p| p.resolution()) .map_or(scene_rect_accum, |res| { diff --git a/crates/re_viewer/src/ui/view_tensor/scene.rs b/crates/re_viewer/src/ui/view_tensor/scene.rs index 627ee2dd92c0..762c78bc3214 100644 --- a/crates/re_viewer/src/ui/view_tensor/scene.rs +++ b/crates/re_viewer/src/ui/view_tensor/scene.rs @@ -17,14 +17,12 @@ impl SceneTensor { pub(crate) fn load(&mut self, ctx: &mut ViewerContext<'_>, query: &SceneQuery<'_>) { crate::profile_function!(); + let store = &ctx.log_db.entity_db.data_store; for (ent_path, props) in query.iter_entities() { let timeline_query = LatestAtQuery::new(query.timeline, query.latest_at); - if let Some(tensor) = re_data_store::query_latest_single::( - &ctx.log_db.entity_db.data_store, - ent_path, - &timeline_query, - ) { + if let Some(tensor) = store.query_latest_component::(ent_path, &timeline_query) + { self.load_tensor_entity(ctx, ent_path, &props, tensor); } } From 1d30c6e68974888c05f5858c28c8a6f01d52514f Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Wed, 24 May 2023 18:22:32 +0200 Subject: [PATCH 03/63] Improved 3D transform ingestion & affine transform support (#2102) * Transform is now Transform3D and has an affine transform with various options * todo notes on sparse enums in transform3d * from instead of into glam impls * add more utilities, port rust examples * better ui display for transform3d * transform3d now also captures direction parent/child child/parent * Fix images with same render order not layering transparently * nicer draw order sample * every draw order given out is individual again to avoid z fighting * affine3 python interface wip * unified affine transform logging from python * hide zero translations * fix up log_rigid3 and deprecate it * example for log_affine3 * Rename RotationAxisAngle * py-lint fixes * re-enable data_table_sizes_basics test * more docs and doc tests for transform3d * implement pinhole via transform3d.py * spelling * better affine transform error messages on python, fix tensor logging again * linting, small improvements * update all uses of log_rigid3 in python examples * fix old python incompatibility issues * split up transform enum into three different components rust only so far * wire up python sdk again to new components * fix pinhole camera not being categorized as spatial * enable custom pinhole ui * Rename to TranslationAndMat3 * fixup some tests in re_log_types * mono component documentation * fixup test_clean_for_polars_modify * change python api to log_transform3d * explicit quaternion type for python * Translation3D is now its own enum to allow explicit non-logged translation * fix quaternion logging in examples * slightly better error messages on wrong type in python transform api * Rust fmt * rust test fix * Avoid using attr.dataclass * comment and cosmetic fixes, fixes for ros demo * translation/rotation/scale are now options * rename creation methods in rust api fro Transform3D * fixup SpaceInfoConnection to be more versatile * Rigid3D helper class on python * a bit nicer rust api, less word duplication * Quaternion is no longer a dataclass in order to enforce spelling out the ordering * fix documentation of datalayout for matrix on python transform api * test/lint fixes * doc fix * fixup ros demo --------- Co-authored-by: Jeremy Leibs --- crates/re_arrow_store/src/arrow_util.rs | 151 ++--- .../re_data_ui/src/component_ui_registry.rs | 3 +- crates/re_data_ui/src/data.rs | 108 +--- crates/re_data_ui/src/lib.rs | 2 + crates/re_data_ui/src/pinhole.rs | 48 ++ crates/re_data_ui/src/transform3d.rs | 196 ++++++ .../src/component_types/disconnected_space.rs | 36 ++ .../src/component_types/draw_order.rs | 2 + .../re_log_types/src/component_types/mat.rs | 8 + .../re_log_types/src/component_types/mod.rs | 17 +- .../src/component_types/pinhole.rs | 135 +++++ .../src/component_types/quaternion.rs | 5 + .../src/component_types/tensor.rs | 1 + .../src/component_types/transform.rs | 279 --------- .../src/component_types/transform3d.rs | 570 ++++++++++++++++++ .../re_log_types/src/component_types/vec.rs | 2 + crates/re_log_types/src/data.rs | 2 - crates/re_log_types/src/data_table.rs | 47 +- crates/re_log_types/src/lib.rs | 16 + crates/re_query/tests/type_tests.rs | 26 +- crates/re_sdk/src/lib.rs | 17 +- crates/re_sdk/src/msg_sender.rs | 4 +- crates/re_viewer/src/misc/queries.rs | 6 +- crates/re_viewer/src/misc/space_info.rs | 92 ++- crates/re_viewer/src/misc/transform_cache.rs | 101 ++-- crates/re_viewer/src/ui/selection_panel.rs | 9 +- .../re_viewer/src/ui/space_view_heuristics.rs | 32 +- crates/re_viewer/src/ui/view_category.rs | 9 +- .../view_spatial/scene/scene_part/cameras.rs | 12 +- .../view_spatial/scene/scene_part/images.rs | 6 +- .../src/ui/view_spatial/space_camera_3d.rs | 4 +- crates/re_viewer/src/ui/view_spatial/ui.rs | 7 +- crates/re_viewer/src/ui/view_spatial/ui_2d.rs | 12 +- examples/python/api_demo/main.py | 62 +- examples/python/arkitscenes/main.py | 26 +- examples/python/car/main.py | 6 +- examples/python/colmap/main.py | 9 +- examples/python/dna/main.py | 8 +- examples/python/notebook/blueprint.ipynb | 128 ++++ examples/python/nyud/main.py | 8 +- examples/python/objectron/main.py | 6 +- examples/python/raw_mesh/main.py | 13 +- examples/python/ros/main.py | 6 +- examples/python/ros/rerun_urdf.py | 16 +- examples/python/tracking_hf_opencv/main.py | 2 +- examples/rust/api_demo/src/main.rs | 50 +- examples/rust/dna/src/main.rs | 13 +- examples/rust/objectron/src/main.rs | 26 +- examples/rust/raw_mesh/src/main.rs | 23 +- rerun_py/docs/gen_common_index.py | 9 +- rerun_py/rerun_sdk/rerun/__init__.py | 34 +- .../rerun_sdk/rerun/components/__init__.py | 24 +- .../rerun/components/disconnected_space.py | 32 + .../rerun_sdk/rerun/components/pinhole.py | 54 ++ .../rerun_sdk/rerun/components/quaternion.py | 19 +- .../rerun_sdk/rerun/components/transform3d.py | 273 +++++++++ rerun_py/rerun_sdk/rerun/log/__init__.py | 9 + rerun_py/rerun_sdk/rerun/log/camera.py | 12 +- .../rerun_sdk/rerun/log/experimental/text.py | 2 +- rerun_py/rerun_sdk/rerun/log/transform.py | 174 +++++- rerun_py/src/arrow.rs | 4 +- rerun_py/src/python_bridge.rs | 96 +-- scripts/lint.py | 3 + 63 files changed, 2191 insertions(+), 921 deletions(-) create mode 100644 crates/re_data_ui/src/pinhole.rs create mode 100644 crates/re_data_ui/src/transform3d.rs create mode 100644 crates/re_log_types/src/component_types/disconnected_space.rs create mode 100644 crates/re_log_types/src/component_types/pinhole.rs delete mode 100644 crates/re_log_types/src/component_types/transform.rs create mode 100644 crates/re_log_types/src/component_types/transform3d.rs create mode 100644 examples/python/notebook/blueprint.ipynb create mode 100644 rerun_py/rerun_sdk/rerun/components/disconnected_space.py create mode 100644 rerun_py/rerun_sdk/rerun/components/pinhole.py create mode 100644 rerun_py/rerun_sdk/rerun/components/transform3d.py diff --git a/crates/re_arrow_store/src/arrow_util.rs b/crates/re_arrow_store/src/arrow_util.rs index ef119bd51b31..44bf32b5bbd8 100644 --- a/crates/re_arrow_store/src/arrow_util.rs +++ b/crates/re_arrow_store/src/arrow_util.rs @@ -233,109 +233,64 @@ fn test_clean_for_polars_nomodify() { assert_eq!(cell.as_arrow_ref(), &*cleaned); } -#[test] -fn test_clean_for_polars_modify() { - use re_log_types::{DataCell, Pinhole, Transform}; - // transforms are a nice pathological type with both Unions and FixedSizeLists - let transforms = vec![Transform::Pinhole(Pinhole { - image_from_cam: [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]].into(), - resolution: None, - })]; +#[cfg(test)] +mod tests { + use arrow2::datatypes::{DataType, Field, UnionMode}; + use arrow2_convert::{ArrowDeserialize, ArrowField, ArrowSerialize}; + use re_log_types::{component_types::Vec3D, Component, DataCell}; - let cell: DataCell = transforms.try_into().unwrap(); - assert_eq!( - *cell.datatype(), - DataType::Union( - vec![ - Field::new("Unknown", DataType::Boolean, false), - Field::new( - "Rigid3", - DataType::Struct(vec![ - Field::new( - "rotation", - DataType::FixedSizeList( - Box::new(Field::new("item", DataType::Float32, false)), - 4 - ), - false - ), - Field::new( - "translation", - DataType::FixedSizeList( - Box::new(Field::new("item", DataType::Float32, false)), - 3 - ), - false - ) - ]), - false - ), - Field::new( - "Pinhole", - DataType::Struct(vec![ - Field::new( - "image_from_cam", - DataType::FixedSizeList( - Box::new(Field::new("item", DataType::Float32, false)), - 9 - ), - false, - ), - Field::new( - "resolution", - DataType::FixedSizeList( - Box::new(Field::new("item", DataType::Float32, false)), - 2 - ), - true, - ), - ]), - false - ) - ], - None, - UnionMode::Dense - ), - ); + use crate::ArrayExt; - let cleaned = cell.as_arrow_ref().clean_for_polars(); + #[derive(Clone, Copy, Debug, PartialEq, ArrowField, ArrowSerialize, ArrowDeserialize)] + #[arrow_field(type = "dense")] + enum TestComponentWithUnionAndFixedSizeList { + Bool(bool), + Vec3D(Vec3D), + } - assert_eq!( - *cleaned.data_type(), - DataType::Struct(vec![ - Field::new("Unknown", DataType::Boolean, false), - Field::new( - "Rigid3", - DataType::Struct(vec![ - Field::new( - "rotation", - DataType::List(Box::new(Field::new("item", DataType::Float32, false)),), - false - ), + impl Component for TestComponentWithUnionAndFixedSizeList { + fn name() -> re_log_types::ComponentName { + "test_component_with_union_and_fixed_size_list".into() + } + } + + #[test] + fn test_clean_for_polars_modify() { + // Pick a type with both Unions and FixedSizeLists + let elements = vec![TestComponentWithUnionAndFixedSizeList::Bool(false)]; + + let cell: DataCell = elements.try_into().unwrap(); + assert_eq!( + *cell.datatype(), + DataType::Union( + vec![ + Field::new("Bool", DataType::Boolean, false), Field::new( - "translation", - DataType::List(Box::new(Field::new("item", DataType::Float32, false)),), + "Vec3D", + DataType::FixedSizeList( + Box::new(Field::new("item", DataType::Float32, false)), + 3 + ), false ) - ]), - false - ), - Field::new( - "Pinhole", - DataType::Struct(vec![ - Field::new( - "image_from_cam", - DataType::List(Box::new(Field::new("item", DataType::Float32, false))), - false, - ), - Field::new( - "resolution", - DataType::List(Box::new(Field::new("item", DataType::Float32, false))), - true, - ), - ]), - false + ], + None, + UnionMode::Dense ) - ],), - ); + ); + + let cleaned = cell.as_arrow_ref().clean_for_polars(); + + assert_eq!( + *cleaned.data_type(), + DataType::Struct(vec![ + Field::new("Bool", DataType::Boolean, false), + Field::new( + "Vec3D", + DataType::List(Box::new(Field::new("item", DataType::Float32, false))), + false + ) + ],) + ); + } } diff --git a/crates/re_data_ui/src/component_ui_registry.rs b/crates/re_data_ui/src/component_ui_registry.rs index f1a37eed5eda..e140cc781120 100644 --- a/crates/re_data_ui/src/component_ui_registry.rs +++ b/crates/re_data_ui/src/component_ui_registry.rs @@ -51,6 +51,7 @@ pub fn create_component_ui_registry() -> ComponentUiRegistry { add::(&mut registry); // add::(&mut registry); // add::(&mut registry); + add::(&mut registry); // add::(&mut registry); // add::(&mut registry); add::(&mut registry); @@ -59,7 +60,7 @@ pub fn create_component_ui_registry() -> ComponentUiRegistry { // add::(&mut registry); add::(&mut registry); add::(&mut registry); - add::(&mut registry); + add::(&mut registry); add::(&mut registry); add::(&mut registry); add::(&mut registry); diff --git a/crates/re_data_ui/src/data.rs b/crates/re_data_ui/src/data.rs index db0f8ca940eb..ec879d726e7d 100644 --- a/crates/re_data_ui/src/data.rs +++ b/crates/re_data_ui/src/data.rs @@ -1,10 +1,8 @@ use egui::Vec2; use re_format::format_f32; -use re_log_types::{ - component_types::ColorRGBA, - component_types::{LineStrip2D, LineStrip3D, Mat3x3, Rect2D, Vec2D, Vec3D, Vec4D}, - Pinhole, Rigid3, Transform, ViewCoordinates, +use re_log_types::component_types::{ + ColorRGBA, LineStrip2D, LineStrip3D, Mat3x3, Rect2D, Vec2D, Vec3D, Vec4D, ViewCoordinates, }; use re_viewer_context::{UiVerbosity, ViewerContext}; @@ -53,24 +51,6 @@ impl DataUi for ColorRGBA { } } -impl DataUi for Transform { - fn data_ui( - &self, - ctx: &mut ViewerContext<'_>, - ui: &mut egui::Ui, - verbosity: UiVerbosity, - query: &re_arrow_store::LatestAtQuery, - ) { - match self { - Transform::Unknown => { - ui.label("Unknown transform"); - } - Transform::Rigid3(rigid3) => rigid3.data_ui(ctx, ui, verbosity, query), - Transform::Pinhole(pinhole) => pinhole.data_ui(ctx, ui, verbosity, query), - } - } -} - impl DataUi for ViewCoordinates { fn data_ui( &self, @@ -90,90 +70,6 @@ impl DataUi for ViewCoordinates { } } -impl DataUi for Rigid3 { - #[allow(clippy::only_used_in_recursion)] - fn data_ui( - &self, - ctx: &mut ViewerContext<'_>, - ui: &mut egui::Ui, - verbosity: UiVerbosity, - query: &re_arrow_store::LatestAtQuery, - ) { - match verbosity { - UiVerbosity::Small => { - ui.label("Rigid 3D transform").on_hover_ui(|ui| { - self.data_ui(ctx, ui, UiVerbosity::All, query); - }); - } - - UiVerbosity::All | UiVerbosity::Reduced => { - let pose = self.parent_from_child(); // TODO(emilk): which one to show? - let rotation = pose.rotation(); - let translation = pose.translation(); - - ui.vertical(|ui| { - ui.label("Rigid 3D transform:"); - ui.indent("rigid3", |ui| { - egui::Grid::new("rigid3").num_columns(2).show(ui, |ui| { - ui.label("rotation"); - ui.monospace(format!("{rotation:?}")); - ui.end_row(); - - ui.label("translation"); - ui.monospace(format!("{translation:?}")); - ui.end_row(); - }); - }); - }); - } - } - } -} - -impl DataUi for Pinhole { - fn data_ui( - &self, - ctx: &mut ViewerContext<'_>, - ui: &mut egui::Ui, - verbosity: UiVerbosity, - query: &re_arrow_store::LatestAtQuery, - ) { - match verbosity { - UiVerbosity::Small => { - ui.label("Pinhole transform").on_hover_ui(|ui| { - self.data_ui(ctx, ui, UiVerbosity::All, query); - }); - } - - UiVerbosity::All | UiVerbosity::Reduced => { - let Pinhole { - image_from_cam: image_from_view, - resolution, - } = self; - - ui.vertical(|ui| { - ui.label("Pinhole transform:"); - ui.indent("pinole", |ui| { - ui.horizontal(|ui| { - ui.label("resolution:"); - if let Some(re_log_types::component_types::Vec2D([x, y])) = resolution { - ui.monospace(format!("{x}x{y}")); - } else { - ui.weak("(none)"); - } - }); - - ui.label("image from view:"); - ui.indent("image_from_view", |ui| { - image_from_view.data_ui(ctx, ui, verbosity, query); - }); - }); - }); - } - } - } -} - impl DataUi for Mat3x3 { fn data_ui( &self, diff --git a/crates/re_data_ui/src/lib.rs b/crates/re_data_ui/src/lib.rs index 4216db834ac0..ba2012aa6a32 100644 --- a/crates/re_data_ui/src/lib.rs +++ b/crates/re_data_ui/src/lib.rs @@ -17,6 +17,8 @@ mod instance_path; mod item; pub mod item_ui; mod log_msg; +mod pinhole; +mod transform3d; pub use crate::image::{ show_zoomed_image_region, show_zoomed_image_region_area_outline, diff --git a/crates/re_data_ui/src/pinhole.rs b/crates/re_data_ui/src/pinhole.rs new file mode 100644 index 000000000000..9abd37689da1 --- /dev/null +++ b/crates/re_data_ui/src/pinhole.rs @@ -0,0 +1,48 @@ +use re_log_types::component_types::Pinhole; +use re_viewer_context::{UiVerbosity, ViewerContext}; + +use crate::DataUi; + +impl DataUi for Pinhole { + fn data_ui( + &self, + ctx: &mut ViewerContext<'_>, + ui: &mut egui::Ui, + verbosity: UiVerbosity, + query: &re_arrow_store::LatestAtQuery, + ) { + match verbosity { + UiVerbosity::Small => { + ui.label("Pinhole transform").on_hover_ui(|ui| { + self.data_ui(ctx, ui, UiVerbosity::All, query); + }); + } + + UiVerbosity::All | UiVerbosity::Reduced => { + let Pinhole { + image_from_cam: image_from_view, + resolution, + } = self; + + ui.vertical(|ui| { + ui.label("Pinhole transform:"); + ui.indent("pinole", |ui| { + ui.horizontal(|ui| { + ui.label("resolution:"); + if let Some(re_log_types::component_types::Vec2D([x, y])) = resolution { + ui.monospace(format!("{x}x{y}")); + } else { + ui.weak("(none)"); + } + }); + + ui.label("image from view:"); + ui.indent("image_from_view", |ui| { + image_from_view.data_ui(ctx, ui, verbosity, query); + }); + }); + }); + } + } + } +} diff --git a/crates/re_data_ui/src/transform3d.rs b/crates/re_data_ui/src/transform3d.rs new file mode 100644 index 000000000000..6ff02ec9fcfc --- /dev/null +++ b/crates/re_data_ui/src/transform3d.rs @@ -0,0 +1,196 @@ +use re_log_types::component_types::{ + Angle, Rotation3D, RotationAxisAngle, Scale3D, Transform3D, Transform3DRepr, + TranslationAndMat3, TranslationRotationScale3D, +}; +use re_viewer_context::{UiVerbosity, ViewerContext}; + +use crate::DataUi; + +impl DataUi for Transform3D { + #[allow(clippy::only_used_in_recursion)] + fn data_ui( + &self, + ctx: &mut ViewerContext<'_>, + ui: &mut egui::Ui, + verbosity: UiVerbosity, + query: &re_arrow_store::LatestAtQuery, + ) { + match verbosity { + UiVerbosity::Small => { + // TODO(andreas): Preview some information instead of just a label with hover ui. + ui.label("3D transform").on_hover_ui(|ui| { + self.data_ui(ctx, ui, UiVerbosity::All, query); + }); + } + + UiVerbosity::All | UiVerbosity::Reduced => { + let dir_string = if self.from_parent { + "parent ➡ child" + } else { + "child ➡ parent" + }; + + ui.vertical(|ui| { + ui.label("3D transform"); + ui.indent("transform_repr", |ui| { + ui.label(dir_string); + self.transform.data_ui(ctx, ui, verbosity, query); + }); + }); + } + } + } +} + +impl DataUi for Transform3DRepr { + #[allow(clippy::only_used_in_recursion)] + fn data_ui( + &self, + ctx: &mut ViewerContext<'_>, + ui: &mut egui::Ui, + verbosity: UiVerbosity, + query: &re_arrow_store::LatestAtQuery, + ) { + match verbosity { + UiVerbosity::Small => { + ui.label("3D transform").on_hover_ui(|ui| { + self.data_ui(ctx, ui, UiVerbosity::All, query); + }); + } + + UiVerbosity::All | UiVerbosity::Reduced => match self { + Transform3DRepr::TranslationAndMat3(translation_matrix) => { + translation_matrix.data_ui(ctx, ui, verbosity, query); + } + Transform3DRepr::TranslationRotationScale(translation_rotation_scale) => { + translation_rotation_scale.data_ui(ctx, ui, verbosity, query); + } + }, + } + } +} + +impl DataUi for TranslationRotationScale3D { + fn data_ui( + &self, + ctx: &mut ViewerContext<'_>, + ui: &mut egui::Ui, + verbosity: UiVerbosity, + query: &re_arrow_store::LatestAtQuery, + ) { + let TranslationRotationScale3D { + translation, + rotation, + scale, + } = self; + + egui::Grid::new("translation_rotation_scale") + .num_columns(2) + .show(ui, |ui| { + // Unlike Rotation/Scale, we don't have a value that indicates that nothing was logged. + // We still skip zero translations though since they are typically not logged explicitly. + if let Some(translation) = translation { + ui.label("translation"); + translation.data_ui(ctx, ui, verbosity, query); + ui.end_row(); + } + + if let Some(rotation) = rotation { + ui.label("rotation"); + rotation.data_ui(ctx, ui, verbosity, query); + ui.end_row(); + } + + if let Some(scale) = scale { + ui.label("scale"); + scale.data_ui(ctx, ui, verbosity, query); + ui.end_row(); + } + }); + } +} + +impl DataUi for Rotation3D { + fn data_ui( + &self, + ctx: &mut ViewerContext<'_>, + ui: &mut egui::Ui, + verbosity: UiVerbosity, + query: &re_arrow_store::LatestAtQuery, + ) { + match self { + Rotation3D::Quaternion(q) => { + // TODO(andreas): Better formatting for quaternions. + ui.label(format!("{q:?}")); + } + Rotation3D::AxisAngle(RotationAxisAngle { axis, angle }) => { + egui::Grid::new("axis_angle").num_columns(2).show(ui, |ui| { + ui.label("axis"); + axis.data_ui(ctx, ui, verbosity, query); + ui.end_row(); + + ui.label("angle"); + match angle { + Angle::Radians(v) => { + ui.label(format!("{}rad", re_format::format_f32(*v))); + } + Angle::Degrees(v) => { + // TODO(andreas): Convert to arc minutes/seconds for very small angles. + // That code should be in re_format! + ui.label(format!("{}°", re_format::format_f32(*v),)); + } + } + ui.end_row(); + }); + } + } + } +} + +impl DataUi for Scale3D { + fn data_ui( + &self, + ctx: &mut ViewerContext<'_>, + ui: &mut egui::Ui, + verbosity: UiVerbosity, + query: &re_arrow_store::LatestAtQuery, + ) { + match self { + Scale3D::Uniform(scale) => { + ui.label(re_format::format_f32(*scale)); + } + Scale3D::ThreeD(v) => { + v.data_ui(ctx, ui, verbosity, query); + } + } + } +} + +impl DataUi for TranslationAndMat3 { + fn data_ui( + &self, + ctx: &mut ViewerContext<'_>, + ui: &mut egui::Ui, + verbosity: UiVerbosity, + query: &re_arrow_store::LatestAtQuery, + ) { + let TranslationAndMat3 { + translation, + matrix, + } = self; + + egui::Grid::new("translation_and_mat3") + .num_columns(2) + .show(ui, |ui| { + if let Some(translation) = translation { + ui.label("translation"); + translation.data_ui(ctx, ui, verbosity, query); + ui.end_row(); + } + + ui.label("matrix"); + matrix.data_ui(ctx, ui, verbosity, query); + ui.end_row(); + }); + } +} diff --git a/crates/re_log_types/src/component_types/disconnected_space.rs b/crates/re_log_types/src/component_types/disconnected_space.rs new file mode 100644 index 000000000000..07102ec26d57 --- /dev/null +++ b/crates/re_log_types/src/component_types/disconnected_space.rs @@ -0,0 +1,36 @@ +use arrow2_convert::{ArrowDeserialize, ArrowField, ArrowSerialize}; + +use crate::Component; + +/// Specifies that the entity path at which this is logged is disconnected from its parent. +/// +/// If a transform or pinhole is logged on the same path, this component will be ignored. +/// +/// This is useful for specifying that a subgraph is independent of the rest of the scene. +/// +/// This component is a "mono-component". See [the crate level docs](crate) for details. +#[derive(Copy, Clone, Debug, ArrowField, ArrowSerialize, ArrowDeserialize)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[arrow_field(transparent)] +#[repr(transparent)] +pub struct DisconnectedSpace(bool); + +impl DisconnectedSpace { + #[inline] + pub fn new() -> Self { + Self(false) + } +} + +impl Default for DisconnectedSpace { + fn default() -> Self { + Self::new() + } +} + +impl Component for DisconnectedSpace { + #[inline] + fn name() -> crate::ComponentName { + "rerun.disconnected_space".into() + } +} diff --git a/crates/re_log_types/src/component_types/draw_order.rs b/crates/re_log_types/src/component_types/draw_order.rs index 66d3d69fc5e3..f7b5c197ba03 100644 --- a/crates/re_log_types/src/component_types/draw_order.rs +++ b/crates/re_log_types/src/component_types/draw_order.rs @@ -10,6 +10,8 @@ use crate::Component; /// /// Draw order for entities with the same draw order is generally undefined. /// +/// This component is a "mono-component". See [the crate level docs](crate) for details. +/// /// ``` /// use re_log_types::component_types::DrawOrder; /// use arrow2_convert::field::ArrowField; diff --git a/crates/re_log_types/src/component_types/mat.rs b/crates/re_log_types/src/component_types/mat.rs index 303f0c6bf9d9..973cabcb7754 100644 --- a/crates/re_log_types/src/component_types/mat.rs +++ b/crates/re_log_types/src/component_types/mat.rs @@ -27,6 +27,14 @@ use super::Vec3D; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Mat3x3([Vec3D; 3]); +impl Mat3x3 { + pub const IDENTITY: Mat3x3 = Mat3x3([ + Vec3D([1.0, 0.0, 0.0]), + Vec3D([0.0, 1.0, 0.0]), + Vec3D([0.0, 0.0, 1.0]), + ]); +} + impl std::ops::Index for Mat3x3 where Idx: std::slice::SliceIndex<[Vec3D]>, diff --git a/crates/re_log_types/src/component_types/mod.rs b/crates/re_log_types/src/component_types/mod.rs index 2eae0fc191a7..bd681e05ee6b 100644 --- a/crates/re_log_types/src/component_types/mod.rs +++ b/crates/re_log_types/src/component_types/mod.rs @@ -22,6 +22,7 @@ mod class_id; mod color; pub mod context; pub mod coordinates; +mod disconnected_space; mod draw_order; mod instance_key; mod keypoint_id; @@ -29,6 +30,7 @@ mod label; mod linestrip; mod mat; mod mesh3d; +mod pinhole; mod point; mod quaternion; mod radius; @@ -38,7 +40,7 @@ mod size; mod tensor; mod text_box; mod text_entry; -mod transform; +mod transform3d; mod vec; pub use arrow::Arrow3D; @@ -47,6 +49,7 @@ pub use class_id::ClassId; pub use color::ColorRGBA; pub use context::{AnnotationContext, AnnotationInfo, ClassDescription}; pub use coordinates::ViewCoordinates; +pub use disconnected_space::DisconnectedSpace; pub use draw_order::DrawOrder; pub use instance_key::InstanceKey; pub use keypoint_id::KeypointId; @@ -54,6 +57,7 @@ pub use label::Label; pub use linestrip::{LineStrip2D, LineStrip3D}; pub use mat::Mat3x3; pub use mesh3d::{EncodedMesh3D, Mesh3D, MeshFormat, MeshId, RawMesh3D}; +pub use pinhole::Pinhole; pub use point::{Point2D, Point3D}; pub use quaternion::Quaternion; pub use radius::Radius; @@ -68,24 +72,29 @@ pub use tensor::{ pub use tensor::{TensorImageLoadError, TensorImageSaveError}; pub use text_box::TextBox; pub use text_entry::TextEntry; -pub use transform::{Pinhole, Rigid3, Transform}; +pub use transform3d::{ + Angle, Rotation3D, RotationAxisAngle, Scale3D, Transform3D, Transform3DRepr, + TranslationAndMat3, TranslationRotationScale3D, +}; pub use vec::{Vec2D, Vec3D, Vec4D}; lazy_static! { //TODO(john): use a run-time type registry - static ref FIELDS: [Field; 27] = [ + static ref FIELDS: [Field; 29] = [ ::field(), ::field(), ::field(), ::field(), ::field(), ::field(), + ::field(), ::field(), ::field(),