From 91f6d1635150cb752273378851e7e1079ea92fd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= <38459088+jo-mueller@users.noreply.github.com> Date: Thu, 1 Jun 2023 16:53:53 +0200 Subject: [PATCH 01/37] added a Feature Histogram Widget Co-Authored-By: Marcelo Zoccoler <26173597+zoccoler@users.noreply.github.com> --- src/napari_matplotlib/napari.yaml | 7 + src/napari_matplotlib/scatter.py | 123 +++++++++++++++++- src/napari_matplotlib/tests/test_histogram.py | 21 ++- 3 files changed, 149 insertions(+), 2 deletions(-) diff --git a/src/napari_matplotlib/napari.yaml b/src/napari_matplotlib/napari.yaml index b736592b..71af0ca6 100644 --- a/src/napari_matplotlib/napari.yaml +++ b/src/napari_matplotlib/napari.yaml @@ -14,6 +14,10 @@ contributions: python_name: napari_matplotlib:FeaturesScatterWidget title: Make a scatter plot of layer features + - id: napari-matplotlib.features_histogram + python_name: napari_matplotlib:FeaturesHistogramWidget + title: Plot feature histograms + - id: napari-matplotlib.slice python_name: napari_matplotlib:SliceWidget title: Plot a 1D slice @@ -28,5 +32,8 @@ contributions: - command: napari-matplotlib.features_scatter display_name: FeaturesScatter + - command: napari-matplotlib.features_histogram + display_name: FeaturesHistogram + - command: napari-matplotlib.slice display_name: 1D slice diff --git a/src/napari_matplotlib/scatter.py b/src/napari_matplotlib/scatter.py index 3b0f918c..b3c1147b 100644 --- a/src/napari_matplotlib/scatter.py +++ b/src/napari_matplotlib/scatter.py @@ -9,7 +9,7 @@ from .base import NapariMPLWidget from .util import Interval -__all__ = ["ScatterWidget", "FeaturesScatterWidget"] +__all__ = ["ScatterWidget", "FeaturesScatterWidget", "FeaturesHistogramWidget"] class ScatterBaseWidget(NapariMPLWidget): @@ -222,3 +222,124 @@ def _on_update_layers(self) -> None: # reset the axis keys self._x_axis_key = None self._y_axis_key = None + + +class FeaturesHistogramWidget(NapariMPLWidget): + n_layers_input = Interval(1, 1) + # All layers that have a .features attributes + input_layer_types = ( + napari.layers.Labels, + napari.layers.Points, + napari.layers.Shapes, + napari.layers.Tracks, + napari.layers.Vectors, + ) + + def __init__(self, napari_viewer: napari.viewer.Viewer): + super().__init__(napari_viewer) + self.axes = self.canvas.figure.subplots() + + self._key_selection_widget = magicgui( + self._set_axis_keys, + x_axis_key={"choices": self._get_valid_axis_keys}, + call_button="plot", + ) + self.layout().addWidget(self._key_selection_widget.native) + + self.update_layers(None) + + def clear(self) -> None: + """ + Clear the axes. + """ + self.axes.clear() + + self.layout().addWidget(self._key_selection_widget.native) + + @property + def x_axis_key(self) -> Optional[str]: + """Key to access x axis data from the FeaturesTable""" + return self._x_axis_key + + @x_axis_key.setter + def x_axis_key(self, key: Optional[str]) -> None: + self._x_axis_key = key + self._draw() + + def _set_axis_keys(self, x_axis_key: str) -> None: + """Set both axis keys and then redraw the plot""" + self._x_axis_key = x_axis_key + self._draw() + + def _get_valid_axis_keys( + self, combo_widget: Optional[ComboBox] = None + ) -> List[str]: + """ + Get the valid axis keys from the layer FeatureTable. + + Returns + ------- + axis_keys : List[str] + The valid axis keys in the FeatureTable. If the table is empty + or there isn't a table, returns an empty list. + """ + if len(self.layers) == 0 or not (hasattr(self.layers[0], "features")): + return [] + else: + return self.layers[0].features.keys() + + def _get_data(self) -> Tuple[List[np.ndarray], str, str]: + """Get the plot data. + + Returns + ------- + data : List[np.ndarray] + List contains X and Y columns from the FeatureTable. Returns + an empty array if nothing to plot. + x_axis_name : str + The title to display on the x axis. Returns + an empty string if nothing to plot. + """ + if not hasattr(self.layers[0], "features"): + # if the selected layer doesn't have a featuretable, + # skip draw + return [], "" + + feature_table = self.layers[0].features + + if ( + (len(feature_table) == 0) + or (self.x_axis_key is None) + ): + return [], "" + + data = feature_table[self.x_axis_key] + x_axis_name = self.x_axis_key.replace("_", " ") + + return data, x_axis_name + + def _on_update_layers(self) -> None: + """ + This is called when the layer selection changes by + ``self.update_layers()``. + """ + if hasattr(self, "_key_selection_widget"): + self._key_selection_widget.reset_choices() + + # reset the axis keys + self._x_axis_key = None + + def draw(self) -> None: + """Clear the axes and histogram the currently selected layer/slice.""" + + data, x_axis_name = self._get_data() + + if len(data) == 0: + return + + _, _, _ = self.axes.hist(data, bins=50, edgecolor='white', + linewidth=0.3) + + # # set ax labels + self.axes.set_xlabel(x_axis_name) + self.axes.set_ylabel('Counts [#]') diff --git a/src/napari_matplotlib/tests/test_histogram.py b/src/napari_matplotlib/tests/test_histogram.py index f497a1a9..54b3ad29 100644 --- a/src/napari_matplotlib/tests/test_histogram.py +++ b/src/napari_matplotlib/tests/test_histogram.py @@ -1,4 +1,4 @@ -from napari_matplotlib import HistogramWidget +from napari_matplotlib import HistogramWidget, FeaturesHistogramWidget def test_example_q_widget(make_napari_viewer, image_data): @@ -6,3 +6,22 @@ def test_example_q_widget(make_napari_viewer, image_data): viewer = make_napari_viewer() viewer.add_image(image_data[0], **image_data[1]) HistogramWidget(viewer) + +def test_feature_histogram(make_napari_viewer): + + import numpy as np + + n_points = 1000 + random_points = np.random.random((n_points,3))*10 + feature1 = np.random.random(n_points) + feature2 = np.random.normal(size=n_points) + + viewer = make_napari_viewer() + viewer.add_points(random_points, properties={'feature1': feature1, 'feature2': feature2}, face_color='feature1', size=1) + + widget = FeaturesHistogramWidget(viewer) + viewer.window.add_dock_widget(widget) + widget._set_axis_keys('feature1') + widget._key_selection_widget() + widget._set_axis_keys('feature2') + widget._key_selection_widget() From 230a303bda3edb39bdb2f19e0649b28df96a1e59 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 26 Jun 2023 11:35:30 +0200 Subject: [PATCH 02/37] Update src/napari_matplotlib/scatter.py Co-authored-by: David Stansby --- src/napari_matplotlib/scatter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/napari_matplotlib/scatter.py b/src/napari_matplotlib/scatter.py index b3c1147b..84fd4e74 100644 --- a/src/napari_matplotlib/scatter.py +++ b/src/napari_matplotlib/scatter.py @@ -340,6 +340,6 @@ def draw(self) -> None: _, _, _ = self.axes.hist(data, bins=50, edgecolor='white', linewidth=0.3) - # # set ax labels + # set ax labels self.axes.set_xlabel(x_axis_name) self.axes.set_ylabel('Counts [#]') From e5cb943c7d457f4574a6ee13f569c86cea80d304 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 26 Jun 2023 11:35:39 +0200 Subject: [PATCH 03/37] Update src/napari_matplotlib/scatter.py Co-authored-by: David Stansby --- src/napari_matplotlib/scatter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/napari_matplotlib/scatter.py b/src/napari_matplotlib/scatter.py index 84fd4e74..80931c7d 100644 --- a/src/napari_matplotlib/scatter.py +++ b/src/napari_matplotlib/scatter.py @@ -337,7 +337,7 @@ def draw(self) -> None: if len(data) == 0: return - _, _, _ = self.axes.hist(data, bins=50, edgecolor='white', + self.axes.hist(data, bins=50, edgecolor='white', linewidth=0.3) # set ax labels From 524fdbd5064c6c635bf4815cd5ff986ca4b328c0 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 26 Jun 2023 11:35:53 +0200 Subject: [PATCH 04/37] Update src/napari_matplotlib/scatter.py Co-authored-by: David Stansby --- src/napari_matplotlib/scatter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/napari_matplotlib/scatter.py b/src/napari_matplotlib/scatter.py index 80931c7d..210a8053 100644 --- a/src/napari_matplotlib/scatter.py +++ b/src/napari_matplotlib/scatter.py @@ -288,7 +288,7 @@ def _get_valid_axis_keys( else: return self.layers[0].features.keys() - def _get_data(self) -> Tuple[List[np.ndarray], str, str]: + def _get_data(self) -> Tuple[np.ndarray, str]: """Get the plot data. Returns From f69761871a301a98d8fac475df6cf5a0bdf56c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 26 Jun 2023 11:44:40 +0200 Subject: [PATCH 05/37] moved new widget to `histogram.py` --- src/napari_matplotlib/histogram.py | 126 ++++++++++++++++++++++++++++- src/napari_matplotlib/scatter.py | 121 --------------------------- 2 files changed, 124 insertions(+), 123 deletions(-) diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index 1e273e70..6a07bdf5 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -1,13 +1,14 @@ -from typing import Optional +from typing import Optional, List, Tuple import napari import numpy as np from qtpy.QtWidgets import QWidget +from magicgui import magicgui, ComboBox from .base import NapariMPLWidget from .util import Interval -__all__ = ["HistogramWidget"] +__all__ = ["HistogramWidget", "FeaturesHistogramWidget"] _COLORS = {"r": "tab:red", "g": "tab:green", "b": "tab:blue"} @@ -63,3 +64,124 @@ def draw(self) -> None: self.axes.hist(data.ravel(), bins=bins, label=layer.name) self.axes.legend() + + +class FeaturesHistogramWidget(NapariMPLWidget): + n_layers_input = Interval(1, 1) + # All layers that have a .features attributes + input_layer_types = ( + napari.layers.Labels, + napari.layers.Points, + napari.layers.Shapes, + napari.layers.Tracks, + napari.layers.Vectors, + ) + + def __init__(self, napari_viewer: napari.viewer.Viewer): + super().__init__(napari_viewer) + self.axes = self.canvas.figure.subplots() + + self._key_selection_widget = magicgui( + self._set_axis_keys, + x_axis_key={"choices": self._get_valid_axis_keys}, + call_button="plot", + ) + self.layout().addWidget(self._key_selection_widget.native) + + self.update_layers(None) + + def clear(self) -> None: + """ + Clear the axes. + """ + self.axes.clear() + + self.layout().addWidget(self._key_selection_widget.native) + + @property + def x_axis_key(self) -> Optional[str]: + """Key to access x axis data from the FeaturesTable""" + return self._x_axis_key + + @x_axis_key.setter + def x_axis_key(self, key: Optional[str]) -> None: + self._x_axis_key = key + self._draw() + + def _set_axis_keys(self, x_axis_key: str) -> None: + """Set both axis keys and then redraw the plot""" + self._x_axis_key = x_axis_key + self._draw() + + def _get_valid_axis_keys( + self, combo_widget: Optional[ComboBox] = None + ) -> List[str]: + """ + Get the valid axis keys from the layer FeatureTable. + + Returns + ------- + axis_keys : List[str] + The valid axis keys in the FeatureTable. If the table is empty + or there isn't a table, returns an empty list. + """ + if len(self.layers) == 0 or not (hasattr(self.layers[0], "features")): + return [] + else: + return self.layers[0].features.keys() + + def _get_data(self) -> Tuple[np.ndarray, str]: + """Get the plot data. + + Returns + ------- + data : List[np.ndarray] + List contains X and Y columns from the FeatureTable. Returns + an empty array if nothing to plot. + x_axis_name : str + The title to display on the x axis. Returns + an empty string if nothing to plot. + """ + if not hasattr(self.layers[0], "features"): + # if the selected layer doesn't have a featuretable, + # skip draw + return [], "" + + feature_table = self.layers[0].features + + if ( + (len(feature_table) == 0) + or (self.x_axis_key is None) + ): + return [], "" + + data = feature_table[self.x_axis_key] + x_axis_name = self.x_axis_key.replace("_", " ") + + return data, x_axis_name + + def _on_update_layers(self) -> None: + """ + This is called when the layer selection changes by + ``self.update_layers()``. + """ + if hasattr(self, "_key_selection_widget"): + self._key_selection_widget.reset_choices() + + # reset the axis keys + self._x_axis_key = None + + def draw(self) -> None: + """Clear the axes and histogram the currently selected layer/slice.""" + + data, x_axis_name = self._get_data() + + if len(data) == 0: + return + + self.axes.hist(data, bins=50, edgecolor='white', + linewidth=0.3) + + # set ax labels + self.axes.set_xlabel(x_axis_name) + self.axes.set_ylabel('Counts [#]') \ No newline at end of file diff --git a/src/napari_matplotlib/scatter.py b/src/napari_matplotlib/scatter.py index eb6e9d27..c3c40c16 100644 --- a/src/napari_matplotlib/scatter.py +++ b/src/napari_matplotlib/scatter.py @@ -248,124 +248,3 @@ def on_update_layers(self) -> None: self._selectors[dim].removeItem(0) # Add keys for newly selected layer self._selectors[dim].addItems(self._get_valid_axis_keys()) - - -class FeaturesHistogramWidget(NapariMPLWidget): - n_layers_input = Interval(1, 1) - # All layers that have a .features attributes - input_layer_types = ( - napari.layers.Labels, - napari.layers.Points, - napari.layers.Shapes, - napari.layers.Tracks, - napari.layers.Vectors, - ) - - def __init__(self, napari_viewer: napari.viewer.Viewer): - super().__init__(napari_viewer) - self.axes = self.canvas.figure.subplots() - - self._key_selection_widget = magicgui( - self._set_axis_keys, - x_axis_key={"choices": self._get_valid_axis_keys}, - call_button="plot", - ) - self.layout().addWidget(self._key_selection_widget.native) - - self.update_layers(None) - - def clear(self) -> None: - """ - Clear the axes. - """ - self.axes.clear() - - self.layout().addWidget(self._key_selection_widget.native) - - @property - def x_axis_key(self) -> Optional[str]: - """Key to access x axis data from the FeaturesTable""" - return self._x_axis_key - - @x_axis_key.setter - def x_axis_key(self, key: Optional[str]) -> None: - self._x_axis_key = key - self._draw() - - def _set_axis_keys(self, x_axis_key: str) -> None: - """Set both axis keys and then redraw the plot""" - self._x_axis_key = x_axis_key - self._draw() - - def _get_valid_axis_keys( - self, combo_widget: Optional[ComboBox] = None - ) -> List[str]: - """ - Get the valid axis keys from the layer FeatureTable. - - Returns - ------- - axis_keys : List[str] - The valid axis keys in the FeatureTable. If the table is empty - or there isn't a table, returns an empty list. - """ - if len(self.layers) == 0 or not (hasattr(self.layers[0], "features")): - return [] - else: - return self.layers[0].features.keys() - - def _get_data(self) -> Tuple[np.ndarray, str]: - """Get the plot data. - - Returns - ------- - data : List[np.ndarray] - List contains X and Y columns from the FeatureTable. Returns - an empty array if nothing to plot. - x_axis_name : str - The title to display on the x axis. Returns - an empty string if nothing to plot. - """ - if not hasattr(self.layers[0], "features"): - # if the selected layer doesn't have a featuretable, - # skip draw - return [], "" - - feature_table = self.layers[0].features - - if ( - (len(feature_table) == 0) - or (self.x_axis_key is None) - ): - return [], "" - - data = feature_table[self.x_axis_key] - x_axis_name = self.x_axis_key.replace("_", " ") - - return data, x_axis_name - - def _on_update_layers(self) -> None: - """ - This is called when the layer selection changes by - ``self.update_layers()``. - """ - if hasattr(self, "_key_selection_widget"): - self._key_selection_widget.reset_choices() - - # reset the axis keys - self._x_axis_key = None - - def draw(self) -> None: - """Clear the axes and histogram the currently selected layer/slice.""" - - data, x_axis_name = self._get_data() - - if len(data) == 0: - return - - self.axes.hist(data, bins=50, edgecolor='white', - linewidth=0.3) - - # set ax labels - self.axes.set_xlabel(x_axis_name) - self.axes.set_ylabel('Counts [#]') From 98d84f604a2fdb8457b78591291ebff61ca486e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 26 Jun 2023 13:12:19 +0200 Subject: [PATCH 06/37] put import at correct location --- src/napari_matplotlib/scatter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/napari_matplotlib/scatter.py b/src/napari_matplotlib/scatter.py index c3c40c16..622a71fb 100644 --- a/src/napari_matplotlib/scatter.py +++ b/src/napari_matplotlib/scatter.py @@ -8,7 +8,7 @@ from .util import Interval __all__ = ["ScatterBaseWidget", "ScatterWidget", - "FeaturesScatterWidget", "FeaturesHistogramWidget"] + "FeaturesScatterWidget"] class ScatterBaseWidget(NapariMPLWidget): From c3d1d018502a90bba9f594fb10fe327a7812e509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 26 Jun 2023 13:12:58 +0200 Subject: [PATCH 07/37] introduced `FEATURES_LAYER_TYPES` variable to be used across widgets --- src/napari_matplotlib/features.py | 9 +++++++++ src/napari_matplotlib/histogram.py | 9 ++------- src/napari_matplotlib/scatter.py | 9 ++------- 3 files changed, 13 insertions(+), 14 deletions(-) create mode 100644 src/napari_matplotlib/features.py diff --git a/src/napari_matplotlib/features.py b/src/napari_matplotlib/features.py new file mode 100644 index 00000000..16a2efe1 --- /dev/null +++ b/src/napari_matplotlib/features.py @@ -0,0 +1,9 @@ +from napari.layers import Labels, Points, Shapes, Tracks, Vectors + +FEATURES_LAYER_TYPES = ( + Labels, + Points, + Shapes, + Tracks, + Vectors, +) \ No newline at end of file diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index 6a07bdf5..1f346c5e 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -7,6 +7,7 @@ from .base import NapariMPLWidget from .util import Interval +from .features import FEATURES_LAYER_TYPES __all__ = ["HistogramWidget", "FeaturesHistogramWidget"] @@ -69,13 +70,7 @@ def draw(self) -> None: class FeaturesHistogramWidget(NapariMPLWidget): n_layers_input = Interval(1, 1) # All layers that have a .features attributes - input_layer_types = ( - napari.layers.Labels, - napari.layers.Points, - napari.layers.Shapes, - napari.layers.Tracks, - napari.layers.Vectors, - ) + input_layer_types = FEATURES_LAYER_TYPES def __init__(self, napari_viewer: napari.viewer.Viewer): super().__init__(napari_viewer) diff --git a/src/napari_matplotlib/scatter.py b/src/napari_matplotlib/scatter.py index 622a71fb..144c604b 100644 --- a/src/napari_matplotlib/scatter.py +++ b/src/napari_matplotlib/scatter.py @@ -6,6 +6,7 @@ from .base import NapariMPLWidget from .util import Interval +from .features import FEATURES_LAYER_TYPES __all__ = ["ScatterBaseWidget", "ScatterWidget", "FeaturesScatterWidget"] @@ -107,13 +108,7 @@ class FeaturesScatterWidget(ScatterBaseWidget): n_layers_input = Interval(1, 1) # All layers that have a .features attributes - input_layer_types = ( - napari.layers.Labels, - napari.layers.Points, - napari.layers.Shapes, - napari.layers.Tracks, - napari.layers.Vectors, - ) + input_layer_types = FEATURES_LAYER_TYPES def __init__( self, From e965cd32b2f927a1d5a6a559c327a4a4df907693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 26 Jun 2023 13:18:30 +0200 Subject: [PATCH 08/37] used `SingleAxesWidget` in FeatureHistogram --- src/napari_matplotlib/histogram.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index c04c2b17..048bf6cf 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -60,14 +60,13 @@ def draw(self) -> None: self.axes.legend() -class FeaturesHistogramWidget(NapariMPLWidget): +class FeaturesHistogramWidget(SingleAxesWidget): n_layers_input = Interval(1, 1) # All layers that have a .features attributes input_layer_types = FEATURES_LAYER_TYPES def __init__(self, napari_viewer: napari.viewer.Viewer): super().__init__(napari_viewer) - self.axes = self.canvas.figure.subplots() self._key_selection_widget = magicgui( self._set_axis_keys, @@ -78,14 +77,6 @@ def __init__(self, napari_viewer: napari.viewer.Viewer): self.update_layers(None) - def clear(self) -> None: - """ - Clear the axes. - """ - self.axes.clear() - - self.layout().addWidget(self._key_selection_widget.native) - @property def x_axis_key(self) -> Optional[str]: """Key to access x axis data from the FeaturesTable""" From 6731648a9775f479d082ea6b4bfa0db59509182b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 26 Jun 2023 13:19:19 +0200 Subject: [PATCH 09/37] Added optional `parent` argument added parent to init --- src/napari_matplotlib/histogram.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index 048bf6cf..ac1d1bfd 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -65,11 +65,13 @@ class FeaturesHistogramWidget(SingleAxesWidget): # All layers that have a .features attributes input_layer_types = FEATURES_LAYER_TYPES - def __init__(self, napari_viewer: napari.viewer.Viewer): - super().__init__(napari_viewer) + def __init__( + self, + napari_viewer: napari.viewer.Viewer, + parent: Optional[QWidget] = None,): + super().__init__(napari_viewer, parent=parent) self._key_selection_widget = magicgui( - self._set_axis_keys, x_axis_key={"choices": self._get_valid_axis_keys}, call_button="plot", ) From dcab365f898cb945207395af2f0e84afd9170dff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 26 Jun 2023 13:21:43 +0200 Subject: [PATCH 10/37] removed unused ComboBox --- src/napari_matplotlib/histogram.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index ac1d1bfd..213a08b3 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -3,7 +3,7 @@ import napari import numpy as np from qtpy.QtWidgets import QWidget -from magicgui import magicgui, ComboBox +from magicgui import magicgui from .base import SingleAxesWidget from .util import Interval @@ -94,9 +94,7 @@ def _set_axis_keys(self, x_axis_key: str) -> None: self._x_axis_key = x_axis_key self._draw() - def _get_valid_axis_keys( - self, combo_widget: Optional[ComboBox] = None - ) -> List[str]: + def _get_valid_axis_keys(self) -> List[str]: """ Get the valid axis keys from the layer FeatureTable. From d1207f0c49d5e6bfe60f16cdd34d5e76822fba9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 26 Jun 2023 14:17:50 +0200 Subject: [PATCH 11/37] updated tests --- src/napari_matplotlib/tests/test_histogram.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/napari_matplotlib/tests/test_histogram.py b/src/napari_matplotlib/tests/test_histogram.py index 847235f1..1b17045f 100644 --- a/src/napari_matplotlib/tests/test_histogram.py +++ b/src/napari_matplotlib/tests/test_histogram.py @@ -44,14 +44,27 @@ def test_feature_histogram(make_napari_viewer): feature2 = np.random.normal(size=n_points) viewer = make_napari_viewer() - viewer.add_points(random_points, properties={'feature1': feature1, 'feature2': feature2}, face_color='feature1', size=1) + viewer.add_points(random_points, + properties={'feature1': feature1, 'feature2': feature2}, + name='points1') + viewer.add_points(random_points, + properties={'feature1': feature1, 'feature2': feature2}, + name='points2') widget = FeaturesHistogramWidget(viewer) viewer.window.add_dock_widget(widget) + + # Check whether changing the selected key changes the plot widget._set_axis_keys('feature1') - widget._key_selection_widget() + fig1 = deepcopy(widget.figure) + widget._set_axis_keys('feature2') - widget._key_selection_widget() + assert_figures_not_equal(widget.figure, fig1) + + #check whether selecting a different layer produces the same plot + viewer.layers.selection.clear() + viewer.layers.selection.add(viewer.layers[1]) + assert_figures_equal(widget.figure, fig1) def test_change_layer(make_napari_viewer, brain_data, astronaut_data): From 476e2f26a5ffd067e318ab36c5666b4b8f38b5ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 26 Jun 2023 14:18:02 +0200 Subject: [PATCH 12/37] Used PyQt instead of magicgui --- src/napari_matplotlib/histogram.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index 213a08b3..1af9a9e1 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -2,8 +2,7 @@ import napari import numpy as np -from qtpy.QtWidgets import QWidget -from magicgui import magicgui +from qtpy.QtWidgets import QComboBox, QLabel, QVBoxLayout, QWidget from .base import SingleAxesWidget from .util import Interval @@ -71,13 +70,14 @@ def __init__( parent: Optional[QWidget] = None,): super().__init__(napari_viewer, parent=parent) - self._key_selection_widget = magicgui( - x_axis_key={"choices": self._get_valid_axis_keys}, - call_button="plot", - ) - self.layout().addWidget(self._key_selection_widget.native) + self.layout().addLayout(QVBoxLayout()) + self._key_selection_widget = QComboBox() + self.layout().addWidget(QLabel("Key:")) + self.layout().addWidget(self._key_selection_widget) - self.update_layers(None) + self._key_selection_widget.currentTextChanged.connect(self._set_axis_keys) + + self._update_layers(None) @property def x_axis_key(self) -> Optional[str]: @@ -139,17 +139,17 @@ def _get_data(self) -> Tuple[np.ndarray, str]: return data, x_axis_name - def _on_update_layers(self) -> None: + def on_update_layers(self) -> None: """ - This is called when the layer selection changes by - ``self.update_layers()``. + Called when the layer selection changes by ``self.update_layers()``. """ - if hasattr(self, "_key_selection_widget"): - self._key_selection_widget.reset_choices() - # reset the axis keys self._x_axis_key = None + # Clear combobox + self._key_selection_widget.clear() + self._key_selection_widget.addItems(self._get_valid_axis_keys()) + def draw(self) -> None: """Clear the axes and histogram the currently selected layer/slice.""" @@ -159,7 +159,7 @@ def draw(self) -> None: return self.axes.hist(data, bins=50, edgecolor='white', - linewidth=0.3) + linewidth=0.3) # set ax labels self.axes.set_xlabel(x_axis_name) From 47ccb37c55d4294a2deb6f7398e3327966cc1de8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 26 Jun 2023 14:18:44 +0200 Subject: [PATCH 13/37] codestyle --- src/napari_matplotlib/tests/test_histogram.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/napari_matplotlib/tests/test_histogram.py b/src/napari_matplotlib/tests/test_histogram.py index 1b17045f..7c71ff2c 100644 --- a/src/napari_matplotlib/tests/test_histogram.py +++ b/src/napari_matplotlib/tests/test_histogram.py @@ -39,7 +39,7 @@ def test_feature_histogram(make_napari_viewer): import numpy as np n_points = 1000 - random_points = np.random.random((n_points,3))*10 + random_points = np.random.random((n_points, 3)) * 10 feature1 = np.random.random(n_points) feature2 = np.random.normal(size=n_points) @@ -61,7 +61,7 @@ def test_feature_histogram(make_napari_viewer): widget._set_axis_keys('feature2') assert_figures_not_equal(widget.figure, fig1) - #check whether selecting a different layer produces the same plot + # check whether selecting a different layer produces the same plot viewer.layers.selection.clear() viewer.layers.selection.add(viewer.layers[1]) assert_figures_equal(widget.figure, fig1) From a40faf2d5e8f7bf72737cf0efd8b1f28232b9261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 26 Jun 2023 14:39:27 +0200 Subject: [PATCH 14/37] codestyle --- src/napari_matplotlib/histogram.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index 1af9a9e1..f4bcc346 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -1,4 +1,5 @@ -from typing import Optional, List, Tuple +from typing import Optional, List, Tuple, Any +import numpy.typing as npt import napari import numpy as np @@ -60,6 +61,9 @@ def draw(self) -> None: class FeaturesHistogramWidget(SingleAxesWidget): + """ + Display a histogram of selected feature attached to selected layer. + """ n_layers_input = Interval(1, 1) # All layers that have a .features attributes input_layer_types = FEATURES_LAYER_TYPES @@ -109,7 +113,7 @@ def _get_valid_axis_keys(self) -> List[str]: else: return self.layers[0].features.keys() - def _get_data(self) -> Tuple[np.ndarray, str]: + def _get_data(self) -> Tuple[npt.NDArray[Any], str]: """Get the plot data. Returns @@ -163,4 +167,4 @@ def draw(self) -> None: # set ax labels self.axes.set_xlabel(x_axis_name) - self.axes.set_ylabel('Counts [#]') \ No newline at end of file + self.axes.set_ylabel('Counts [#]') From ed658cdbfce9aafa2ea0517cbaaee704a47b71da Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 18:47:13 +0000 Subject: [PATCH 15/37] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/napari_matplotlib/features.py | 2 +- src/napari_matplotlib/histogram.py | 29 +++++++++---------- src/napari_matplotlib/scatter.py | 5 ++-- src/napari_matplotlib/tests/test_histogram.py | 23 ++++++++------- 4 files changed, 30 insertions(+), 29 deletions(-) diff --git a/src/napari_matplotlib/features.py b/src/napari_matplotlib/features.py index 16a2efe1..34abf104 100644 --- a/src/napari_matplotlib/features.py +++ b/src/napari_matplotlib/features.py @@ -6,4 +6,4 @@ Shapes, Tracks, Vectors, -) \ No newline at end of file +) diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index f4bcc346..01c2c6a4 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -1,13 +1,13 @@ -from typing import Optional, List, Tuple, Any -import numpy.typing as npt +from typing import Any, List, Optional, Tuple import napari import numpy as np +import numpy.typing as npt from qtpy.QtWidgets import QComboBox, QLabel, QVBoxLayout, QWidget from .base import SingleAxesWidget -from .util import Interval from .features import FEATURES_LAYER_TYPES +from .util import Interval __all__ = ["HistogramWidget", "FeaturesHistogramWidget"] @@ -64,14 +64,16 @@ class FeaturesHistogramWidget(SingleAxesWidget): """ Display a histogram of selected feature attached to selected layer. """ + n_layers_input = Interval(1, 1) # All layers that have a .features attributes input_layer_types = FEATURES_LAYER_TYPES def __init__( - self, - napari_viewer: napari.viewer.Viewer, - parent: Optional[QWidget] = None,): + self, + napari_viewer: napari.viewer.Viewer, + parent: Optional[QWidget] = None, + ): super().__init__(napari_viewer, parent=parent) self.layout().addLayout(QVBoxLayout()) @@ -79,7 +81,9 @@ def __init__( self.layout().addWidget(QLabel("Key:")) self.layout().addWidget(self._key_selection_widget) - self._key_selection_widget.currentTextChanged.connect(self._set_axis_keys) + self._key_selection_widget.currentTextChanged.connect( + self._set_axis_keys + ) self._update_layers(None) @@ -132,10 +136,7 @@ def _get_data(self) -> Tuple[npt.NDArray[Any], str]: feature_table = self.layers[0].features - if ( - (len(feature_table) == 0) - or (self.x_axis_key is None) - ): + if (len(feature_table) == 0) or (self.x_axis_key is None): return [], "" data = feature_table[self.x_axis_key] @@ -156,15 +157,13 @@ def on_update_layers(self) -> None: def draw(self) -> None: """Clear the axes and histogram the currently selected layer/slice.""" - data, x_axis_name = self._get_data() if len(data) == 0: return - self.axes.hist(data, bins=50, edgecolor='white', - linewidth=0.3) + self.axes.hist(data, bins=50, edgecolor="white", linewidth=0.3) # set ax labels self.axes.set_xlabel(x_axis_name) - self.axes.set_ylabel('Counts [#]') + self.axes.set_ylabel("Counts [#]") diff --git a/src/napari_matplotlib/scatter.py b/src/napari_matplotlib/scatter.py index 08464bc4..8a9f55ae 100644 --- a/src/napari_matplotlib/scatter.py +++ b/src/napari_matplotlib/scatter.py @@ -5,11 +5,10 @@ from qtpy.QtWidgets import QComboBox, QLabel, QVBoxLayout, QWidget from .base import SingleAxesWidget -from .util import Interval from .features import FEATURES_LAYER_TYPES +from .util import Interval -__all__ = ["ScatterBaseWidget", "ScatterWidget", - "FeaturesScatterWidget"] +__all__ = ["ScatterBaseWidget", "ScatterWidget", "FeaturesScatterWidget"] class ScatterBaseWidget(SingleAxesWidget): diff --git a/src/napari_matplotlib/tests/test_histogram.py b/src/napari_matplotlib/tests/test_histogram.py index 7c71ff2c..793c1a35 100644 --- a/src/napari_matplotlib/tests/test_histogram.py +++ b/src/napari_matplotlib/tests/test_histogram.py @@ -2,7 +2,7 @@ import pytest -from napari_matplotlib import HistogramWidget, FeaturesHistogramWidget +from napari_matplotlib import FeaturesHistogramWidget, HistogramWidget from napari_matplotlib.tests.helpers import ( assert_figures_equal, assert_figures_not_equal, @@ -35,7 +35,6 @@ def test_histogram_3D(make_napari_viewer, brain_data): def test_feature_histogram(make_napari_viewer): - import numpy as np n_points = 1000 @@ -44,21 +43,25 @@ def test_feature_histogram(make_napari_viewer): feature2 = np.random.normal(size=n_points) viewer = make_napari_viewer() - viewer.add_points(random_points, - properties={'feature1': feature1, 'feature2': feature2}, - name='points1') - viewer.add_points(random_points, - properties={'feature1': feature1, 'feature2': feature2}, - name='points2') + viewer.add_points( + random_points, + properties={"feature1": feature1, "feature2": feature2}, + name="points1", + ) + viewer.add_points( + random_points, + properties={"feature1": feature1, "feature2": feature2}, + name="points2", + ) widget = FeaturesHistogramWidget(viewer) viewer.window.add_dock_widget(widget) # Check whether changing the selected key changes the plot - widget._set_axis_keys('feature1') + widget._set_axis_keys("feature1") fig1 = deepcopy(widget.figure) - widget._set_axis_keys('feature2') + widget._set_axis_keys("feature2") assert_figures_not_equal(widget.figure, fig1) # check whether selecting a different layer produces the same plot From 9d6988e4bf3ca5982475381994275b92d567fce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 30 Jun 2023 13:18:45 +0200 Subject: [PATCH 16/37] added check for `_key_selection_widget` removed `reset_choices()` statement Revert "removed `reset_choices()` statement" This reverts commit ed8fc0acd3a53f446ba71313be5209b0b589caff. removed `reset_choices()` from feature histogram From a2b83caea0f07446d4252d821f8f2f3cc3a27895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 30 Jun 2023 16:19:15 +0200 Subject: [PATCH 17/37] removed `reset_choices` from old widget --- src/napari_matplotlib/scatter.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/napari_matplotlib/scatter.py b/src/napari_matplotlib/scatter.py index 8a9f55ae..a4148bd2 100644 --- a/src/napari_matplotlib/scatter.py +++ b/src/napari_matplotlib/scatter.py @@ -217,13 +217,6 @@ def on_update_layers(self) -> None: """ Called when the layer selection changes by ``self.update_layers()``. """ - if hasattr(self, "_key_selection_widget"): - self._key_selection_widget.reset_choices() - - # reset the axis keys - self._x_axis_key = None - self._y_axis_key = None - # Clear combobox for dim in ["x", "y"]: while self._selectors[dim].count() > 0: From 2c95034c4d70e73a49399ba522b83406dd073a34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 30 Jun 2023 16:43:19 +0200 Subject: [PATCH 18/37] added figure test --- .../tests/baseline/test_feature_histogram.png | Bin 0 -> 9681 bytes src/napari_matplotlib/tests/test_histogram.py | 27 ++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 src/napari_matplotlib/tests/baseline/test_feature_histogram.png diff --git a/src/napari_matplotlib/tests/baseline/test_feature_histogram.png b/src/napari_matplotlib/tests/baseline/test_feature_histogram.png new file mode 100644 index 0000000000000000000000000000000000000000..1892af4467a4aee2346385437d4fc0c45216eccd GIT binary patch literal 9681 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

