# Dims object in napari
There is one global `Dims` object that lives on the `ViewerModel` and is accessable as `viewer.dims`. The properties of this `Dims` object get computed upon addition or modification of layers or when certain actions in the GUI are triggered (e.g. change between 2D/3D views, a change in the displayed axes or a change in the dimension slider positions). This `Dims` object is tightly coupled to GUI components like the `QtDims` widget which contains a `QtDimsSliderWidget` for each non-displayed axis. A couple of the optional VisPy overlays like the axes overlay also access `Dims` attributes.
The only place where `Layer` attributes contribute to setting `Dims` object parameters is during determination of `Dims.range` within `ViewerModel._on_layer_change`. This determination is done via `LayerList._ranges` which will determine the overall range of the data in world coordinates across all layers.
## Dims events
The following events exist on the Dims object
- `dims.events.ndim`
- `dims.events.ndisplay`
- `dims.events.order`
- `dims.events.range`
- `dims.events.current_step`
- `dims.events.axis_labels`
- `dims.events.last_used`
`Dims.ndim` describes the maximal number of data dimensions across all layers while `Dims.ndisplay` is the number of displayed dimensions currently being displayed (2 or 3). `Dims.order` is the current order of the dimensions (the last 2 or 3 are displayed). `Dims.range` is a list of (min, max, step) tuples in world coordinates for each dimension. `Dims.current_step` is the current slider position on each dimension. `Dims.axis_labels` is a tuple of strings containing the label for each axis (used in the slider widgets of the GUI). Finally `Dims.last_used` records which axis was last interacted with (in the GUI, the slider widget for the most recently used axis is highlighted relative to the rest).
Currently singleton dimensions are NOT inserted at the beginning of any layers with dimensions less than `Dims.ndim`. When there are a mixed number of dimensions across layers, it is assumed that layers line up according to the last dimension.
## Dims event connections
A more detailed graph with the various `Dims` events in red is shown below. Methods/events that would be triggered by user interaction with the GUI are colored in blue. This can give some idea of what leads to firing of these `Dims` events as well as what classes listen to these events.
```graphviz
strict digraph "" {
graph [edges="{'arrowsize': '4.0', 'penwidth': '2.0'}",
rankdir=LR
];
node [penwidth=2.0,
shape=rectangle
];
{
graph [rank=same];
"Dims.events.ndim" [color=red];
"Dims.events.current_step" [color=red];
"Dims.events.range" [color=red];
"Dims.events.ndisplay" [color=red];
"Dims.events.order" [color=red];
"Dims.events.last_used" [color=red];
"Dims.events.axis_labels" [color=red];
}
{
graph [rank=same];
"QtDims._update_nsliders" [color=black];
"QtDims._update_slider" [color=black];
"QtDims._update_range" [color=black];
"QtDims._update_display" [color=black];
"QtDims._on_last_used_changed" [color=black];
}
{
graph [rank=same];
"VispyAxesOverlay._on_data_change" [color=black];
}
{
graph [rank=same];
"InteractionBoxMouseBindings._on_dim_change" [color=black];
"InteractionBoxMouseBindings._on_ndisplay_change" [color=black];
}
{
graph [rank=same];
"ViewerModel._update_layers" [color=black];
"ViewerModel.reset_view" [color=black];
}
"Dims.events.ndim" -> "QtDims._update_nsliders" [color=red];
"Dims.events.current_step" -> "QtDims._update_slider" [color=red];
"AnimationWorker._on_axis_changed" [color=black];
"Dims.events.current_step" -> "AnimationWorker._on_axis_changed" [color=red];
"Dims.events.current_step" -> "ViewerModel._update_layers" [color=red];
"Dims.events.range" -> "QtDims._update_range" [color=red];
"Dims.events.range" -> "VispyAxesOverlay._on_data_change" [color=red];
"Dims.events.ndisplay" -> "QtDims._update_display" [color=red];
"QtViewerButtons._set_ndisplay_mode_checkstate" [color=black];
"Dims.events.ndisplay" -> "QtViewerButtons._set_ndisplay_mode_checkstate" [color=red];
"VispyCamera._on_ndisplay_change" [color=black];
"Dims.events.ndisplay" -> "VispyCamera._on_ndisplay_change" [color=red];
"Dims.events.ndisplay" -> "VispyAxesOverlay._on_data_change" [color=red];
"Dims.events.ndisplay" -> "InteractionBoxMouseBindings._on_ndisplay_change" [color=red];
"Dims.events.ndisplay" -> "ViewerModel._update_layers" [color=red];
"Dims.events.ndisplay" -> "ViewerModel.reset_view" [color=red];
"Dims.events.order" -> "QtDims._update_display" [color=red];
"QtDimsSorter.move_indices" [color=black];
"Dims.events.order" -> "QtDimsSorter.move_indices" [color=red];
"Dims.events.order" -> "VispyAxesOverlay._on_data_change" [color=red];
"Dims.events.order" -> "InteractionBoxMouseBindings._on_dim_change" [color=red];
"Dims.events.order" -> "ViewerModel._update_layers" [color=red];
"Dims.events.order" -> "ViewerModel.reset_view" [color=red];
"Dims.events.last_used" -> "QtDims._on_last_used_changed" [color=red];
"QtDimSliderWidget._pull_label" [color=black];
"Dims.events.axis_labels" -> "QtDimSliderWidget._pull_label" [color=red];
"Dims.events.axis_labels" -> "VispyAxesOverlay._on_data_change" [color=red];
"QtDims._update_display" -> "Dims.events.last_used" [color=black];
"ViewerModel._update_layers" -> "Dims.events.ndim" [color=black];
"ViewerModel._on_layers_change" [color=black];
"Dims.reset" [color=black];
"ViewerModel._on_layers_change" -> "Dims.reset" [color=black];
"Dims.set_range" [color=black];
"ViewerModel._on_layers_change" -> "Dims.set_range" [color=black];
"Viewer toggle_ndisplay" [color=blue];
"Viewer toggle_ndisplay" -> "Dims.events.ndisplay" [color=black];
"Dims._roll" [color=black];
"Dims._roll" -> "Dims.events.order" [color=black];
"Dims._transpose" [color=black];
"Dims._transpose" -> "Dims.events.order" [color=black];
"Dims.reset" -> "Dims.events.current_step" [color=black];
"Dims.reset" -> "Dims.events.range" [color=black];
"Dims.reset" -> "Dims.events.order" [color=black];
"QtDimsSorter.axes_list.events.reordered" [color=blue];
"QtDimsSorter.axes_list.events.reordered" -> "Dims.events.order" [color=black];
"Dims.set_range" -> "Dims.events.range" [color=black];
"Dims.set_current_step" [color=black];
"Dims.set_current_step" -> "Dims.events.current_step" [color=black];
"Dims._increment_dims_left" [color=black];
"Dims._increment_dims_left" -> "Dims.events.current_step" [color=black];
"Dims._increment_dims_right" [color=black];
"Dims._increment_dims_right" -> "Dims.events.current_step" [color=black];
"Viewer increment_dims_left" [color=blue];
"Viewer increment_dims_left" -> "Dims._increment_dims_left" [color=black];
"Viewer increment_dims_right" [color=blue];
"Viewer increment_dims_right" -> "Dims._increment_dims_right" [color=black];
"Dims.set_axis_label" [color=black];
"Dims.set_axis_label" -> "Dims.events.last_used" [color=black];
"Dims.set_axis_label" -> "Dims.events.axis_labels" [color=black];
"QtDims._remove_slider_widget" [color=black];
"QtDims._remove_slider_widget" -> "Dims.events.last_used" [color=black];
"QtDimsSlider.slider.sliderPressed" [color=blue];
"QtDimsSlider.slider.sliderPressed" -> "Dims.events.last_used" [color=black];
"Dims.set_point" [color=black];
"Dims.set_point" -> "Dims.set_current_step" [color=black];
"QtDimSliderWidget._set_slice_from_label" [color=blue];
"QtDimSliderWidget._set_slice_from_label" -> "Dims.set_current_step" [color=black];
"QtDimSliderWidget._value_changed" [color=blue];
"QtDimSliderWidget._value_changed" -> "Dims.set_current_step" [color=black];
"QtDims._set_frame" -> "Dims.set_current_step" [color=black];
"AnimationWorker.frame_requested" [color=blue];
"AnimationWorker.frame_requested" -> "QtDims._set_frame" [color=black];
"QtDimSliderWidget._update_label" [color=blue];
"QtDimSliderWidget._update_label" -> "Dims.set_axis_label" [color=black];
"ViewerModel._on_add_layer" [color=blue];
"ViewerModel._on_add_layer" -> "ViewerModel._on_layers_change" [color=black];
"ViewerModel._on_add_layer" -> "Dims.set_point" [color=black];
"Viewer roll_axes" [color=blue];
"Viewer roll_axes" -> "Dims._roll" [color=black];
"Viewer transpose_axes" [color=blue];
"Viewer transpose_axes" -> "Dims._transpose" [color=black];
"ViewerModel._on_remove_layer" [color=blue];
"ViewerModel._on_remove_layer" -> "ViewerModel._on_layers_change" [color=black];
"LayerList.events.reordered" [color=blue];
"LayerList.events.reordered" -> "ViewerModel._on_layers_change" [color=black];
"Layer.events.data" [color=black];
"Layer.events.data" -> "ViewerModel._on_layers_change" [color=black];
"Layer.events.affine" [color=black];
"Layer.events.affine" -> "ViewerModel._on_layers_change" [color=black];
"Layer.events.rotate" [color=black];
"Layer.events.rotate" -> "ViewerModel._on_layers_change" [color=black];
"Layer.events.scale" [color=black];
"Layer.events.scale" -> "ViewerModel._on_layers_change" [color=black];
"Layer.events.shear" [color=black];
"Layer.events.shear" -> "ViewerModel._on_layers_change" [color=black];
"Layer.events.translate" [color=black];
"Layer.events.translate" -> "ViewerModel._on_layers_change" [color=black];
}
```
To summarize the features of the graph above:
Adding or removing a layer, reordering the layers or changing the transform properties (affine, shear, scale, translate, rotate) of any layer will result in a call to `ViewerModel._on_layers_change`, ultimately leading to multiple Dims properties being recomputed (`Dims.current_step`, `Dims.ndim`, `Dims.range`, `Dims.order`)
Hitting the button to toggle between a 2D/3D view will trigger `Dims.events.ndisplay`.
Hitting the buttons to roll the axes or transpose the axes will trigger `Dims.events.order.`
Changing the slider position either directly or via playing an animation will result in triggering `Dims.events.current_step`.
`Dims.event.ndim` is only triggered indirectly through `ViewerModel._update_layers`.
`Dims.events.axis_labels` gets triggered when the axis label is updated in the `QtDimsSliderWidget`
`Dims.events.last_used` is triggered when an axis label is set, a slider in the `QtDimsSliderWidget` is pressed or a slider is removed from the `QtDims` widget.
# Dims-related layer properties
Some `Layer` properties closely mirror those of the dims object and get updated when the `Dims` model emits certain events. Specifically `dims.events.ndisplay`, `dims.events.order` and `dims.events.current_step` will trigger a call to `viewer._update_layers` which in turn will loop over all layers, calling `Layer._slice_dims` on each. This will lead to updating of corresponding private attributes of the layer: `_dims_order`, `_ndim`, `_dims_point` and `_ndisplay`. Other private properties of the layer get calculated dynamically based on these:
```python
@property
def _dims_displayed(self):
return self._dims_order[-self._ndisplay :]
@property
def _dims_not_displayed(self):
return self._dims_order[: -self._ndisplay]
@property
def _dims_displayed_order(self):
displayed = self._dims_displayed
# equivalent to: order = np.argsort(displayed)
order = sorted(range(len(displayed)), key=lambda x: displayed[x])
return tuple(order)
@property
def _displayed_axes(self):
# assignment upfront to avoid repeated computation of properties
_dims_displayed = self._dims_displayed
_dims_displayed_order = self._dims_displayed_order
displayed_axes = [_dims_displayed[i] for i in _dims_displayed_order]
return displayed_axes
@property
def _corner_pixels_displayed(self):
displayed_axes = self._displayed_axes
corner_pixels_displayed = self.corner_pixels[:, displayed_axes]
return corner_pixels_displayed
# Note: self.corner_pixels gets set in Layer._update_draw
```
## Relation between Layer.extent and Dims
Each layer in napari has its own cached `extent` property that holds the data extent in both data coordinates and world coordinates. These get computed directly from the data and transforms.
The `LayerList` (accessible as `viewer.layers`) also has an `extent` property. `LayerList.extent` dynamically calculates the overall extent in world coordinates and the step size by looping over all layers. It is called by `ViewerModel._sliced_extent_world` which is itself called when the view is reset (`ViewerModel.reset_view`) or the 2D image grid changes (`ViewerModel._on_grid_change`). `LayerList.extent` also gets used whenever a new `Labels`, `Shapes` or `Points` label is added via the corresponding GUI button.
The `LayerList._ranges` attribute is closely related to `LayerList.extent` but also takes into account the physical point sizes or pixel extents when computing the ranges. The viewer model will set `Dims.range` based on `LayerList._ranges` whenever there is a call to `ViewerModel._on_layer_change` which happens whenever a layer is added or removed or the layer transforms change.
### Events triggering update of dims-related Layer attributes
The `Dims`-related private attributes of a layer get updated whenever `Layer._update_dims` or `Layer._slice_dims` is called. The paths by which `Layer._update_dims` gets called are illustrated in the graph below:
```graphviz
strict digraph "" {
graph [edges="{'arrowsize': '4.0', 'penwidth': '2.0'}",
rankdir=LR
];
node [penwidth=2.0,
shape=rectangle
];
"Layer._dims_order" [color=brown];
"Layer._dims_point" [color=brown];
"Layer._ndim" [color=brown];
"Layer._ndisplay" [color=brown];
"Dims.events.ndisplay" [color=red];
"Dims.events.ndisplay" -> "ViewerModel._update_layers" [color=black];
"Dims.events.order" [color=red];
"Dims.events.order" -> "ViewerModel._update_layers" [color=black];
"Dims.events.current_step" [color=red];
"Dims.events.current_step" -> "ViewerModel._update_layers" [color=black];
"Layer._slice_dims" -> "Layer._dims_order" [color=black];
"Layer._slice_dims" -> "Layer._dims_point" [color=black];
"Layer._slice_dims" -> "Layer._ndisplay" [color=black];
"Layer._slice_dims" -> "Layer._update_dims" [color=black];
"Layer._update_dims" -> "Layer._dims_order" [color=black];
"Layer._update_dims" -> "Layer._dims_point" [color=black];
"Layer._update_dims" -> "Layer._ndim" [color=black];
"Layer.__init__" -> "Layer._dims_order" [color=black];
"Layer.__init__" -> "Layer._dims_point" [color=black];
"Layer.__init__" -> "Layer._ndim" [color=black];
"Layer.__init__" -> "Layer._ndisplay" [color=black];
"ViewerModel._update_layers" -> "Layer._slice_dims" [color=black];
"ViewerModel._on_add_layer" -> "ViewerModel._update_layers" [color=black];
"Layer transform properties
affine
rotate
scale
shear
translate" -> "Layer._update_dims" [color=black];
"<Layer Subclass>.__init__" -> "Layer._update_dims" [color=black];
"<Layer Subclass>.data (setter)" -> "Layer._update_dims" [color=black];
"Shapes.shape_type (setter)" -> "Layer._update_dims" [color=black];
"Shapes._add_shapes" -> "Layer._update_dims" [color=black];
"Shapes._finish_drawing" -> "Layer._update_dims" [color=black];
"Surface.vertices (setter)" -> "Layer._update_dims" [color=black];
}
```