Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions napari/_qt/_tests/test_qt_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,11 @@ def test_axes_labels(make_napari_viewer):
assert tuple(axes_visual.node.text.text) == ('2', '1', '0')


def test_axes_visual_corresponds_to_axes_labels():
"""If we create an axes overlay visual, the text should be corresponding to the axes_labels of the particular layer of
which the axes are visualized"""


@pytest.fixture()
def qt_viewer(qtbot):
qt_viewer = QtViewer(ViewerModel())
Expand Down
33 changes: 33 additions & 0 deletions napari/components/_tests/test_viewer_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -974,3 +974,36 @@ def test_slice_order_with_mixed_dims():
assert image_2d._slice.image.view.shape == (4, 5)
assert image_3d._slice.image.view.shape == (3, 5)
assert image_4d._slice.image.view.shape == (2, 5)


def test_viewer_add_layer_with_axes_labels():
"When we add a layer to the viewer model, the axis labels in the dims should be properly updated"
viewer = ViewerModel(ndisplay=2)
assert viewer.dims.axes_labels == ('-2', '-1')
with pytest.raises(ValueError):
viewer.add_image(np.zeros((4, 5)), axes_labels=('z', 'y', 'x'))
viewer.add_image(np.zeros((4, 5)), axes_labels=('x', 'y'))
assert viewer.dims.axes_labels == ('x', 'y')

# Ensure axes labels stay the same when image with same axes labels are added
viewer.add_image(np.zeros((4, 5)), axes_labels=('x', 'y'))
assert viewer.dims.axes_labels == ('x', 'y')

# Ensure axes labels are updated when layer with different axes labels than currently present are added.
viewer.add_image(np.zeros((4, 5, 5)), axes_labels=('a', 'b', 'c'))
assert viewer.dims.axes_labels == ('a', 'b', 'c', 'x', 'y')

assert viewer.dims.displayed == ('a', 'b', 'c')
assert viewer.dims.not_displayed == ('x', 'y')


def test_viewer_multiple_layer_axes_labels():
"""When adding multiple layers to the viewer with the same axes labels the axes labels of the dims model should stay
the same. When layers do not share axes labels, the axes labels in the dims model should be updated. The attribute
not_displayed of the dims model should also be properly updated.
"""


