Skip to content

Commit 1b568d9

Browse files
jprochazkWumpf
andauthored
Improve 2D heuristic fallback values for image opacity (#9787)
Co-authored-by: Andreas Reich <[email protected]>
1 parent 04bb859 commit 1b568d9

20 files changed

+216
-72
lines changed

.github/workflows/reusable_checks_rust.yml

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ jobs:
5555
# ---------------------------------------------------------------------------
5656

5757
rs-lints:
58-
name: Rust lints (fmt, check, clippy, tests, doc)
58+
name: Rust lints (fmt, check, clippy, doc)
5959
# TODO(andreas): setup-vulkan doesn't work on 24.4 right now due to missing .so
6060
runs-on: ubuntu-22.04-large
6161
steps:
@@ -76,22 +76,9 @@ jobs:
7676
with:
7777
pixi-version: v0.41.4
7878

79-
# Install the Vulkan SDK, so we can use the software rasterizer.
80-
# TODO(andreas): It would be nice if `setup_software_rasterizer.py` could do that for us as well (note though that this action here is very fast when cached!)
81-
- name: Install Vulkan SDK
82-
uses: rerun-io/[email protected]
83-
with:
84-
vulkan_version: ${{ env.VULKAN_SDK_VERSION }}
85-
install_runtime: true
86-
cache: true
87-
stripdown: true
88-
89-
- name: Setup software rasterizer
90-
run: pixi run python ./scripts/ci/setup_software_rasterizer.py
91-
9279
- name: Rust checks (PR subset)
9380
if: ${{ inputs.CHANNEL == 'pr' }}
94-
run: pixi run rs-check --only base_checks sdk_variations cargo_deny wasm docs tests
81+
run: pixi run rs-check --only base_checks sdk_variations cargo_deny wasm docs
9582

9683
- name: Rust most checks & tests
9784
if: ${{ inputs.CHANNEL == 'main' }}
@@ -107,6 +94,48 @@ jobs:
10794
# See tests/assets/rrd/README.md for more
10895
run: pixi run check-backwards-compatibility
10996

97+
rs-tests:
98+
name: Rust lints (tests)
99+
# TODO(andreas): setup-vulkan doesn't work on 24.4 right now due to missing .so
100+
runs-on: ubuntu-22.04-large
101+
env:
102+
RUSTFLAGS: --cfg=web_sys_unstable_apis
103+
RUSTDOCFLAGS: ""
104+
steps:
105+
- uses: actions/checkout@v4
106+
with:
107+
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || '' }}
108+
lfs: true
109+
110+
- name: Set up Rust
111+
uses: ./.github/actions/setup-rust
112+
with:
113+
cache_key: "build-linux"
114+
save_cache: true
115+
workload_identity_provider: ${{ secrets.GOOGLE_WORKLOAD_IDENTITY_PROVIDER }}
116+
service_account: ${{ secrets.GOOGLE_SERVICE_ACCOUNT }}
117+
118+
- uses: prefix-dev/[email protected]
119+
with:
120+
pixi-version: v0.41.4
121+
122+
# Install the Vulkan SDK, so we can use the software rasterizer.
123+
# TODO(andreas): It would be nice if `setup_software_rasterizer.py` could do that for us as well (note though that this action here is very fast when cached!)
124+
- name: Install Vulkan SDK
125+
uses: rerun-io/[email protected]
126+
with:
127+
vulkan_version: ${{ env.VULKAN_SDK_VERSION }}
128+
install_runtime: true
129+
cache: true
130+
stripdown: true
131+
132+
- name: Setup software rasterizer
133+
run: pixi run python ./scripts/ci/setup_software_rasterizer.py
134+
135+
- name: Rust checks (PR subset)
136+
if: ${{ inputs.CHANNEL == 'pr' }}
137+
run: pixi run rs-check --only tests
138+
110139
- name: Upload test results
111140
uses: actions/upload-artifact@v4
112141
if: always()

crates/viewer/re_view_spatial/src/ui.rs

Lines changed: 62 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use re_types::{
66
blueprint::components::VisualBounds2D, components::ViewCoordinates, image::ImageKind,
77
};
88
use re_ui::UiExt as _;
9-
use re_viewer_context::{HoverHighlight, SelectionHighlight, ViewHighlights, ViewState};
9+
use re_viewer_context::{HoverHighlight, ImageInfo, SelectionHighlight, ViewHighlights, ViewState};
1010

1111
use crate::{
1212
Pinhole,
@@ -37,13 +37,21 @@ impl From<AutoSizeUnit> for WidgetText {
3737
}
3838
}
3939

