77from napari .layers import Image
88from napari .layers ._multiscale_data import MultiScaleData
99from qtpy .QtWidgets import (
10- QAbstractSpinBox ,
1110 QComboBox ,
12- QDoubleSpinBox ,
1311 QFormLayout ,
1412 QGroupBox ,
1513 QLabel ,
3028def _get_bins (
3129 data : npt .NDArray [Any ],
3230 num_bins : int = 100 ,
33- start : int | float | None = None ,
34- stop : int | float | None = None ,
3531) -> npt .NDArray [Any ]:
3632 """Create evenly spaced bins with a given interval.
3733
38- If `start` or `stop` are `None`, they will be set based on the minimum
39- and maximum values, respectively, of the data.
40-
4134 Parameters
4235 ----------
4336 data : napari.layers.Layer.data
4437 Napari layer data.
4538 num_bins : integer, optional
46- Number of evenly-spaced bins to create.
47- start : integer or real, optional
48- Start bin edge. Defaults to the minimum value of `data`.
49- stop : integer or real, optional
50- Stop bin edge. Defaults to the maximum value of `data`.
39+ Number of evenly-spaced bins to create. Defaults to 100.
5140
5241 Returns
5342 -------
5443 bin_edges : numpy.ndarray
5544 Array of evenly spaced bin edges.
5645 """
57- start = np .min (data ) if start is None else start
58- stop = np .max (data ) if stop is None else stop
59-
6046 if data .dtype .kind in {"i" , "u" }:
6147 # Make sure integer data types have integer sized bins
62- step = np .ceil (( stop - start ) / num_bins )
63- return np .arange (start , stop + step , step )
48+ step = np .ceil (np . ptp ( data ) / num_bins )
49+ return np .arange (np . min ( data ), np . max ( data ) + step , step )
6450 else :
6551 # For other data types we can use exactly `num_bins` bins
6652 # (and `num_bins` + 1 bin edges)
67- return np .linspace (start , stop , num_bins + 1 )
53+ return np .linspace (np . min ( data ), np . max ( data ) , num_bins + 1 )
6854
6955
7056class HistogramWidget (SingleAxesWidget ):
@@ -82,53 +68,28 @@ def __init__(
8268 ):
8369 super ().__init__ (napari_viewer , parent = parent )
8470
85- # Create widgets for setting bin parameters
86- bins_start = QDoubleSpinBox ()
87- bins_start .setStepType (QAbstractSpinBox .AdaptiveDecimalStepType )
88- bins_start .setRange (- 1e10 , 1e10 )
89- bins_start .setValue (0 )
90- bins_start .setWrapping (False )
91- bins_start .setKeyboardTracking (False )
92- bins_start .setDecimals (2 )
93-
94- bins_stop = QDoubleSpinBox ()
95- bins_stop .setStepType (QAbstractSpinBox .AdaptiveDecimalStepType )
96- bins_stop .setRange (- 1e10 , 1e10 )
97- bins_stop .setValue (100 )
98- bins_start .setWrapping (False )
99- bins_stop .setKeyboardTracking (False )
100- bins_stop .setDecimals (2 )
101-
102- bins_num = QSpinBox ()
103- bins_num .setRange (1 , 100_000 )
104- bins_num .setValue (101 )
105- bins_num .setWrapping (False )
106- bins_num .setKeyboardTracking (False )
71+ num_bins_widget = QSpinBox ()
72+ num_bins_widget .setRange (1 , 100_000 )
73+ num_bins_widget .setValue (101 )
74+ num_bins_widget .setWrapping (False )
75+ num_bins_widget .setKeyboardTracking (False )
10776
10877 # Set bins widget layout
10978 bins_selection_layout = QFormLayout ()
110- bins_selection_layout .addRow ("start" , bins_start )
111- bins_selection_layout .addRow ("stop" , bins_stop )
112- bins_selection_layout .addRow ("num" , bins_num )
79+ bins_selection_layout .addRow ("num bins" , num_bins_widget )
11380
11481 # Group the widgets and add to main layout
115- bins_widget_group = QGroupBox ("Bins " )
116- bins_widget_group_layout = QVBoxLayout ()
117- bins_widget_group_layout .addLayout (bins_selection_layout )
118- bins_widget_group .setLayout (bins_widget_group_layout )
119- self .layout ().addWidget (bins_widget_group )
82+ params_widget_group = QGroupBox ("Params " )
83+ params_widget_group_layout = QVBoxLayout ()
84+ params_widget_group_layout .addLayout (bins_selection_layout )
85+ params_widget_group .setLayout (params_widget_group_layout )
86+ self .layout ().addWidget (params_widget_group )
12087
12188 # Add callbacks
122- bins_start .valueChanged .connect (self ._draw )
123- bins_stop .valueChanged .connect (self ._draw )
124- bins_num .valueChanged .connect (self ._draw )
89+ num_bins_widget .valueChanged .connect (self ._draw )
12590
12691 # Store widgets for later usage
127- self ._bin_widgets = {
128- "start" : bins_start ,
129- "stop" : bins_stop ,
130- "num" : bins_num ,
131- }
92+ self .num_bins_widget = num_bins_widget
13293
13394 self ._update_layers (None )
13495 self .viewer .events .theme .connect (self ._on_napari_theme_changed )
@@ -144,27 +105,9 @@ def on_update_layers(self) -> None:
144105 if not self .layers :
145106 return
146107
147- # Reset the bin start, stop and step values based on new layer data
108+ # Reset the num bins based on new layer data
148109 layer_data = self ._get_layer_data (self .layers [0 ])
149- self .autoset_widget_bins (data = layer_data )
150-
151- # Only allow integer bins for integer data
152- # And only allow values greater than 0 for unsigned integers
153- n_decimals = 0 if np .issubdtype (layer_data .dtype , np .integer ) else 2
154- is_unsigned = layer_data .dtype .kind == "u"
155- minimum_value = 0 if is_unsigned else - 1e10
156-
157- # Disable callbacks whilst widget values might change
158- for widget in self ._bin_widgets .values ():
159- widget .blockSignals (True )
160-
161- self ._bin_widgets ["start" ].setDecimals (n_decimals )
162- self ._bin_widgets ["stop" ].setDecimals (n_decimals )
163- self ._bin_widgets ["start" ].setMinimum (minimum_value )
164- self ._bin_widgets ["stop" ].setMinimum (minimum_value )
165-
166- for widget in self ._bin_widgets .values ():
167- widget .blockSignals (False )
110+ self ._set_widget_nums_bins (data = layer_data )
168111
169112 def _update_contrast_lims (self ) -> None :
170113 for lim , line in zip (
@@ -174,50 +117,10 @@ def _update_contrast_lims(self) -> None:
174117
175118 self .figure .canvas .draw ()
176119
177- def autoset_widget_bins (self , data : npt .NDArray [Any ]) -> None :
178- """Update widgets with bins determined from the image data"""
120+ def _set_widget_nums_bins (self , data : npt .NDArray [Any ]) -> None :
121+ """Update num_bins widget with bins determined from the image data"""
179122 bins = _get_bins (data )
180-
181- # Disable callbacks whilst setting widget values
182- for widget in self ._bin_widgets .values ():
183- widget .blockSignals (True )
184-
185- self .bins_start = bins [0 ]
186- self .bins_stop = bins [- 1 ]
187- self .bins_num = bins .size - 1
188-
189- for widget in self ._bin_widgets .values ():
190- widget .blockSignals (False )
191-
192- @property
193- def bins_start (self ) -> float :
194- """Minimum bin edge"""
195- return self ._bin_widgets ["start" ].value ()
196-
197- @bins_start .setter
198- def bins_start (self , start : int | float ) -> None :
199- """Set the minimum bin edge"""
200- self ._bin_widgets ["start" ].setValue (start )
201-
202- @property
203- def bins_stop (self ) -> float :
204- """Maximum bin edge"""
205- return self ._bin_widgets ["stop" ].value ()
206-
207- @bins_stop .setter
208- def bins_stop (self , stop : int | float ) -> None :
209- """Set the maximum bin edge"""
210- self ._bin_widgets ["stop" ].setValue (stop )
211-
212- @property
213- def bins_num (self ) -> int :
214- """Number of bins to use"""
215- return self ._bin_widgets ["num" ].value ()
216-
217- @bins_num .setter
218- def bins_num (self , num : int ) -> None :
219- """Set the number of bins to use"""
220- self ._bin_widgets ["num" ].setValue (num )
123+ self .num_bins_widget .setValue (bins .size - 1 )
221124
222125 def _get_layer_data (self , layer : napari .layers .Layer ) -> npt .NDArray [Any ]:
223126 """Get the data associated with a given layer"""
@@ -248,9 +151,7 @@ def draw(self) -> None:
248151 # whole cube into memory.
249152 bins = _get_bins (
250153 data ,
251- num_bins = self .bins_num ,
252- start = self .bins_start ,
253- stop = self .bins_stop ,
154+ num_bins = self .num_bins_widget .value (),
254155 )
255156
256157 if layer .rgb :
0 commit comments