Skip to content

Commit

Permalink
port tensor slice selection to archetype_eager
Browse files Browse the repository at this point in the history
  • Loading branch information
Wumpf committed Jan 15, 2025
1 parent f33a410 commit 10e0b21
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 164 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ namespace rerun.blueprint.archetypes;

/// Specifies a 2D slice of a tensor.
table TensorSliceSelection (
// TODO:?
//"attr.rust.archetype_eager",
"attr.rerun.scope": "blueprint",
"attr.rust.derive": "Default, Hash, PartialEq, Eq"
"attr.rust.archetype_eager",
"attr.rerun.scope": "blueprint"
) {
/// Which dimension to map to width.
///
Expand Down

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

77 changes: 45 additions & 32 deletions crates/viewer/re_view_tensor/src/dimension_mapping.rs
Original file line number Diff line number Diff line change
@@ -1,46 +1,59 @@
use egui::NumExt as _;

use re_types::{
blueprint::{archetypes::TensorSliceSelection, components::TensorDimensionIndexSlider},
blueprint::components::TensorDimensionIndexSlider,
components::{TensorDimensionIndexSelection, TensorHeightDimension, TensorWidthDimension},
datatypes::TensorDimensionSelection,
};
use re_viewport_blueprint::ViewProperty;

use crate::TensorDimension;

/// Loads slice selection from blueprint and makes modifications (without writing back) such that it is valid
/// for the given tensor shape.
/// Selection of a 2D slice of a tensor.
///
/// This is a best effort function and will insert fallbacks as needed.
/// Note that fallbacks are defined on the spot here and don't use the component fallback system.
/// We don't need the fallback system here since we're also not using generic ui either.
///
/// General rules for scrubbing the input data:
/// * out of bounds dimensions and indices are clamped to valid
/// * missing width/height is filled in if there's at least 2 dimensions.
pub fn load_tensor_slice_selection_and_make_valid(
slice_selection: &ViewProperty,
shape: &[TensorDimension],
) -> Result<TensorSliceSelection, re_types::DeserializationError> {
re_tracing::profile_function!();

let mut width = slice_selection.component_or_empty::<TensorWidthDimension>()?;
let mut height = slice_selection.component_or_empty::<TensorHeightDimension>()?;
let mut indices =
slice_selection.component_array_or_empty::<TensorDimensionIndexSelection>()?;
let mut slider = slice_selection.component_array::<TensorDimensionIndexSlider>()?;

make_width_height_valid(shape, &mut width, &mut height);
make_indices_valid(shape, &mut indices, width, height);
make_slider_valid(shape.len() as _, &mut slider, &indices, width, height);

Ok(TensorSliceSelection {
width,
height,
indices: Some(indices),
slider,
})
/// This is practically a deserialized & validated version of [`re_types::blueprint::archetypes::TensorSliceSelection`].
#[derive(Clone, Debug, Hash)]
pub struct TensorSliceSelection {
pub width: Option<TensorWidthDimension>,
pub height: Option<TensorHeightDimension>,
pub indices: Vec<TensorDimensionIndexSelection>,
pub slider: Option<Vec<TensorDimensionIndexSlider>>,
}

impl TensorSliceSelection {
/// Loads slice selection from blueprint and makes modifications (without writing back) such that it is valid
/// for the given tensor shape.
///
/// This is a best effort function and will insert fallbacks as needed.
/// Note that fallbacks are defined on the spot here and don't use the component fallback system.
/// We don't need the fallback system here since we're also not using generic ui either.
///
/// General rules for scrubbing the input data:
/// * out of bounds dimensions and indices are clamped to valid
/// * missing width/height is filled in if there's at least 2 dimensions.
pub fn load_and_make_valid(
slice_selection: &ViewProperty,
shape: &[TensorDimension],
) -> Result<Self, re_types::DeserializationError> {
re_tracing::profile_function!();

let mut width = slice_selection.component_or_empty::<TensorWidthDimension>()?;
let mut height = slice_selection.component_or_empty::<TensorHeightDimension>()?;
let mut indices =
slice_selection.component_array_or_empty::<TensorDimensionIndexSelection>()?;
let mut slider = slice_selection.component_array::<TensorDimensionIndexSlider>()?;

make_width_height_valid(shape, &mut width, &mut height);
make_indices_valid(shape, &mut indices, width, height);
make_slider_valid(shape.len() as _, &mut slider, &indices, width, height);

Ok(Self {
width,
height,
indices,
slider,
})
}
}

fn make_width_height_valid(
Expand Down
20 changes: 6 additions & 14 deletions crates/viewer/re_view_tensor/src/tensor_dimension_mapper.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
use re_types::{
blueprint::archetypes::TensorSliceSelection, datatypes::TensorDimensionIndexSelection,
};
use re_types::datatypes::TensorDimensionIndexSelection;
use re_ui::UiExt as _;
use re_viewer_context::ViewerContext;
use re_viewport_blueprint::ViewProperty;

use crate::TensorDimension;
use crate::{dimension_mapping::TensorSliceSelection, TensorDimension};

#[derive(Clone, Copy, PartialEq, Eq)]
enum DragDropAddress {
Expand Down Expand Up @@ -41,9 +39,7 @@ impl DragDropAddress {
index: shape[h.dimension as usize].size / 2, // Select middle if this becomes index fixed.
}),
#[allow(clippy::unwrap_used)]
Self::Selector(selector_idx) => {
Some(slice_selection.indices.as_ref().unwrap()[*selector_idx].0)
}
Self::Selector(selector_idx) => Some(slice_selection.indices[*selector_idx].0),
Self::NewSelector => None,
}
}
Expand Down Expand Up @@ -74,7 +70,7 @@ impl DragDropAddress {
slice_property.save_blueprint_component(ctx, &height);
}
Self::Selector(selector_idx) => {
let mut indices = slice_selection.indices.clone().unwrap_or_default();
let mut indices = slice_selection.indices.clone();
let mut slider = slice_selection.slider.clone().unwrap_or_default();
if let Some(new_selection) = new_selection {
indices[*selector_idx] = new_selection.into();
Expand All @@ -90,7 +86,7 @@ impl DragDropAddress {
Self::NewSelector => {
// NewSelector can only be a drop *target*, therefore dim_idx can't be None!
if let Some(new_selection) = new_selection {
let mut indices = slice_selection.indices.clone().unwrap_or_default();
let mut indices = slice_selection.indices.clone();
let mut slider = slice_selection.slider.clone().unwrap_or_default();
indices.push(new_selection.into());
slider.push(new_selection.dimension.into()); // Enable slider by default.
Expand Down Expand Up @@ -206,15 +202,11 @@ pub fn dimension_mapping_ui(
ui.vertical(|ui| {
ui.label("Selectors");

let Some(indices) = &slice_selection.indices else {
return;
};

// Use Grid instead of Vertical layout to match styling of the parallel Grid for
egui::Grid::new("selectiongrid")
.num_columns(2)
.show(ui, |ui| {
for (selector_idx, selector) in indices.iter().enumerate() {
for (selector_idx, selector) in slice_selection.indices.iter().enumerate() {
tensor_dimension_ui(
ui,
drag_context_id,
Expand Down
Loading

0 comments on commit 10e0b21

Please sign in to comment.