Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 3a8261a

Browse filesBrowse files
authored
Merge pull request #208 from dstansby/fix-slicing
Improve slicing experience
2 parents d7e88a9 + 9eb43ee commit 3a8261a
Copy full SHA for 3a8261a

File tree

3 files changed

+80
-39
lines changed
Filter options

3 files changed

+80
-39
lines changed

‎docs/changelog.rst

Copy file name to clipboardExpand all lines: docs/changelog.rst
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,17 @@ Changelog
22
=========
33
1.0.3
44
-----
5+
Changes
6+
~~~~~~~
7+
- The slice widget is now limited to slicing along the x/y dimensions. Support
8+
for slicing along z has been removed for now to make the code simpler.
9+
- The slice widget now uses a slider to select the slice value.
10+
511
Bug fixes
612
~~~~~~~~~
713
- Fixed creating 1D slices of 2D images.
14+
- Removed the limitation that only the first 99 indices could be sliced using
15+
the slice widget.
816

917
1.0.2
1018
-----

‎src/napari_matplotlib/slice.py

Copy file name to clipboardExpand all lines: src/napari_matplotlib/slice.py
+48-39Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
1-
from typing import Any, Dict, List, Optional, Tuple
1+
from typing import Any, List, Optional, Tuple
22

33
import matplotlib.ticker as mticker
44
import napari
55
import numpy as np
66
import numpy.typing as npt
7-
from qtpy.QtWidgets import QComboBox, QHBoxLayout, QLabel, QSpinBox, QWidget
7+
from qtpy.QtCore import Qt
8+
from qtpy.QtWidgets import (
9+
QComboBox,
10+
QLabel,
11+
QSlider,
12+
QVBoxLayout,
13+
QWidget,
14+
)
815

916
from .base import SingleAxesWidget
1017
from .util import Interval
1118

1219
__all__ = ["SliceWidget"]
1320

14-
_dims_sel = ["x", "y"]
15-
1621

1722
class SliceWidget(SingleAxesWidget):
1823
"""
@@ -30,28 +35,46 @@ def __init__(
3035
# Setup figure/axes
3136
super().__init__(napari_viewer, parent=parent)
3237

33-
button_layout = QHBoxLayout()
34-
self.layout().addLayout(button_layout)
35-
3638
self.dim_selector = QComboBox()
39+
self.dim_selector.addItems(["x", "y"])
40+
41+
self.slice_selector = QSlider(orientation=Qt.Orientation.Horizontal)
42+
43+
# Create widget layout
44+
button_layout = QVBoxLayout()
3745
button_layout.addWidget(QLabel("Slice axis:"))
3846
button_layout.addWidget(self.dim_selector)
39-
self.dim_selector.addItems(["x", "y", "z"])
40-
41-
self.slice_selectors = {}
42-
for d in _dims_sel:
43-
self.slice_selectors[d] = QSpinBox()
44-
button_layout.addWidget(QLabel(f"{d}:"))
45-
button_layout.addWidget(self.slice_selectors[d])
47+
button_layout.addWidget(self.slice_selector)
48+
self.layout().addLayout(button_layout)
4649

4750
# Setup callbacks
48-
# Re-draw when any of the combon/spin boxes are updated
51+
# Re-draw when any of the combo/slider is updated
4952
self.dim_selector.currentTextChanged.connect(self._draw)
50-
for d in _dims_sel:
51-
self.slice_selectors[d].textChanged.connect(self._draw)
53+
self.slice_selector.valueChanged.connect(self._draw)
5254

5355
self._update_layers(None)
5456

57+
def on_update_layers(self) -> None:
58+
"""
59+
Called when layer selection is updated.
60+
"""
61+
if not len(self.layers):
62+
return
63+
if self.current_dim_name == "x":
64+
max = self._layer.data.shape[-2]
65+
elif self.current_dim_name == "y":
66+
max = self._layer.data.shape[-1]
67+
else:
68+
raise RuntimeError("dim name must be x or y")
69+
self.slice_selector.setRange(0, max - 1)
70+
71+
@property
72+
def _slice_width(self) -> int:
73+
"""
74+
Width of the slice being plotted.
75+
"""
76+
return self._layer.data.shape[self.current_dim_index]
77+
5578
@property
5679
def _layer(self) -> napari.layers.Layer:
5780
"""
@@ -73,7 +96,7 @@ def current_dim_index(self) -> int:
7396
"""
7497
# Note the reversed list because in napari the z-axis is the first
7598
# numpy axis
76-
return self._dim_names[::-1].index(self.current_dim_name)
99+
return self._dim_names.index(self.current_dim_name)
77100

78101
@property
79102
def _dim_names(self) -> List[str]:
@@ -82,45 +105,31 @@ def _dim_names(self) -> List[str]:
82105
dimensionality of the currently selected data.
83106
"""
84107
if self._layer.data.ndim == 2:
85-
return ["x", "y"]
108+
return ["y", "x"]
86109
elif self._layer.data.ndim == 3:
87-
return ["x", "y", "z"]
110+
return ["z", "y", "x"]
88111
else:
89112
raise RuntimeError("Don't know how to handle ndim != 2 or 3")
90113

