Skip to content

Commit

Permalink
Support annotation context / classids for GeoPoints (#8124)
Browse files Browse the repository at this point in the history
### What

In order to do so...
* moves annotation scene context system to re_space_view
* move some utilities for resolving annotation contexts to re_space_view
* doing so, I noticed that it always processes keypoints, so I added a
variant without keypoints to it
* doing so I noticed that _almost everything_ queries keypoints for no
reason at all - keypoints are only supported on points 2d & 3d (check
the fbs definitions! also the way we speced it it doesn't make sense
otherwise)
         * -> rip out a lot of of keypoint queries 
* move color resolve utility to re_space_view
* breathe

This allows me to have automagically colored points on nuscenes!
(separate pr for this update coming soon!)

![image](https://github.com/user-attachments/assets/bb807f2f-0704-413b-852f-6578c5e5d898)



### Checklist
* [x] I have read and agree to [Contributor
Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and
the [Code of
Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md)
* [x] I've included a screenshot or gif (if applicable)
* [x] I have tested the web demo (if applicable):
* Using examples from latest `main` build:
[rerun.io/viewer](https://rerun.io/viewer/pr/8124?manifest_url=https://app.rerun.io/version/main/examples_manifest.json)
* Using full set of examples from `nightly` build:
[rerun.io/viewer](https://rerun.io/viewer/pr/8124?manifest_url=https://app.rerun.io/version/nightly/examples_manifest.json)
* [x] The PR title and labels are set such as to maximize their
usefulness for the next release's CHANGELOG
* [x] If applicable, add a new check to the [release
checklist](https://github.com/rerun-io/rerun/blob/main/tests/python/release_checklist)!
* [x] If have noted any breaking changes to the log API in
`CHANGELOG.md` and the migration guide

- [PR Build Summary](https://build.rerun.io/pr/8124)
- [Recent benchmark results](https://build.rerun.io/graphs/crates.html)
- [Wasm size tracking](https://build.rerun.io/graphs/sizes.html)

To run all checks from `main`, comment on the PR with `@rerun-bot
full-check`.

To deploy documentation changes immediately after merging this PR, add
the `deploy docs` label.
  • Loading branch information
Wumpf authored Nov 14, 2024
1 parent 95b118f commit 0e72fb3
Show file tree
Hide file tree
Showing 32 changed files with 340 additions and 295 deletions.
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 @@ -28,6 +28,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
131 changes: 131 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,131 @@
use ahash::HashMap;

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

use crate::clamped_or_nothing;

/// 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> {
re_tracing::profile_function_if!(10_000 < num_instances);

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(|c| egui::Color32::from(*c)).collect()
} else if colors.len() == 1 {
// Common happy path
vec![egui::Color32::from(*last_color); num_instances]
} else {
let colors = clamped_or_nothing(colors, num_instances);
colors.map(|c| egui::Color32::from(*c)).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(
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

0 comments on commit 0e72fb3

Please sign in to comment.