From a7b09d4ce297cad9dd6fa424403c8398ec20bc74 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Thu, 11 Jan 2024 10:55:41 +0000 Subject: [PATCH 01/68] Add widgets to set bin parameters for histograms Also set np.linspace dtype based on image dtype --- src/napari_matplotlib/histogram.py | 130 +++++++++++++++++++++++++---- 1 file changed, 116 insertions(+), 14 deletions(-) diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index 2db2f08..fd44d6f 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -4,12 +4,7 @@ import numpy as np import numpy.typing as npt from matplotlib.container import BarContainer -from qtpy.QtWidgets import ( - QComboBox, - QLabel, - QVBoxLayout, - QWidget, -) +from qtpy.QtWidgets import QComboBox, QLabel, QVBoxLayout, QWidget, QGroupBox, QFormLayout, QDoubleSpinBox, QSpinBox, QAbstractSpinBox from .base import SingleAxesWidget from .features import FEATURES_LAYER_TYPES @@ -34,6 +29,50 @@ def __init__( parent: Optional[QWidget] = None, ): super().__init__(napari_viewer, parent=parent) + + # Create widgets for setting bin parameters + bins_start = QDoubleSpinBox() + bins_start.setObjectName("bins start") + bins_start.setStepType(QAbstractSpinBox.AdaptiveDecimalStepType) + bins_start.setRange(-1e10, 1e10) + bins_start.setValue(0) + bins_start.setWrapping(True) + bins_start.setKeyboardTracking(False) + bins_start.setDecimals(2) + + bins_stop = QDoubleSpinBox() + bins_stop.setObjectName("bins stop") + bins_stop.setStepType(QAbstractSpinBox.AdaptiveDecimalStepType) + bins_stop.setRange(-1e10, 1e10) + bins_stop.setValue(100) + bins_stop.setKeyboardTracking(False) + bins_stop.setDecimals(2) + + bins_num = QSpinBox() + bins_num.setObjectName("bins num") + bins_num.setRange(1, 100_000) + bins_num.setValue(101) + bins_num.setWrapping(False) + bins_num.setKeyboardTracking(False) + + # Set bins widget layout + bins_selection_layout = QFormLayout() + bins_selection_layout.addRow("start", bins_start) + bins_selection_layout.addRow("stop", bins_stop) + bins_selection_layout.addRow("num", bins_num) + + # Group the widgets and add to main layout + bins_widget_group = QGroupBox("Bins") + bins_widget_group_layout = QVBoxLayout() + bins_widget_group_layout.addLayout(bins_selection_layout) + bins_widget_group.setLayout(bins_widget_group_layout) + self.layout().addWidget(bins_widget_group) + + # Add callbacks + bins_start.valueChanged.connect(self._draw) + bins_stop.valueChanged.connect(self._draw) + bins_num.valueChanged.connect(self._draw) + self._update_layers(None) self.viewer.events.theme.connect(self._on_napari_theme_changed) @@ -53,11 +92,47 @@ def _update_contrast_lims(self) -> None: self.figure.canvas.draw() - def draw(self) -> None: - """ - Clear the axes and histogram the currently selected layer/slice. - """ - layer = self.layers[0] + @property + def bins_start(self) -> float: + """Minimum bin edge""" + return self.findChild(QDoubleSpinBox, name="bins start").value() + + @bins_start.setter + def bins_start(self, start: int | float) -> None: + """Set the minimum bin edge""" + self.findChild(QDoubleSpinBox, name="bins start").setValue(start) + + @property + def bins_stop(self) -> float: + """Maximum bin edge""" + return self.findChild(QDoubleSpinBox, name="bins stop").value() + + @bins_stop.setter + def bins_stop(self, stop: int | float) -> None: + """Set the maximum bin edge""" + self.findChild(QDoubleSpinBox, name="bins stop").setValue(stop) + + @property + def bins_num(self) -> int: + """Number of bins to use""" + return self.findChild(QSpinBox, name="bins num").value() + + @bins_num.setter + def bins_num(self, num: int) -> None: + """Set the number of bins to use""" + self.findChild(QSpinBox, name="bins num").setValue(num) + + def autoset_widget_bins(self, data: npt.ArrayLike) -> None: + """Update widgets with bins determined from the image data""" + + bins = np.linspace(np.min(data), np.max(data), 100, dtype=data.dtype) + self.bins_start = bins[0] + self.bins_stop = bins[-1] + self.bins_num = bins.size + + + def _get_layer_data(self, layer) -> np.ndarray: + """Get the data associated with a given layer""" if layer.data.ndim - layer.rgb == 3: # 3D data, can be single channel or RGB @@ -65,18 +140,45 @@ def draw(self) -> None: self.axes.set_title(f"z={self.current_z}") else: data = layer.data + # Read data into memory if it's a dask array data = np.asarray(data) + return data + + def on_update_layers(self) -> None: + """ + Called when the layer selection changes by ``self._update_layers()``. + """ + + if not self.layers: + return + + # Reset to bin start, stop and step + layer_data = self._get_layer_data(self.layers[0]) + self.autoset_widget_bins(data=layer_data) + + # Only allow integer bins for integer data + n_decimals = 0 if np.issubdtype(layer_data.dtype, np.integer) else 2 + self.findChild(QDoubleSpinBox, name="bins start").setDecimals(n_decimals) + self.findChild(QDoubleSpinBox, name="bins stop").setDecimals(n_decimals) + + def draw(self) -> None: + """ + Clear the axes and histogram the currently selected layer/slice. + """ + layer = self.layers[0] + data = self._get_layer_data(layer) + # Important to calculate bins after slicing 3D data, to avoid reading # whole cube into memory. if data.dtype.kind in {"i", "u"}: # Make sure integer data types have integer sized bins - step = abs(np.max(data) - np.min(data)) // 100 + step = (self.bins_start - self.bins_stop) // self.bins_num step = max(1, step) - bins = np.arange(np.min(data), np.max(data) + step, step) + bins = np.arange(self.bins_start, self.bins_stop + step, step) else: - bins = np.linspace(np.min(data), np.max(data), 100) + bins = np.linspace(self.bins_start, self.bins_stop, self.bins_num) if layer.rgb: # Histogram RGB channels independently From 96b2f0cd3cc6f5400ba4f8eaca71ea9482a56a07 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Thu, 11 Jan 2024 11:44:02 +0000 Subject: [PATCH 02/68] Add test for histogram widget when setting bin parameters --- .../tests/baseline/test_histogram_2D_bins.png | Bin 0 -> 21366 bytes src/napari_matplotlib/tests/test_histogram.py | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 src/napari_matplotlib/tests/baseline/test_histogram_2D_bins.png diff --git a/src/napari_matplotlib/tests/baseline/test_histogram_2D_bins.png b/src/napari_matplotlib/tests/baseline/test_histogram_2D_bins.png new file mode 100644 index 0000000000000000000000000000000000000000..eb43c3108147079f9410a40936975d94158ac6f0 GIT binary patch literal 21366 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 b-^Cj#YN-+8Uxv&S3Wb(bypnn8+Wr3z5b2@o literal 0 HcmV?d00001 diff --git a/src/napari_matplotlib/tests/test_histogram.py b/src/napari_matplotlib/tests/test_histogram.py index 1ceca51..b392ef5 100644 --- a/src/napari_matplotlib/tests/test_histogram.py +++ b/src/napari_matplotlib/tests/test_histogram.py @@ -9,6 +9,20 @@ assert_figures_not_equal, ) +@pytest.mark.mpl_image_compare +def test_histogram_2D_bins(make_napari_viewer, astronaut_data): + viewer = make_napari_viewer() + viewer.theme = "light" + viewer.add_image(astronaut_data[0], **astronaut_data[1]) + widget = HistogramWidget(viewer) + viewer.window.add_dock_widget(widget) + widget.bins_start = -50 + widget.bins_stop = 300 + widget.bins_num = 35 + fig = widget.figure + # Need to return a copy, as original figure is too eagerley garbage + # collected by the widget + return deepcopy(fig) @pytest.mark.mpl_image_compare def test_histogram_2D(make_napari_viewer, astronaut_data): @@ -20,7 +34,6 @@ def test_histogram_2D(make_napari_viewer, astronaut_data): # collected by the widget return deepcopy(fig) - @pytest.mark.mpl_image_compare def test_histogram_3D(make_napari_viewer, brain_data): viewer = make_napari_viewer() From b8623ededfa0dd4c922f507a38a9430b86665328 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Thu, 11 Jan 2024 11:50:21 +0000 Subject: [PATCH 03/68] Update changelog --- docs/changelog.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index cb591f9..96b353d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -7,6 +7,11 @@ Changes - Dropped support for Python 3.8, and added support for Python 3.11. - Histogram plots of points and vector layers are now coloured with their napari colourmap. - Added support for Matplotlib 3.8 +- Add widgets for setting histogram bin parameters + +Bug fixes +~~~~~~~~~ +- Use integer bin limits for integer images in ``HistogramWidget`` Bug fixes ~~~~~~~~~ From 4e4fb84b8dc7a1049dd7b1795425d06e117310f5 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Thu, 11 Jan 2024 11:52:56 +0000 Subject: [PATCH 04/68] Make linters happy --- src/napari_matplotlib/histogram.py | 24 +++++++++++++------ src/napari_matplotlib/tests/test_histogram.py | 3 +++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index fd44d6f..c527266 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -4,7 +4,17 @@ import numpy as np import numpy.typing as npt from matplotlib.container import BarContainer -from qtpy.QtWidgets import QComboBox, QLabel, QVBoxLayout, QWidget, QGroupBox, QFormLayout, QDoubleSpinBox, QSpinBox, QAbstractSpinBox +from qtpy.QtWidgets import ( + QAbstractSpinBox, + QComboBox, + QDoubleSpinBox, + QFormLayout, + QGroupBox, + QLabel, + QSpinBox, + QVBoxLayout, + QWidget, +) from .base import SingleAxesWidget from .features import FEATURES_LAYER_TYPES @@ -124,16 +134,13 @@ def bins_num(self, num: int) -> None: def autoset_widget_bins(self, data: npt.ArrayLike) -> None: """Update widgets with bins determined from the image data""" - bins = np.linspace(np.min(data), np.max(data), 100, dtype=data.dtype) self.bins_start = bins[0] self.bins_stop = bins[-1] self.bins_num = bins.size - def _get_layer_data(self, layer) -> np.ndarray: """Get the data associated with a given layer""" - if layer.data.ndim - layer.rgb == 3: # 3D data, can be single channel or RGB data = layer.data[self.current_z] @@ -150,7 +157,6 @@ def on_update_layers(self) -> None: """ Called when the layer selection changes by ``self._update_layers()``. """ - if not self.layers: return @@ -160,8 +166,12 @@ def on_update_layers(self) -> None: # Only allow integer bins for integer data n_decimals = 0 if np.issubdtype(layer_data.dtype, np.integer) else 2 - self.findChild(QDoubleSpinBox, name="bins start").setDecimals(n_decimals) - self.findChild(QDoubleSpinBox, name="bins stop").setDecimals(n_decimals) + self.findChild(QDoubleSpinBox, name="bins start").setDecimals( + n_decimals + ) + self.findChild(QDoubleSpinBox, name="bins stop").setDecimals( + n_decimals + ) def draw(self) -> None: """ diff --git a/src/napari_matplotlib/tests/test_histogram.py b/src/napari_matplotlib/tests/test_histogram.py index b392ef5..58acf23 100644 --- a/src/napari_matplotlib/tests/test_histogram.py +++ b/src/napari_matplotlib/tests/test_histogram.py @@ -9,6 +9,7 @@ assert_figures_not_equal, ) + @pytest.mark.mpl_image_compare def test_histogram_2D_bins(make_napari_viewer, astronaut_data): viewer = make_napari_viewer() @@ -24,6 +25,7 @@ def test_histogram_2D_bins(make_napari_viewer, astronaut_data): # collected by the widget return deepcopy(fig) + @pytest.mark.mpl_image_compare def test_histogram_2D(make_napari_viewer, astronaut_data): viewer = make_napari_viewer() @@ -34,6 +36,7 @@ def test_histogram_2D(make_napari_viewer, astronaut_data): # collected by the widget return deepcopy(fig) + @pytest.mark.mpl_image_compare def test_histogram_3D(make_napari_viewer, brain_data): viewer = make_napari_viewer() From 5ac1f712cfa8f760a5e4e9ad028fd8882d087ee4 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Thu, 11 Jan 2024 12:18:55 +0000 Subject: [PATCH 05/68] Fix type hints --- src/napari_matplotlib/histogram.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index c527266..3615172 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -1,4 +1,4 @@ -from typing import Any, Optional, cast +from typing import Any, Optional, Union, cast import napari import numpy as np @@ -108,7 +108,7 @@ def bins_start(self) -> float: return self.findChild(QDoubleSpinBox, name="bins start").value() @bins_start.setter - def bins_start(self, start: int | float) -> None: + def bins_start(self, start: Union[int, float]) -> None: """Set the minimum bin edge""" self.findChild(QDoubleSpinBox, name="bins start").setValue(start) @@ -118,7 +118,7 @@ def bins_stop(self) -> float: return self.findChild(QDoubleSpinBox, name="bins stop").value() @bins_stop.setter - def bins_stop(self, stop: int | float) -> None: + def bins_stop(self, stop: Union[int, float]) -> None: """Set the maximum bin edge""" self.findChild(QDoubleSpinBox, name="bins stop").setValue(stop) @@ -132,14 +132,14 @@ def bins_num(self, num: int) -> None: """Set the number of bins to use""" self.findChild(QSpinBox, name="bins num").setValue(num) - def autoset_widget_bins(self, data: npt.ArrayLike) -> None: + def autoset_widget_bins(self, data: npt.NDArray[Any]) -> None: """Update widgets with bins determined from the image data""" bins = np.linspace(np.min(data), np.max(data), 100, dtype=data.dtype) self.bins_start = bins[0] self.bins_stop = bins[-1] self.bins_num = bins.size - def _get_layer_data(self, layer) -> np.ndarray: + def _get_layer_data(self, layer: napari.layers.Layer) -> npt.NDArray[Any]: """Get the data associated with a given layer""" if layer.data.ndim - layer.rgb == 3: # 3D data, can be single channel or RGB From fab29063cdb87b848c35002847656f663ac051ef Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Thu, 11 Jan 2024 12:51:53 +0000 Subject: [PATCH 06/68] Don't allow bins lower than 0 if dtype is unisgned --- examples/histogram.py | 2 +- src/napari_matplotlib/histogram.py | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/examples/histogram.py b/examples/histogram.py index ccda491..17111f5 100644 --- a/examples/histogram.py +++ b/examples/histogram.py @@ -5,7 +5,7 @@ import napari viewer = napari.Viewer() -viewer.open_sample("napari", "kidney") +viewer.open_sample("napari", "coins") viewer.window.add_plugin_dock_widget( plugin_name="napari-matplotlib", widget_name="Histogram" diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index 3615172..348a506 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -46,7 +46,7 @@ def __init__( bins_start.setStepType(QAbstractSpinBox.AdaptiveDecimalStepType) bins_start.setRange(-1e10, 1e10) bins_start.setValue(0) - bins_start.setWrapping(True) + bins_start.setWrapping(False) bins_start.setKeyboardTracking(False) bins_start.setDecimals(2) @@ -55,6 +55,7 @@ def __init__( bins_stop.setStepType(QAbstractSpinBox.AdaptiveDecimalStepType) bins_stop.setRange(-1e10, 1e10) bins_stop.setValue(100) + bins_start.setWrapping(False) bins_stop.setKeyboardTracking(False) bins_stop.setDecimals(2) @@ -165,13 +166,17 @@ def on_update_layers(self) -> None: self.autoset_widget_bins(data=layer_data) # Only allow integer bins for integer data + # And only allow values greater than 0 for unsigned integers n_decimals = 0 if np.issubdtype(layer_data.dtype, np.integer) else 2 - self.findChild(QDoubleSpinBox, name="bins start").setDecimals( - n_decimals - ) - self.findChild(QDoubleSpinBox, name="bins stop").setDecimals( - n_decimals - ) + is_unsigned = layer_data.dtype.kind == "u" + minimum_value = 0 if is_unsigned else -1e10 + + bins_start = self.findChild(QDoubleSpinBox, name="bins start") + bins_stop = self.findChild(QDoubleSpinBox, name="bins stop") + bins_start.setDecimals(n_decimals) + bins_stop.setDecimals(n_decimals) + bins_start.setMinimum(minimum_value) + bins_stop.setMinimum(minimum_value) def draw(self) -> None: """ From 55531747ef1e8c5597df9c081c44bc92a6dc3d3c Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Thu, 11 Jan 2024 13:12:56 +0000 Subject: [PATCH 07/68] Update changelog --- docs/changelog.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 96b353d..226cbb5 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -13,10 +13,6 @@ Bug fixes ~~~~~~~~~ - Use integer bin limits for integer images in ``HistogramWidget`` -Bug fixes -~~~~~~~~~ -- Use integer bin limits for integer images in ``HistogramWidget`` - 1.1.0 ----- Additions From e86d4f692a5252881924a3e35c317d1cb24a1723 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Thu, 11 Jan 2024 13:13:17 +0000 Subject: [PATCH 08/68] Undo changes to example of HistogramWidget --- examples/histogram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/histogram.py b/examples/histogram.py index 17111f5..ccda491 100644 --- a/examples/histogram.py +++ b/examples/histogram.py @@ -5,7 +5,7 @@ import napari viewer = napari.Viewer() -viewer.open_sample("napari", "coins") +viewer.open_sample("napari", "kidney") viewer.window.add_plugin_dock_widget( plugin_name="napari-matplotlib", widget_name="Histogram" From c5e08861b18084217eb297bb042996ea46f44cc3 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Mon, 15 Jan 2024 15:59:34 +0000 Subject: [PATCH 09/68] Fix autosetting bins from data --- src/napari_matplotlib/histogram.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index 348a506..b0a04f1 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -135,7 +135,19 @@ def bins_num(self, num: int) -> None: def autoset_widget_bins(self, data: npt.NDArray[Any]) -> None: """Update widgets with bins determined from the image data""" - bins = np.linspace(np.min(data), np.max(data), 100, dtype=data.dtype) + if data.dtype.kind in {"i", "u"}: + # Make sure integer data types have integer sized bins + # We can't use unsigned ints when calculating the step, otherwise + # the following warning is raised: + # 'RuntimeWarning: overflow encountered in scalar subtract' + step = ( + abs(np.min(data).astype(int) - np.max(data).astype(int)) // 100 + ) + step = max(1, step) + bins = np.arange(np.min(data), np.max(data) + step, step) + else: + bins = np.linspace(np.min(data), np.max(data), 100) + self.bins_start = bins[0] self.bins_stop = bins[-1] self.bins_num = bins.size From 127d325d37133447dd9ba462c5a4a2f806ae86c4 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Mon, 15 Jan 2024 16:05:24 +0000 Subject: [PATCH 10/68] remove duplicate on_update_layers method --- src/napari_matplotlib/histogram.py | 84 ++++++++++++++---------------- 1 file changed, 40 insertions(+), 44 deletions(-) diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index b0a04f1..4371ea9 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -95,6 +95,26 @@ def on_update_layers(self) -> None: for layer in self.viewer.layers: layer.events.contrast_limits.connect(self._update_contrast_lims) + if not self.layers: + return + + # Reset to bin start, stop and step + layer_data = self._get_layer_data(self.layers[0]) + self.autoset_widget_bins(data=layer_data) + + # Only allow integer bins for integer data + # And only allow values greater than 0 for unsigned integers + n_decimals = 0 if np.issubdtype(layer_data.dtype, np.integer) else 2 + is_unsigned = layer_data.dtype.kind == "u" + minimum_value = 0 if is_unsigned else -1e10 + + bins_start = self.findChild(QDoubleSpinBox, name="bins start") + bins_stop = self.findChild(QDoubleSpinBox, name="bins stop") + bins_start.setDecimals(n_decimals) + bins_stop.setDecimals(n_decimals) + bins_start.setMinimum(minimum_value) + bins_stop.setMinimum(minimum_value) + def _update_contrast_lims(self) -> None: for lim, line in zip( self.layers[0].contrast_limits, self._contrast_lines @@ -103,6 +123,25 @@ def _update_contrast_lims(self) -> None: self.figure.canvas.draw() + def autoset_widget_bins(self, data: npt.NDArray[Any]) -> None: + """Update widgets with bins determined from the image data""" + + if data.dtype.kind in {"i", "u"}: + # Make sure integer data types have integer sized bins + # We can't use unsigned ints when calculating the step, otherwise + # the following warning is raised: + # 'RuntimeWarning: overflow encountered in scalar subtract' + step = abs(np.min(data).astype(int) - np.max(data).astype(int) // 100) + step = max(1, step) + bins = np.arange(np.min(data), np.max(data) + step, step) + else: + bins = np.linspace(np.min(data), np.max(data), 100) + + self.bins_start = bins[0] + self.bins_stop = bins[-1] + self.bins_num = bins.size + + @property def bins_start(self) -> float: """Minimum bin edge""" @@ -133,25 +172,6 @@ def bins_num(self, num: int) -> None: """Set the number of bins to use""" self.findChild(QSpinBox, name="bins num").setValue(num) - def autoset_widget_bins(self, data: npt.NDArray[Any]) -> None: - """Update widgets with bins determined from the image data""" - if data.dtype.kind in {"i", "u"}: - # Make sure integer data types have integer sized bins - # We can't use unsigned ints when calculating the step, otherwise - # the following warning is raised: - # 'RuntimeWarning: overflow encountered in scalar subtract' - step = ( - abs(np.min(data).astype(int) - np.max(data).astype(int)) // 100 - ) - step = max(1, step) - bins = np.arange(np.min(data), np.max(data) + step, step) - else: - bins = np.linspace(np.min(data), np.max(data), 100) - - self.bins_start = bins[0] - self.bins_stop = bins[-1] - self.bins_num = bins.size - def _get_layer_data(self, layer: napari.layers.Layer) -> npt.NDArray[Any]: """Get the data associated with a given layer""" if layer.data.ndim - layer.rgb == 3: @@ -166,30 +186,6 @@ def _get_layer_data(self, layer: napari.layers.Layer) -> npt.NDArray[Any]: return data - def on_update_layers(self) -> None: - """ - Called when the layer selection changes by ``self._update_layers()``. - """ - if not self.layers: - return - - # Reset to bin start, stop and step - layer_data = self._get_layer_data(self.layers[0]) - self.autoset_widget_bins(data=layer_data) - - # Only allow integer bins for integer data - # And only allow values greater than 0 for unsigned integers - n_decimals = 0 if np.issubdtype(layer_data.dtype, np.integer) else 2 - is_unsigned = layer_data.dtype.kind == "u" - minimum_value = 0 if is_unsigned else -1e10 - - bins_start = self.findChild(QDoubleSpinBox, name="bins start") - bins_stop = self.findChild(QDoubleSpinBox, name="bins stop") - bins_start.setDecimals(n_decimals) - bins_stop.setDecimals(n_decimals) - bins_start.setMinimum(minimum_value) - bins_stop.setMinimum(minimum_value) - def draw(self) -> None: """ Clear the axes and histogram the currently selected layer/slice. @@ -201,7 +197,7 @@ def draw(self) -> None: # whole cube into memory. if data.dtype.kind in {"i", "u"}: # Make sure integer data types have integer sized bins - step = (self.bins_start - self.bins_stop) // self.bins_num + step = abs((self.bins_start - self.bins_stop) // self.bins_num) step = max(1, step) bins = np.arange(self.bins_start, self.bins_stop + step, step) else: From b8ffdb79a7c20fe0ceaf7b9a0dbd8174c52c63ec Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Mon, 15 Jan 2024 16:05:35 +0000 Subject: [PATCH 11/68] Make linters happy --- src/napari_matplotlib/histogram.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index 4371ea9..294e509 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -125,13 +125,14 @@ def _update_contrast_lims(self) -> None: def autoset_widget_bins(self, data: npt.NDArray[Any]) -> None: """Update widgets with bins determined from the image data""" - if data.dtype.kind in {"i", "u"}: # Make sure integer data types have integer sized bins # We can't use unsigned ints when calculating the step, otherwise # the following warning is raised: # 'RuntimeWarning: overflow encountered in scalar subtract' - step = abs(np.min(data).astype(int) - np.max(data).astype(int) // 100) + step = abs( + np.min(data).astype(int) - np.max(data).astype(int) // 100 + ) step = max(1, step) bins = np.arange(np.min(data), np.max(data) + step, step) else: @@ -141,7 +142,6 @@ def autoset_widget_bins(self, data: npt.NDArray[Any]) -> None: self.bins_stop = bins[-1] self.bins_num = bins.size - @property def bins_start(self) -> float: """Minimum bin edge""" From 88760cf203f89df667bb3711a47e1a588626c5b9 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Mon, 15 Jan 2024 16:21:15 +0000 Subject: [PATCH 12/68] Add HistogramWidget._bin_widgets attribute for storing bin widgets --- src/napari_matplotlib/histogram.py | 46 ++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index 294e509..b846af6 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -42,7 +42,6 @@ def __init__( # Create widgets for setting bin parameters bins_start = QDoubleSpinBox() - bins_start.setObjectName("bins start") bins_start.setStepType(QAbstractSpinBox.AdaptiveDecimalStepType) bins_start.setRange(-1e10, 1e10) bins_start.setValue(0) @@ -51,7 +50,6 @@ def __init__( bins_start.setDecimals(2) bins_stop = QDoubleSpinBox() - bins_stop.setObjectName("bins stop") bins_stop.setStepType(QAbstractSpinBox.AdaptiveDecimalStepType) bins_stop.setRange(-1e10, 1e10) bins_stop.setValue(100) @@ -60,7 +58,6 @@ def __init__( bins_stop.setDecimals(2) bins_num = QSpinBox() - bins_num.setObjectName("bins num") bins_num.setRange(1, 100_000) bins_num.setValue(101) bins_num.setWrapping(False) @@ -84,6 +81,13 @@ def __init__( bins_stop.valueChanged.connect(self._draw) bins_num.valueChanged.connect(self._draw) + # Store widgets for later usage + self._bin_widgets = { + "start": bins_start, + "stop": bins_stop, + "num": bins_num, + } + self._update_layers(None) self.viewer.events.theme.connect(self._on_napari_theme_changed) @@ -108,12 +112,17 @@ def on_update_layers(self) -> None: is_unsigned = layer_data.dtype.kind == "u" minimum_value = 0 if is_unsigned else -1e10 - bins_start = self.findChild(QDoubleSpinBox, name="bins start") - bins_stop = self.findChild(QDoubleSpinBox, name="bins stop") - bins_start.setDecimals(n_decimals) - bins_stop.setDecimals(n_decimals) - bins_start.setMinimum(minimum_value) - bins_stop.setMinimum(minimum_value) + # Disable callbacks whilst widget values might change + for widget in self._bin_widgets.values(): + widget.blockSignals(True) + + self._bin_widgets["start"].setDecimals(n_decimals) + self._bin_widgets["stop"].setDecimals(n_decimals) + self._bin_widgets["start"].setMinimum(minimum_value) + self._bin_widgets["stop"].setMinimum(minimum_value) + + for widget in self._bin_widgets.values(): + widget.blockSignals(False) def _update_contrast_lims(self) -> None: for lim, line in zip( @@ -138,39 +147,46 @@ def autoset_widget_bins(self, data: npt.NDArray[Any]) -> None: else: bins = np.linspace(np.min(data), np.max(data), 100) + # Disable callbacks whilst setting widget values + for widget in self._bin_widgets.values(): + widget.blockSignals(True) + self.bins_start = bins[0] self.bins_stop = bins[-1] self.bins_num = bins.size + for widget in self._bin_widgets.values(): + widget.blockSignals(False) + @property def bins_start(self) -> float: """Minimum bin edge""" - return self.findChild(QDoubleSpinBox, name="bins start").value() + return self._bin_widgets["start"].value() @bins_start.setter def bins_start(self, start: Union[int, float]) -> None: """Set the minimum bin edge""" - self.findChild(QDoubleSpinBox, name="bins start").setValue(start) + self._bin_widgets["start"].setValue(start) @property def bins_stop(self) -> float: """Maximum bin edge""" - return self.findChild(QDoubleSpinBox, name="bins stop").value() + return self._bin_widgets["stop"].value() @bins_stop.setter def bins_stop(self, stop: Union[int, float]) -> None: """Set the maximum bin edge""" - self.findChild(QDoubleSpinBox, name="bins stop").setValue(stop) + self._bin_widgets["stop"].setValue(stop) @property def bins_num(self) -> int: """Number of bins to use""" - return self.findChild(QSpinBox, name="bins num").value() + return self._bin_widgets["num"].value() @bins_num.setter def bins_num(self, num: int) -> None: """Set the number of bins to use""" - self.findChild(QSpinBox, name="bins num").setValue(num) + self._bin_widgets["num"].setValue(num) def _get_layer_data(self, layer: napari.layers.Layer) -> npt.NDArray[Any]: """Get the data associated with a given layer""" From d56942b72abd8e65f821ba07feb99ff6f0a25526 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Mon, 15 Jan 2024 16:33:50 +0000 Subject: [PATCH 13/68] Fix calculation of bins from widget values --- src/napari_matplotlib/histogram.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index b846af6..3215b14 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -136,12 +136,7 @@ def autoset_widget_bins(self, data: npt.NDArray[Any]) -> None: """Update widgets with bins determined from the image data""" if data.dtype.kind in {"i", "u"}: # Make sure integer data types have integer sized bins - # We can't use unsigned ints when calculating the step, otherwise - # the following warning is raised: - # 'RuntimeWarning: overflow encountered in scalar subtract' - step = abs( - np.min(data).astype(int) - np.max(data).astype(int) // 100 - ) + step = abs(np.max(data) - np.min(data)) // 100 step = max(1, step) bins = np.arange(np.min(data), np.max(data) + step, step) else: @@ -213,7 +208,7 @@ def draw(self) -> None: # whole cube into memory. if data.dtype.kind in {"i", "u"}: # Make sure integer data types have integer sized bins - step = abs((self.bins_start - self.bins_stop) // self.bins_num) + step = abs(self.bins_stop - self.bins_start) // self.bins_num step = max(1, step) bins = np.arange(self.bins_start, self.bins_stop + step, step) else: From 11590b251846ab9b6023663bacb6d286c3e5a15f Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Mon, 15 Jan 2024 16:51:22 +0000 Subject: [PATCH 14/68] Calculate step using n_bins-1 n_bins corresponds to number of bin edges rather than bins --- src/napari_matplotlib/histogram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index 3215b14..4ce6ebc 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -208,7 +208,7 @@ def draw(self) -> None: # whole cube into memory. if data.dtype.kind in {"i", "u"}: # Make sure integer data types have integer sized bins - step = abs(self.bins_stop - self.bins_start) // self.bins_num + step = abs(self.bins_stop - self.bins_start) // (self.bins_num - 1) step = max(1, step) bins = np.arange(self.bins_start, self.bins_stop + step, step) else: From 08a5086f345e5db2418f2f4e1f5f4e5b169567a4 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Mon, 15 Jan 2024 16:52:16 +0000 Subject: [PATCH 15/68] cannot use negative start bin for uint data --- .../tests/baseline/test_histogram_2D_bins.png | Bin 21366 -> 19894 bytes src/napari_matplotlib/tests/test_histogram.py | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/napari_matplotlib/tests/baseline/test_histogram_2D_bins.png b/src/napari_matplotlib/tests/baseline/test_histogram_2D_bins.png index eb43c3108147079f9410a40936975d94158ac6f0..db401612c44fb7eb92dc1ae9f2fd66a7bbd69fd6 100644 GIT binary patch literal 19894 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 BUXB0& literal 21366 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 b-^Cj#YN-+8Uxv&S3Wb(bypnn8+Wr3z5b2@o diff --git a/src/napari_matplotlib/tests/test_histogram.py b/src/napari_matplotlib/tests/test_histogram.py index 58acf23..399db3d 100644 --- a/src/napari_matplotlib/tests/test_histogram.py +++ b/src/napari_matplotlib/tests/test_histogram.py @@ -17,8 +17,8 @@ def test_histogram_2D_bins(make_napari_viewer, astronaut_data): viewer.add_image(astronaut_data[0], **astronaut_data[1]) widget = HistogramWidget(viewer) viewer.window.add_dock_widget(widget) - widget.bins_start = -50 - widget.bins_stop = 300 + widget.bins_start = 0 + widget.bins_stop = 350 widget.bins_num = 35 fig = widget.figure # Need to return a copy, as original figure is too eagerley garbage From 65e84f8692e7ad42586304a643621b02dab95670 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Mon, 15 Jan 2024 16:56:00 +0000 Subject: [PATCH 16/68] Make HistogramWidget bins_num widget correspond to number of bins rather than number of bin edges --- 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 4ce6ebc..728dfc8 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -148,7 +148,7 @@ def autoset_widget_bins(self, data: npt.NDArray[Any]) -> None: self.bins_start = bins[0] self.bins_stop = bins[-1] - self.bins_num = bins.size + self.bins_num = bins.size - 1 for widget in self._bin_widgets.values(): widget.blockSignals(False) @@ -208,11 +208,13 @@ def draw(self) -> None: # whole cube into memory. if data.dtype.kind in {"i", "u"}: # Make sure integer data types have integer sized bins - step = abs(self.bins_stop - self.bins_start) // (self.bins_num - 1) + step = abs(self.bins_stop - self.bins_start) // (self.bins_num) step = max(1, step) bins = np.arange(self.bins_start, self.bins_stop + step, step) else: - bins = np.linspace(self.bins_start, self.bins_stop, self.bins_num) + bins = np.linspace( + self.bins_start, self.bins_stop, self.bins_num + 1 + ) if layer.rgb: # Histogram RGB channels independently From 8a7d9e46da49492172df8f36e0f579c995407a65 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Mon, 15 Jan 2024 17:07:02 +0000 Subject: [PATCH 17/68] fix typo in comment about using 128 bins for float data --- src/napari_matplotlib/histogram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index 2329c1a..39bcfa4 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -31,7 +31,7 @@ def _get_bins(data: npt.NDArray[Any]) -> npt.NDArray[Any]: step = np.ceil(np.ptp(data) / 100) return np.arange(np.min(data), np.max(data) + step, step) else: - # For other data types, just have 128 evenly spaced bins + # For other data types, just have 99 evenly spaced bins return np.linspace(np.min(data), np.max(data), 100) From 7c4cdc87328480e53bd07847e79fcf27b9fef697 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Mon, 15 Jan 2024 17:10:07 +0000 Subject: [PATCH 18/68] Update changelog --- docs/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 673a0ef..f4caaf3 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -20,6 +20,7 @@ Other changes - The ``HistogramWidget`` now has two vertical lines showing the contrast limits used to render the selected layer in the main napari window. - Added an example gallery for the ``FeaturesHistogramWidget``. +- Add widgets for setting bin parameters for ``HistogramWidget``. 1.2.0 ----- @@ -28,7 +29,6 @@ Changes - Dropped support for Python 3.8, and added support for Python 3.11. - Histogram plots of points and vector layers are now coloured with their napari colourmap. - Added support for Matplotlib 3.8 -- Add widgets for setting histogram bin parameters 1.1.0 ----- From 6261f4c36fb531146cb270f3ad2bf9fc5d62ac1c Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Wed, 14 Feb 2024 10:54:40 +0000 Subject: [PATCH 19/68] Add 'num_bins, 'start', and 'stop' parameters to '_get_bins' --- src/napari_matplotlib/histogram.py | 56 ++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index bec5c61..5036071 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -27,15 +27,44 @@ _COLORS = {"r": "tab:red", "g": "tab:green", "b": "tab:blue"} -def _get_bins(data: npt.NDArray[Any]) -> npt.NDArray[Any]: +def _get_bins( + data: npt.NDArray[Any], + num_bins: int = 100, + start: Optional[Union[int, float]] = None, + stop: Optional[Union[int, float]] = None, +) -> npt.NDArray[Any]: + """Create evenly spaced bins with a given interval. + + If `start` or `stop` are `None`, they will be set based on the minimum + and maximum values, respectively, of the data. + + Parameters + ---------- + data : napari.layers.Layer.data + Napari layer data. + num_bins : integer, optional + Number of evenly-spaced bins to create. + start : integer or real, optional + Start bin edge. Defaults to the minimum value of `data`. + stop : integer or real, optional + Stop bin edge. Defaults to the maximum value of `data`. + + Returns + ------- + bin_edges : numpy.ndarray + Array of evenly spaced bin edges. + """ + start = np.min(data) if start is None else start + stop = np.max(data) if stop is None else stop + if data.dtype.kind in {"i", "u"}: # Make sure integer data types have integer sized bins - step = np.ceil(np.ptp(data) / 100) - return np.arange(np.min(data), np.max(data) + step, step) + step = np.ceil((stop - start) / num_bins) + return np.arange(start, stop + step, step) else: - # For other data types, just have 100 evenly spaced bins - # (and 101 bin edges) - return np.linspace(np.min(data), np.max(data), 101) + # For other data types we can use exactly `num_bins` bins + # (and `num_bins` + 1 bin edges) + return np.linspace(start, stop, num_bins + 1) class HistogramWidget(SingleAxesWidget): @@ -217,15 +246,12 @@ def draw(self) -> None: # Important to calculate bins after slicing 3D data, to avoid reading # whole cube into memory. - if data.dtype.kind in {"i", "u"}: - # Make sure integer data types have integer sized bins - step = abs(self.bins_stop - self.bins_start) // (self.bins_num) - step = max(1, step) - bins = np.arange(self.bins_start, self.bins_stop + step, step) - else: - bins = np.linspace( - self.bins_start, self.bins_stop, self.bins_num + 1 - ) + bins = _get_bins( + data, + num_bins=self.bins_num, + start=self.bins_start, + stop=self.bins_stop, + ) if layer.rgb: # Histogram RGB channels independently From 426a0f5f8401300b1c8c0e93c479e5559e1dec97 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Sat, 25 May 2024 10:36:31 +0100 Subject: [PATCH 20/68] use '| None' rather than Optional[Union[...]] for type hints --- src/napari_matplotlib/histogram.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index 042ef8f..fe281c4 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -1,4 +1,4 @@ -from typing import Any, Optional, Union, cast +from typing import Any, cast import napari import numpy as np @@ -30,8 +30,8 @@ def _get_bins( data: npt.NDArray[Any], num_bins: int = 100, - start: Optional[Union[int, float]] = None, - stop: Optional[Union[int, float]] = None, + start: int | float | None = None, + stop: int | float | None = None, ) -> npt.NDArray[Any]: """Create evenly spaced bins with a given interval. @@ -195,7 +195,7 @@ def bins_start(self) -> float: return self._bin_widgets["start"].value() @bins_start.setter - def bins_start(self, start: Union[int, float]) -> None: + def bins_start(self, start: int | float) -> None: """Set the minimum bin edge""" self._bin_widgets["start"].setValue(start) @@ -205,7 +205,7 @@ def bins_stop(self) -> float: return self._bin_widgets["stop"].value() @bins_stop.setter - def bins_stop(self, stop: Union[int, float]) -> None: + def bins_stop(self, stop: int | float) -> None: """Set the maximum bin edge""" self._bin_widgets["stop"].setValue(stop) From 67a864101d2d7d037bfbe8f9f3e1b55569cd125e Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Sat, 25 May 2024 11:02:24 +0100 Subject: [PATCH 21/68] remove widgest to set start and stop values for histogram bins --- src/napari_matplotlib/histogram.py | 145 +++--------------- .../tests/baseline/test_histogram_2D_bins.png | Bin 19894 -> 19832 bytes src/napari_matplotlib/tests/test_histogram.py | 4 +- 3 files changed, 24 insertions(+), 125 deletions(-) diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index fe281c4..aeef841 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -7,9 +7,7 @@ from napari.layers import Image from napari.layers._multiscale_data import MultiScaleData from qtpy.QtWidgets import ( - QAbstractSpinBox, QComboBox, - QDoubleSpinBox, QFormLayout, QGroupBox, QLabel, @@ -30,41 +28,29 @@ def _get_bins( data: npt.NDArray[Any], num_bins: int = 100, - start: int | float | None = None, - stop: int | float | None = None, ) -> npt.NDArray[Any]: """Create evenly spaced bins with a given interval. - If `start` or `stop` are `None`, they will be set based on the minimum - and maximum values, respectively, of the data. - Parameters ---------- data : napari.layers.Layer.data Napari layer data. num_bins : integer, optional - Number of evenly-spaced bins to create. - start : integer or real, optional - Start bin edge. Defaults to the minimum value of `data`. - stop : integer or real, optional - Stop bin edge. Defaults to the maximum value of `data`. + Number of evenly-spaced bins to create. Defaults to 100. Returns ------- bin_edges : numpy.ndarray Array of evenly spaced bin edges. """ - start = np.min(data) if start is None else start - stop = np.max(data) if stop is None else stop - if data.dtype.kind in {"i", "u"}: # Make sure integer data types have integer sized bins - step = np.ceil((stop - start) / num_bins) - return np.arange(start, stop + step, step) + step = np.ceil(np.ptp(data) / num_bins) + return np.arange(np.min(data), np.max(data) + step, step) else: # For other data types we can use exactly `num_bins` bins # (and `num_bins` + 1 bin edges) - return np.linspace(start, stop, num_bins + 1) + return np.linspace(np.min(data), np.max(data), num_bins + 1) class HistogramWidget(SingleAxesWidget): @@ -82,53 +68,28 @@ def __init__( ): super().__init__(napari_viewer, parent=parent) - # Create widgets for setting bin parameters - bins_start = QDoubleSpinBox() - bins_start.setStepType(QAbstractSpinBox.AdaptiveDecimalStepType) - bins_start.setRange(-1e10, 1e10) - bins_start.setValue(0) - bins_start.setWrapping(False) - bins_start.setKeyboardTracking(False) - bins_start.setDecimals(2) - - bins_stop = QDoubleSpinBox() - bins_stop.setStepType(QAbstractSpinBox.AdaptiveDecimalStepType) - bins_stop.setRange(-1e10, 1e10) - bins_stop.setValue(100) - bins_start.setWrapping(False) - bins_stop.setKeyboardTracking(False) - bins_stop.setDecimals(2) - - bins_num = QSpinBox() - bins_num.setRange(1, 100_000) - bins_num.setValue(101) - bins_num.setWrapping(False) - bins_num.setKeyboardTracking(False) + num_bins_widget = QSpinBox() + num_bins_widget.setRange(1, 100_000) + num_bins_widget.setValue(101) + num_bins_widget.setWrapping(False) + num_bins_widget.setKeyboardTracking(False) # Set bins widget layout bins_selection_layout = QFormLayout() - bins_selection_layout.addRow("start", bins_start) - bins_selection_layout.addRow("stop", bins_stop) - bins_selection_layout.addRow("num", bins_num) + bins_selection_layout.addRow("num bins", num_bins_widget) # Group the widgets and add to main layout - bins_widget_group = QGroupBox("Bins") - bins_widget_group_layout = QVBoxLayout() - bins_widget_group_layout.addLayout(bins_selection_layout) - bins_widget_group.setLayout(bins_widget_group_layout) - self.layout().addWidget(bins_widget_group) + params_widget_group = QGroupBox("Params") + params_widget_group_layout = QVBoxLayout() + params_widget_group_layout.addLayout(bins_selection_layout) + params_widget_group.setLayout(params_widget_group_layout) + self.layout().addWidget(params_widget_group) # Add callbacks - bins_start.valueChanged.connect(self._draw) - bins_stop.valueChanged.connect(self._draw) - bins_num.valueChanged.connect(self._draw) + num_bins_widget.valueChanged.connect(self._draw) # Store widgets for later usage - self._bin_widgets = { - "start": bins_start, - "stop": bins_stop, - "num": bins_num, - } + self.num_bins_widget = num_bins_widget self._update_layers(None) self.viewer.events.theme.connect(self._on_napari_theme_changed) @@ -144,27 +105,9 @@ def on_update_layers(self) -> None: if not self.layers: return - # Reset the bin start, stop and step values based on new layer data + # Reset the num bins based on new layer data layer_data = self._get_layer_data(self.layers[0]) - self.autoset_widget_bins(data=layer_data) - - # Only allow integer bins for integer data - # And only allow values greater than 0 for unsigned integers - n_decimals = 0 if np.issubdtype(layer_data.dtype, np.integer) else 2 - is_unsigned = layer_data.dtype.kind == "u" - minimum_value = 0 if is_unsigned else -1e10 - - # Disable callbacks whilst widget values might change - for widget in self._bin_widgets.values(): - widget.blockSignals(True) - - self._bin_widgets["start"].setDecimals(n_decimals) - self._bin_widgets["stop"].setDecimals(n_decimals) - self._bin_widgets["start"].setMinimum(minimum_value) - self._bin_widgets["stop"].setMinimum(minimum_value) - - for widget in self._bin_widgets.values(): - widget.blockSignals(False) + self._set_widget_nums_bins(data=layer_data) def _update_contrast_lims(self) -> None: for lim, line in zip( @@ -174,50 +117,10 @@ def _update_contrast_lims(self) -> None: self.figure.canvas.draw() - def autoset_widget_bins(self, data: npt.NDArray[Any]) -> None: - """Update widgets with bins determined from the image data""" + def _set_widget_nums_bins(self, data: npt.NDArray[Any]) -> None: + """Update num_bins widget with bins determined from the image data""" bins = _get_bins(data) - - # Disable callbacks whilst setting widget values - for widget in self._bin_widgets.values(): - widget.blockSignals(True) - - self.bins_start = bins[0] - self.bins_stop = bins[-1] - self.bins_num = bins.size - 1 - - for widget in self._bin_widgets.values(): - widget.blockSignals(False) - - @property - def bins_start(self) -> float: - """Minimum bin edge""" - return self._bin_widgets["start"].value() - - @bins_start.setter - def bins_start(self, start: int | float) -> None: - """Set the minimum bin edge""" - self._bin_widgets["start"].setValue(start) - - @property - def bins_stop(self) -> float: - """Maximum bin edge""" - return self._bin_widgets["stop"].value() - - @bins_stop.setter - def bins_stop(self, stop: int | float) -> None: - """Set the maximum bin edge""" - self._bin_widgets["stop"].setValue(stop) - - @property - def bins_num(self) -> int: - """Number of bins to use""" - return self._bin_widgets["num"].value() - - @bins_num.setter - def bins_num(self, num: int) -> None: - """Set the number of bins to use""" - self._bin_widgets["num"].setValue(num) + self.num_bins_widget.setValue(bins.size - 1) def _get_layer_data(self, layer: napari.layers.Layer) -> npt.NDArray[Any]: """Get the data associated with a given layer""" @@ -248,9 +151,7 @@ def draw(self) -> None: # whole cube into memory. bins = _get_bins( data, - num_bins=self.bins_num, - start=self.bins_start, - stop=self.bins_stop, + num_bins=self.num_bins_widget.value(), ) if layer.rgb: diff --git a/src/napari_matplotlib/tests/baseline/test_histogram_2D_bins.png b/src/napari_matplotlib/tests/baseline/test_histogram_2D_bins.png index db401612c44fb7eb92dc1ae9f2fd66a7bbd69fd6..98e3cde1254b75ffc92e7786f8106fe4cce3c16f 100644 GIT binary patch literal 19832 zcmeIaXH-*NyEYm{1q+G|6{M(i1e9K+C<2Bm9YPb3PCz<@Y6BGkMT*jU=t!5YAfWV? z1PE0`dM9+qH&^f}@3Z%NzOv65;~Qt}9}Y~i%3O2a^DftYUF+q2WjV@YG{;~t7$yAP zT{Retj1dMqs6%lCd=usR}Kp}22(P2eq?L!Y-?r0=xXNZWMyy1 z$1Tjwd!5nJ+4+%^C=USa3^wb-2)(H^zcXyqsa^QY zDi^ENPl*TRJuBFntF(d+9@M1%Qo?;$mEz64*9Q-To{$#wS3L7;o%{w;vG%HnMMPy> zh#Z5!@QV0wyO5W-@Pdfmv-XLVqhM4pMXF1ar2k9`Q!tXgkcJ%qe;?2sq<}tl-;K8$ zn;)zo1Pl`mjG?+!!lmI zd}$>Zft$hnsOPb6ofgQ<$k5VikBjn}cu0AfKV8P)TEii5bt?DMNb$onPuGWW?LLBf zeSKB@CSNJ~OlSLRd~3WKy~{oO`Qd5~s&CU2KV-jtePYmkOgLOS%%-m^wftw7f`I+d z+13Wn#V_PV#=9t`_cv6nrmRpl7>>DtGWGTrUoEmDCl=TFOcbFf`I3l%scCDY7rZ=^ zwg@wM0Sq2@-@eT;%5Yi8*5%;l#)hUUfG6ODqgH~1R`J7ovMRThQjQq-lrd#|{CNM} zLZ!5uN;3{wI>-Xg%*(^2;A>a|>Jpy^8s2dJ;|33xN{ek=HWrPQ;&^4=zJ0sweoNG6 z$GuR(#l>Z7vF;Q62Y%u$y?nY@@vNbhaQ`p|)Z3!ajc2S<3!u z?~0Rjq7QrDLj!*zmv!K0a)^Y6t!*|32ghoxy{{fF_;O-raxP}!SDU7{cNLRWdz`qn zwYA!Ugm=`OO}i2Zw+TUf8heHAPcyt-$a83-hJZ85!x{N1g-C3@VER6u8OFpc8q2lj#v(eNnY~J!b<}L? zL)5%OhO=(d-28o|Y4wk{?a@(siDOqKk$pl<3 zq0ZXYRzu09xU0e~a+fpj>qa?ZcQy9?`}B56qW%R@Zd>g9BVi`EfB;5bsr4DwNUL`~S+^bec_*B=>{)fYolRXfJ7 z;V{;)slkd6iJe7hO%1K|ZJ7(3c$Gozw{J%7o13R4B_$Pk;<$q@-mD!)Cnnymudk<( zF!@x&B_^h)S8AtfU|>)f-EiD(cF{ia;kWlWWsDMrT`wi?H9o&k(swFxrb=mHI`azd z5Q^po#SCq=4t}v=%by1;=v8)#0W0RKH%8nUphr+L*2$f~De&yda1QAw+Ix-h`ff+t zS7!jbGC+N2eiazlZAM0J-5MpZ_2raL!uxku9<)wlmfeFncov3@jdcv;BDf8bo;*27 z6cej7%+tz2WDE>c%PT8KFiAJq5+Wr$`ohO*k7JnGrv`{URIAy7oHn!#I>U?g{r*B& zA+P+B(L8Z`JxiAq!z$FwR=#PmWBoA!0fDI>3fx$1j$X0Ppy;5_HWRb!)Kz%YEst08 zQKB-No83`W#aa{w(Cz z{oNoPt@^8a+niHGL^n$-x2IykP6L-~AYj^{!f*WJu5bKMmFLWNbR4$!aQn;$i#RPs z#n%nNRkzjA%E+5TJU&YbT*~vA0ybJ%X=$>$y1FQ@d3^(~CFa(xOTW#z!yNW9I2aq^ zvue6KDJLm02Lo=Ta_8R`24)nrU!v3P*iFe-KBU|K5JM-CVu$h z3j(E4HqzbHyY7Nbpwn&GZJi%HwZGS_GQWB>u(-aA(GQ#lyFrwu3G9+YNg*3K1V;z5EkVwVe2Qh*8GeheMz_Xc}kD@`0165c*` zbXBAOoN+0q&G@l9FGvA|!Xe16)Qdhu?Uw|%jk$ROy8B6K%3ZVdR;^^b672K=$n6`~ zMtxxS9Ik#GccjfPx&u6{AC)Qsk@?~U#qJ2X>uA=QEhWa?12)cn}Sd0*5S*5~<2Ie;efMBdno zVh({XcJ6fGO2KUXqxYW;<`!(5lcPA0uRh|>&pnD+7ru1q((PYVQym8$&i7y4 zP0sFI6m?Yy8;TPMqXN_Y0Hz@Ssz59EelnhJGNXvSfAkGAGjm5b#W6ZjKgIGNS9i6o zg`{n~v<$O8G)qQQw2`dm#tUZV=chKVBuSIM`RN|LmFjwOl^+b&@8nbT11P}%`4Ink z*#FOM;~&qTL?#YhYQ&2!U7XRaO01*X@?G7p`=y`k9>>YvCz&~83ivI-;qcf`tJOo` z!3*T@nm2O&sIHRIi#stDLtHArz9~HvKg9BpiN{jQXUg+ibYCu;89YQqS_CQbI_u219jF5ZRw{_&9aC zcAg=MxVs+y<_#kpvLXS;pZCCyno0`fJ~qqLelU_WgI7jWe7D{)P1Wqp)a;B(hh}SK z(RBQ9s|x?L?ZwLqP)8?z^`lr8E=sydjnY%!d@Ykn`kyrMO$VJe7-2 zw$|pd?7e673UsccR$7HHWlk1gp4fPKH`;mjoeZ=d2gRRj6;D)BU)M>H@ZPfQOp-x- zdXyNWjJB)ZG#B5ROqN$rP}kDZs=)9bBkhbUt;SB@=#jV>C8vYp;evh1bB?{2C&ZN> zK1`V}o1h{)YHp~iHTXsHRj(p2Lv!3j*HqPTT-|BZA%m4ZC{>}hlhpr z%&KJpTe1Gt@*y+@gtyH~n~OovZ2G;yapme&czr(Qsv=GZ1F&}g(F*5ObzN>y*Es~8gqKcdXXi8~E7H8>ec}f` z3y!N-ul{8mS7l#cOgw!}7P!F)=8gF7!{7ppw*O1pP}($8pNX*zm`MBb63tt-GyvO>iy)0b7-H8SSThO z5K1J2ros}$P$+qG9({g+Lr_pl+sL~Zn`;0zUF^QkrnnjJyH}Q5xhxOOlzN&XG?#=t z-(3Z0UYEz4$;7L=f6wc$LAPNHo!da7+pxEGi1^z3y^Tgm>+UqASFd09^kk@O8W_+` zvX}hc3GYGBnt4o4Z;5s7GmD(67~ahdzATc2GxX$=;uGWBH*6E`s#P_EM_&$e^#@SW zvcUVxU9!IOiFzU>4%;+Ga7En|ufZUXU#Lp(+1=Xo-CONeA?&QQXIQS=)i*R)Z!C^R z-7?EGF)@jnn;@Wy-IoFpg{UBF>&$RWA(~}aM3;=5(pt6d#Z53jevjV7#0*$_f*E0k z1D|59JP{MKbhENFm>~~Xt2xrj%4*M-o13Fc#avNaR@OgFB|dTal_Qs4xrXQZTz931 z3O{}Udka$1#RCj8907tKJXsX zCz9nU6^zAXn1~M*DY#0w|WJrLhp^yU~3T)PXX6eSI7p+{>ls&!1-(7q8My zf0Gh3pbsiQhy2iY?_B)2}Fr=#(ecQHBn%$LTg(a zR61yAYvX3qmTKw5cJR!Xc+K^&az5F+jFB(k8-b{bbxbNOU|yd)2X*8W^N zC`6)AxWPT2cdRO%2I^=wN#7{DN{1L?&iKfMNMJN17YQ6nKcXr8X(~^eHsXODMPVG~tX7#;Y(+$-=Bnp5V``zy}hh1`P6+l$D z=0x!5rInY9GBXMDgP^~=C?~0fKt$CTEeur=eyXe3j6RtlwD75Lg2KYIaVQ}}9bIy9 zIr)%MXDSL%(41I3S1Zd%ynd7sjGw~#<;#}g z^())Wo1LrWq!j#6F~Fj1CfZ^%v$IdEYShY`m?V|EE!d$9NMYiMA*i3omW?mXg^n+mMvScy(tynoFPpt^ulxS#Cm=g*$)&W`{0K&JR_AXCS}F)W_pt3g{G})(BRf6hX<>ygLHa`x2gbPA zHhtooQ+u2CaJ4tadFNAR@MWtKBuKr>N1nL1`mTw9v_?L(++{9pcyCQ;Yh-s_U~;*c z8UFnFbJWCKsmpqi*PtsN8JaSaOPdTY^;J0@=c^3zqX&~D6y@N;YYQofJ@+g@+unpr zk{|j^^!~RKfL!T-lb3#jiI4hWT|GT1tnprIkSEE`&Zfq%{}tSIE9y;DU0q9Fy$akx z%E-vbtEkvhb1<>93TcOG=lJdYIZe-{r$1q@K6F2<)BQB>N3Ln7#2WFga+EfQ8&j} zTe(e_v7Vs#N+BaZ$p-92*&>N4qifvyrvJZ>mWj0nh+^V{!V&-$g%4(}TdhO6cde2` zr``Drb9UHYIrf`o#7%1fKMf6yNs!K4pMFZ@!S_bWaA? z*4~c`H9bFm<{}Esivczs79O73IMIfwzhGptvA+p;YA04U4~icxj4hAIX~byL_?-Gi z`&>_8tlbP{uJ+2Vh4~zqE=K2=tpTr@uM6o4YHAg)J^C-4h5$1u&;4rWe*&b$tb0n` z(M_KDcuUOnBBHLsz9xl2+6J5tON6i)kZfp{n*=2aaPX?965T?g2JEC~NWUh4UwgB1 zH5yJW@E7@;&Pb-tsj1JY{_WRq$3>fmO7jv0lGeTLCO4olv5k_dN?6+e{ciXlu;YIZ z_`jZoL``C86y)Vs10;70E8G^a#*qrKqAqHnM6GthySWfB+CnpW0B5y`^FYM=EWb=j zP8M)qd@!yOeP*#Mzl~#=K5){wb-Nk^oS|5zjQ1Z6yPP9$Aoo1k znM}iH{`$<>vo5o}H<1G$Z?6+=OXj1+^~M?oncy5eJlcCjq>kWs^|WtJJ;|vGk(TXo zXR1EA=Yz7$x8(vgR~OW<5E^h#1}IpOM>osvbYj4{)29>PUdto=y=&(ZKqVE1(eSTt_Ly;oCom+AEs~ixkoau$zAnDcB0f5X2e5M|pW=H6${p207+WN#ljl z+)H4D1NW|hLb>JWm9`C+Sanx`#!^pf>A(;Bz!xt%l!S(}mzN!p!_|Ww5Oe@L6wrNy zDx@*_nq06YvDzC^ZTn$}I3Y<0ve##s`g)0?6rTeiw4z=fAtAYX8JWHKz`$1zOYz}v z-*#3SDd+lDsO~ov=yMF8xHJ{6Ok^dDxWLr_aF%;XRAYT-6&=c?ZI;UPeag9{w|z=c z!^Nc-G;Bba)8~!^MFxPZqSh4@!vOBFtZ%_{1{qm`uJ@z;QR@N zee2&RR=%4T!b53jD!JBHP%1^S)24-eJ4lOZ+|cg_K#P+tYt}6<^RF*8LjAejNYOI= zTIf!71|4Y z^T)x&K6?jBNKi=*X1D4MFCQnaxF{d^*<+?u?BX)*(?5r)Z zUA)k9s#R!M!^$cH)a9f0!>T%L@V#n-a%Zdcn<{7pR8+Odayj1`DFPeV7=6_ai<}7` zSHKr5XGK0J8w}7Z5)c%$CQM^M3Ae|pLP;BeuBlm%1AW<_3+tV73|0$6JmHZ-1H*z3 zzfsA^$;}jN8xcx3y(G?8?nRY5PbK;6Zg1m%S4@vA2`9ddg-GTmySUIiTB@H8?Ul)` z>mCu8wUjOKg%3x+@8C_28_h&Lc_0f%z zK<57N4=QNh6-Q6f%Z>WTwLzLpif0^m8(YJ)*l%#==$^2tcFVLuBwlZkF#@Tq4fg{Pymw0g!ryX zbD>5Lo0*%#Lz|kL!zJfPF!*v9`|@RiCAe^ZA^I*4x8e1``X3A{&t(EZhuF81QfMeW zCgw77s0dzzn!F-&_pas7Z#KKTulFe$j{rqOac9kKbws+xdd=AP>x;KrdntHAm(gAl zIlb4t(A5b^BAh-%F3^`q?lRY}85;I3I?L01?{1b>PR}O?A$TaDuN2w#sSP?dm#%i} z7Bazw1|B6ZqX%;);ysl;Jn8`MYxcVql+IfBTU{8g(cO5HvbySQwmO;E#Z$dl3Z)uP z^U9k5NNoiIlf248?mmqMiP8o!t$Z^=ZwCpww&hd5qBRW-b3x~aR+I&@%8^G{p~P)3 z3Ba7tx--!hsvPt9QN`eNH(DjIx~xougPUWSsIRZVR56(Q`t|EyyN-^#_#hf+(@HH0 z&%g>?=V$r9t^dX>57V_i@MK-uCfK4KJ@sqV2Qpsh$Wfy`L+aQ3kv&)1g^k-EaG%T==|X#}cgkU8wyu6g%YxaGy6wyX#sAM9J> zHYjHU^o2?(byQ1T{T&3LLR=`H-I)MPo)J;Yjb8A&im&g2IGw2Tx8b$_#VGv8 z`gcrUdSfxsj{DUb?Sj$N&MHqs*kH5mO1xJm&stF0#q+~waCzOYr#5LMeV*K+4~e+$ z0SF?xTfYdn?QK?OW~)DmIM6D=g23VcVAD-@N}^}>`QzW)Qn-jl>XjsC z1XI=ekU9AGiyE(T_|1Pk*karLZfy_fo_h1)Lk4S{*YxXM0AIUor@+g-yHfo23+~0X zEtzMK#XYM2Kk&dxSv*&P`~PLd#;Fc*4Xlc2?cwkQG>q{D)X%0!@Gsz{{ISwRdP;X1sg(6`w|ab*0#Q& z_OnD6Fs2i946wE%Aa?q}M0JcZJm+7l?Y4<@DUZ$Ux4s)L9P|@}#zc{DEl418aUKi1TNWp1Bu#)jXrJT(wMj zOyK|pi3D>n{YSb+sYS&6?)5u}n!ZI8V4m!)Cf{~!Ku~7o=RYj{m79IPxbay68RO6W zCzwXTC9=#7e|3N3RB}IcFIzL?sH?P1n1(JBeA{L{tHX@1h4XyoQd^1B{c<*evV!uF(g@ly*Lo z!^++}ZCH=Legt>GZ$t{RrStQmdABH)P}V={r$3yt(1|s`3r&5gTIpf_ryB{aKMK$Yu*q0OXc^D7*Jvj5v9!4Frj`7#m=;C|7DrGLfV6f4bFq2ST`?6yO z&;5gp3F}?c94jj7iR`=AgH|{#%BZcxIs?SV+F$feh z8tZ^zN!W@V?0X63+fP`@&=gpXIkV z*-+*gV~?LY)kFWLVhHI@$0uTY(j{<)deRDHp&SP(sQfSIYxQxdMwRWD*4ele+_fGJ zQ42q3KVZZrUri9LrZ0Od*pm`S3G#Fk?` zJqH_R&}N=*hyC`NWd(JxKARkCK%z^Fnwn;kNc6971`8ndhKZg7w@gzzJL_*mqp7BO z_4@|;5L`d^P=e`FMA${cYx_z5qUdLBFn2LR$?<0n!RTa)>Jlj`3;-!kuu|)fO#cQc z`DVPQqox*C?D(8!utCDP?r&-SXJ;tU_G2`G!>^oU%wB?=01VLePP~B2XVUao`Gy;h zEw~6=6chwscNkJI9&vJh6`IqT)wiH2zkn1h(Rrr5w8my?1D!VU7-sS@tk<$?v2E;? z_m+rDQxk8tGFu!OvSo$wzF$o@#Q0j=IYnTH&L$IwI5RSFOa5(ONjvr^ z7+LNuUsm<+X7S8)@P*5|7O;1j3@{sx+7eHP$hP6xRHWaH7Lz*HIsRZ1N;!T-n3dkO z0!)-39_pJ434F-~n@YedNHAg+I}m<8bL}GW@EN}~0H7&@y^Z!c5|?_gL!#xDO$J5Ytr}1L zRL_u$dJT5kMRmrOl!d7*y_Ce-)RZO@fSJCAZ1dTMEnd@OqCX?5OfL+KM) z*J9nb1s1$v9jcIXx$=FoFoJSB_1ja;f|RuH0CzQZfh8o5%0PSj&lX~oAkP<3)M|PJ zw{8;MbFrerkM{u$c5Abps4!f)w?*x(pr8P?TxYsfBvhT9i;kS4&)%8h`?=9X>Cza& zfa^+CSlzP81L+#zKNt*G?7A9w&atWQ12Q@w?}Dfn?Zlni-@kvK?&E_f#8$;d9v)>- z65ZRY1M$w|lIgh&%I|uvg-zDi2b$&YY#%+(a~+WqX}A?=_i0IX{O3>0l{BR-jPxn? zt5@$_lQXpW-4x1eh5c|A)m&_d27I*sVkh+cr?JMIwe6L7A`^fj>T8Hk%U$OghZ50!e zMech_&7(>a1=eu$k>`vpgC1$CneB)%UKR`>1%AM-wn%P;nG_?&o#Qr(lW2u(J4Ui{ zm&p&qX@nmobahwQK#Y`+AO8l#EnEPQVYFSfE>yDsB+dZzqi=wlU4w2mcC6uh_vFnY z<#((yT_@`Geo*F|4F-3c?R0JJ<8NF%U}5eql<`+P6p&F zhW`N4ZTlS{J8|jkq~!p?4mcB(?Kjh=*%;u>+fQO_WUW4&F+D?>bvhS3k@}ZU2kY9^ z!ZpGz+tNfFaTTD)?U(S#@W5A1U3Pd*9J_<)SwM#3%ENJHVK^e2nSr3-#fx8&Ho|9B zydGtc5V3=Fn&uOVpw&-FOWerkfSdUTG0cN&Upoi9Ps|GHggqnx_->M4(raFfh$evM z*ZSYe0^u0kC93FF7Pz(LVoC2W0xFu24*;zaK7!4{&C}&;3`^e(aE?LvZj13^|KL-= z!}yBj!5pX6SRs+1wUO7PrQ|=940Ap= z8u!DnD#$tNY=A3v{91A=`Ur4Js^>1uX*qPxn*kLIE+#zcfl$#A z*yoG;smPAzsast%qq)UAKRBD32~EqXbBKw`gD;pMG_Q0BkqTOv?0`rf3P_$LKu^E{ zP{?bpNc&svflUe_dNqZ06Dt`b1YA$5!Ue+2@Nfs1H4qUqjI1Z5RLEdd50!5ntW;bn zZNaUG1^F@_2`M_#-v}C+Mn)bo0>uJ*XgH_>JUQ~%BAEtJGwx7JZ4Lge$Y)oy^?h6( zpiq1pTeAEf8VLxdsrH7xRleDP_Y7%S6e23K8`5OQSHE|rDJ@uoQUf*eB_OKmMh67I zNx6#DqHS7-@}f@>!zpxd^C+X4^byf|{3*iI{C*U=C}%LGrBGeUp*B+SgpYO`0^ zKGnC`ZaG?e_*IDx|3(vMxTLpiAghK_)eJYpu7gAu_Bn{uq9T6r#m`S^1gtd*eMpr} zZTCyLV5hXp6z9>DjCnf*Od$;uAD_C)t<9cG+kmD$4IauFlp_a7BrT~2u{clQH9^{= z4|z}6UrdaN-HV1mVU-9nytlEhJ(pE~XAyLlKq5$`#t{i4)P__*7`Ex% zh}P>EK0t~ZYr(g^v$UmBZ| zNy*8=4lEY`MZ)sO0XEj^T6Us;z#C8m36u}|V}+Uj!0$#=rhaZ|+}aJ3S9tBMrU~M? zWkB5@1NK6K^tejgCVjxgPNuo>oyc)|CEm{+&}uBtdrOsCuFYdNt!9ZxVZ?uIyco0%zGy3hh zqcg9>MPX9LYVJ;VNC?fRRB~jhW@b;jf3wCPW3`-eFDkK$0jYs~d%bS@o?6|WH{_IW zP=nF6WOomCy?k_kl6Q@zALQVF3q6)xcMh~*#b->aGHbq#^R7v6SH$}!vMQvWifqR@ zJSD5$`${p@z$Q~~^Z?M){BBY(QXtIZKWxZ$2~m(!C=3>IYprJof>Lt4_Zo6A)eLuc znmF4)M5n5!C>eC2E*X-OQa%dAJW9xd#gFRk#dmD*tyX~=)%J(cqXc1dQt3&x`d2M3 ze4I5{+YoyMW=VU1spVPsn0DQi8|aXIK6>cQjnR)H55atZkO=6F6+cu9fLV?{hn->h z9WvDo9W7XLS3t<1-~oqsaplr7V8O{$Jpk6A`UPUv#l7VYs1^W)H@@3OO0!@Zzo#5^y@ISKy+LhHdM$=7fr%4Bb+0L);_BLM>)1b=l`6Olst`l%h$4 zP*&&3fa~n+Ai-_H>KJgrGe7K{#h9UK5LpV#&o9ftU>9G2JlhhWg02-!^U+B69FX=~ zwckokKOVHZ7Ljn4hSUuZ+{AG;#B4>+Tn5hgMpJh#zCG`IFN3hh z7jEVDqT|)k*}$IuazBtpPGg<@ydq*I3fc% z&kx=q?{rnr$Iy|}&$|~6!YEE1qVnQ0_rBIjx$vbm+cSpgot;6#hj)gU2YN-clU4u#2{Bv>?1E)2{9@BiTBL5e34eIf4S=5FAyvC~piMJ?r{ zgDug6v`tNl6^|KUFvg4E+n(Af|Mfr7t`K%YiufFXyfgqu&{$KVNXuB&qfcHT|Wl9qf*HX zh?{YH6OOIJYIc@f6*+Lu2I6z7Cm&^Cw*xGG;r9~SU&xOA>Yw-N} zLsZYW4pJm6miN(ty$MZ|1@;D*CVy<7PJy12wpMz7_d`oM#y4PhfUNooD4Ia9OB9gL zydIiTm3ss^Y-FYrYnqF~^qmbe);XL&(m44sp0*DOy;=$RRVPhjOD_D2K7=MN{kn7( zj0>v%VNdR=c@>!y{+i1FvJ&3c_erkgf?Z!Ks4<(oUn^8S0Q(0Z3REwPG%_p-l*-!L z{4v5MjV8UlpFi9^f&yp*NLs-%K!5*4=}3vf6Gc#cCXPj-nwVrRBE}>I!ooL=HfTFD z-iex-P4&i!FVl%(b6wm=?JXJoz}bh62DDxQH9SCx{z&iX1}YTd=mbq|_P58f$F0y5 z<+8HrrD-FVk?KBCEuG%nVgOr$P;i^#sN;S)-N}_34JrUo&I=Z`mFM7BE7YtOntkt2 z<*m~q6hu#-=rZzBRlOj34vd5;13%D+C!BckH$~eY*8xr{8n`dMq6Q0gLTFRZI4fWH zTWnniLTx5e)}`sk`4@EOK{y7WWNBXt=a=+X+-|uW@E7JmKv}-n~eYxpmy0a}luI-s6zAIj2qEBi?+BIbJig zM~K-_+G4eHjFY1%IlG*WQ|6v_yH;u4c=;WDE$SAMDbOiaip#%Rw4QG|)~fk@>F~9b zipGr|v_ht+TY*BTdB|K+rm4f6){m#i_~}%n@|+j~XJ=Y_ku+N;S=c2I`MN8sy(61Y z$zz!V;!x}6wsX+j`V)X!?yV($@a``Tc3beN>dC!U%eH?OTbEjD+;_ooQ88LQ^gE@u z)TTTucPt^m4d#5GS(DjVl`L`pslI^N2%y2JLaShQ0w~e|xT9`;WrJjSqVMe-1~qQv zLKP%C0Tgkg_HJ^MBx7)v8vIdy`WO{eB}pIlX9M!=a^wTRP2)1$Ny26uCPzhCn-OL+ zzPl-7Ki__OP#hvm!;DTZTS(D+{50k=Dp3RtZlLOD)6f7)r7E`wpbes)%m9%Gw(;uC zNygvgN&&NJgQ$r3Jclv6uI~00Z6FjW28xS=;^K$ygX%^~<-idWNdHMYL{iB9(0>L* zfggRUfEJkFZa~;Ky4AcJVySJf&4~hDHq0VG>OYD^DW+g*8X8dR3otzeZQ6gUx5-T) z$Pd;})pg)25NCH!*>?J5<`fik&9Xzo6b=HzBoGJ%I@w(PIoPwH&_WfWf!V=E`0s4p ztOV)+O3*->GhBEnnAM7%_5m$3)-8|VjHJqePe-pls3aI-1z(eNs+9KG_8Z6ii;==w zc@G179w{D(R}gk!khnfI93PLDzDG{=TMp1~z#YlNe)DENX4g71iy0muU(>;vYIITP z_Msj?F+74gc7y6q>2lr!9XtaQT&_CIaFch@>8OwZ1~VNI+Qn`9+!^-$ZJo$*JHr;DP3@*?A?ULPYek0uY!Uwi=%Y{9!(ncU%$Q=Fo z?`P)N1`=bRKL@IhPN4ELg8EdYlmAEjuBL3GbVH?20 z7oBwkdIDu2$n0>l+s@6c-kLV|4!Y$drUCQ#`8J^VUticT=(%uRoR6qWdGQ8gM|Yh_ zxyz0_z^V9c0A992bOLXH@ZmKr=jG+)_ue}3`wbBN;Ozy!T0LuLr;^SCnbuZ3aq}rq z31JE+UN%~nrU5cGK%lgMbc#jg!_{`x5J=)K?kT_e<3~NvJMLBgPcSiT9(FW#u3tVB zV~uH_uc^`1v$fR^^vOm(a^FmiTm>rxNNy^+y5i%avt3!fyIDRv$qd$=$9dTJ_!Q1G zBGX0jgm7V3>CVKbgl%6zz4g|(9N=u3q z&~Oa|Hspu5i96`d?ytJ(00z?3(%SNph_|;ePF%?RqP?Y+tJ9e@JWv+0?LR@e0^}s0 zhu44*b|aU@D|G?9_+h0qJTh|I-;nRC(1KgdN-3SB&w$&yfx5TQFn=2k2u%lgo|?D0 zA{q7wdEVFLPDqZqXCoIke!f%jGR!O0#e;!QHPZ_AtY-z#ua-It_@@;b7zGb5cJ}s8 zZttbQtD$#q)U-~HmDUZA6#xqWRB)?-PY!#6(XDCuruH3cb9fgZdxf=~G{SY3ivj{x ziDh0^R(Ak{PXaJ{Ay4?Yi)q6*yO7RumzkCrmi`A2QYhNqYyX?EH{D;HRfF8;1q|78 z3H^IgKy( zNZH;Tg+ya8UH7Ca&U!W7M&MU@XB1c@fHhkdN&uT&+4OQ`kbq=v%wW9HdASShmn#yR z$Cf6)y}j07V;>J`f!2weD&AwXk#OD#F!pu7Px|3GKD2JXV}<2jVHZnWqZB?s0ptEDx4lS?j=Fd7TJ0>cIzQtun--T_uXZ=*LHV9>EAICzdF#r zhNQyV^KO_dZTn5HM#7%tFwQ2gIBzwEEDCEF)6U=$YpisIBV!3462`b@3yF`U|Njlc8IL z85|@fe*4vdRvDpZ^wLzQN9n!rZ9sL@(ACW#%nWrdrH$<=^tTH8h_x{5{A=S}GJWnrw}q-#L4ntx} zSwkk$eFV2904Y&Dc=+U5DIn>CNSJfz{xd3Z9Cs$=mF2ahrJn}ViLszperD8Qv6Wrp zk^(PA+^lmDl0Ka085kCbY4uai(;xzY_Y|~l$KE>J;y_PhD;N!AmH@h*vo}RPRRN^g z`B;KF@I=-_m8@aCse!(q0*!TLp{_yt_nqfeN6S7nqrtEOFXcwa{b7-30ZT>$CW0M>zPHy=h|=h(NRfXGz1(&Iq8U%vCj$euDZaxc8t3+*mYuid(*-GY6gZA zZ*I1_gg3Ukvw$znTlPPNOL~6X`RkmPH6l~1h7bX%Ww$3Qq^ezYNSf)$9JF*+qs7|emU?FfnE*SMd{%BtUIp{DE z+ZrnRWSF=+OthZsFNR#|^z?Mtn~@H)NN;J$oz4(oiP-D~=yd{*zO3|Dd6t5U@_TJi zD8-6u-Zb)HIB{?SYd;840ZYo~h=!Y4AcnEPIoR1d3s>ShfpjX#GEnAeUZvV*x5Z@b zaUR=}c0kA{EGzJ201`OZ_~5kwQ)_rutDebDejVK8$Cl1U@R1{6{AQfQ^@7oP% z!s#gwLozcn&z(J+JXEzkiPf{mV2GzLGn#0?NptN&*7EpfCRU;uumr f_aELXvO+wt$rD`O5jBT{PJ+uQ-_5=A;K~00H$!n| literal 19894 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 BUXB0& diff --git a/src/napari_matplotlib/tests/test_histogram.py b/src/napari_matplotlib/tests/test_histogram.py index 399db3d..435973b 100644 --- a/src/napari_matplotlib/tests/test_histogram.py +++ b/src/napari_matplotlib/tests/test_histogram.py @@ -17,9 +17,7 @@ def test_histogram_2D_bins(make_napari_viewer, astronaut_data): viewer.add_image(astronaut_data[0], **astronaut_data[1]) widget = HistogramWidget(viewer) viewer.window.add_dock_widget(widget) - widget.bins_start = 0 - widget.bins_stop = 350 - widget.bins_num = 35 + widget.num_bins_widget.setValue(25) fig = widget.figure # Need to return a copy, as original figure is too eagerley garbage # collected by the widget From 70dc2567a17869f635d0b38189d2b07720717ed0 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Fri, 3 May 2024 15:16:10 +0100 Subject: [PATCH 22/68] Only update layers when selection is valid --- docs/changelog.rst | 6 ++++++ src/napari_matplotlib/base.py | 18 +++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 697e483..cab6ca6 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -13,6 +13,12 @@ napari-matplotlib now adheres to `SPEC 0 None: self._update_layers ) + @property + def _valid_layer_selection(self) -> bool: + """ + Return `True` if layer selection is valid. + """ + return self.n_selected_layers in self.n_layers_input and all( + isinstance(layer, self.input_layer_types) for layer in self.layers + ) + def _update_layers(self, event: napari.utils.events.Event) -> None: """ Update the ``layers`` attribute with currently selected layers and re-draw. """ self.layers = list(self.viewer.layers.selection) self.layers = sorted(self.layers, key=lambda layer: layer.name) - self.on_update_layers() + if self._valid_layer_selection: + self.on_update_layers() self._draw() def _draw(self) -> None: @@ -243,10 +253,7 @@ def _draw(self) -> None: with mplstyle.context(self.napari_theme_style_sheet): # everything should be done in the style context self.clear() - if self.n_selected_layers in self.n_layers_input and all( - isinstance(layer, self.input_layer_types) - for layer in self.layers - ): + if self._valid_layer_selection: self.draw() self.canvas.draw() # type: ignore[no-untyped-call] @@ -269,6 +276,7 @@ def on_update_layers(self) -> None: Called when the selected layers are updated. This is a no-op, and is intended for derived classes to override. + It is only called if a selected layer is one of the input layer types. """ From 72e5791a56dfddb7c1c5646f7d1c69cfb6d1d022 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Sat, 25 May 2024 09:36:29 +0100 Subject: [PATCH 23/68] Only set colors if x_axis_key is string --- src/napari_matplotlib/histogram.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index adbbae6..560676c 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -209,10 +209,12 @@ def draw(self) -> None: # get the colormap from the layer depending on its type if isinstance(self.layers[0], napari.layers.Points): colormap = self.layers[0].face_colormap - self.layers[0].face_color = self.x_axis_key + if self.x_axis_key: + self.layers[0].face_color = self.x_axis_key elif isinstance(self.layers[0], napari.layers.Vectors): colormap = self.layers[0].edge_colormap - self.layers[0].edge_color = self.x_axis_key + if self.x_axis_key: + self.layers[0].edge_color = self.x_axis_key else: colormap = None From 2874081c869b32b17977c4df54a7267d59a9a286 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Sat, 25 May 2024 09:44:58 +0100 Subject: [PATCH 24/68] Debugging --- src/napari_matplotlib/base.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/napari_matplotlib/base.py b/src/napari_matplotlib/base.py index 9440b74..97f4146 100644 --- a/src/napari_matplotlib/base.py +++ b/src/napari_matplotlib/base.py @@ -229,6 +229,10 @@ def _valid_layer_selection(self) -> bool: """ Return `True` if layer selection is valid. """ + print(f"{self.n_selected_layers=}") + print(f"{self.n_layers_input=}") + print(f"{self.layers=}") + print(f"{self.input_layer_types=}") return self.n_selected_layers in self.n_layers_input and all( isinstance(layer, self.input_layer_types) for layer in self.layers ) From 3ebea2fdcbe375a079a445bdbffc0d504b62cece Mon Sep 17 00:00:00 2001 From: David Stansby Date: Sat, 25 May 2024 10:01:58 +0100 Subject: [PATCH 25/68] Put self._draw() in clause --- src/napari_matplotlib/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/napari_matplotlib/base.py b/src/napari_matplotlib/base.py index 97f4146..eb023c3 100644 --- a/src/napari_matplotlib/base.py +++ b/src/napari_matplotlib/base.py @@ -245,7 +245,7 @@ def _update_layers(self, event: napari.utils.events.Event) -> None: self.layers = sorted(self.layers, key=lambda layer: layer.name) if self._valid_layer_selection: self.on_update_layers() - self._draw() + self._draw() def _draw(self) -> None: """ From b01809f49893af89965e85a0c98fb3c0dbfc3b79 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Fri, 12 Jul 2024 15:52:02 +0200 Subject: [PATCH 26/68] Always call on_update_layers --- src/napari_matplotlib/base.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/napari_matplotlib/base.py b/src/napari_matplotlib/base.py index eb023c3..720333e 100644 --- a/src/napari_matplotlib/base.py +++ b/src/napari_matplotlib/base.py @@ -229,10 +229,6 @@ def _valid_layer_selection(self) -> bool: """ Return `True` if layer selection is valid. """ - print(f"{self.n_selected_layers=}") - print(f"{self.n_layers_input=}") - print(f"{self.layers=}") - print(f"{self.input_layer_types=}") return self.n_selected_layers in self.n_layers_input and all( isinstance(layer, self.input_layer_types) for layer in self.layers ) @@ -243,8 +239,8 @@ def _update_layers(self, event: napari.utils.events.Event) -> None: """ self.layers = list(self.viewer.layers.selection) self.layers = sorted(self.layers, key=lambda layer: layer.name) + self.on_update_layers() if self._valid_layer_selection: - self.on_update_layers() self._draw() def _draw(self) -> None: @@ -280,7 +276,6 @@ def on_update_layers(self) -> None: Called when the selected layers are updated. This is a no-op, and is intended for derived classes to override. - It is only called if a selected layer is one of the input layer types. """ From 8a545953b8de227fb4f0f79ffddf384ccadcac26 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Fri, 12 Jul 2024 16:00:36 +0200 Subject: [PATCH 27/68] Fix layer selection --- src/napari_matplotlib/histogram.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index 560676c..2881cf7 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -55,8 +55,10 @@ def on_update_layers(self) -> None: Called when the selected layers are updated. """ super().on_update_layers() - for layer in self.viewer.layers: - layer.events.contrast_limits.connect(self._update_contrast_lims) + if self._valid_layer_selection: + self.layers[0].events.contrast_limits.connect( + self._update_contrast_lims + ) def _update_contrast_lims(self) -> None: for lim, line in zip( From 25b9b0ee979694d3fc33f9812115cc47b91efe35 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Fri, 12 Jul 2024 16:13:42 +0200 Subject: [PATCH 28/68] Update changelog --- docs/changelog.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index cab6ca6..54e6bba 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,13 @@ Changelog ========= +2.0.3 +----- +Bug fixes +~~~~~~~~~ +- Fix an error that happened when the histogram widget was open, but a layer that doesn't support + histogramming (e.g., a labels layer) was selected. + 2.0.2 ----- Dependencies @@ -13,12 +20,6 @@ napari-matplotlib now adheres to `SPEC 0 Date: Fri, 12 Jul 2024 16:24:48 +0200 Subject: [PATCH 29/68] Update changelog --- docs/changelog.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 4c2509f..60dd72b 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,12 @@ Changelog ========= +2.1.0 +----- +New features +~~~~~~~~~~~~ +- Added a GUI element to manually set the number of bins in the histogram widgets. + 2.0.3 ----- Bug fixes @@ -48,7 +54,6 @@ Other changes - The ``HistogramWidget`` now has two vertical lines showing the contrast limits used to render the selected layer in the main napari window. - Added an example gallery for the ``FeaturesHistogramWidget``. -- Add widgets for setting bin parameters for ``HistogramWidget``. 1.2.0 ----- From 597e1c5aee7c92d22217887092a4e67303af62f3 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Fri, 12 Jul 2024 16:55:24 +0200 Subject: [PATCH 30/68] Update test image --- .../scatter/baseline/test_scatter_2D.png | Bin 18394 -> 18788 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/napari_matplotlib/tests/scatter/baseline/test_scatter_2D.png b/src/napari_matplotlib/tests/scatter/baseline/test_scatter_2D.png index a11bda5f281b731712a2d1c6076f72569e0ff5e0..0685b192b01b00ab02c5bceb4d4c966a8264cb4a 100644 GIT binary patch literal 18788 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 Date: Mon, 15 Jul 2024 20:06:37 +0100 Subject: [PATCH 31/68] Revert "Update test image" This reverts commit 597e1c5aee7c92d22217887092a4e67303af62f3. --- .../scatter/baseline/test_scatter_2D.png | Bin 18788 -> 18394 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/napari_matplotlib/tests/scatter/baseline/test_scatter_2D.png b/src/napari_matplotlib/tests/scatter/baseline/test_scatter_2D.png index 0685b192b01b00ab02c5bceb4d4c966a8264cb4a..a11bda5f281b731712a2d1c6076f72569e0ff5e0 100644 GIT binary patch literal 18394 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 Date: Mon, 22 Jul 2024 17:45:02 +0000 Subject: [PATCH 32/68] [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.10.1 → v1.11.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.10.1...v1.11.0) - [github.com/astral-sh/ruff-pre-commit: v0.5.1 → v0.5.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.1...v0.5.4) --- .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 ebae2d3..54ee202 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,14 +17,14 @@ repos: - id: napari-plugin-checks - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.10.1 + rev: v1.11.0 hooks: - id: mypy additional_dependencies: [numpy, matplotlib] - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.5.1' + rev: 'v0.5.4' hooks: - id: ruff From 2e8330f509a93f30a6a1f8ccc6cd6d7627e04122 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 17:48:13 +0000 Subject: [PATCH 33/68] [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.5.4 → v0.5.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.4...v0.5.5) --- .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 54ee202..7ede235 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.5.4' + rev: 'v0.5.5' hooks: - id: ruff From 3db8cae7298debeea16577065739047feeab137a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 17:56:44 +0000 Subject: [PATCH 34/68] [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.11.0 → v1.11.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.11.0...v1.11.1) - [github.com/astral-sh/ruff-pre-commit: v0.5.5 → v0.5.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.5...v0.5.6) --- .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 7ede235..14ccabc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,14 +17,14 @@ repos: - id: napari-plugin-checks - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.11.0 + rev: v1.11.1 hooks: - id: mypy additional_dependencies: [numpy, matplotlib] - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.5.5' + rev: 'v0.5.6' hooks: - id: ruff From 6e0348dafef343dbaf9272c1231cbfed33b4cc7c Mon Sep 17 00:00:00 2001 From: Kabilar Gunalan Date: Thu, 8 Aug 2024 16:16:28 -0500 Subject: [PATCH 35/68] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e4551d2..fb7aa63 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ A plugin to create Matplotlib plots from napari layers ## Introduction `napari-matplotlib` is a bridge between `napari` and `matplotlib`, making it easy to create publication quality `Matplotlib` plots based on the data loaded in `napari` layers. -Documentaiton can be found at https://napari-matplotlib.github.io/ +Documentation can be found at https://napari-matplotlib.github.io/ ## Contributing From 0efe4432143760800872f224e45b9a295e7619cb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 19 Aug 2024 17:42:28 +0000 Subject: [PATCH 36/68] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black-pre-commit-mirror: 24.4.2 → 24.8.0](https://github.com/psf/black-pre-commit-mirror/compare/24.4.2...24.8.0) - [github.com/astral-sh/ruff-pre-commit: v0.5.6 → v0.6.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.6...v0.6.1) --- .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 14ccabc..cbf9cb8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/psf/black-pre-commit-mirror - rev: 24.4.2 + rev: 24.8.0 hooks: - id: black @@ -24,7 +24,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.5.6' + rev: 'v0.6.1' hooks: - id: ruff From 3dbfc475bd981cfe11a48c21eb9af81c1f44ca58 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 17:48:50 +0000 Subject: [PATCH 37/68] [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.11.1 → v1.11.2](https://github.com/pre-commit/mirrors-mypy/compare/v1.11.1...v1.11.2) - [github.com/astral-sh/ruff-pre-commit: v0.6.1 → v0.6.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.1...v0.6.2) --- .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 cbf9cb8..26d290e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,14 +17,14 @@ repos: - id: napari-plugin-checks - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.11.1 + rev: v1.11.2 hooks: - id: mypy additional_dependencies: [numpy, matplotlib] - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.6.1' + rev: 'v0.6.2' hooks: - id: ruff From 0acbb986caa2637881369787c403b70e8390d8bf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 17:43:14 +0000 Subject: [PATCH 38/68] [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.6.2 → v0.6.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.2...v0.6.3) --- .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 26d290e..bd4cb83 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.6.2' + rev: 'v0.6.3' hooks: - id: ruff From 0878bc1af0d261ea01ae82c6eb18b3428c3aae6c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 10:06:11 +0000 Subject: [PATCH 39/68] Bump actions/download-artifact from 3 to 4.1.7 in /.github/workflows Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 3 to 4.1.7. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v3...v4.1.7) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 39c1ab5..e1a8621 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -50,7 +50,7 @@ jobs: if: contains(github.ref, 'tags') steps: - uses: actions/checkout@v3 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4.1.7 with: name: docs From 183f4e2e9e5bdd21ba960c351de44bce15dd8355 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 17:45:46 +0000 Subject: [PATCH 40/68] [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.6.3 → v0.6.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.3...v0.6.4) --- .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 bd4cb83..9b8c446 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.6.3' + rev: 'v0.6.4' hooks: - id: ruff From d1b75b1e2a4355f7a338f60089500e188de8226a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 17:43:13 +0000 Subject: [PATCH 41/68] [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.6.4 → v0.6.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.4...v0.6.5) --- .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 9b8c446..8fb0414 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.6.4' + rev: 'v0.6.5' hooks: - id: ruff From bbba362e8cee5eaa96b0c8afa117df648708cef8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 17:43:16 +0000 Subject: [PATCH 42/68] [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.6.5 → v0.6.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.5...v0.6.7) --- .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 8fb0414..331601a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.6.5' + rev: 'v0.6.7' hooks: - id: ruff From d21e7c8a2390040722651807ed9988184952ae58 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 17:47:20 +0000 Subject: [PATCH 43/68] [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.6.7 → v0.6.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.7...v0.6.8) --- .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 331601a..e807909 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.6.7' + rev: 'v0.6.8' hooks: - id: ruff From 7e860f541d3398adaa567f3e56c665e2c58d5bfc Mon Sep 17 00:00:00 2001 From: David Stansby Date: Tue, 1 Oct 2024 12:35:54 +0100 Subject: [PATCH 44/68] Support napari>=0.5 (#274) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove as_dict kwarg * Pin to napari >= 0.5 * Fix scatter test? * Update test image * Fix brain data * Fix another as_dict arg * Fix FutureWarning * Fix scatter test's OverflowError And revert to original baseline images. * Try updating mypy's python version. * Revert shape → newshape and suppress deprecation warning. --------- Co-authored-by: Sam Cunliffe Co-authored-by: Sam Cunliffe --- pyproject.toml | 20 ++++++++++++------- setup.cfg | 2 +- src/napari_matplotlib/base.py | 6 +++--- .../tests/scatter/test_scatter.py | 6 ++++-- src/napari_matplotlib/tests/test_theme.py | 2 +- 5 files changed, 22 insertions(+), 14 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 05f7df6..f76831a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,10 +13,18 @@ filterwarnings = [ "ignore:distutils Version classes are deprecated:DeprecationWarning", "ignore:`np.bool8` is a deprecated alias for `np.bool_`:DeprecationWarning", # Coming from pydantic via napari - "ignore:Pickle, copy, and deepcopy support will be removed from itertools in Python 3.14.:DeprecationWarning" + "ignore:Pickle, copy, and deepcopy support will be removed from itertools in Python 3.14.:DeprecationWarning", + # Until we stop supporting older numpy versions (<2.1) + "ignore:(?s).*`newshape` keyword argument is deprecated.*$:DeprecationWarning", ] qt_api = "pyqt6" -addopts = ["--mpl", "--mpl-baseline-relative", "--strict-config", "--strict-markers", "-ra"] +addopts = [ + "--mpl", + "--mpl-baseline-relative", + "--strict-config", + "--strict-markers", + "-ra", +] minversion = "7" testpaths = ["src/napari_matplotlib/tests"] log_cli_level = "INFO" @@ -54,17 +62,15 @@ ignore = [ convention = "numpy" [tool.mypy] -python_version = "3.10" +python_version = "3.12" # Block below are checks that form part of mypy 'strict' mode strict = true disallow_subclassing_any = false # TODO: fix -warn_return_any = false # TODO: fix +warn_return_any = false # TODO: fix ignore_missing_imports = true enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] [[tool.mypy.overrides]] -module = [ - "napari_matplotlib/tests/*", -] +module = ["napari_matplotlib/tests/*"] disallow_untyped_defs = false diff --git a/setup.cfg b/setup.cfg index 073478a..a3709e6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,7 +28,7 @@ project_urls = packages = find: install_requires = matplotlib - napari<0.5 + napari>=0.5 numpy>=1.23 tinycss2 python_requires = >=3.10 diff --git a/src/napari_matplotlib/base.py b/src/napari_matplotlib/base.py index 720333e..ca69a54 100644 --- a/src/napari_matplotlib/base.py +++ b/src/napari_matplotlib/base.py @@ -42,7 +42,7 @@ def __init__( super().__init__(parent=parent) self.viewer = napari_viewer self.napari_theme_style_sheet = style_sheet_from_theme( - get_theme(napari_viewer.theme, as_dict=False) + get_theme(napari_viewer.theme) ) # Sets figure.* style @@ -84,7 +84,7 @@ def _on_napari_theme_changed(self, event: Event) -> None: Event that triggered the callback. """ self.napari_theme_style_sheet = style_sheet_from_theme( - get_theme(event.value, as_dict=False) + get_theme(event.value) ) self._replace_toolbar_icons() @@ -97,7 +97,7 @@ def _napari_theme_has_light_bg(self) -> bool: bool True if theme's background colour has hsl lighter than 50%, False if darker. """ - theme = napari.utils.theme.get_theme(self.viewer.theme, as_dict=False) + theme = napari.utils.theme.get_theme(self.viewer.theme) _, _, bg_lightness = theme.background.as_hsl_tuple() return bg_lightness > 0.5 diff --git a/src/napari_matplotlib/tests/scatter/test_scatter.py b/src/napari_matplotlib/tests/scatter/test_scatter.py index a225863..0c60660 100644 --- a/src/napari_matplotlib/tests/scatter/test_scatter.py +++ b/src/napari_matplotlib/tests/scatter/test_scatter.py @@ -15,7 +15,9 @@ def test_scatter_2D(make_napari_viewer, astronaut_data): viewer.add_image(astronaut_data[0], **astronaut_data[1], name="astronaut") viewer.add_image( - astronaut_data[0] * -1, **astronaut_data[1], name="astronaut_reversed" + astronaut_data[0] * -1.0, + **astronaut_data[1], + name="astronaut_reversed", ) # De-select existing selection viewer.layers.selection.clear() @@ -36,7 +38,7 @@ def test_scatter_3D(make_napari_viewer, brain_data): viewer.add_image(brain_data[0], **brain_data[1], name="brain") viewer.add_image( - brain_data[0] * -1, **brain_data[1], name="brain_reversed" + brain_data[0] * -1.0, **brain_data[1], name="brain_reversed" ) # De-select existing selection viewer.layers.selection.clear() diff --git a/src/napari_matplotlib/tests/test_theme.py b/src/napari_matplotlib/tests/test_theme.py index 2310f32..5fedc43 100644 --- a/src/napari_matplotlib/tests/test_theme.py +++ b/src/napari_matplotlib/tests/test_theme.py @@ -29,7 +29,7 @@ def _mock_up_theme() -> None: Based on: https://napari.org/stable/gallery/new_theme.html """ - blue_theme = napari.utils.theme.get_theme("dark", False) + blue_theme = napari.utils.theme.get_theme("dark") blue_theme.label = "blue" blue_theme.background = "#4169e1" # my favourite shade of blue napari.utils.theme.register_theme( From 37fce7f0cbeee2ff149196b574f02398b5d73b19 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 20:37:49 +0200 Subject: [PATCH 45/68] [pre-commit.ci] pre-commit autoupdate (#289) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.6.0 → v5.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.6.0...v5.0.0) - [github.com/astral-sh/ruff-pre-commit: v0.6.8 → v0.6.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.8...v0.6.9) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .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 e807909..924bc64 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: check-docstring-first - id: end-of-file-fixer @@ -24,7 +24,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.6.8' + rev: 'v0.6.9' hooks: - id: ruff From 9f49b7acc7bc9fbcb958b105272e4fe384bcaeca Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 14 Oct 2024 19:04:30 +0100 Subject: [PATCH 46/68] [pre-commit.ci] pre-commit autoupdate (#290) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black-pre-commit-mirror: 24.8.0 → 24.10.0](https://github.com/psf/black-pre-commit-mirror/compare/24.8.0...24.10.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .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 924bc64..ff538b9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/psf/black-pre-commit-mirror - rev: 24.8.0 + rev: 24.10.0 hooks: - id: black From b5178753a74158fb256852edf8a851a8598e3fe0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 18:33:49 +0000 Subject: [PATCH 47/68] [pre-commit.ci] pre-commit autoupdate (#291) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.11.2 → v1.13.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.11.2...v1.13.0) - [github.com/astral-sh/ruff-pre-commit: v0.6.9 → v0.7.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.9...v0.7.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .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 ff538b9..761bf7b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,14 +17,14 @@ repos: - id: napari-plugin-checks - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.11.2 + rev: v1.13.0 hooks: - id: mypy additional_dependencies: [numpy, matplotlib] - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.6.9' + rev: 'v0.7.1' hooks: - id: ruff From 20cef6e780527a9d943f9306d79f7a51a90a5acd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2024 18:54:57 +0000 Subject: [PATCH 48/68] [pre-commit.ci] pre-commit autoupdate (#292) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.7.1 → v0.7.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.1...v0.7.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .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 761bf7b..dea49b6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.7.1' + rev: 'v0.7.2' hooks: - id: ruff From ced5d9da6eaf47d00d66cfd7f0b3c4273ed73150 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 17:48:49 +0000 Subject: [PATCH 49/68] [pre-commit.ci] pre-commit autoupdate (#293) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.7.2 → v0.7.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.2...v0.7.3) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .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 dea49b6..3e5d679 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.7.2' + rev: 'v0.7.3' hooks: - id: ruff From 89749f3f7b24be038fafe2e224d70274a741dabd Mon Sep 17 00:00:00 2001 From: Sam Cunliffe Date: Tue, 4 Feb 2025 14:42:30 +0000 Subject: [PATCH 50/68] Update actions to latest versions. --- .github/workflows/docs.yml | 4 ++-- .github/workflows/test_and_deploy.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index e1a8621..965463c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -17,9 +17,9 @@ jobs: name: Build & Upload Artifact runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - uses: actions/setup-python@v3 + - uses: actions/setup-python@v5 with: python-version: "3.10" diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml index 2a8b731..3709ff5 100644 --- a/.github/workflows/test_and_deploy.yml +++ b/.github/workflows/test_and_deploy.yml @@ -27,10 +27,10 @@ jobs: python-version: ['3.10', '3.11', '3.12'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} From 71280f645904d19d16437ab648efe7efe3a27509 Mon Sep 17 00:00:00 2001 From: Sam Cunliffe Date: Tue, 4 Feb 2025 14:45:03 +0000 Subject: [PATCH 51/68] Missed a few. --- .github/workflows/docs.yml | 2 +- .github/workflows/test_and_deploy.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 965463c..fda82b6 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -49,7 +49,7 @@ jobs: needs: build-docs if: contains(github.ref, 'tags') steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/download-artifact@v4.1.7 with: name: docs diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml index 3709ff5..69e2e2a 100644 --- a/.github/workflows/test_and_deploy.yml +++ b/.github/workflows/test_and_deploy.yml @@ -54,7 +54,7 @@ jobs: run: python -m tox - name: Upload pytest test results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: pytest-results-${{ matrix.platform }} py${{ matrix.python-version }} path: reports/ @@ -83,9 +83,9 @@ jobs: permissions: id-token: write steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: "3.x" - name: Install build From 72a4a7f6cbcf420a2441a8d000f3c1a7c2f45fea Mon Sep 17 00:00:00 2001 From: Sam Cunliffe Date: Tue, 4 Feb 2025 14:46:35 +0000 Subject: [PATCH 52/68] Upload docs. --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index fda82b6..67dd158 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -38,7 +38,7 @@ jobs: working-directory: ./docs - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: docs path: docs/_build From 80c782af77ff9334f92bbb3a2f1f11a9a33c546b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 17:29:38 +0000 Subject: [PATCH 53/68] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black-pre-commit-mirror: 24.10.0 → 25.1.0](https://github.com/psf/black-pre-commit-mirror/compare/24.10.0...25.1.0) - [github.com/pre-commit/mirrors-mypy: v1.13.0 → v1.15.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.13.0...v1.15.0) - [github.com/astral-sh/ruff-pre-commit: v0.7.3 → v0.9.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.3...v0.9.9) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3e5d679..fb1a8ea 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/psf/black-pre-commit-mirror - rev: 24.10.0 + rev: 25.1.0 hooks: - id: black @@ -17,14 +17,14 @@ repos: - id: napari-plugin-checks - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.13.0 + rev: v1.15.0 hooks: - id: mypy additional_dependencies: [numpy, matplotlib] - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.7.3' + rev: 'v0.9.9' hooks: - id: ruff From 5f1129e441d004d636a5067d1107e3e42b90241b Mon Sep 17 00:00:00 2001 From: Sam Cunliffe Date: Tue, 4 Mar 2025 16:47:48 +0000 Subject: [PATCH 54/68] Ignore typing for now. --- src/napari_matplotlib/histogram.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index acdd840..f874f0c 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -28,7 +28,7 @@ def _get_bins( data: npt.NDArray[Any], num_bins: int = 100, -) -> npt.NDArray[Any]: +) -> npt.NDArray[np.floating]: """Create evenly spaced bins with a given interval. Parameters @@ -161,13 +161,13 @@ def draw(self) -> None: for i, c in enumerate("rgb"): self.axes.hist( data[..., i].ravel(), - bins=bins.tolist(), + bins=bins.tolist(), # type: ignore[arg-type] label=c, histtype="step", color=_COLORS[c], ) else: - self.axes.hist(data.ravel(), bins=bins.tolist(), label=layer.name) + self.axes.hist(data.ravel(), bins=bins.tolist(), label=layer.name) # type: ignore[arg-type] self._contrast_lines = [ self.axes.axvline(lim, color="white") @@ -297,7 +297,7 @@ def draw(self) -> None: bins = _get_bins(data) - _, bins, patches = self.axes.hist(data, bins=bins.tolist()) + _, bins, patches = self.axes.hist(data, bins=bins.tolist()) # type: ignore[arg-type] patches = cast(BarContainer, patches) # recolor the histogram plot From 3dd313761ef430016d8a90cd0e70ce54eb9045fe Mon Sep 17 00:00:00 2001 From: Sam Cunliffe Date: Tue, 4 Mar 2025 17:16:23 +0000 Subject: [PATCH 55/68] Create dependabot.yml --- .github/dependabot.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..64284b9 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +--- +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" From 439dc67314d483d022b33eb4d544c8efdab00ec9 Mon Sep 17 00:00:00 2001 From: Sam Cunliffe Date: Tue, 4 Mar 2025 17:27:30 +0000 Subject: [PATCH 56/68] Add group so all updates come in a single PR. --- .github/dependabot.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 64284b9..af3b9f0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,3 +5,7 @@ updates: directory: "/" schedule: interval: "monthly" + groups: + github-actions: + patterns: + - "*" From 64e5c7095ee22f071d316e349a4d0e8f59654ac0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Mar 2025 17:33:13 +0000 Subject: [PATCH 57/68] Bump the github-actions group with 2 updates Bumps the github-actions group with 2 updates: [actions/download-artifact](https://github.com/actions/download-artifact) and [codecov/codecov-action](https://github.com/codecov/codecov-action). Updates `actions/download-artifact` from 4.1.7 to 4.1.9 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4.1.7...v4.1.9) Updates `codecov/codecov-action` from 4 to 5 - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/docs.yml | 2 +- .github/workflows/test_and_deploy.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 67dd158..e0e477a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -50,7 +50,7 @@ jobs: if: contains(github.ref, 'tags') steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4.1.7 + - uses: actions/download-artifact@v4.1.9 with: name: docs diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml index 69e2e2a..77853f7 100644 --- a/.github/workflows/test_and_deploy.yml +++ b/.github/workflows/test_and_deploy.yml @@ -62,7 +62,7 @@ jobs: if: ${{ always() }} - name: Coverage - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 # Don't run coverage on merge queue CI to avoid duplicating reports # to codecov. See https://github.com/matplotlib/napari-matplotlib/issues/155 if: github.event_name != 'merge_group' From 067fd10afa65c3f7379ffe7a6f701cee517acff4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Mar 2025 17:22:39 +0000 Subject: [PATCH 58/68] [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.9.9 → v0.9.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.9...v0.9.10) --- .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 fb1a8ea..0547e1d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.9.9' + rev: 'v0.9.10' hooks: - id: ruff From 228458957c7fa2fd0afcbef250a1627001a08ce7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 17:32:41 +0000 Subject: [PATCH 59/68] [pre-commit.ci] pre-commit autoupdate (#299) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.9.10 → v0.11.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.10...v0.11.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .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 0547e1d..cb440b3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.9.10' + rev: 'v0.11.0' hooks: - id: ruff From 2d6a88650595456cc6c8901d03281c9cf99351cc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Mar 2025 18:13:32 +0000 Subject: [PATCH 60/68] [pre-commit.ci] pre-commit autoupdate (#300) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.0 → v0.11.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.0...v0.11.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .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 cb440b3..733413d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.11.0' + rev: 'v0.11.2' hooks: - id: ruff From 1fb935f7a09e22db18f3dbd6f22ab6d6b393b9f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Apr 2025 13:03:27 +0100 Subject: [PATCH 61/68] Bump actions/download-artifact in the github-actions group (#301) Bumps the github-actions group with 1 update: [actions/download-artifact](https://github.com/actions/download-artifact). Updates `actions/download-artifact` from 4.1.9 to 4.2.1 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4.1.9...v4.2.1) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: 4.2.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index e0e477a..b26b499 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -50,7 +50,7 @@ jobs: if: contains(github.ref, 'tags') steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4.1.9 + - uses: actions/download-artifact@v4.2.1 with: name: docs From b81aa2d3a7e14efce9089e5a4d26a88c95ef72ae Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 21:23:51 +0100 Subject: [PATCH 62/68] [pre-commit.ci] pre-commit autoupdate (#302) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.2 → v0.11.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.2...v0.11.4) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .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 733413d..a172dab 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.11.2' + rev: 'v0.11.4' hooks: - id: ruff From e13454a105c55f1f489222cb5bf247d21cf5e536 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 10:42:39 +0100 Subject: [PATCH 63/68] [pre-commit.ci] pre-commit autoupdate (#303) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.4 → v0.11.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.4...v0.11.6) * Remove type:ignore flags for bins in histogram.py. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Sam Cunliffe --- .pre-commit-config.yaml | 2 +- src/napari_matplotlib/histogram.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a172dab..bd929a1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.11.4' + rev: 'v0.11.6' hooks: - id: ruff diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py index f874f0c..85bba9d 100644 --- a/src/napari_matplotlib/histogram.py +++ b/src/napari_matplotlib/histogram.py @@ -161,13 +161,13 @@ def draw(self) -> None: for i, c in enumerate("rgb"): self.axes.hist( data[..., i].ravel(), - bins=bins.tolist(), # type: ignore[arg-type] + bins=bins.tolist(), label=c, histtype="step", color=_COLORS[c], ) else: - self.axes.hist(data.ravel(), bins=bins.tolist(), label=layer.name) # type: ignore[arg-type] + self.axes.hist(data.ravel(), bins=bins.tolist(), label=layer.name) self._contrast_lines = [ self.axes.axvline(lim, color="white") @@ -297,7 +297,7 @@ def draw(self) -> None: bins = _get_bins(data) - _, bins, patches = self.axes.hist(data, bins=bins.tolist()) # type: ignore[arg-type] + _, bins, patches = self.axes.hist(data, bins=bins.tolist()) patches = cast(BarContainer, patches) # recolor the histogram plot From c8fe163f25a3b31d4aa2f640e3756fc0302c45e3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 29 Apr 2025 06:41:45 +0100 Subject: [PATCH 64/68] [pre-commit.ci] pre-commit autoupdate (#304) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.6 → v0.11.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.6...v0.11.7) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .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 bd929a1..81613d0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.11.6' + rev: 'v0.11.7' hooks: - id: ruff From f7a33364e2367334404ef3fc7d94a119f586c1d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 May 2025 10:52:55 +0100 Subject: [PATCH 65/68] Bump actions/download-artifact in the github-actions group (#305) Bumps the github-actions group with 1 update: [actions/download-artifact](https://github.com/actions/download-artifact). Updates `actions/download-artifact` from 4.2.1 to 4.3.0 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4.2.1...v4.3.0) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: 4.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index b26b499..55eb1aa 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -50,7 +50,7 @@ jobs: if: contains(github.ref, 'tags') steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4.2.1 + - uses: actions/download-artifact@v4.3.0 with: name: docs From 912ede93ae95936c5e8e5b654a10dd811c290933 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 6 May 2025 09:26:19 +0100 Subject: [PATCH 66/68] [pre-commit.ci] pre-commit autoupdate (#306) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.7 → v0.11.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.7...v0.11.8) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .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 81613d0..e1b0baf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.11.7' + rev: 'v0.11.8' hooks: - id: ruff From db2be01b230dcfcec984660ba42625e165c907de Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 13 May 2025 06:13:27 +0100 Subject: [PATCH 67/68] [pre-commit.ci] pre-commit autoupdate (#307) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.8 → v0.11.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.8...v0.11.9) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .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 e1b0baf..e592dea 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: 'v0.11.8' + rev: 'v0.11.9' hooks: - id: ruff From 64edb23935c1bfc1c3bdbbf5d6d947c2abe2b02f Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sun, 20 Jul 2025 16:49:40 -0400 Subject: [PATCH 68/68] CI: Harden GHA configuration (#308) * CI: pin actions by SHA This eliminates the possibility of a tag being changed under us. * CI: pin actions by SHA This eliminates the possibility of a tag being changed under us. * CI: auto-fix via zizmor May include: - Avoids risky string interpolation. - Prevents checkout premissions from leaking * CI: Restrict default permissions Reduces risk of arbitrary code is run by attacker. * CI: run pre-commit on GHA --- .github/workflows/docs.yml | 12 +++++++++--- .github/workflows/test_and_deploy.yml | 25 ++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 55eb1aa..6a5d182 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,4 +1,6 @@ name: Build docs +permissions: + contents: read on: @@ -18,12 +20,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: actions/setup-python@v5 with: python-version: "3.10" - - uses: tlambert03/setup-qt-libs@v1 + - uses: tlambert03/setup-qt-libs@19e4ef2d781d81f5f067182e228b54ec90d23b76 # v1 - name: Install Dependencies run: | @@ -32,7 +36,7 @@ jobs: sudo apt install graphviz --yes - name: Build Docs - uses: aganders3/headless-gui@v2 + uses: aganders3/headless-gui@f85dd6316993505dfc5f21839d520ae440c84816 # v2 with: run: make html working-directory: ./docs @@ -50,12 +54,14 @@ jobs: if: contains(github.ref, 'tags') steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: actions/download-artifact@v4.3.0 with: name: docs - name: Push to GitHub pages - uses: JamesIves/github-pages-deploy-action@v4 + uses: JamesIves/github-pages-deploy-action@6c2d9db40f9296374acc17b90404b6e8864128c8 # v4 with: folder: html ssh-key: ${{ secrets.DEPLOY_KEY }} diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml index 77853f7..0521c9e 100644 --- a/.github/workflows/test_and_deploy.yml +++ b/.github/workflows/test_and_deploy.yml @@ -1,4 +1,6 @@ name: tests +permissions: + contents: read on: push: @@ -17,6 +19,19 @@ concurrency: cancel-in-progress: true jobs: + pre-commit: + name: precommit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 + with: + python-version: "3.x" + - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 + with: + extra_args: --hook-stage manual --all-files test: name: ${{ matrix.platform }} py${{ matrix.python-version }} runs-on: ${{ matrix.platform }} @@ -28,6 +43,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 @@ -35,7 +52,7 @@ jobs: python-version: ${{ matrix.python-version }} # these libraries enable testing on Qt on linux - - uses: tlambert03/setup-qt-libs@v1 + - uses: tlambert03/setup-qt-libs@19e4ef2d781d81f5f067182e228b54ec90d23b76 # v1 # strategy borrowed from vispy for installing opengl libs on windows - name: Install Windows OpenGL @@ -62,7 +79,7 @@ jobs: if: ${{ always() }} - name: Coverage - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5 # Don't run coverage on merge queue CI to avoid duplicating reports # to codecov. See https://github.com/matplotlib/napari-matplotlib/issues/155 if: github.event_name != 'merge_group' @@ -84,6 +101,8 @@ jobs: id-token: write steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Set up Python uses: actions/setup-python@v5 with: @@ -99,4 +118,4 @@ jobs: python -m build . - name: Publish package - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4 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