40+
/// Number of images per image kind.
41+
#[derive(Clone, Copy, Default)]
42+
pub struct ImageCounts {
43+
pub segmentation: usize,
44+
pub color: usize,
45+
pub depth: usize,
46+
}
47+
4048
/// TODO(andreas): Should turn this "inside out" - [`SpatialViewState`] should be used by [`View3DState`], not the other way round.
4149
#[derive(Clone, Default)]
4250
pub struct SpatialViewState {
4351
pub bounding_boxes: SceneBoundingBoxes,
4452

45-
/// Number of images & depth images processed last frame.
46-
pub num_non_segmentation_images_last_frame: usize,
53+
/// Number of images per image kind processed last frame.
54+
pub image_counts_last_frame: ImageCounts,
4755

4856
/// Last frame's picking result.
4957
pub previous_picking_result: Option<PickingResult>,
@@ -80,19 +88,24 @@ impl SpatialViewState {
8088
.update(ui, &system_output.view_systems, space_kind);
8189

8290
let view_systems = &system_output.view_systems;
83-
self.num_non_segmentation_images_last_frame = view_systems
84-
.iter_visualizer_data::<SpatialViewVisualizerData>()
85-
.flat_map(|data| {
86-
data.pickable_rects.iter().map(|pickable_rect| {
87-
if let PickableRectSourceData::Image { image, .. } = &pickable_rect.source_data
88-
{
89-
(image.kind != ImageKind::Segmentation) as usize
90-
} else {
91-
0
92-
}
93-
})
94-
})
95-
.sum();
91+
92+
for data in view_systems.iter_visualizer_data::<SpatialViewVisualizerData>() {
93+
for pickable_rect in &data.pickable_rects {
94+
let PickableRectSourceData::Image {
95+
image: ImageInfo { kind, .. },
96+
..
97+
} = &pickable_rect.source_data
98+
else {
99+
continue;
100+
};
101+
102+
match kind {
103+
ImageKind::Segmentation => self.image_counts_last_frame.segmentation += 1,
104+
ImageKind::Color => self.image_counts_last_frame.color += 1,
105+
ImageKind::Depth => self.image_counts_last_frame.depth += 1,
106+
}
107+
}
108+
}
96109
}
97110

98111
pub fn bounding_box_ui(&self, ui: &mut egui::Ui, spatial_kind: SpatialViewKind) {
@@ -148,6 +161,39 @@ impl SpatialViewState {
148161
});
149162
}
150163
}
164+
165+
pub fn fallback_opacity_for_image_kind(&self, kind: ImageKind) -> f32 {
166+
// If we have multiple images in the same view, they should not be fully opaque
167+
// if there is at least one image of the same kind with equal or lower draw order.
168+
//
169+
// Here we also assume that if the opacity is unchanged, neither is the draw order.
170+
//
171+
// By default, the draw order is (front to back):
172+
// * segmentation image
173+
// * color image
174+
// * depth image
175+
let counts = self.image_counts_last_frame;
176+
match kind {
177+
ImageKind::Segmentation => {
178+
if counts.color + counts.depth > 0 {
179+
// Segmentation images should always be opaque if there was more than one image in the view,
180+
// excluding other segmentation images.
181+
0.5
182+
} else {
183+
1.0
184+
}
185+
}
186+
ImageKind::Color => {
187+
if counts.depth > 0 {
188+
0.5
189+
} else {
190+
1.0
191+
}
192+
}
193+
// NOTE: Depth images do not support opacity
194+
ImageKind::Depth => 1.0,
195+
}
196+
}
151197
}
152198

