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: pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy