Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support annotation context / classids for GeoPoints #8124

Merged
merged 2 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6083,8 +6083,10 @@ dependencies = [
name = "re_space_view"
version = "0.20.0-alpha.4+dev"
dependencies = [
"ahash",
"bytemuck",
"egui",
"glam",
"itertools 0.13.0",
"nohash-hasher",
"re_chunk_store",
Expand All @@ -6094,6 +6096,7 @@ dependencies = [
"re_query",
"re_renderer",
"re_tracing",
"re_types",
"re_types_core",
"re_ui",
"re_viewer_context",
Expand Down Expand Up @@ -6145,6 +6148,7 @@ dependencies = [
name = "re_space_view_map"
version = "0.20.0-alpha.4+dev"
dependencies = [
"bytemuck",
"egui",
"glam",
"itertools 0.13.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ table GeoPoints (
/// \py As either 0-1 floats or 0-255 integers, with separate alpha.
colors: [rerun.components.Color] ("attr.rerun.component_recommended", nullable, order: 2100);

/// Optional class Ids for the points.
///
/// The [components.ClassId] provides colors if not specified explicitly.
class_ids: [rerun.components.ClassId] ("attr.rerun.component_optional", nullable, order: 3200);

//TODO(ab): add `Label` and `ShowLabels` components
//TODO(ab): add `Altitude` component
}
47 changes: 42 additions & 5 deletions crates/store/re_types/src/archetypes/geo_points.rs

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

3 changes: 3 additions & 0 deletions crates/viewer/re_space_view/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,14 @@ re_query.workspace = true
re_renderer.workspace = true
re_tracing.workspace = true
re_types_core.workspace = true
re_types.workspace = true
re_ui.workspace = true
re_viewer_context.workspace = true
re_viewport_blueprint.workspace = true

ahash.workspace = true
bytemuck.workspace = true
egui.workspace = true
glam.workspace = true
itertools.workspace = true
nohash-hasher.workspace = true
137 changes: 137 additions & 0 deletions crates/viewer/re_space_view/src/annotation_context_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use ahash::HashMap;

use re_types::components::Color;
use re_viewer_context::{Annotations, QueryContext, ResolvedAnnotationInfos};

use crate::clamped_or_nothing;

#[inline]
fn to_egui_color(color: &Color) -> egui::Color32 {
let [r, g, b, a] = color.to_array();
egui::Color32::from_rgba_unmultiplied(r, g, b, a)
}
Wumpf marked this conversation as resolved.
Show resolved Hide resolved

/// Process [`Color`] components using annotations and default colors.
pub fn process_color_slice<'a>(
ctx: &QueryContext<'_>,
fallback_provider: &'a dyn re_viewer_context::TypedComponentFallbackProvider<Color>,
num_instances: usize,
annotation_infos: &'a ResolvedAnnotationInfos,
colors: &'a [Color],
) -> Vec<egui::Color32> {
// NOTE: Do not put tracing scopes here, this is called for every entity/timestamp in a frame.
Wumpf marked this conversation as resolved.
Show resolved Hide resolved

if let Some(last_color) = colors.last() {
// If we have colors we can ignore the annotation infos/contexts.

if colors.len() == num_instances {
// Common happy path
colors.iter().map(to_egui_color).collect()
} else if colors.len() == 1 {
// Common happy path
vec![to_egui_color(last_color); num_instances]
} else {
let colors = clamped_or_nothing(colors, num_instances);
colors.map(to_egui_color).collect()
}
} else {
match annotation_infos {
ResolvedAnnotationInfos::Same(count, annotation_info) => {
re_tracing::profile_scope!("no colors, same annotation");
let color = annotation_info
.color()
.unwrap_or_else(|| fallback_provider.fallback_for(ctx).into());
vec![color; *count]
}
ResolvedAnnotationInfos::Many(annotation_info) => {
re_tracing::profile_scope!("no-colors, many annotations");
let fallback = fallback_provider.fallback_for(ctx).into();
annotation_info
.iter()
.map(|annotation_info| annotation_info.color().unwrap_or(fallback))
.collect()
}
}
}
}

pub type Keypoints = HashMap<
(re_types::components::ClassId, i64),
HashMap<re_types::datatypes::KeypointId, glam::Vec3>,
>;

/// Resolves all annotations and keypoints for the given entity view.
pub fn process_annotation_and_keypoint_slices(
latest_at: re_log_types::TimeInt,
num_instances: usize,
positions: impl Iterator<Item = glam::Vec3>,
keypoint_ids: &[re_types::components::KeypointId],
class_ids: &[re_types::components::ClassId],
annotations: &Annotations,
) -> (ResolvedAnnotationInfos, Keypoints) {
re_tracing::profile_function!();

let mut keypoints: Keypoints = HashMap::default();

// No need to process annotations if we don't have class-ids
if class_ids.is_empty() {
let resolved_annotation = annotations
.resolved_class_description(None)
.annotation_info();

return (
ResolvedAnnotationInfos::Same(num_instances, resolved_annotation),
keypoints,
);
};

let class_ids = clamped_or_nothing(class_ids, num_instances);

if keypoint_ids.is_empty() {
let annotation_info = class_ids
.map(|&class_id| {
let class_description = annotations.resolved_class_description(Some(class_id));
class_description.annotation_info()
})
.collect();

(
ResolvedAnnotationInfos::Many(annotation_info),
Default::default(),
)
} else {
let keypoint_ids = clamped_or_nothing(keypoint_ids, num_instances);
let annotation_info = itertools::izip!(positions, keypoint_ids, class_ids)
.map(|(position, keypoint_id, &class_id)| {
let class_description = annotations.resolved_class_description(Some(class_id));

keypoints
.entry((class_id, latest_at.as_i64()))
.or_default()
.insert(keypoint_id.0, position);
class_description.annotation_info_with_keypoint(keypoint_id.0)
})
.collect();

(ResolvedAnnotationInfos::Many(annotation_info), keypoints)
}
}

/// Resolves all annotations for the given entity view.
pub fn process_annotation_slices(
Copy link
Member Author

@Wumpf Wumpf Nov 13, 2024

Choose a reason for hiding this comment

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

this method is new. Everything else in here is just moved code, didn't touch it one bit

latest_at: re_log_types::TimeInt,
num_instances: usize,
class_ids: &[re_types::components::ClassId],
annotations: &Annotations,
) -> ResolvedAnnotationInfos {
let (annotations, _keypoints) = process_annotation_and_keypoint_slices(
latest_at,
num_instances,
std::iter::empty(), // positions are only needed for keypoint lookup
&[],
class_ids,
annotations,
);

annotations
}
23 changes: 23 additions & 0 deletions crates/viewer/re_space_view/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
pub mod controls;

mod annotation_context_utils;
mod annotation_scene_context;
mod heuristics;
mod instance_hash_conversions;
mod outlines;
Expand All @@ -12,6 +14,10 @@ mod results_ext;
mod screenshot;
mod view_property_ui;

pub use annotation_context_utils::{
process_annotation_and_keypoint_slices, process_annotation_slices, process_color_slice,
};
pub use annotation_scene_context::AnnotationSceneContext;
pub use heuristics::suggest_space_view_for_each_entity;
pub use instance_hash_conversions::{
instance_path_hash_from_picking_layer_id, picking_layer_id_from_instance_path_hash,
Expand Down Expand Up @@ -53,3 +59,20 @@ pub fn diff_component_filter<T: re_types_core::Component>(
.any(|instances| instances.iter().any(filter))
})
}

/// Clamp the last value in `values` in order to reach a length of `clamped_len`.
///
/// Returns an empty iterator if values is empty.
#[inline]
pub fn clamped_or_nothing<T>(values: &[T], clamped_len: usize) -> impl Iterator<Item = &T> + Clone {
let Some(last) = values.last() else {
return itertools::Either::Left(std::iter::empty());
};

itertools::Either::Right(
values
.iter()
.chain(std::iter::repeat(last))
.take(clamped_len),
)
}
1 change: 1 addition & 0 deletions crates/viewer/re_space_view_map/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ re_ui.workspace = true
re_viewer_context.workspace = true
re_viewport_blueprint.workspace = true

bytemuck.workspace = true
egui.workspace = true
glam.workspace = true
itertools.workspace = true
Expand Down
7 changes: 6 additions & 1 deletion crates/viewer/re_space_view_map/src/map_space_view.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use egui::{Context, NumExt as _, Rect, Response};
use re_space_view::AnnotationSceneContext;
use walkers::{HttpTiles, Map, MapMemory, Tiles};

use re_data_ui::{item_ui, DataUi};
Expand Down Expand Up @@ -119,7 +120,11 @@ Displays geospatial primitives on a map.
system_registry: &mut SpaceViewSystemRegistrator<'_>,
) -> Result<(), SpaceViewClassRegistryError> {
system_registry.register_visualizer::<GeoPointsVisualizer>()?;
system_registry.register_visualizer::<GeoLineStringsVisualizer>()
system_registry.register_visualizer::<GeoLineStringsVisualizer>()?;

system_registry.register_context_system::<AnnotationSceneContext>()?;

Ok(())
}

fn new_state(&self) -> Box<dyn SpaceViewState> {
Expand Down
Loading
Loading