|
9 | 9 | from .base import NapariMPLWidget
|
10 | 10 | from .util import Interval
|
11 | 11 |
|
12 |
| -__all__ = ["ScatterWidget", "FeaturesScatterWidget"] |
| 12 | +__all__ = ["ScatterWidget", "FeaturesScatterWidget", "FeaturesHistogramWidget"] |
13 | 13 |
|
14 | 14 |
|
15 | 15 | class ScatterBaseWidget(NapariMPLWidget):
|
@@ -222,3 +222,124 @@ def _on_update_layers(self) -> None:
|
222 | 222 | # reset the axis keys
|
223 | 223 | self._x_axis_key = None
|
224 | 224 | self._y_axis_key = None
|
| 225 | + |
| 226 | + |
| 227 | +class FeaturesHistogramWidget(NapariMPLWidget): |
| 228 | + n_layers_input = Interval(1, 1) |
| 229 | + # All layers that have a .features attributes |
| 230 | + input_layer_types = ( |
| 231 | + napari.layers.Labels, |
| 232 | + napari.layers.Points, |
| 233 | + napari.layers.Shapes, |
| 234 | + napari.layers.Tracks, |
| 235 | + napari.layers.Vectors, |
| 236 | + ) |
| 237 | + |
| 238 | + def __init__(self, napari_viewer: napari.viewer.Viewer): |
| 239 | + super().__init__(napari_viewer) |
| 240 | + self.axes = self.canvas.figure.subplots() |
| 241 | + |
| 242 | + self._key_selection_widget = magicgui( |
| 243 | + self._set_axis_keys, |
| 244 | + x_axis_key={"choices": self._get_valid_axis_keys}, |
| 245 | + call_button="plot", |
| 246 | + ) |
| 247 | + self.layout().addWidget(self._key_selection_widget.native) |
| 248 | + |
| 249 | + self.update_layers(None) |
| 250 | + |
| 251 | + def clear(self) -> None: |
| 252 | + """ |
| 253 | + Clear the axes. |
| 254 | + """ |
| 255 | + self.axes.clear() |
| 256 | + |
| 257 | + self.layout().addWidget(self._key_selection_widget.native) |
| 258 | + |
| 259 | + @property |
| 260 | + def x_axis_key(self) -> Optional[str]: |
| 261 | + """Key to access x axis data from the FeaturesTable""" |
| 262 | + return self._x_axis_key |
| 263 | + |
| 264 | + @x_axis_key.setter |
| 265 | + def x_axis_key(self, key: Optional[str]) -> None: |
| 266 | + self._x_axis_key = key |
| 267 | + self._draw() |
| 268 | + |
| 269 | + def _set_axis_keys(self, x_axis_key: str) -> None: |
| 270 | + """Set both axis keys and then redraw the plot""" |
| 271 | + self._x_axis_key = x_axis_key |
| 272 | + self._draw() |
| 273 | + |
| 274 | + def _get_valid_axis_keys( |
| 275 | + self, combo_widget: Optional[ComboBox] = None |
| 276 | + ) -> List[str]: |
| 277 | + """ |
| 278 | + Get the valid axis keys from the layer FeatureTable. |
| 279 | +
|
| 280 | + Returns |
| 281 | + ------- |
| 282 | + axis_keys : List[str] |
| 283 | + The valid axis keys in the FeatureTable. If the table is empty |
| 284 | + or there isn't a table, returns an empty list. |
| 285 | + """ |
| 286 | + if len(self.layers) == 0 or not (hasattr(self.layers[0], "features")): |
| 287 | + return [] |
| 288 | + else: |
| 289 | + return self.layers[0].features.keys() |
| 290 | + |
| 291 | + def _get_data(self) -> Tuple[List[np.ndarray], str, str]: |
| 292 | + """Get the plot data. |
| 293 | +
|
| 294 | + Returns |
| 295 | + ------- |
| 296 | + data : List[np.ndarray] |
| 297 | + List contains X and Y columns from the FeatureTable. Returns |
| 298 | + an empty array if nothing to plot. |
| 299 | + x_axis_name : str |
| 300 | + The title to display on the x axis. Returns |
| 301 | + an empty string if nothing to plot. |
| 302 | + """ |
| 303 | + if not hasattr(self.layers[0], "features"): |
| 304 | + # if the selected layer doesn't have a featuretable, |
| 305 | + # skip draw |
| 306 | + return [], "" |
| 307 | + |
| 308 | + feature_table = self.layers[0].features |
| 309 | + |
| 310 | + if ( |
| 311 | + (len(feature_table) == 0) |
| 312 | + or (self.x_axis_key is None) |
| 313 | + ): |
| 314 | + return [], "" |
| 315 | + |
| 316 | + data = feature_table[self.x_axis_key] |
| 317 | + x_axis_name = self.x_axis_key.replace("_", " ") |
| 318 | + |
| 319 | + return data, x_axis_name |
| 320 | + |
| 321 | + def _on_update_layers(self) -> None: |
| 322 | + """ |
| 323 | + This is called when the layer selection changes by |
| 324 | + ``self.update_layers()``. |
| 325 | + """ |
| 326 | + if hasattr(self, "_key_selection_widget"): |
| 327 | + self._key_selection_widget.reset_choices() |
| 328 | + |
| 329 | + # reset the axis keys |
| 330 | + self._x_axis_key = None |
| 331 | + |
| 332 | + def draw(self) -> None: |
| 333 | + """Clear the axes and histogram the currently selected layer/slice.""" |
| 334 | + |
| 335 | + data, x_axis_name = self._get_data() |
| 336 | + |
| 337 | + if len(data) == 0: |
| 338 | + return |
| 339 | + |
| 340 | + _, _, _ = self.axes.hist(data, bins=50, edgecolor='white', |
| 341 | + linewidth=0.3) |
| 342 | + |
| 343 | + # # set ax labels |
| 344 | + self.axes.set_xlabel(x_axis_name) |
| 345 | + self.axes.set_ylabel('Counts [#]') |
0 commit comments