|
| 1 | +from typing import Dict |
| 2 | + |
1 | 3 | import napari
|
| 4 | +import numpy as np |
2 | 5 | from qtpy.QtWidgets import QComboBox, QHBoxLayout, QSpinBox
|
3 | 6 |
|
4 |
| -from napari_matplotlib.base import NapariMPLWidget |
| 7 | +from napari_matplotlib.base import SingleLayerWidget |
5 | 8 |
|
6 | 9 | __all__ = ["SliceWidget"]
|
7 | 10 |
|
8 | 11 | _dims = ["x", "y", "z"]
|
9 | 12 |
|
10 | 13 |
|
11 |
| -class SliceWidget(NapariMPLWidget): |
| 14 | +class SliceWidget(SingleLayerWidget): |
| 15 | + """ |
| 16 | + Plot a 1D slice along a given dimension. |
| 17 | + """ |
| 18 | + |
12 | 19 | def __init__(self, napari_viewer: napari.viewer.Viewer):
|
13 | 20 | super().__init__(napari_viewer)
|
14 | 21 |
|
15 |
| - self.layer = self.viewer.layers[-1] |
16 |
| - |
17 | 22 | button_layout = QHBoxLayout()
|
18 | 23 | self.layout().addLayout(button_layout)
|
19 | 24 |
|
20 | 25 | self.dim_selector = QComboBox()
|
21 | 26 | button_layout.addWidget(self.dim_selector)
|
| 27 | + self.dim_selector.addItems(_dims) |
22 | 28 |
|
23 |
| - self.selectors = {} |
| 29 | + self.slice_selectors = {} |
24 | 30 | for d in _dims:
|
25 |
| - self.selectors[d] = QSpinBox() |
26 |
| - button_layout.addWidget(self.selectors[d]) |
| 31 | + self.slice_selectors[d] = QSpinBox() |
| 32 | + button_layout.addWidget(self.slice_selectors[d]) |
| 33 | + |
| 34 | + self.update_slice_selectors() |
| 35 | + self.viewer.dims.events.current_step.connect(self.draw) |
| 36 | + |
| 37 | + self.draw() |
| 38 | + |
| 39 | + @property |
| 40 | + def current_dim(self) -> str: |
| 41 | + """ |
| 42 | + Currently selected slice dimension. |
| 43 | + """ |
| 44 | + return self.dim_selector.currentText() |
| 45 | + |
| 46 | + @property |
| 47 | + def current_dim_index(self) -> int: |
| 48 | + """ |
| 49 | + Currently selected slice dimension index. |
| 50 | + """ |
| 51 | + # Note the reversed list because in napari the z-axis is the first |
| 52 | + # numpy axis |
| 53 | + return _dims[::-1].index(self.current_dim) |
27 | 54 |
|
28 |
| - self.update_dim_selector() |
29 |
| - self.viewer.layers.selection.events.changed.connect( |
30 |
| - self.update_dim_selector |
31 |
| - ) |
| 55 | + @property |
| 56 | + def selector_values(self) -> Dict[str, int]: |
| 57 | + return {d: self.slice_selectors[d].value() for d in _dims} |
32 | 58 |
|
33 |
| - def update_dim_selector(self) -> None: |
| 59 | + def update_slice_selectors(self) -> None: |
34 | 60 | """
|
35 |
| - Update options in the dimension selector from currently selected layer. |
| 61 | + Update range and enabled status of the slice selectors, and the value |
| 62 | + of the z slice selector. |
36 | 63 | """
|
37 |
| - dims = ["x", "y", "z"] |
38 |
| - self.dim_selector.clear() |
39 |
| - self.dim_selector.addItems(dims[0 : self.layer.data.ndim]) |
| 64 | + # Update min/max |
| 65 | + for i, dim in enumerate(_dims): |
| 66 | + self.slice_selectors[dim].setRange(0, self.layer.data.shape[i]) |
| 67 | + |
| 68 | + # The z dimension is always set by current z in the viewer |
| 69 | + self.slice_selectors["z"].setValue(self.current_z) |
| 70 | + self.slice_selectors[self.current_dim].setEnabled(False) |
| 71 | + |
| 72 | + def draw(self) -> None: |
| 73 | + self.update_slice_selectors() |
| 74 | + self.axes.clear() |
| 75 | + x = np.arange(self.layer.data.shape[self.current_dim_index]) |
| 76 | + |
| 77 | + slices = [] |
| 78 | + for d in _dims: |
| 79 | + if d == self.current_dim: |
| 80 | + # Select all data along this axis |
| 81 | + slices.append(slice(None)) |
| 82 | + else: |
| 83 | + # Select specific index |
| 84 | + val = self.selector_values[d] |
| 85 | + slices.append(slice(val, val + 1)) |
| 86 | + |
| 87 | + slices = slices[::-1] |
| 88 | + y = self.layer.data[tuple(slices)].ravel() |
| 89 | + |
| 90 | + self.axes.plot(x, y) |
| 91 | + self.axes.set_xlabel(self.current_dim) |
| 92 | + self.axes.set_title(self.layer.name) |
| 93 | + |
| 94 | + self.canvas.draw() |
0 commit comments