4
4
import numpy as np
5
5
import numpy .typing as npt
6
6
from matplotlib .container import BarContainer
7
- from qtpy .QtWidgets import (
8
- QComboBox ,
9
- QLabel ,
10
- QVBoxLayout ,
11
- QWidget ,
12
- )
7
+ from qtpy .QtWidgets import QComboBox , QLabel , QVBoxLayout , QWidget , QGroupBox , QFormLayout , QDoubleSpinBox , QSpinBox , QAbstractSpinBox
13
8
14
9
from .base import SingleAxesWidget
15
10
from .features import FEATURES_LAYER_TYPES
@@ -34,26 +29,138 @@ def __init__(
34
29
parent : Optional [QWidget ] = None ,
35
30
):
36
31
super ().__init__ (napari_viewer , parent = parent )
32
+
33
+ # Create widgets for setting bin parameters
34
+ bins_start = QDoubleSpinBox ()
35
+ bins_start .setObjectName ("bins start" )
36
+ bins_start .setStepType (QAbstractSpinBox .AdaptiveDecimalStepType )
37
+ bins_start .setRange (- 1e10 , 1e10 )
38
+ bins_start .setValue (0 )
39
+ bins_start .setWrapping (True )
40
+ bins_start .setKeyboardTracking (False )
41
+ bins_start .setDecimals (2 )
42
+
43
+ bins_stop = QDoubleSpinBox ()
44
+ bins_stop .setObjectName ("bins stop" )
45
+ bins_stop .setStepType (QAbstractSpinBox .AdaptiveDecimalStepType )
46
+ bins_stop .setRange (- 1e10 , 1e10 )
47
+ bins_stop .setValue (100 )
48
+ bins_stop .setKeyboardTracking (False )
49
+ bins_stop .setDecimals (2 )
50
+
51
+ bins_num = QSpinBox ()
52
+ bins_num .setObjectName ("bins num" )
53
+ bins_num .setRange (1 , 100_000 )
54
+ bins_num .setValue (101 )
55
+ bins_num .setWrapping (False )
56
+ bins_num .setKeyboardTracking (False )
57
+
58
+ # Set bins widget layout
59
+ bins_selection_layout = QFormLayout ()
60
+ bins_selection_layout .addRow ("start" , bins_start )
61
+ bins_selection_layout .addRow ("stop" , bins_stop )
62
+ bins_selection_layout .addRow ("num" , bins_num )
63
+
64
+ # Group the widgets and add to main layout
65
+ bins_widget_group = QGroupBox ("Bins" )
66
+ bins_widget_group_layout = QVBoxLayout ()
67
+ bins_widget_group_layout .addLayout (bins_selection_layout )
68
+ bins_widget_group .setLayout (bins_widget_group_layout )
69
+ self .layout ().addWidget (bins_widget_group )
70
+
71
+ # Add callbacks
72
+ bins_start .valueChanged .connect (self ._draw )
73
+ bins_stop .valueChanged .connect (self ._draw )
74
+ bins_num .valueChanged .connect (self ._draw )
75
+
37
76
self ._update_layers (None )
38
77
39
- def draw (self ) -> None :
40
- """
41
- Clear the axes and histogram the currently selected layer/slice.
42
- """
43
- layer = self .layers [0 ]
78
+ @property
79
+ def bins_start (self ) -> float :
80
+ """Minimum bin edge"""
81
+ return self .findChild (QDoubleSpinBox , name = "bins start" ).value ()
82
+
83
+ @bins_start .setter
84
+ def bins_start (self , start : int | float ) -> None :
85
+ """Set the minimum bin edge"""
86
+ self .findChild (QDoubleSpinBox , name = "bins start" ).setValue (start )
87
+
88
+ @property
89
+ def bins_stop (self ) -> float :
90
+ """Maximum bin edge"""
91
+ return self .findChild (QDoubleSpinBox , name = "bins stop" ).value ()
92
+
93
+ @bins_stop .setter
94
+ def bins_stop (self , stop : int | float ) -> None :
95
+ """Set the maximum bin edge"""
96
+ self .findChild (QDoubleSpinBox , name = "bins stop" ).setValue (stop )
97
+
98
+ @property
99
+ def bins_num (self ) -> int :
100
+ """Number of bins to use"""
101
+ return self .findChild (QSpinBox , name = "bins num" ).value ()
102
+
103
+ @bins_num .setter
104
+ def bins_num (self , num : int ) -> None :
105
+ """Set the number of bins to use"""
106
+ self .findChild (QSpinBox , name = "bins num" ).setValue (num )
107
+
108
+ def autoset_widget_bins (self , data : npt .ArrayLike ) -> None :
109
+ """Update widgets with bins determined from the image data"""
110
+
111
+ bins = np .linspace (np .min (data ), np .max (data ), 100 , dtype = data .dtype )
112
+ self .bins_start = bins [0 ]
113
+ self .bins_stop = bins [- 1 ]
114
+ self .bins_num = bins .size
115
+
116
+
117
+ def _get_layer_data (self , layer ) -> np .ndarray :
118
+ """Get the data associated with a given layer"""
44
119
45
120
if layer .data .ndim - layer .rgb == 3 :
46
121
# 3D data, can be single channel or RGB
47
122
data = layer .data [self .current_z ]
48
123
self .axes .set_title (f"z={ self .current_z } " )
49
124
else :
50
125
data = layer .data
126
+
51
127
# Read data into memory if it's a dask array
52
128
data = np .asarray (data )
53
129
130
+ return data
131
+
132
+ def on_update_layers (self ) -> None :
133
+ """
134
+ Called when the layer selection changes by ``self._update_layers()``.
135
+ """
136
+
137
+ if not self .layers :
138
+ return
139
+
140
+ # Reset to bin start, stop and step
141
+ layer_data = self ._get_layer_data (self .layers [0 ])
142
+ self .autoset_widget_bins (data = layer_data )
143
+
144
+ # Only allow integer bins for integer data
145
+ n_decimals = 0 if np .issubdtype (layer_data .dtype , np .integer ) else 2
146
+ self .findChild (QDoubleSpinBox , name = "bins start" ).setDecimals (n_decimals )
147
+ self .findChild (QDoubleSpinBox , name = "bins stop" ).setDecimals (n_decimals )
148
+
149
+ def draw (self ) -> None :
150
+ """
151
+ Clear the axes and histogram the currently selected layer/slice.
152
+ """
153
+ layer = self .layers [0 ]
154
+ data = self ._get_layer_data (layer )
155
+
54
156
# Important to calculate bins after slicing 3D data, to avoid reading
55
157
# whole cube into memory.
56
- bins = np .linspace (np .min (data ), np .max (data ), 100 , dtype = data .dtype )
158
+ bins = np .linspace (
159
+ self .bins_start ,
160
+ self .bins_stop ,
161
+ self .bins_num ,
162
+ dtype = data .dtype ,
163
+ )
57
164
58
165
if layer .rgb :
59
166
# Histogram RGB channels independently
0 commit comments