91-
@property
92-
def _selector_values(self) -> Dict[str, int]:
93-
"""
94-
Values of the slice selectors.
95-
96-
Mapping from dimension name to value.
97-
"""
98-
return {d: self.slice_selectors[d].value() for d in _dims_sel}
99-
100114
def _get_xy(self) -> Tuple[npt.NDArray[Any], npt.NDArray[Any]]:
101115
"""
102116
Get data for plotting.
103117
"""
104-
dim_index = self.current_dim_index
105-
if self._layer.data.ndim == 2:
106-
dim_index -= 1
107-
x = np.arange(self._layer.data.shape[dim_index])
108-
109-
vals = self._selector_values
110-
vals.update({"z": self.current_z})
118+
val = self.slice_selector.value()
111119

112120
slices = []
113121
for dim_name in self._dim_names:
114122
if dim_name == self.current_dim_name:
115123
# Select all data along this axis
116124
slices.append(slice(None))
125+
elif dim_name == "z":
126+
# Only select the currently viewed z-index
127+
slices.append(slice(self.current_z, self.current_z + 1))
117128
else:
118129
# Select specific index
119-
val = vals[dim_name]
120130
slices.append(slice(val, val + 1))
121131

122-
# Reverse since z is the first axis in napari
123-
slices = slices[::-1]
132+
x = np.arange(self._slice_width)
124133
y = self._layer.data[tuple(slices)].ravel()
125134

126135
return x, y

‎src/napari_matplotlib/tests/test_slice.py

Copy file name to clipboardExpand all lines: src/napari_matplotlib/tests/test_slice.py
+24Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,27 @@ def test_slice_2D(make_napari_viewer, astronaut_data):
3737
# Need to return a copy, as original figure is too eagerley garbage
3838
# collected by the widget
3939
return deepcopy(fig)
40+
41+
42+
def test_slice_axes(make_napari_viewer, astronaut_data):
43+
viewer = make_napari_viewer()
44+
viewer.theme = "light"
45+
46+
# Take first RGB channel
47+
data = astronaut_data[0][:256, :, 0]
48+
# Shape:
49+
# x: 0 > 512
50+
# y: 0 > 256
51+
assert data.ndim == 2, data.shape
52+
# Make sure data isn't square for later tests
53+
assert data.shape[0] != data.shape[1]
54+
viewer.add_image(data)
55+
56+
widget = SliceWidget(viewer)
57+
assert widget._dim_names == ["y", "x"]
58+
assert widget.current_dim_name == "x"
59+
assert widget.slice_selector.value() == 0
60+
assert widget.slice_selector.minimum() == 0
61+
assert widget.slice_selector.maximum() == data.shape[0] - 1
62+
# x/y are flipped in napari
63+
assert widget._slice_width == data.shape[1]

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.