From addff9c2adfa57880e7833a04e6c39c744e73123 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Thu, 6 Feb 2025 17:44:48 +0100 Subject: [PATCH] Automated `check_annotations` (#8958) Title. --- Cargo.lock | 1 + crates/viewer/re_view_spatial/Cargo.toml | 1 + .../re_view_spatial/tests/annotations.rs | 211 ++++++++++++++++++ .../annotations_hover_background.png | 3 + .../annotations_hover_rect_green.png | 3 + .../snapshots/annotations_hover_rect_red.png | 3 + .../annotations_hover_region_green.png | 3 + .../annotations_hover_region_red.png | 3 + .../tests/snapshots/annotations_overview.png | 3 + .../release_checklist/check_annotations.py | 67 ------ 10 files changed, 231 insertions(+), 67 deletions(-) create mode 100644 crates/viewer/re_view_spatial/tests/annotations.rs create mode 100644 crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_background.png create mode 100644 crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_rect_green.png create mode 100644 crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_rect_red.png create mode 100644 crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_region_green.png create mode 100644 crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_region_red.png create mode 100644 crates/viewer/re_view_spatial/tests/snapshots/annotations_overview.png delete mode 100644 tests/python/release_checklist/check_annotations.py diff --git a/Cargo.lock b/Cargo.lock index 24e5dde057e1..6dc021222107 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6965,6 +6965,7 @@ dependencies = [ "once_cell", "ordered-float 4.4.0", "re_chunk_store", + "re_component_ui", "re_data_ui", "re_entity_db", "re_error", diff --git a/crates/viewer/re_view_spatial/Cargo.toml b/crates/viewer/re_view_spatial/Cargo.toml index 7b440159bf46..b8368dcc88c0 100644 --- a/crates/viewer/re_view_spatial/Cargo.toml +++ b/crates/viewer/re_view_spatial/Cargo.toml @@ -64,6 +64,7 @@ web-time.workspace = true [dev-dependencies] +re_component_ui.workspace = true re_viewer_context = { workspace = true, features = ["testing"] } re_viewport.workspace = true re_viewport_blueprint = { workspace = true, features = ["testing"] } diff --git a/crates/viewer/re_view_spatial/tests/annotations.rs b/crates/viewer/re_view_spatial/tests/annotations.rs new file mode 100644 index 000000000000..e13387df47d9 --- /dev/null +++ b/crates/viewer/re_view_spatial/tests/annotations.rs @@ -0,0 +1,211 @@ +use re_chunk_store::RowId; +use re_log_types::TimePoint; +use re_view_spatial::SpatialView2D; +use re_viewer_context::test_context::TestContext; +use re_viewer_context::{RecommendedView, ViewClass, ViewId}; +use re_viewport_blueprint::test_context_ext::TestContextExt; +use re_viewport_blueprint::ViewBlueprint; + +#[test] +pub fn test_annotations() { + let mut test_context = get_test_context(); + + { + use ndarray::{s, Array, ShapeBuilder}; + + // Log an annotation context to assign a label and color to each class + test_context.log_entity("/".into(), |builder| { + builder.with_archetype( + RowId::new(), + TimePoint::default(), + &re_types::archetypes::AnnotationContext::new([ + (0, "black", re_types::datatypes::Rgba32::from_rgb(0, 0, 0)), + (1, "red", re_types::datatypes::Rgba32::from_rgb(255, 0, 0)), + (2, "green", re_types::datatypes::Rgba32::from_rgb(0, 255, 0)), + ]), + ) + }); + + // Log a batch of 2 rectangles with different `class_ids` + test_context.log_entity("detections".into(), |builder| { + builder.with_archetype( + RowId::new(), + TimePoint::default(), + &re_types::archetypes::Boxes2D::from_mins_and_sizes( + [(200.0, 50.0), (75.0, 150.0)], + [(30.0, 30.0), (20.0, 20.0)], + ) + .with_class_ids([1, 2]), + ) + }); + + test_context.log_entity("segmentation/image".into(), |builder| { + let mut image = Array::::zeros((200, 300).f()); + image.slice_mut(s![50..100, 50..120]).fill(1); + image.slice_mut(s![100..180, 130..280]).fill(2); + + builder.with_archetype( + RowId::new(), + TimePoint::default(), + &re_types::archetypes::SegmentationImage::try_from(image) + .unwrap() + .with_draw_order(0.0), + ) + }); + } + + let view_id = setup_blueprint(&mut test_context); + run_view_ui_and_save_snapshot( + &mut test_context, + view_id, + "annotations", + // We need quite a bunch of pixels to be able to stack the double hover pop-ups. + egui::vec2(300.0, 300.0) * 2.0, + ); +} + +fn get_test_context() -> TestContext { + let mut test_context = TestContext::default(); + + // It's important to first register the view class before adding any entities, + // otherwise the `VisualizerEntitySubscriber` for our visualizers doesn't exist yet, + // and thus will not find anything applicable to the visualizer. + test_context.register_view_class::(); + + // Make sure we can draw stuff in the hover tables. + test_context.component_ui_registry = re_component_ui::create_component_ui_registry(); + // Also register the legacy UIs. + re_data_ui::register_component_uis(&mut test_context.component_ui_registry); + + test_context +} + +fn setup_blueprint(test_context: &mut TestContext) -> ViewId { + test_context.setup_viewport_blueprint(|_ctx, blueprint| { + let view_blueprint = ViewBlueprint::new( + re_view_spatial::SpatialView2D::identifier(), + RecommendedView::root(), + ); + + let view_id = view_blueprint.id; + blueprint.add_views(std::iter::once(view_blueprint), None, None); + + view_id + }) +} + +fn run_view_ui_and_save_snapshot( + test_context: &mut TestContext, + view_id: ViewId, + name: &str, + size: egui::Vec2, +) { + let mut harness = test_context + .setup_kittest_for_rendering() + .with_size(size) + .build(|ctx| { + re_ui::apply_style_and_install_loaders(ctx); + + egui::CentralPanel::default().show(ctx, |ui| { + test_context.run(ctx, |ctx| { + let view_class = ctx + .view_class_registry + .get_class_or_log_error(SpatialView2D::identifier()); + + let view_blueprint = ViewBlueprint::try_from_db( + view_id, + ctx.store_context.blueprint, + ctx.blueprint_query, + ) + .expect("we just created that view"); + + let mut view_states = test_context.view_states.lock(); + let view_state = view_states.get_mut_or_create(view_id, view_class); + + let (view_query, system_execution_output) = + re_viewport::execute_systems_for_view( + ctx, + &view_blueprint, + ctx.current_query().at(), // TODO(andreas): why is this even needed to be passed in? + view_state, + ); + + view_class + .ui(ctx, ui, view_state, &view_query, system_execution_output) + .expect("failed to run graph view ui"); + }); + + test_context.handle_system_commands(); + }); + }); + + { + // There should be one view with an image and a batch of 2 rectangles. + // + // The image should contain a red region and a green region. + // There should be 1 red rectangle and 1 green rectangle. + + let name = format!("{name}_overview"); + harness.run(); + harness.snapshot(&name); + } + + { + // Hover over each of the elements and confirm it shows the label as "red" or "green" as + // expected. + // + // *Note*: when hovering the rectangles, a tooltip pertaining to the image will _also_ + // appear and indicate a label of "0". This is expected as the image is black at this + // location. + + { + let name = format!("{name}_hover_background"); + let raw_input = harness.input_mut(); + raw_input + .events + .push(egui::Event::PointerMoved((50.0, 200.0).into())); + harness.run(); + harness.snapshot(&name); + } + + { + let name = format!("{name}_hover_rect_red"); + let raw_input = harness.input_mut(); + raw_input + .events + .push(egui::Event::PointerMoved((200.0, 250.0).into())); + harness.run(); + harness.snapshot(&name); + } + + { + let name = format!("{name}_hover_rect_green"); + let raw_input = harness.input_mut(); + raw_input + .events + .push(egui::Event::PointerMoved((300.0, 400.0).into())); + harness.run(); + harness.snapshot(&name); + } + + { + let name = format!("{name}_hover_region_green"); + let raw_input = harness.input_mut(); + raw_input + .events + .push(egui::Event::PointerMoved((175.0, 450.).into())); + harness.run(); + harness.snapshot(&name); + } + + { + let name = format!("{name}_hover_region_red"); + let raw_input = harness.input_mut(); + raw_input + .events + .push(egui::Event::PointerMoved((425., 275.0).into())); + harness.run(); + harness.snapshot(&name); + } + } +} diff --git a/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_background.png b/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_background.png new file mode 100644 index 000000000000..485acd7eee28 --- /dev/null +++ b/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_background.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed59059fa6c6595dbd57b268d6cf6e99c69d31fc4d2b64f2710bbb2e20861082 +size 21743 diff --git a/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_rect_green.png b/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_rect_green.png new file mode 100644 index 000000000000..04155a3f6b69 --- /dev/null +++ b/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_rect_green.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a964bd83adae496c3e50a67f592ba03fc14c0c3ff2c507ccaa0cac86c54e796e +size 20829 diff --git a/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_rect_red.png b/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_rect_red.png new file mode 100644 index 000000000000..7684f6ffd02f --- /dev/null +++ b/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_rect_red.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bff7a0bb1378749fc6e0190f28e404ed84a4d18511b623b08692c9d35c2af543 +size 22104 diff --git a/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_region_green.png b/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_region_green.png new file mode 100644 index 000000000000..ef76d4a7f839 --- /dev/null +++ b/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_region_green.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9535e2264640ce3ad290545ca062b5d37eeabb5f082e7f902dce9d3ef682e849 +size 35393 diff --git a/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_region_red.png b/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_region_red.png new file mode 100644 index 000000000000..1f7dca0bd1fc --- /dev/null +++ b/crates/viewer/re_view_spatial/tests/snapshots/annotations_hover_region_red.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:488327c24e043e58574b2023ba847f97f80eae016c1edfeaa3a9c059a3384f21 +size 33606 diff --git a/crates/viewer/re_view_spatial/tests/snapshots/annotations_overview.png b/crates/viewer/re_view_spatial/tests/snapshots/annotations_overview.png new file mode 100644 index 000000000000..e99a5d753bd2 --- /dev/null +++ b/crates/viewer/re_view_spatial/tests/snapshots/annotations_overview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc172522ded899a0490dff5558904ca1a98cc0f680a39aa50adb45f4bdc240e7 +size 10751 diff --git a/tests/python/release_checklist/check_annotations.py b/tests/python/release_checklist/check_annotations.py deleted file mode 100644 index a63db849ce91..000000000000 --- a/tests/python/release_checklist/check_annotations.py +++ /dev/null @@ -1,67 +0,0 @@ -from __future__ import annotations - -import os -from argparse import Namespace -from uuid import uuid4 - -import numpy as np -import rerun as rr - -README = """\ -# Annotations - -This checks whether annotations behave correctly - -### Actions - -There should be one view with an image and a batch of 2 rectangles. - -The image should contain a red region and a green region. -There should be 1 red rectangle and 1 green rectangle. - -Hover over each of the elements and confirm it shows the label as "red" or "green" as expected. - -*Note*: when hovering the rectangles, a tooltip pertaining to the image will _also_ appear and indicate a label of "0". -This is expected as the image is black at this location. -""" - - -def log_readme() -> None: - rr.log("readme", rr.TextDocument(README, media_type=rr.MediaType.MARKDOWN), static=True) - - -def log_annotations() -> None: - # Log an annotation context to assign a label and color to each class - rr.log( - "/", - rr.AnnotationContext([(0, "black", (0, 0, 0)), (1, "red", (255, 0, 0)), (2, "green", (0, 255, 0))]), - static=True, - ) - - # Log a batch of 2 rectangles with different `class_ids` - rr.log("detections", rr.Boxes2D(mins=[[200, 50], [75, 150]], sizes=[[30, 30], [20, 20]], class_ids=[1, 2])) - - # Create a simple segmentation image - - image = np.zeros((200, 300), dtype=np.uint8) - image[50:100, 50:120] = 1 - image[100:180, 130:280] = 2 - rr.log("segmentation/image", rr.SegmentationImage(image)) - - -def run(args: Namespace) -> None: - rr.script_setup(args, f"{os.path.basename(__file__)}", recording_id=uuid4()) - - log_readme() - log_annotations() - - rr.send_blueprint(rr.blueprint.Blueprint(auto_layout=True, auto_views=True), make_active=True, make_default=True) - - -if __name__ == "__main__": - import argparse - - parser = argparse.ArgumentParser(description="Interactive release checklist") - rr.script_add_args(parser) - args = parser.parse_args() - run(args)