def test_viewer_annotation_layer_axes_labels():
"""When adding a new layer to annotate (for example with shapes layer) an image layer, the axes labels of the parent
layer should be inherited."""
2 changes: 2 additions & 0 deletions napari/layers/_scalar_field/scalar_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ def __init__(
custom_interpolation_kernel_2d=None,
depiction='volume',
experimental_clipping_planes=None,
layer_axis_labels=None,
metadata=None,
multiscale=None,
name=None,
Expand Down Expand Up @@ -219,6 +220,7 @@ def __init__(
shear=shear,
affine=affine,
opacity=opacity,
layer_axis_labels=layer_axis_labels,
blending=blending,
visible=visible,
multiscale=multiscale,
Expand Down
58 changes: 0 additions & 58 deletions napari/layers/_tests/test_utils.py

This file was deleted.

52 changes: 52 additions & 0 deletions napari/layers/base/_tests/test_named_axes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import numpy as np
import pytest

from napari.layers import Image


# These tests would be for testing the layers itself
def test_get_layer_axes_labels():
"For a given layer we should be able to retrieve axes labels."
shape = (10, 15)
np.random.seed(0)
data = np.random.random(shape)
layer = Image(data, axes_labels=("y", "x"))
assert layer.axes_labels == ("y", "x")


def test_set_layer_axes_labels():
"For a given layer we should be able to set the axes labels."
shape = (10, 15)
np.random.seed(0)
data = np.random.random(shape)
layer = Image(data, axes_label=("y", "x"))
with pytest.raises(ValueError):
layer.axes_labels = ("z", "y", "x")

layer.axes_labels = ("z", "t")
assert layer.axes_labels == ("z", "t")

# Note: should we give a warning if reversing axes labels for example y,x to x, y


def test_default_axes_labels():
"""If no axes labels are given, default names should be assigned equal to the number of dimensions of a particular
layer."""
# Note that for broadcasting the default names should be based on negative integers, so ..., -2, -1.
shape = (10, 15)
data = np.random.random(shape)
layer = Image(data)
assert layer.axes_labels == (-2, -1)


def test_ndim_match_length_axes_labels():
"""The number of dimensions must match the length of axes labels when creating a layer and should throw an error
if not the case"""
shape = (10, 15)
data = np.random.random(shape)
layer = Image(data, axes_labels=("y", "x"))
assert layer.dim == 2


def test_slice_using_axes_labels():
"We should be able to slice based on current axes labels and an interval."
41 changes: 41 additions & 0 deletions napari/layers/base/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import warnings
from abc import ABC, ABCMeta, abstractmethod
from collections import defaultdict
from collections.abc import Iterable
from contextlib import contextmanager
from functools import cached_property
from typing import (
Expand All @@ -19,6 +20,7 @@
Generator,
List,
Optional,
Sequence,
Tuple,
Type,
Union,
Expand Down Expand Up @@ -315,6 +317,7 @@ def __init__(
experimental_clipping_planes=None,
mode='pan_zoom',
projection_mode='none',
layer_axis_labels=None,
):
super().__init__()

Expand Down Expand Up @@ -357,6 +360,10 @@ def __init__(

self._ndim = ndim

self.layer_axis_labels = self._validate_coerce_axis_labels(
layer_axis_labels
)

self._slice_input = _SliceInput(
ndisplay=2,
world_slice=_ThickNDSlice.make_full(ndim=ndim),
Expand Down Expand Up @@ -548,6 +555,40 @@ def _mode_setter_helper(self, mode_in: Union[Mode, str]) -> StringEnum:

return mode

def _validate_coerce_axis_labels(
self, axis_labels: Sequence[str | int] | None
) -> tuple[str]:
"""Check proper input of axis labels and coerce it to a tuple of strings.

Paramters
---------
axis_labels : Sequence[str | int] | None
Axis labels of the layer

Returns
-------
tuple[str]
Validated axis labels coerced to a tuple of strings
"""
if axis_labels is None:
axis_labels = tuple(str(-i) for i in range(self._ndim, 0, -1))
elif (
isinstance(axis_labels, Iterable)
and len(axis_labels) == self._ndim
):
axis_labels = tuple(str(i) for i in axis_labels)

return axis_labels

@property
def axis_labels(self) -> tuple[str]:
"""The axis labels of the layer."""
return self._axis_labels

@axis_labels.setter
def axis_labels(self, axis_labels: Sequence[str | int] | None):
self._axis_labels = self._validate_coerce_axis_labels(axis_labels)

@property
def mode(self) -> str:
"""str: Interactive mode
Expand Down
2 changes: 2 additions & 0 deletions napari/layers/image/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ def __init__(
self,
data,
*,
layer_axis_labels=None,
affine=None,
attenuation=0.05,
blending='translucent',
Expand Down Expand Up @@ -279,6 +280,7 @@ def __init__(
opacity=opacity,
plane=plane,
projection_mode=projection_mode,
layer_axis_labels=layer_axis_labels,
rendering=rendering,
rotate=rotate,
scale=scale,
Expand Down
4 changes: 2 additions & 2 deletions napari/layers/labels/labels.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ def __init__(
self,
data,
*,
layer_axis_labels=None,
affine=None,
blending='translucent',
cache=True,
Expand Down Expand Up @@ -318,10 +319,9 @@ def __init__(
self._show_selected_label = False
self._contour = 0

data = self._ensure_int_labels(data)

super().__init__(
data,
layer_axis_labels=layer_axis_labels,
rendering=rendering,
depiction=depiction,
name=name,
Expand Down
2 changes: 2 additions & 0 deletions napari/layers/points/points.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ def __init__(
self,
data=None,
*,
layer_axis_labels=None,
ndim=None,
features=None,
feature_defaults=None,
Expand Down Expand Up @@ -450,6 +451,7 @@ def __init__(
super().__init__(
data,
ndim,
layer_axis_labels=layer_axis_labels,
name=name,
metadata=metadata,
scale=scale,
Expand Down
2 changes: 2 additions & 0 deletions napari/layers/shapes/shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,7 @@ class Shapes(Layer):

def __init__(
self,
layer_axis_labels=None,
data=None,
*,
ndim=None,
Expand Down Expand Up @@ -467,6 +468,7 @@ def __init__(

super().__init__(
data,
layer_axis_labels=layer_axis_labels,
ndim=ndim,
name=name,
metadata=metadata,
Expand Down
4 changes: 3 additions & 1 deletion napari/layers/surface/surface.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ class Surface(IntensityVisualizationMixin, Layer):
def __init__(
self,
data,
layer_axis_labels=None,
*,
features=None,
feature_defaults=None,
Expand Down Expand Up @@ -219,7 +220,8 @@ def __init__(

super().__init__(
data,
ndim,
layer_axis_labels=layer_axis_labels,
ndim=ndim,
name=name,
metadata=metadata,
scale=scale,
Expand Down
2 changes: 2 additions & 0 deletions napari/layers/tracks/tracks.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ class Tracks(Layer):
def __init__(
self,
data,
layer_axis_labels=None,
*,
features=None,
properties=None,
Expand Down Expand Up @@ -131,6 +132,7 @@ def __init__(
super().__init__(
data,
ndim,
layer_axis_labels=layer_axis_labels,
name=name,
metadata=metadata,
scale=scale,
Expand Down