Skip to content

Commit 5e477b0

Browse files
authored
Merge pull request #29 from matplotlib/slice
Add SliceSelector
2 parents 43dd094 + c946a62 commit 5e477b0

File tree

6 files changed

+152
-1
lines changed

6 files changed

+152
-1
lines changed

examples/slice.py

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""
2+
1D slices
3+
=========
4+
"""
5+
import napari
6+
7+
viewer = napari.Viewer()
8+
viewer.open_sample("napari", "kidney")
9+
10+
viewer.window.add_plugin_dock_widget(
11+
plugin_name="napari-matplotlib", widget_name="1D slice"
12+
)
13+
14+
if __name__ == "__main__":
15+
napari.run()

src/napari_matplotlib/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66

77
from .histogram import * # NoQA
88
from .scatter import * # NoQA
9+
from .slice import * # NoQA

src/napari_matplotlib/base.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def setup_callbacks(self) -> None:
8383

8484
def update_layers(self, event: napari.utils.events.Event) -> None:
8585
"""
86-
Update the currently selected layers and re-draw.
86+
Update the layers attribute with currently selected layers and re-draw.
8787
"""
8888
self.layers = list(self.viewer.layers.selection)
8989
self._draw()

src/napari_matplotlib/napari.yaml

+7
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,16 @@ contributions:
1010
python_name: napari_matplotlib:ScatterWidget
1111
title: Make a scatter plot
1212

13+
- id: napari-matplotlib.slice
14+
python_name: napari_matplotlib:SliceWidget
15+
title: Plot a 1D slice
16+
1317
widgets:
1418
- command: napari-matplotlib.histogram
1519
display_name: Histogram
1620

1721
- command: napari-matplotlib.scatter
1822
display_name: Scatter
23+
24+
- command: napari-matplotlib.slice
25+
display_name: 1D slice

src/napari_matplotlib/slice.py

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
from typing import Dict, Tuple
2+
3+
import napari
4+
import numpy as np
5+
from qtpy.QtWidgets import QComboBox, QHBoxLayout, QLabel, QSpinBox
6+
7+
from napari_matplotlib.base import NapariMPLWidget
8+
9+
__all__ = ["SliceWidget"]
10+
11+
_dims_sel = ["x", "y"]
12+
_dims = ["x", "y", "z"]
13+
14+
15+
class SliceWidget(NapariMPLWidget):
16+
"""
17+
Plot a 1D slice along a given dimension.
18+
"""
19+
20+
n_layers_input = 1
21+
22+
def __init__(self, napari_viewer: napari.viewer.Viewer):
23+
# Setup figure/axes
24+
super().__init__(napari_viewer)
25+
self.axes = self.canvas.figure.subplots()
26+
27+
button_layout = QHBoxLayout()
28+
self.layout().addLayout(button_layout)
29+
30+
self.dim_selector = QComboBox()
31+
button_layout.addWidget(QLabel("Slice axis:"))
32+
button_layout.addWidget(self.dim_selector)
33+
self.dim_selector.addItems(_dims)
34+
35+
self.slice_selectors = {}
36+
for d in _dims_sel:
37+
self.slice_selectors[d] = QSpinBox()
38+
button_layout.addWidget(QLabel(f"{d}:"))
39+
button_layout.addWidget(self.slice_selectors[d])
40+
41+
# Setup callbacks
42+
# Re-draw when any of the combon/spin boxes are updated
43+
self.dim_selector.currentTextChanged.connect(self._draw)
44+
for d in _dims_sel:
45+
self.slice_selectors[d].textChanged.connect(self._draw)
46+
47+
self.update_layers(None)
48+
49+
@property
50+
def layer(self):
51+
return self.layers[0]
52+
53+
@property
54+
def current_dim(self) -> str:
55+
"""
56+
Currently selected slice dimension.
57+
"""
58+
return self.dim_selector.currentText()
59+
60+
@property
61+
def current_dim_index(self) -> int:
62+
"""
63+
Currently selected slice dimension index.
64+
"""
65+
# Note the reversed list because in napari the z-axis is the first
66+
# numpy axis
67+
return _dims[::-1].index(self.current_dim)
68+
69+
@property
70+
def selector_values(self) -> Dict[str, int]:
71+
return {d: self.slice_selectors[d].value() for d in _dims_sel}
72+
73+
def update_slice_selectors(self) -> None:
74+
"""
75+
Update range and enabled status of the slice selectors, and the value
76+
of the z slice selector.
77+
"""
78+
# Update min/max
79+
for i, dim in enumerate(_dims_sel):
80+
self.slice_selectors[dim].setRange(0, self.layer.data.shape[i])
81+
82+
def get_xy(self) -> Tuple[np.ndarray, np.ndarray]:
83+
"""
84+
Get data for plotting.
85+
"""
86+
x = np.arange(self.layer.data.shape[self.current_dim_index])
87+
88+
vals = self.selector_values
89+
vals.update({"z": self.current_z})
90+
91+
slices = []
92+
for d in _dims:
93+
if d == self.current_dim:
94+
# Select all data along this axis
95+
slices.append(slice(None))
96+
else:
97+
# Select specific index
98+
val = vals[d]
99+
slices.append(slice(val, val + 1))
100+
101+
# Reverse since z is the first axis in napari
102+
slices = slices[::-1]
103+
y = self.layer.data[tuple(slices)].ravel()
104+
105+
return x, y
106+
107+
def clear(self) -> None:
108+
self.axes.cla()
109+
110+
def draw(self) -> None:
111+
"""
112+
Clear axes and draw a 1D plot.
113+
"""
114+
x, y = self.get_xy()
115+
116+
self.axes.plot(x, y)
117+
self.axes.set_xlabel(self.current_dim)
118+
self.axes.set_title(self.layer.name)
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import numpy as np
2+
3+
from napari_matplotlib import SliceWidget
4+
5+
6+
def test_scatter(make_napari_viewer):
7+
# Smoke test adding a histogram widget
8+
viewer = make_napari_viewer()
9+
viewer.add_image(np.random.random((100, 100, 100)))
10+
SliceWidget(viewer)

0 commit comments

Comments
 (0)