153199
pub fn create_labels(

crates/viewer/re_view_spatial/src/visualizers/encoded_image.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use re_types::{
22
archetypes::EncodedImage,
33
components::{DrawOrder, MediaType, Opacity},
4+
image::ImageKind,
45
};
56
use re_view::{HybridResults, diff_component_filter};
67
use re_viewer_context::{
@@ -13,6 +14,7 @@ use re_viewer_context::{
1314
use crate::{
1415
PickableRectSourceData, PickableTexturedRect,
1516
contexts::SpatialSceneEntityContext,
17+
ui::SpatialViewState,
1618
view_kind::SpatialViewKind,
1719
visualizers::{filter_visualizable_2d_entities, textured_rect_from_image},
1820
};
@@ -209,8 +211,21 @@ impl EncodedImageVisualizer {
209211
}
210212

211213
impl TypedComponentFallbackProvider<Opacity> for EncodedImageVisualizer {
212-
fn fallback_for(&self, _ctx: &re_viewer_context::QueryContext<'_>) -> Opacity {
213-
1.0.into()
214+
fn fallback_for(&self, ctx: &re_viewer_context::QueryContext<'_>) -> Opacity {
215+
// Color images should be transparent whenever they're on top of other images,
216+
// But fully opaque if there are no other images in the scene.
217+
let Some(view_state) = ctx.view_state.as_any().downcast_ref::<SpatialViewState>() else {
218+
return 1.0.into();
219+
};
220+
221+
// Known cosmetic issues with this approach:
222+
// * The first frame we have more than one image, the image will be opaque.
223+
// It's too complex to do a full view query just for this here.
224+
// However, we should be able to analyze the `DataQueryResults` instead to check how many entities are fed to the Image/DepthImage visualizers.
225+
// * In 3D scenes, images that are on a completely different plane will cause this to become transparent.
226+
view_state
227+
.fallback_opacity_for_image_kind(ImageKind::Color)
228+
.into()
214229
}
215230
}
216231

crates/viewer/re_view_spatial/src/visualizers/images.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use re_viewer_context::{
1414
use crate::{
1515
PickableRectSourceData, PickableTexturedRect,
1616
contexts::SpatialSceneEntityContext,
17+
ui::SpatialViewState,
1718
view_kind::SpatialViewKind,
1819
visualizers::{filter_visualizable_2d_entities, textured_rect_from_image},
1920
};
@@ -182,8 +183,21 @@ impl ImageVisualizer {
182183
}
183184

184185
impl TypedComponentFallbackProvider<Opacity> for ImageVisualizer {
185-
fn fallback_for(&self, _ctx: &re_viewer_context::QueryContext<'_>) -> Opacity {
186-
1.0.into()
186+
fn fallback_for(&self, ctx: &re_viewer_context::QueryContext<'_>) -> Opacity {
187+
// Color images should be transparent whenever they're on top of other images,
188+
// But fully opaque if there are no other images in the scene.
189+
let Some(view_state) = ctx.view_state.as_any().downcast_ref::<SpatialViewState>() else {
190+
return 1.0.into();
191+
};
192+
193+
// Known cosmetic issues with this approach:
194+
// * The first frame we have more than one image, the color image will be opaque.
195+
// It's too complex to do a full view query just for this here.
196+
// However, we should be able to analyze the `DataQueryResults` instead to check how many entities are fed to the Image/DepthImage visualizers.
197+
// * In 3D scenes, images that are on a completely different plane will cause this to become transparent.
198+
view_state
199+
.fallback_opacity_for_image_kind(ImageKind::Color)
200+
.into()
187201
}
188202
}
189203

crates/viewer/re_view_spatial/src/visualizers/segmentation_images.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -188,12 +188,9 @@ impl TypedComponentFallbackProvider<Opacity> for SegmentationImageVisualizer {
188188
// It's too complex to do a full view query just for this here.
189189
// However, we should be able to analyze the `DataQueryResults` instead to check how many entities are fed to the Image/DepthImage visualizers.
190190
// * In 3D scenes, images that are on a completely different plane will cause this to become transparent.
191-
if view_state.num_non_segmentation_images_last_frame == 0 {
192-
1.0
193-
} else {
194-
0.5
195-
}
196-
.into()
191+
view_state
192+
.fallback_opacity_for_image_kind(ImageKind::Segmentation)
193+
.into()
197194
}
198195
}
199196

crates/viewer/re_view_spatial/src/visualizers/videos.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ use super::{
3434
entity_iterator::process_archetype,
3535
};
3636

37+
// TODO(#9832): Support opacity for videos
38+
// TODO(jan): Fallback opacity in the same way as color/depth/segmentation images
3739
pub struct VideoFrameReferenceVisualizer {
3840
pub data: SpatialViewVisualizerData,
3941
}
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading

crates/viewer/re_view_spatial/tests/transform_hierarchy.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ fn run_view_ui_and_save_snapshot(
237237
});
238238
harness.run_steps(8);
239239

240+
let mut success = true;
240241
for time in 0..=7 {
241242
let name = format!("{name}_{}_{time}", timeline.name());
242243

@@ -251,11 +252,12 @@ fn run_view_ui_and_save_snapshot(
251252
let num_pixels = (size.x * size.y).ceil() as u64;
252253

253254
use re_viewer_context::test_context::HarnessExt as _;
254-
harness.snapshot_with_broken_pixels_threshold(
255+
success = harness.try_snapshot_with_broken_pixels_threshold(
255256
&name,
256257
num_pixels,
257258
broken_percent_threshold,
258259
);
259260
}
261+
assert!(success, "one or more snapshots failed");
260262
}
261263
}

0 commit comments

Comments
 (0)