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,6 +29,50 @@ 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
self .viewer .events .theme .connect (self ._on_napari_theme_changed )
39
78
@@ -53,30 +92,93 @@ def _update_contrast_lims(self) -> None:
53
92
54
93
self .figure .canvas .draw ()
55
94
56
- def draw (self ) -> None :
57
- """
58
- Clear the axes and histogram the currently selected layer/slice.
59
- """
60
- layer = self .layers [0 ]
95
+ @property
96
+ def bins_start (self ) -> float :
97
+ """Minimum bin edge"""
98
+ return self .findChild (QDoubleSpinBox , name = "bins start" ).value ()
99
+
100
+ @bins_start .setter
101
+ def bins_start (self , start : int | float ) -> None :
102
+ """Set the minimum bin edge"""
103
+ self .findChild (QDoubleSpinBox , name = "bins start" ).setValue (start )
104
+
105
+ @property
106
+ def bins_stop (self ) -> float :
107
+ """Maximum bin edge"""
108
+ return self .findChild (QDoubleSpinBox , name = "bins stop" ).value ()
109
+
110
+ @bins_stop .setter
111
+ def bins_stop (self , stop : int | float ) -> None :
112
+ """Set the maximum bin edge"""
113
+ self .findChild (QDoubleSpinBox , name = "bins stop" ).setValue (stop )
114
+
115
+ @property
116
+ def bins_num (self ) -> int :
117
+ """Number of bins to use"""
118
+ return self .findChild (QSpinBox , name = "bins num" ).value ()
119
+
120
+ @bins_num .setter
121
+ def bins_num (self , num : int ) -> None :
122
+ """Set the number of bins to use"""
123
+ self .findChild (QSpinBox , name = "bins num" ).setValue (num )
124
+
125
+ def autoset_widget_bins (self , data : npt .ArrayLike ) -> None :
126
+ """Update widgets with bins determined from the image data"""
127
+
128
+ bins = np .linspace (np .min (data ), np .max (data ), 100 , dtype = data .dtype )
129
+ self .bins_start = bins [0 ]
130
+ self .bins_stop = bins [- 1 ]
131
+ self .bins_num = bins .size
132
+
133
+
134
+ def _get_layer_data (self , layer ) -> np .ndarray :
135
+ """Get the data associated with a given layer"""
61
136
62
137
if layer .data .ndim - layer .rgb == 3 :
63
138
# 3D data, can be single channel or RGB
64
139
data = layer .data [self .current_z ]
65
140
self .axes .set_title (f"z={ self .current_z } " )
66
141
else :
67
142
data = layer .data
143
+
68
144
# Read data into memory if it's a dask array
69
145
data = np .asarray (data )
70
146
147
+ return data
148
+
149
+ def on_update_layers (self ) -> None :
150
+ """
151
+ Called when the layer selection changes by ``self._update_layers()``.
152
+ """
153
+
154
+ if not self .layers :
155
+ return
156
+
157
+ # Reset to bin start, stop and step
158
+ layer_data = self ._get_layer_data (self .layers [0 ])
159
+ self .autoset_widget_bins (data = layer_data )
160
+
161
+ # Only allow integer bins for integer data
162
+ n_decimals = 0 if np .issubdtype (layer_data .dtype , np .integer ) else 2
163
+ self .findChild (QDoubleSpinBox , name = "bins start" ).setDecimals (n_decimals )
164
+ self .findChild (QDoubleSpinBox , name = "bins stop" ).setDecimals (n_decimals )
165
+
166
+ def draw (self ) -> None :
167
+ """
168
+ Clear the axes and histogram the currently selected layer/slice.
169
+ """
170
+ layer = self .layers [0 ]
171
+ data = self ._get_layer_data (layer )
172
+
71
173
# Important to calculate bins after slicing 3D data, to avoid reading
72
174
# whole cube into memory.
73
175
if data .dtype .kind in {"i" , "u" }:
74
176
# Make sure integer data types have integer sized bins
75
- step = abs ( np . max ( data ) - np . min ( data )) // 100
177
+ step = ( self . bins_start - self . bins_stop ) // self . bins_num
76
178
step = max (1 , step )
77
- bins = np .arange (np . min ( data ), np . max ( data ) + step , step )
179
+ bins = np .arange (self . bins_start , self . bins_stop + step , step )
78
180
else :
79
- bins = np .linspace (np . min ( data ), np . max ( data ), 100 )
181
+ bins = np .linspace (self . bins_start , self . bins_stop , self . bins_num )
80
182
81
183
if layer .rgb :
82
184
# Histogram RGB channels independently
0 commit comments