G!zI=`*>W|$!N)+K$@ar zE%%zsaYfy3kEM-_<{W8L>&4+Dc0}=m0_=!*f^cH&#!iK*Ko~+KwXou(G(O@7oJ8`{ zm$nbc0<{}y+oHNR+V`)SqMeduPMCAEYT%5=72&RLmc8wR$Q$Gu(AW6jByvzjyy5*X z@9<&dxf^A56WTG?UXsL3%;)7VImxKiKy#A_2A}KO>uU+54KVv?r=-4D8Ql>`(61xe zCX6&-N37%@)e33I7S6k3^>j+q+GoU(BuXEH7eS(ryQp>#`EF-xBa%T!baJS19vK;z zHV$N0@v6s91I3RQ$t|ECDNU!33S$mIM$-6o*C5FXo&cJfSY*?($OmNYwei&6;$s_6 zD}plUIu;9MvO^s@TT|Gvsua(bf>9G)6tD{Oz(D%sZ@@tSRYw|6n(%l~>mg>LnwlB8(?GP!YfwvZH-m?r( zh9A~qVp|q0!i%Jc^SELWv}sC;6~Q#6v>*784{0Dqtb&H?n3oN2q>XTIVu-3l%lN{O z+vCqbKj;`VtH%sC)C%i}&EEpHRvZMJg?CZQjy0o^is%vkDCO3r^Ng$5P|fXA-60bN zLm^c%;imCS2ly;Dsqd(wt)ZJhNyK-pPtb|0^`F5M+~E7b2oSld3`_Dg@-@;~7)>7KXns`VW7UM| ze0J95q4nZXP1jGfFET>6M6EJ`y_?TeG&gib{pzkok}-xew_AbVz|}!u;#? zn$$c#u>5vsoP<*AU6Kk=AD~()hU*Z)M-DpLal^au5!|d6axAcXG1-kb=I2D|Kf}|c zSn#HgqV&D+CcvDJ5I-iwzSkeN!`T%PjQovdjt92T)U>rb9Kk0WC3-ui9*_u+1hwk3Z9U^cwZKZc?oE?vabU;BU@4YQbCyTfH=9c%A3@2x-zD6qe zZb#XowW1%dISPQB+M~zz5qd#%Dy;9F;>S4gI?8d*jO7cwuQ!9kg3nkB!K?1^Ok0LO zqsBY)SYX(Y^)O0K79U}hIQ#@rD{d>2DT!a-E91p(uDq7s3wTz64eY=yEb^l!c^^Co zs0aeO;BI@#WIpQD^pCv&8QRl2C#^y`fLwN#ahQJf%N4V Date: Tue, 4 Jul 2023 11:47:44 +0200 Subject: [PATCH 19/37] added baseline image for histogram creation --- baseline/test_feature_histogram2.png | Bin 0 -> 12860 bytes src/napari_matplotlib/tests/test_histogram.py | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 baseline/test_feature_histogram2.png diff --git a/baseline/test_feature_histogram2.png b/baseline/test_feature_histogram2.png new file mode 100644 index 0000000000000000000000000000000000000000..b7bb19e039d73adfd05f0d3860a5a1c4d0711f72 GIT binary patch literal 12860 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 Date: Mon, 31 Jul 2023 20:37:13 +0000 Subject: [PATCH 20/37] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.0.280 → v0.0.281](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.280...v0.0.281) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index caf38d16..1fb3a9df 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,7 +29,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.0.280' + rev: 'v0.0.281' hooks: - id: ruff From 523212962063ad4f7173c892126f6cb7c8d1062d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 20:22:12 +0000 Subject: [PATCH 21/37] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.4.1 → v1.5.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.4.1...v1.5.0) - [github.com/astral-sh/ruff-pre-commit: v0.0.281 → v0.0.284](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.281...v0.0.284) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1fb3a9df..33aee57b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,14 +22,14 @@ repos: - id: napari-plugin-checks - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.4.1 + rev: v1.5.0 hooks: - id: mypy additional_dependencies: [numpy, matplotlib] - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.0.281' + rev: 'v0.0.284' hooks: - id: ruff From f1360063fdad4314156881eb32142d874f8920bc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 21 Aug 2023 20:26:54 +0000 Subject: [PATCH 22/37] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.5.0 → v1.5.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.5.0...v1.5.1) - [github.com/astral-sh/ruff-pre-commit: v0.0.284 → v0.0.285](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.284...v0.0.285) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 33aee57b..4e322a0d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,14 +22,14 @@ repos: - id: napari-plugin-checks - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.5.0 + rev: v1.5.1 hooks: - id: mypy additional_dependencies: [numpy, matplotlib] - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.0.284' + rev: 'v0.0.285' hooks: - id: ruff From 2119f45d6d1133b6d818334cdd1920d3ea55a77f Mon Sep 17 00:00:00 2001 From: David Stansby Date: Wed, 23 Aug 2023 14:34:57 +0100 Subject: [PATCH 23/37] Fix testing extra name --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 229b5777..e1fc9e73 100644 --- a/setup.cfg +++ b/setup.cfg @@ -55,7 +55,7 @@ docs = sphinx-automodapi sphinx-gallery testing = - napari[pyqt6-experimental]>=0.4.18 + napari[pyqt6_experimental]>=0.4.18 pooch pyqt6 pytest From 70248a71822c364ba526e45acd53337776b1a3d1 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Wed, 23 Aug 2023 14:41:57 +0100 Subject: [PATCH 24/37] Remove setup-cfg-format from pre-commit --- .pre-commit-config.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4e322a0d..b78e43e1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,11 +6,6 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - - repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.4.0 - hooks: - - id: setup-cfg-fmt - - repo: https://github.com/psf/black rev: 23.7.0 hooks: From 726b66aca1ba56a690264501e75272258211dbad Mon Sep 17 00:00:00 2001 From: David Stansby Date: Wed, 23 Aug 2023 14:27:14 +0100 Subject: [PATCH 25/37] Explcitly check dimensions in slice tests --- src/napari_matplotlib/tests/conftest.py | 4 +++- src/napari_matplotlib/tests/test_slice.py | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/napari_matplotlib/tests/conftest.py b/src/napari_matplotlib/tests/conftest.py index 6b2a813f..4d07c706 100644 --- a/src/napari_matplotlib/tests/conftest.py +++ b/src/napari_matplotlib/tests/conftest.py @@ -1,7 +1,9 @@ import os from pathlib import Path +from typing import Any, Dict, Tuple import numpy as np +import numpy.typing as npt import pytest from skimage import data @@ -18,7 +20,7 @@ def image_data(request): @pytest.fixture -def astronaut_data(): +def astronaut_data() -> Tuple[npt.NDArray[Any], Dict[Any, Any]]: return data.astronaut(), {"rgb": True} diff --git a/src/napari_matplotlib/tests/test_slice.py b/src/napari_matplotlib/tests/test_slice.py index 412e71c3..fa25c3d4 100644 --- a/src/napari_matplotlib/tests/test_slice.py +++ b/src/napari_matplotlib/tests/test_slice.py @@ -9,7 +9,11 @@ def test_slice_3D(make_napari_viewer, brain_data): viewer = make_napari_viewer() viewer.theme = "light" + + data = brain_data[0] + assert data.ndim == 3 viewer.add_image(brain_data[0], **brain_data[1]) + axis = viewer.dims.last_used slice_no = brain_data[0].shape[0] - 1 viewer.dims.set_current_step(axis, slice_no) @@ -23,7 +27,11 @@ def test_slice_3D(make_napari_viewer, brain_data): def test_slice_2D(make_napari_viewer, astronaut_data): viewer = make_napari_viewer() viewer.theme = "light" + + data = astronaut_data[0] + assert data.ndim == 2 viewer.add_image(astronaut_data[0], **astronaut_data[1]) + fig = SliceWidget(viewer).figure # Need to return a copy, as original figure is too eagerley garbage # collected by the widget From 8ac1cd2c68118123017135622f54bc443ea032b8 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Wed, 23 Aug 2023 14:47:42 +0100 Subject: [PATCH 26/37] Fix data dimensions --- src/napari_matplotlib/tests/test_slice.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/napari_matplotlib/tests/test_slice.py b/src/napari_matplotlib/tests/test_slice.py index fa25c3d4..0111e185 100644 --- a/src/napari_matplotlib/tests/test_slice.py +++ b/src/napari_matplotlib/tests/test_slice.py @@ -11,11 +11,11 @@ def test_slice_3D(make_napari_viewer, brain_data): viewer.theme = "light" data = brain_data[0] - assert data.ndim == 3 - viewer.add_image(brain_data[0], **brain_data[1]) + assert data.ndim == 3, data.shape + viewer.add_image(data, **brain_data[1]) axis = viewer.dims.last_used - slice_no = brain_data[0].shape[0] - 1 + slice_no = data.shape[0] - 1 viewer.dims.set_current_step(axis, slice_no) fig = SliceWidget(viewer).figure # Need to return a copy, as original figure is too eagerley garbage @@ -28,9 +28,10 @@ def test_slice_2D(make_napari_viewer, astronaut_data): viewer = make_napari_viewer() viewer.theme = "light" - data = astronaut_data[0] - assert data.ndim == 2 - viewer.add_image(astronaut_data[0], **astronaut_data[1]) + # Take first RGB channel + data = astronaut_data[0][:, :, 0] + assert data.ndim == 2, data.shape + viewer.add_image(data, **astronaut_data[1]) fig = SliceWidget(viewer).figure # Need to return a copy, as original figure is too eagerley garbage From 2b026ec08cc2bf9bfa5acfb4305d816316bef1bd Mon Sep 17 00:00:00 2001 From: David Stansby Date: Wed, 23 Aug 2023 14:50:26 +0100 Subject: [PATCH 27/37] Update 3D slice test image --- .../tests/baseline/test_slice_3D.png | Bin 14698 -> 14356 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/napari_matplotlib/tests/baseline/test_slice_3D.png b/src/napari_matplotlib/tests/baseline/test_slice_3D.png index 43c8c3b6c0b276f89860ef1b630fafdd219070b9..c30211dab12d3c276a1427990fc86c01b2dff46a 100644 GIT binary patch literal 14356 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 From bc496fcb528239466379b0ffea2f067760c89247 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Wed, 23 Aug 2023 14:50:52 +0100 Subject: [PATCH 28/37] Fix adding 2D slice data --- src/napari_matplotlib/tests/test_slice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/napari_matplotlib/tests/test_slice.py b/src/napari_matplotlib/tests/test_slice.py index 0111e185..32eb9ad4 100644 --- a/src/napari_matplotlib/tests/test_slice.py +++ b/src/napari_matplotlib/tests/test_slice.py @@ -31,7 +31,7 @@ def test_slice_2D(make_napari_viewer, astronaut_data): # Take first RGB channel data = astronaut_data[0][:, :, 0] assert data.ndim == 2, data.shape - viewer.add_image(data, **astronaut_data[1]) + viewer.add_image(data) fig = SliceWidget(viewer).figure # Need to return a copy, as original figure is too eagerley garbage From ed278ec3471dff3845a465c9329a9cfb669f8d79 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Wed, 23 Aug 2023 14:59:59 +0100 Subject: [PATCH 29/37] Fix slicing 2D images --- src/napari_matplotlib/slice.py | 37 +++++++++++++----- .../tests/baseline/test_slice_2D.png | Bin 21061 -> 33587 bytes 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/napari_matplotlib/slice.py b/src/napari_matplotlib/slice.py index e3aa80b2..f0d01f3f 100644 --- a/src/napari_matplotlib/slice.py +++ b/src/napari_matplotlib/slice.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Optional, Tuple +from typing import Any, Dict, List, Optional, Tuple import matplotlib.ticker as mticker import napari @@ -12,7 +12,6 @@ __all__ = ["SliceWidget"] _dims_sel = ["x", "y"] -_dims = ["x", "y", "z"] class SliceWidget(SingleAxesWidget): @@ -37,7 +36,7 @@ def __init__( self.dim_selector = QComboBox() button_layout.addWidget(QLabel("Slice axis:")) button_layout.addWidget(self.dim_selector) - self.dim_selector.addItems(_dims) + self.dim_selector.addItems(["x", "y", "z"]) self.slice_selectors = {} for d in _dims_sel: @@ -61,7 +60,7 @@ def _layer(self) -> napari.layers.Layer: return self.layers[0] @property - def current_dim(self) -> str: + def current_dim_name(self) -> str: """ Currently selected slice dimension. """ @@ -74,12 +73,27 @@ def current_dim_index(self) -> int: """ # Note the reversed list because in napari the z-axis is the first # numpy axis - return _dims[::-1].index(self.current_dim) + return self._dim_names[::-1].index(self.current_dim_name) + + @property + def _dim_names(self) -> List[str]: + """ + List of dimension names. This is a property as it varies depending on the + dimensionality of the currently selected data. + """ + if self._layer.data.ndim == 2: + return ["x", "y"] + elif self._layer.data.ndim == 3: + return ["x", "y", "z"] + else: + raise RuntimeError("Don't know how to handle ndim != 2 or 3") @property def _selector_values(self) -> Dict[str, int]: """ Values of the slice selectors. + + Mapping from dimension name to value. """ return {d: self.slice_selectors[d].value() for d in _dims_sel} @@ -87,19 +101,22 @@ def _get_xy(self) -> Tuple[npt.NDArray[Any], npt.NDArray[Any]]: """ Get data for plotting. """ - x = np.arange(self._layer.data.shape[self.current_dim_index]) + dim_index = self.current_dim_index + if self._layer.data.ndim == 2: + dim_index -= 1 + x = np.arange(self._layer.data.shape[dim_index]) vals = self._selector_values vals.update({"z": self.current_z}) slices = [] - for d in _dims: - if d == self.current_dim: + for dim_name in self._dim_names: + if dim_name == self.current_dim_name: # Select all data along this axis slices.append(slice(None)) else: # Select specific index - val = vals[d] + val = vals[dim_name] slices.append(slice(val, val + 1)) # Reverse since z is the first axis in napari @@ -115,7 +132,7 @@ def draw(self) -> None: x, y = self._get_xy() self.axes.plot(x, y) - self.axes.set_xlabel(self.current_dim) + self.axes.set_xlabel(self.current_dim_name) self.axes.set_title(self._layer.name) # Make sure all ticks lie on integer values self.axes.xaxis.set_major_locator( diff --git a/src/napari_matplotlib/tests/baseline/test_slice_2D.png b/src/napari_matplotlib/tests/baseline/test_slice_2D.png index 639f25b84b2bfc224f3344d75995265fa56aa79e..ee3ce3b69a960ddfb07c347ef92c8c5f351d2472 100644 GIT binary patch literal 33587 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 Date: Wed, 23 Aug 2023 15:00:59 +0100 Subject: [PATCH 30/37] Add changelog --- docs/changelog.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 103cd80e..f7425bfe 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,5 +1,11 @@ Changelog ========= +1.0.3 +----- +Bug fixes +~~~~~~~~~ +- Fixed creating 1D slices of 2D images. + 1.0.2 ----- Bug fixes From 08ee5d097caa97e09805fe5faae1eb8b29e9042b Mon Sep 17 00:00:00 2001 From: David Stansby Date: Wed, 23 Aug 2023 15:05:22 +0100 Subject: [PATCH 31/37] Remove conftest typing --- src/napari_matplotlib/tests/conftest.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/napari_matplotlib/tests/conftest.py b/src/napari_matplotlib/tests/conftest.py index 4d07c706..6b2a813f 100644 --- a/src/napari_matplotlib/tests/conftest.py +++ b/src/napari_matplotlib/tests/conftest.py @@ -1,9 +1,7 @@ import os from pathlib import Path -from typing import Any, Dict, Tuple import numpy as np -import numpy.typing as npt import pytest from skimage import data @@ -20,7 +18,7 @@ def image_data(request): @pytest.fixture -def astronaut_data() -> Tuple[npt.NDArray[Any], Dict[Any, Any]]: +def astronaut_data(): return data.astronaut(), {"rgb": True} From a9d41dbfd73311577e9aa6bed3c00c24f30981d6 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Wed, 23 Aug 2023 16:31:10 +0100 Subject: [PATCH 32/37] Fix slicing selection --- src/napari_matplotlib/slice.py | 85 ++++++++++++----------- src/napari_matplotlib/tests/test_slice.py | 24 +++++++ 2 files changed, 70 insertions(+), 39 deletions(-) diff --git a/src/napari_matplotlib/slice.py b/src/napari_matplotlib/slice.py index f0d01f3f..43635a83 100644 --- a/src/napari_matplotlib/slice.py +++ b/src/napari_matplotlib/slice.py @@ -1,18 +1,23 @@ -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, List, Optional, Tuple import matplotlib.ticker as mticker import napari import numpy as np import numpy.typing as npt -from qtpy.QtWidgets import QComboBox, QHBoxLayout, QLabel, QSpinBox, QWidget +from qtpy.QtCore import Qt +from qtpy.QtWidgets import ( + QComboBox, + QLabel, + QSlider, + QVBoxLayout, + QWidget, +) from .base import SingleAxesWidget from .util import Interval __all__ = ["SliceWidget"] -_dims_sel = ["x", "y"] - class SliceWidget(SingleAxesWidget): """ @@ -30,28 +35,44 @@ def __init__( # Setup figure/axes super().__init__(napari_viewer, parent=parent) - button_layout = QHBoxLayout() - self.layout().addLayout(button_layout) - self.dim_selector = QComboBox() + self.dim_selector.addItems(["x", "y"]) + + self.slice_selector = QSlider(orientation=Qt.Orientation.Horizontal) + + # Create widget layout + button_layout = QVBoxLayout() button_layout.addWidget(QLabel("Slice axis:")) button_layout.addWidget(self.dim_selector) - self.dim_selector.addItems(["x", "y", "z"]) - - self.slice_selectors = {} - for d in _dims_sel: - self.slice_selectors[d] = QSpinBox() - button_layout.addWidget(QLabel(f"{d}:")) - button_layout.addWidget(self.slice_selectors[d]) + button_layout.addWidget(self.slice_selector) + self.layout().addLayout(button_layout) # Setup callbacks - # Re-draw when any of the combon/spin boxes are updated + # Re-draw when any of the combo/slider is updated self.dim_selector.currentTextChanged.connect(self._draw) - for d in _dims_sel: - self.slice_selectors[d].textChanged.connect(self._draw) + self.slice_selector.valueChanged.connect(self._draw) self._update_layers(None) + def on_update_layers(self) -> None: + """ + Called when layer selection is updated. + """ + if self.current_dim_name == "x": + max = self._layer.data.shape[-2] + elif self.current_dim_name == "y": + max = self._layer.data.shape[-1] + else: + raise RuntimeError("dim name must be x or y") + self.slice_selector.setRange(0, max) + + @property + def _slice_width(self) -> int: + """ + Width of the slice being plotted. + """ + return self._layer.data.shape[self.current_dim_index] - 1 + @property def _layer(self) -> napari.layers.Layer: """ @@ -73,7 +94,7 @@ def current_dim_index(self) -> int: """ # Note the reversed list because in napari the z-axis is the first # numpy axis - return self._dim_names[::-1].index(self.current_dim_name) + return self._dim_names.index(self.current_dim_name) @property def _dim_names(self) -> List[str]: @@ -82,45 +103,31 @@ def _dim_names(self) -> List[str]: dimensionality of the currently selected data. """ if self._layer.data.ndim == 2: - return ["x", "y"] + return ["y", "x"] elif self._layer.data.ndim == 3: - return ["x", "y", "z"] + return ["z", "y", "x"] else: raise RuntimeError("Don't know how to handle ndim != 2 or 3") - @property - def _selector_values(self) -> Dict[str, int]: - """ - Values of the slice selectors. - - Mapping from dimension name to value. - """ - return {d: self.slice_selectors[d].value() for d in _dims_sel} - def _get_xy(self) -> Tuple[npt.NDArray[Any], npt.NDArray[Any]]: """ Get data for plotting. """ - dim_index = self.current_dim_index - if self._layer.data.ndim == 2: - dim_index -= 1 - x = np.arange(self._layer.data.shape[dim_index]) - - vals = self._selector_values - vals.update({"z": self.current_z}) + val = self.slice_selector.value() slices = [] for dim_name in self._dim_names: if dim_name == self.current_dim_name: # Select all data along this axis slices.append(slice(None)) + elif dim_name == "z": + # Only select the currently viewed z-index + slices.append(slice(self.current_z, self.current_z + 1)) else: # Select specific index - val = vals[dim_name] slices.append(slice(val, val + 1)) - # Reverse since z is the first axis in napari - slices = slices[::-1] + x = np.arange(self._slice_width) y = self._layer.data[tuple(slices)].ravel() return x, y diff --git a/src/napari_matplotlib/tests/test_slice.py b/src/napari_matplotlib/tests/test_slice.py index 32eb9ad4..ab38f716 100644 --- a/src/napari_matplotlib/tests/test_slice.py +++ b/src/napari_matplotlib/tests/test_slice.py @@ -37,3 +37,27 @@ def test_slice_2D(make_napari_viewer, astronaut_data): # Need to return a copy, as original figure is too eagerley garbage # collected by the widget return deepcopy(fig) + + +def test_slice_axes(make_napari_viewer, astronaut_data): + viewer = make_napari_viewer() + viewer.theme = "light" + + # Take first RGB channel + data = astronaut_data[0][:256, :, 0] + # Shape: + # x: 0 > 512 + # y: 0 > 256 + assert data.ndim == 2, data.shape + # Make sure data isn't square for later tests + assert data.shape[0] != data.shape[1] + viewer.add_image(data) + + widget = SliceWidget(viewer) + assert widget._dim_names == ["y", "x"] + assert widget.current_dim_name == "x" + assert widget.slice_selector.value() == 0 + assert widget.slice_selector.minimum() == 0 + assert widget.slice_selector.maximum() == data.shape[0] + # x/y are flipped in napari + assert widget._slice_width == data.shape[1] From b73bf6fb8415ba8a1c8fe2eef8501d2e1459ccbb Mon Sep 17 00:00:00 2001 From: David Stansby Date: Wed, 23 Aug 2023 16:33:27 +0100 Subject: [PATCH 33/37] Add changelog --- docs/changelog.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index f7425bfe..2304fecf 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -2,9 +2,17 @@ Changelog ========= 1.0.3 ----- +Changes +~~~~~~~ +- The slice widget is now limited to slicing along the x/y dimensions. Support + for slicing along z has been removed for now to make the code simpler. +- The slice widget now uses a slider to select the slice value. + Bug fixes ~~~~~~~~~ - Fixed creating 1D slices of 2D images. +- Removed the limitation that only the first 99 indices could be sliced using + the slice widget. 1.0.2 ----- From 9eb43eefaa4e114b9b1772c8df59e66963afce11 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Wed, 23 Aug 2023 16:44:14 +0100 Subject: [PATCH 34/37] Fix tests --- src/napari_matplotlib/slice.py | 6 ++++-- src/napari_matplotlib/tests/test_slice.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/napari_matplotlib/slice.py b/src/napari_matplotlib/slice.py index 43635a83..393f2e45 100644 --- a/src/napari_matplotlib/slice.py +++ b/src/napari_matplotlib/slice.py @@ -58,20 +58,22 @@ def on_update_layers(self) -> None: """ Called when layer selection is updated. """ + if not len(self.layers): + return if self.current_dim_name == "x": max = self._layer.data.shape[-2] elif self.current_dim_name == "y": max = self._layer.data.shape[-1] else: raise RuntimeError("dim name must be x or y") - self.slice_selector.setRange(0, max) + self.slice_selector.setRange(0, max - 1) @property def _slice_width(self) -> int: """ Width of the slice being plotted. """ - return self._layer.data.shape[self.current_dim_index] - 1 + return self._layer.data.shape[self.current_dim_index] @property def _layer(self) -> napari.layers.Layer: diff --git a/src/napari_matplotlib/tests/test_slice.py b/src/napari_matplotlib/tests/test_slice.py index ab38f716..368a7ded 100644 --- a/src/napari_matplotlib/tests/test_slice.py +++ b/src/napari_matplotlib/tests/test_slice.py @@ -58,6 +58,6 @@ def test_slice_axes(make_napari_viewer, astronaut_data): assert widget.current_dim_name == "x" assert widget.slice_selector.value() == 0 assert widget.slice_selector.minimum() == 0 - assert widget.slice_selector.maximum() == data.shape[0] + assert widget.slice_selector.maximum() == data.shape[0] - 1 # x/y are flipped in napari assert widget._slice_width == data.shape[1] From 5e9604b9789b121517bdd3f8444511e9796e1cac Mon Sep 17 00:00:00 2001 From: David Stansby Date: Fri, 25 Aug 2023 16:58:28 +0100 Subject: [PATCH 35/37] Fix test figure location --- .../tests/baseline/test_feature_histogram2.png | Bin 0 -> 12860 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/napari_matplotlib/tests/baseline/test_feature_histogram2.png diff --git a/src/napari_matplotlib/tests/baseline/test_feature_histogram2.png b/src/napari_matplotlib/tests/baseline/test_feature_histogram2.png new file mode 100644 index 0000000000000000000000000000000000000000..b7bb19e039d73adfd05f0d3860a5a1c4d0711f72 GIT binary patch literal 12860 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 Date: Fri, 25 Aug 2023 17:00:10 +0100 Subject: [PATCH 36/37] Run pre-commit and fix typing --- src/napari_matplotlib/histogram.py | 8 ++++---- src/napari_matplotlib/tests/test_histogram.py | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index c5eb9f41..66aa7acc 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -122,7 +122,7 @@ def _get_valid_axis_keys(self) -> List[str]: else: return self.layers[0].features.keys() - def _get_data(self) -> Tuple[npt.NDArray[Any], str]: + def _get_data(self) -> Tuple[Optional[npt.NDArray[Any]], str]: """Get the plot data. Returns @@ -137,12 +137,12 @@ def _get_data(self) -> Tuple[npt.NDArray[Any], str]: if not hasattr(self.layers[0], "features"): # if the selected layer doesn't have a featuretable, # skip draw - return [], "" + return None, "" feature_table = self.layers[0].features if (len(feature_table) == 0) or (self.x_axis_key is None): - return [], "" + return None, "" data = feature_table[self.x_axis_key] x_axis_name = self.x_axis_key.replace("_", " ") @@ -164,7 +164,7 @@ def draw(self) -> None: """Clear the axes and histogram the currently selected layer/slice.""" data, x_axis_name = self._get_data() - if len(data) == 0: + if data is None: return self.axes.hist(data, bins=50, edgecolor="white", linewidth=0.3) diff --git a/src/napari_matplotlib/tests/test_histogram.py b/src/napari_matplotlib/tests/test_histogram.py index a478a2c2..006c042f 100644 --- a/src/napari_matplotlib/tests/test_histogram.py +++ b/src/napari_matplotlib/tests/test_histogram.py @@ -1,5 +1,6 @@ from copy import deepcopy +import numpy as np import pytest from napari_matplotlib import FeaturesHistogramWidget, HistogramWidget @@ -35,8 +36,6 @@ def test_histogram_3D(make_napari_viewer, brain_data): def test_feature_histogram(make_napari_viewer): - import numpy as np - n_points = 1000 random_points = np.random.random((n_points, 3)) * 10 feature1 = np.random.random(n_points) @@ -72,7 +71,8 @@ def test_feature_histogram(make_napari_viewer): @pytest.mark.mpl_image_compare def test_feature_histogram2(make_napari_viewer): - import numpy as np + import numpy as np + np.random.seed(0) n_points = 1000 random_points = np.random.random((n_points, 3)) * 10 From e1ccfb18d4c48abd14037490e1acd4bf3a27baa1 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Fri, 25 Aug 2023 16:55:09 +0100 Subject: [PATCH 37/37] Update docs --- docs/changelog.rst | 6 +++++- docs/user_guide.rst | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 2304fecf..6f77e0c3 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,7 +1,11 @@ Changelog ========= -1.0.3 +1.1.0 ----- +Additions +~~~~~~~~~ +- Added a widget to draw a histogram of features. + Changes ~~~~~~~ - The slice widget is now limited to slicing along the x/y dimensions. Support diff --git a/docs/user_guide.rst b/docs/user_guide.rst index 0872e540..fbd48db1 100644 --- a/docs/user_guide.rst +++ b/docs/user_guide.rst @@ -30,6 +30,7 @@ These widgets plot the data stored in the ``.features`` attribute of individual Currently available are: - 2D scatter plots of two features against each other. +- Histograms of individual features. To use these: