From f20670dc55cf5ffe5d72820f3778c3fe16bf46d4 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Sun, 23 Jun 2024 23:38:44 +1000 Subject: [PATCH 01/11] Fix importerror and message for lxml_html_clean (#7017) Closes #6798 See https://github.com/napari/napari/issues/6798#issuecomment-2181495221 for discussion. --- napari/utils/notebook_display.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/napari/utils/notebook_display.py b/napari/utils/notebook_display.py index fd3e27fcadb..5727bf50fb7 100644 --- a/napari/utils/notebook_display.py +++ b/napari/utils/notebook_display.py @@ -9,7 +9,7 @@ from lxml.html.clean import Cleaner lxml_unavailable = False -except ModuleNotFoundError: +except ImportError: lxml_unavailable = True from napari.utils.io import imsave_png @@ -79,9 +79,9 @@ def _clean_alt_text(self, alt_text): if alt_text is not None: if lxml_unavailable: warn( - 'The lxml library is not installed, and is required to ' - 'sanitize alt text for napari screenshots. Alt-text ' - 'will be stripped altogether without lxml.' + 'The lxml_html_clean library is not installed, and is ' + 'required to sanitize alt text for napari screenshots. ' + 'Alt Text will be stripped altogether.' ) return None # cleaner won't recognize escaped script tags, so always unescape From c43a5ace496e98445bec86ffc55e7caafb1fffc8 Mon Sep 17 00:00:00 2001 From: Peter Sobolewski <76622105+psobolewskiPhD@users.noreply.github.com> Date: Sun, 23 Jun 2024 09:39:29 -0400 Subject: [PATCH 02/11] [UX/UI] flip the default for the reader plugin dialog to *not* have the Remember box checked (#7016) # References and relevant issues Closes: https://github.com/napari/napari/issues/7010 # Description - flip the defaults for the reader plugin dialog for the `Remember this plugin` checkbox from True (checked) to False (unchecked) - flip the tests to match --- napari/_qt/dialogs/_tests/test_reader_dialog.py | 6 +++--- napari/_qt/dialogs/qt_reader_dialog.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/napari/_qt/dialogs/_tests/test_reader_dialog.py b/napari/_qt/dialogs/_tests/test_reader_dialog.py index 63cdfcbeeec..3c724f7fcdd 100644 --- a/napari/_qt/dialogs/_tests/test_reader_dialog.py +++ b/napari/_qt/dialogs/_tests/test_reader_dialog.py @@ -42,7 +42,7 @@ def test_reader_defaults(reader_dialog, tmpdir): assert widg.findChild(QLabel).text().startswith('Choose reader') assert widg._get_plugin_choice() == 'p1' - assert widg.persist_checkbox.isChecked() + assert not widg.persist_checkbox.isChecked() def test_reader_with_error_message(reader_dialog): @@ -84,10 +84,10 @@ def test_get_plugin_choice(tmpdir, reader_dialog): def test_get_persist_choice(tmpdir, reader_dialog): file_pth = tmpdir.join('my_file.tif') widg = reader_dialog(pth=file_pth, readers={'p1': 'p1', 'p2': 'p2'}) - assert widg._get_persist_choice() + assert not widg._get_persist_choice() widg.persist_checkbox.toggle() - assert not widg._get_persist_choice() + assert widg._get_persist_choice() def test_prepare_dialog_options_no_readers(): diff --git a/napari/_qt/dialogs/qt_reader_dialog.py b/napari/_qt/dialogs/qt_reader_dialog.py index 61352c64abe..faabfa674d6 100644 --- a/napari/_qt/dialogs/qt_reader_dialog.py +++ b/napari/_qt/dialogs/qt_reader_dialog.py @@ -27,7 +27,7 @@ def __init__( parent: QWidget = None, readers: Optional[dict[str, str]] = None, error_message: str = '', - persist_checked: bool = True, + persist_checked: bool = False, ) -> None: if readers is None: readers = {} @@ -194,7 +194,7 @@ def handle_gui_reading( pth=_path, error_message=error_message, readers=readers, - persist_checked=not plugin_override, + persist_checked=plugin_override, ) display_name, persist = readerDialog.get_user_choices() if display_name: From ee48e71f10cdb5a5f03543d5e038eaec86b71d45 Mon Sep 17 00:00:00 2001 From: Peter Sobolewski <76622105+psobolewskiPhD@users.noreply.github.com> Date: Sun, 23 Jun 2024 09:40:02 -0400 Subject: [PATCH 03/11] Make Shift-Up/Down in layer list expand and contract selection (#6606) # References and relevant issues Closes https://github.com/napari/napari/issues/6604 # Description Qt behavior is for Shift-Up/Down to only expand selection. This isn't consistent with file managers like macOS Finder or most (all?) text editors, when selecting lines. E.g. Shift-Up adds next item to selection, but then Shift-Down will remove it--and vice versa. As a result in this PR I first prevent Qt from handling Shift-Up and Shift-Down. Then I tweak the (unused) `shift=True` behavior of layers.select_next (and thus select_previous) Finally I add Viewer keybindings to handle Shift-Up/Down to select_next(shift=True) and select_previous(shift=True) Note I hard-code these, so can't be user modified, like some of our other base keybinds. New behavior: https://github.com/napari/napari/assets/76622105/aec778f7-42bc-4dd7-adf5-04780232f0ee --- napari/_qt/containers/qt_layer_list.py | 1 + napari/components/_viewer_key_bindings.py | 12 ++++++ .../events/_tests/test_selectable_list.py | 42 +++++++++++++++++++ .../events/containers/_selectable_list.py | 10 +++-- 4 files changed, 62 insertions(+), 3 deletions(-) diff --git a/napari/_qt/containers/qt_layer_list.py b/napari/_qt/containers/qt_layer_list.py index f94acf6875a..af22a7fd246 100644 --- a/napari/_qt/containers/qt_layer_list.py +++ b/napari/_qt/containers/qt_layer_list.py @@ -73,6 +73,7 @@ def keyPressEvent(self, e: Optional[QKeyEvent]) -> None: e.modifiers() & Qt.KeyboardModifier.AltModifier or e.modifiers() & Qt.KeyboardModifier.ControlModifier or e.modifiers() & Qt.KeyboardModifier.MetaModifier + or e.modifiers() & Qt.KeyboardModifier.ShiftModifier ): e.ignore() elif e.key() != Qt.Key.Key_Space: diff --git a/napari/components/_viewer_key_bindings.py b/napari/components/_viewer_key_bindings.py index d378e3128ad..09a9b4049a3 100644 --- a/napari/components/_viewer_key_bindings.py +++ b/napari/components/_viewer_key_bindings.py @@ -2,6 +2,8 @@ from typing import TYPE_CHECKING +from app_model.types import KeyCode, KeyMod + from napari.components.viewer_model import ViewerModel from napari.utils.action_manager import action_manager from napari.utils.theme import available_themes, get_system_theme @@ -32,6 +34,16 @@ def _inner(func): return _inner +@ViewerModel.bind_key(KeyMod.Shift | KeyCode.UpArrow, overwrite=True) +def extend_selection_to_layer_above(viewer: Viewer): + viewer.layers.select_next(shift=True) + + +@ViewerModel.bind_key(KeyMod.Shift | KeyCode.DownArrow, overwrite=True) +def extend_selection_to_layer_below(viewer: Viewer): + viewer.layers.select_previous(shift=True) + + @register_viewer_action(trans._('Reset scroll.')) def reset_scroll_progress(viewer: Viewer): # on key press diff --git a/napari/utils/events/_tests/test_selectable_list.py b/napari/utils/events/_tests/test_selectable_list.py index 505b7db921b..dc4472146ca 100644 --- a/napari/utils/events/_tests/test_selectable_list.py +++ b/napari/utils/events/_tests/test_selectable_list.py @@ -32,3 +32,45 @@ def test_del_discards_from_selection(): selectable_list = _make_selectable_list_and_select_first(['a', 'b', 'c']) del selectable_list[0] assert 'a' not in selectable_list.selection + + +def test_select_next(): + selectable_list = _make_selectable_list_and_select_first(['a', 'b', 'c']) + assert 'a' in selectable_list.selection + selectable_list.select_next() + assert 'a' not in selectable_list.selection + assert 'b' in selectable_list.selection + + +def test_select_previous(): + selectable_list = _make_selectable_list_and_select_first(['a', 'b', 'c']) + selectable_list.selection.active = 'c' + assert 'a' not in selectable_list.selection + assert 'c' in selectable_list.selection + selectable_list.select_previous() + assert 'c' not in selectable_list.selection + assert 'b' in selectable_list.selection + + +def test_shift_select_next_previous(): + selectable_list = _make_selectable_list_and_select_first(['a', 'b', 'c']) + assert 'a' in selectable_list.selection + selectable_list.select_next(shift=True) + assert 'a' in selectable_list.selection + assert 'b' in selectable_list.selection + selectable_list.select_previous(shift=True) + assert 'a' in selectable_list.selection + assert 'b' not in selectable_list.selection + + +def test_shift_select_previous_next(): + selectable_list = _make_selectable_list_and_select_first(['a', 'b', 'c']) + selectable_list.selection.active = 'c' + assert 'a' not in selectable_list.selection + assert 'c' in selectable_list.selection + selectable_list.select_previous(shift=True) + assert 'b' in selectable_list.selection + assert 'c' in selectable_list.selection + selectable_list.select_next(shift=True) + assert 'b' not in selectable_list.selection + assert 'c' in selectable_list.selection diff --git a/napari/utils/events/containers/_selectable_list.py b/napari/utils/events/containers/_selectable_list.py index 23d6559d909..9a7bcde7dd9 100644 --- a/napari/utils/events/containers/_selectable_list.py +++ b/napari/utils/events/containers/_selectable_list.py @@ -129,13 +129,17 @@ def move_selected(self, index: int, insert: int) -> None: def select_next(self, step: int = 1, shift: bool = False) -> None: """Selects next item from list.""" - if self.selection: + if self.selection and self.selection._current: idx = self.index(self.selection._current) + step if len(self) > idx >= 0: next_layer = self[idx] if shift: - self.selection.add(next_layer) - self.selection._current = next_layer + if next_layer in self.selection: + self.selection.remove(self.selection._current) + self.selection._current = next_layer + else: + self.selection.add(next_layer) + self.selection._current = next_layer else: self.selection.active = next_layer elif len(self) > 0: From 9ecbd1ba7865cff2cdbf4da962b3fd33bc65cfd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Althviz=20Mor=C3=A9?= <16781833+dalthviz@users.noreply.github.com> Date: Sun, 23 Jun 2024 06:40:41 -0700 Subject: [PATCH 04/11] Add tests for viewer scale bar attributes (`colored` and `ticks`) (#7006) # References and relevant issues Closes #6990 # Description Add test to check visibility of scale bar when changing viewer `colored` and `ticks` attribute --- napari/_qt/_tests/test_qt_viewer.py | 97 +++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/napari/_qt/_tests/test_qt_viewer.py b/napari/_qt/_tests/test_qt_viewer.py index f45fedb3c3e..da265fc7ee9 100644 --- a/napari/_qt/_tests/test_qt_viewer.py +++ b/napari/_qt/_tests/test_qt_viewer.py @@ -1087,3 +1087,100 @@ def test_points_2d_to_3d(make_napari_viewer): QApplication.processEvents() viewer.dims.ndisplay = 3 QApplication.processEvents() + + +@skip_local_popups +def test_scale_bar_colored(qt_viewer, qtbot): + viewer = qt_viewer.viewer + scale_bar = viewer.scale_bar + + # Add black image + data = np.zeros((2, 2)) + viewer.add_image(data) + + # Check scale bar is not visible (all the canvas is black - `[0, 0, 0, 255]`) + def check_all_black(): + screenshot = qt_viewer.screenshot(flash=False) + assert np.all(screenshot == [0, 0, 0, 255], axis=-1).all() + + qtbot.waitUntil(check_all_black) + + # Check scale bar is visible (canvas has white `[1, 1, 1, 255]` in it) + def check_white_scale_bar(): + screenshot = qt_viewer.screenshot(flash=False) + assert not np.all(screenshot == [0, 0, 0, 255], axis=-1).all() + assert np.all(screenshot == [1, 1, 1, 255], axis=-1).any() + + scale_bar.visible = True + qtbot.waitUntil(check_white_scale_bar) + + # Check scale bar is colored (canvas has fuchsia `[1, 0, 1, 255]` and not white in it) + def check_colored_scale_bar(): + screenshot = qt_viewer.screenshot(flash=False) + assert not np.all(screenshot == [1, 1, 1, 255], axis=-1).any() + assert np.all(screenshot == [1, 0, 1, 255], axis=-1).any() + + scale_bar.colored = True + qtbot.waitUntil(check_colored_scale_bar) + + # Check scale bar is still visible but not colored (canvas has white again but not fuchsia in it) + def check_only_white_scale_bar(): + screenshot = qt_viewer.screenshot(flash=False) + assert np.all(screenshot == [1, 1, 1, 255], axis=-1).any() + assert not np.all(screenshot == [1, 0, 1, 255], axis=-1).any() + + scale_bar.colored = False + qtbot.waitUntil(check_only_white_scale_bar) + + +@skip_local_popups +def test_scale_bar_ticks(qt_viewer, qtbot): + viewer = qt_viewer.viewer + scale_bar = viewer.scale_bar + + # Add black image + data = np.zeros((2, 2)) + viewer.add_image(data) + + # Check scale bar is not visible (all the canvas is black - `[0, 0, 0, 255]`) + def check_all_black(): + screenshot = qt_viewer.screenshot(flash=False) + assert np.all(screenshot == [0, 0, 0, 255], axis=-1).all() + + qtbot.waitUntil(check_all_black) + + # Check scale bar is visible (canvas has white `[1, 1, 1, 255]` in it) + def check_white_scale_bar(): + screenshot = qt_viewer.screenshot(flash=False) + assert not np.all(screenshot == [0, 0, 0, 255], axis=-1).all() + assert np.all(screenshot == [1, 1, 1, 255], axis=-1).any() + + scale_bar.visible = True + qtbot.waitUntil(check_white_scale_bar) + + # Check scale bar has ticks active and take screenshot for later comparison + assert scale_bar.ticks + screenshot_with_ticks = qt_viewer.screenshot(flash=False) + + # Check scale bar without ticks (still white present but new screenshot differs from ticks one) + def check_no_ticks_scale_bar(): + screenshot = qt_viewer.screenshot(flash=False) + assert np.all(screenshot == [1, 1, 1, 255], axis=-1).any() + npt.assert_raises( + AssertionError, + npt.assert_array_equal, + screenshot, + screenshot_with_ticks, + ) + + scale_bar.ticks = False + qtbot.waitUntil(check_no_ticks_scale_bar) + + # Check scale bar again has ticks (still white present and new screenshot corresponds with ticks one) + def check_ticks_scale_bar(): + screenshot = qt_viewer.screenshot(flash=False) + assert np.all(screenshot == [1, 1, 1, 255], axis=-1).any() + npt.assert_array_equal(screenshot, screenshot_with_ticks) + + scale_bar.ticks = True + qtbot.waitUntil(check_ticks_scale_bar) From e53d6661ff39e538499f21b37c1da19e54d80626 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Sun, 23 Jun 2024 15:41:07 +0200 Subject: [PATCH 05/11] Again fix translate of overlays (this time with tests) (#6927) # References and relevant issues closes #6897 fix again bug from #6633 # Description This PR is fixing again problems with transforms of overlays, this time it contains basic tests. --------- Co-authored-by: Lorenzo Gaifas Co-authored-by: Juan Nunez-Iglesias --- .../_vispy/_tests/test_vispy_image_layer.py | 116 ++++++++++++++++++ napari/_vispy/layers/base.py | 42 +++---- 2 files changed, 133 insertions(+), 25 deletions(-) diff --git a/napari/_vispy/_tests/test_vispy_image_layer.py b/napari/_vispy/_tests/test_vispy_image_layer.py index 61434ea06af..7045b4966c3 100644 --- a/napari/_vispy/_tests/test_vispy_image_layer.py +++ b/napari/_vispy/_tests/test_vispy_image_layer.py @@ -1,6 +1,7 @@ from itertools import permutations import numpy as np +import numpy.testing as npt import pytest from napari._vispy._tests.utils import vispy_image_scene_size @@ -83,3 +84,118 @@ def test_no_float32_texture_support(monkeypatch): ) image = Image(np.zeros((16, 8, 4, 2), dtype='uint8'), scale=(1, 2, 4, 8)) VispyImageLayer(image) + + +@pytest.fixture() +def im_layer() -> Image: + return Image(np.zeros((10, 10))) + + +@pytest.fixture() +def pyramid_layer() -> Image: + return Image([np.zeros((20, 20)), np.zeros((10, 10))]) + + +def test_base_create(im_layer): + VispyImageLayer(im_layer) + + +def set_translate(layer): + layer.translate = (10, 10) + + +def set_affine_translate(layer): + layer.affine.translate = (10, 10) + layer.events.affine() + + +def set_rotate(layer): + layer.rotate = 90 + + +def set_affine_rotate(layer): + layer.affine.rotate = 90 + layer.events.affine() + + +def no_op(layer): + pass + + +@pytest.mark.parametrize( + ('translate', 'exp_translate'), + [ + (set_translate, (10, 10)), + (set_affine_translate, (10, 10)), + (no_op, (0, 0)), + ], + ids=('translate', 'affine_translate', 'no_op'), +) +@pytest.mark.parametrize( + ('rotate', 'exp_rotate'), + [ + (set_rotate, ((0, -1), (1, 0))), + (set_affine_rotate, ((0, -1), (1, 0))), + (no_op, ((1, 0), (0, 1))), + ], + ids=('rotate', 'affine_rotate', 'no_op'), +) +def test_transforming_child_node( + im_layer, translate, exp_translate, rotate, exp_rotate +): + layer = VispyImageLayer(im_layer) + npt.assert_array_almost_equal( + layer.node.transform.matrix[-1][:2], (-0.5, -0.5) + ) + npt.assert_array_almost_equal( + layer.node.transform.matrix[:2, :2], ((1, 0), (0, 1)) + ) + rotate(im_layer) + translate(im_layer) + npt.assert_array_almost_equal( + layer.node.children[0].transform.matrix[:2, :2], ((1, 0), (0, 1)) + ) + npt.assert_array_almost_equal( + layer.node.children[0].transform.matrix[-1][:2], (0.5, 0.5) + ) + npt.assert_array_almost_equal( + layer.node.transform.matrix[:2, :2], exp_rotate + ) + if translate == set_translate and rotate == set_affine_rotate: + npt.assert_array_almost_equal( + layer.node.transform.matrix[-1][:2], + np.dot( + np.linalg.inv(exp_rotate), + np.array([-0.5, -0.5]) + exp_translate, + ), + ) + else: + npt.assert_array_almost_equal( + layer.node.transform.matrix[-1][:2], + np.dot(np.linalg.inv(exp_rotate), (-0.5, -0.5)) + exp_translate, + # np.dot(np.linalg.inv(im_layer.affine.rotate), exp_translate) + ) + + +def test_transforming_child_node_pyramid(pyramid_layer): + layer = VispyImageLayer(pyramid_layer) + corner_pixels_world = np.array([[0, 0], [20, 20]]) + npt.assert_array_almost_equal( + layer.node.transform.matrix[-1][:2], (-0.5, -0.5) + ) + npt.assert_array_almost_equal( + layer.node.children[0].transform.matrix[-1][:2], (0.5, 0.5) + ) + pyramid_layer.translate = (-10, -10) + pyramid_layer._update_draw( + scale_factor=1, + corner_pixels_displayed=corner_pixels_world, + shape_threshold=(10, 10), + ) + + npt.assert_array_almost_equal( + layer.node.transform.matrix[-1][:2], (-0.5, -0.5) + ) + npt.assert_array_almost_equal( + layer.node.children[0].transform.matrix[-1][:2], (-9.5, -9.5) + ) diff --git a/napari/_vispy/layers/base.py b/napari/_vispy/layers/base.py index 115ced14f7d..780e10024b8 100644 --- a/napari/_vispy/layers/base.py +++ b/napari/_vispy/layers/base.py @@ -214,7 +214,8 @@ def _on_matrix_change(self): affine_matrix[: matrix.shape[0], : matrix.shape[1]] = matrix affine_matrix[-1, : len(translate)] = translate - offset = np.zeros(len(self.layer._slice_input.displayed)) + child_offset = np.zeros(len(self.layer._slice_input.displayed)) + dims_displayed = self.layer._slice_input.displayed if self._array_like and self.layer._slice_input.ndisplay == 2: # Perform pixel offset to shift origin from top left corner @@ -230,33 +231,24 @@ def _on_matrix_change(self): affine_offset = np.eye(4) affine_offset[-1, : len(offset)] = offset[::-1] affine_matrix = affine_matrix @ affine_offset + if self.layer.multiscale: + # For performance reasons, when displaying multiscale images, + # only the part of the data that is visible on the canvas is + # sent as a texture to the GPU. This means that the texture + # gets an additional transform, to position the texture + # correctly offset from the origin of the full data. However, + # child nodes, which include overlays such as bounding boxes, + # should *not* receive this offset, so we undo it here: + child_offset = ( + np.ones(offset_matrix.shape[1]) / 2 + - self.layer.corner_pixels[0][dims_displayed][::-1] + ) + else: + child_offset = np.ones(offset_matrix.shape[1]) / 2 self._master_transform.matrix = affine_matrix - # Because of performance reason, for multiscale images - # we load only visible part of data to GPU. - # To place this part of data correctly we update transform, - # but this leads to incorrect placement of child layers. - # To fix this we need to update child layers transform. - dims_displayed = self.layer._slice_input.displayed - simplified_transform = self.layer._transforms.simplified - if simplified_transform is None: - raise ValueError( - 'simplified transform is None' - ) # pragma: no cover - translate_child = ( - self.layer.translate[dims_displayed] - + self.layer.affine.translate[dims_displayed] - )[::-1] - offset[::-1] - trans_rotate = simplified_transform.rotate[ - np.ix_(dims_displayed, dims_displayed) - ] - trans_scale = simplified_transform.scale[dims_displayed][::-1] - new_translate = ( - trans_rotate @ (translate_child - translate) / trans_scale - ) - child_matrix = np.eye(4) - child_matrix[-1, : len(translate)] = new_translate + child_matrix[-1, : len(child_offset)] = child_offset for child in self.node.children: child.transform.matrix = child_matrix From 74ecc56c6bf91b1280e6ddca4a54d442cc30d0dd Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Mon, 24 Jun 2024 23:22:09 +1000 Subject: [PATCH 06/11] Update CITATION.cff with contributors for 0.5.0 (#6958) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks to @Czaki for reminding me that there's no need to do this manually when he created a [script](https://github.com/napari/napari-release-tools/blob/main/find_contributors_without_citation.py) to get this info. 😅 **Edit:** categorising into not responded/added/declined: Not yet responded: @AmirAflak @DanGonite57 @mstabrin Added: @AndrewAnnex @MarchisLost @aeisenbarth @imagejan @jamesyan-git @jo-mueller @maweigert @odinsbane @palec87 @perlman Declined: @stefanv Thank you for contributing to napari! If you would like to be included in the CITATION.cff file for the next release, please comment by filling in the following template (in a code block) and I'll add your response to the file: ``` - given-names: family-names: affiliation: orcid: (optional, format as url: https://orcid.org/0000-0000-0000-0000) alias: (gh handle) ``` --------- Co-authored-by: Grzegorz Bokota --- CITATION.cff | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/CITATION.cff b/CITATION.cff index a58a177a340..0483d680998 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -103,6 +103,11 @@ authors: affiliation: Chan Zuckerberg Initiative orcid: https://orcid.org/0000-0002-3841-8344 alias: aganders3 +- given-names: Andrew + family-names: Annex + affiliation: SETI Institute/NASA ARC + orcid: https://orcid.org/0000-0002-0253-2313 + alias: AndrewAnnex - given-names: Peter family-names: Boone alias: boonepeter @@ -121,6 +126,16 @@ authors: CNRS, Palaiseau, France orcid: https://orcid.org/0000-0002-9441-9173 alias: ClementCaporal +- given-names: Jan + family-names: Eglinger + affiliation: Friedrich Miescher Institute for Biomedical Research (FMI), Basel (Switzerland) + orcid: https://orcid.org/0000-0001-7234-1435 + alias: imagejan +- given-names: Andreas + family-names: Eisenbarth + affiliation: EMBL Heidelberg, Germany + orcid: https://orcid.org/0000-0002-1113-9556 + alias: aeisenbarth - given-names: Jeremy family-names: Freeman affiliation: Chan Zuckerberg Initiative @@ -209,11 +224,21 @@ authors: family-names: Nauroth-Kreß affiliation: University Hospital Würzburg - Institute of Neuroradiology alias: Chris-N-K +- given-names: David + family-names: Palecek + affiliation: Algarve Centre of Marine Sciences (CCMAR) + orcid: https://orcid.org/0009-0003-9328-8540 + alias: palec87 - given-names: Constantin family-names: Pape affiliation: Georg-August-Universität Göttingen orcid: https://orcid.org/0000-0001-6562-7187 alias: constantinpape +- given-names: Eric + family-names: Perlman + affiliation: Yikes LLC + orcid: https://orcid.org/0000-0001-5542-1302 + alias: perlman - given-names: Kim family-names: Pevey alias: kcpevey @@ -227,6 +252,9 @@ authors: affiliation: Brown University orcid: https://orcid.org/0000-0003-4501-5428 alias: kir0ul +- given-names: David + family-names: Pinto + alias: MarchisLost - given-names: Jaime family-names: Rodríguez-Guerra affiliation: Quansight Labs @@ -242,11 +270,19 @@ authors: affiliation: European Bioinformatics Institute - European Molecular Biology Laboratory orcid: https://orcid.org/0000-0002-2447-5911 alias: ctr26 +- given-names: James + family-names: Ryan + alias: jamesyan-git - given-names: Gabriel family-names: Selzer affiliation: University of Wisconsin-Madison orcid: https://orcid.org/0009-0002-2400-1940 alias: gselzer +- given-names: MB + family-names: Smith + affiliation: AI lab for Living Technologies, University Medical Centre Utrecht (The Netherlands) + orcid: https://orcid.org/0000-0002-1405-0100 + alias: odinsbane - given-names: Paul family-names: Smith affiliation: University College London @@ -255,6 +291,11 @@ authors: - given-names: Konstantin family-names: Sofiiuk alias: ksofiyuk +- given-names: Johannes + family-names: Soltwedel + affiliation: DFG cluster of excellence 'Physics of Life', TU Dresden + orcid: https://orcid.org/0000-0003-1273-2412 + alias: jo-mueller - given-names: David family-names: Stansby affiliation: University College London @@ -269,6 +310,11 @@ authors: family-names: Wadhwa affiliation: Quansight Labs alias: ppwadhwa +- given-names: Martin + family-names: Weigert + affiliation: TU-Dresden / EPFL + orcid: https://orcid.org/0000-0002-7780-9057 + alias: maweigert - given-names: Jonas family-names: Windhager affiliation: ETH Zurich / University of Zurich From 1a5362affc4e9c83fe11613074c0d2e951f1222c Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Tue, 25 Jun 2024 09:35:53 +1000 Subject: [PATCH 07/11] Add convenience input validation for Labels colormaps (#7025) --- napari/layers/labels/_tests/test_labels.py | 20 ++ napari/layers/labels/labels.py | 2 + napari/utils/colormaps/_accelerated_cmap.py | 233 +++++++++++++++ .../utils/colormaps/_tests/test_colormap.py | 70 ++++- napari/utils/colormaps/colormap.py | 268 ++++-------------- napari/utils/colormaps/colormap_utils.py | 2 +- 6 files changed, 365 insertions(+), 230 deletions(-) create mode 100644 napari/utils/colormaps/_accelerated_cmap.py diff --git a/napari/layers/labels/_tests/test_labels.py b/napari/layers/labels/_tests/test_labels.py index b30e92bcf1b..67a87017056 100644 --- a/napari/layers/labels/_tests/test_labels.py +++ b/napari/layers/labels/_tests/test_labels.py @@ -419,6 +419,26 @@ def test_custom_color_dict(): assert not (layer.get_color(1) == np.array([1.0, 1.0, 1.0, 1.0])).all() +@pytest.mark.parametrize( + 'colormap_like', + [ + ['red', 'blue'], + [[1, 0, 0, 1], [0, 0, 1, 1]], + {None: 'transparent', 1: 'red', 2: 'blue'}, + {None: [0, 0, 0, 0], 1: [1, 0, 0, 1], 2: [0, 0, 1, 1]}, + defaultdict(lambda: 'transparent', {1: 'red', 2: 'blue'}), + ], +) +def test_colormap_simple_data_types(colormap_like): + """Test that setting colormap with list or dict of colors works.""" + data = np.random.randint(20, size=(10, 15)) + # test in constructor + _ = Labels(data, colormap=colormap_like) + # test assignment + layer = Labels(data) + layer.colormap = colormap_like + + def test_metadata(): """Test setting labels metadata.""" np.random.seed(0) diff --git a/napari/layers/labels/labels.py b/napari/layers/labels/labels.py index 9254ec89d68..96c350b2064 100644 --- a/napari/layers/labels/labels.py +++ b/napari/layers/labels/labels.py @@ -53,6 +53,7 @@ from napari.utils.colormaps.colormap import ( CyclicLabelColormap, LabelColormapBase, + _normalize_label_colormap, ) from napari.utils.colormaps.colormap_utils import shuffle_and_extend_colormap from napari.utils.events import EmitterGroup, Event @@ -500,6 +501,7 @@ def colormap(self, colormap: LabelColormapBase): self._set_colormap(colormap) def _set_colormap(self, colormap): + colormap = _normalize_label_colormap(colormap) if isinstance(colormap, CyclicLabelColormap): self._random_colormap = colormap self._original_random_colormap = colormap diff --git a/napari/utils/colormaps/_accelerated_cmap.py b/napari/utils/colormaps/_accelerated_cmap.py new file mode 100644 index 00000000000..d6a7c35908d --- /dev/null +++ b/napari/utils/colormaps/_accelerated_cmap.py @@ -0,0 +1,233 @@ +""" +Colormap utility functions to be sped-up by numba JIT. + +These should stay in a separate module because they need to be reloaded during +testing, which can break instance/class relationships when done dynamically. +See https://github.com/napari/napari/pull/7025#issuecomment-2186190719. +""" + +from typing import TYPE_CHECKING + +import numpy as np + +if TYPE_CHECKING: + from numba import typed + + from napari.utils.colormaps import DirectLabelColormap + + +__all__ = ( + 'minimum_dtype_for_labels', + 'zero_preserving_modulo', + 'labels_raw_to_texture_direct', + 'zero_preserving_modulo_numpy', +) + +MAPPING_OF_UNKNOWN_VALUE = 0 +# For direct mode we map all unknown values to single value +# for simplicity of implementation we select 0 + + +def minimum_dtype_for_labels(num_colors: int) -> np.dtype: + """Return the minimum texture dtype that can hold given number of colors. + + Parameters + ---------- + num_colors : int + Number of unique colors in the data. + + Returns + ------- + np.dtype + Minimum dtype that can hold the number of colors. + """ + if num_colors <= np.iinfo(np.uint8).max: + return np.dtype(np.uint8) + if num_colors <= np.iinfo(np.uint16).max: + return np.dtype(np.uint16) + return np.dtype(np.float32) + + +def zero_preserving_modulo_numpy( + values: np.ndarray, n: int, dtype: np.dtype, to_zero: int = 0 +) -> np.ndarray: + """``(values - 1) % n + 1``, but with one specific value mapped to 0. + + This ensures (1) an output value in [0, n] (inclusive), and (2) that + no nonzero values in the input are zero in the output, other than the + ``to_zero`` value. + + Parameters + ---------- + values : np.ndarray + The dividend of the modulo operator. + n : int + The divisor. + dtype : np.dtype + The desired dtype for the output array. + to_zero : int, optional + A specific value to map to 0. (By default, 0 itself.) + + Returns + ------- + np.ndarray + The result: 0 for the ``to_zero`` value, ``values % n + 1`` + everywhere else. + """ + res = ((values - 1) % n + 1).astype(dtype) + res[values == to_zero] = 0 + return res + + +def _zero_preserving_modulo_loop( + values: np.ndarray, n: int, dtype: np.dtype, to_zero: int = 0 +) -> np.ndarray: + """``(values - 1) % n + 1``, but with one specific value mapped to 0. + + This ensures (1) an output value in [0, n] (inclusive), and (2) that + no nonzero values in the input are zero in the output, other than the + ``to_zero`` value. + + Parameters + ---------- + values : np.ndarray + The dividend of the modulo operator. + n : int + The divisor. + dtype : np.dtype + The desired dtype for the output array. + to_zero : int, optional + A specific value to map to 0. (By default, 0 itself.) + + Returns + ------- + np.ndarray + The result: 0 for the ``to_zero`` value, ``values % n + 1`` + everywhere else. + """ + result = np.empty_like(values, dtype=dtype) + # need to preallocate numpy array for asv memory benchmarks + return _zero_preserving_modulo_inner_loop(values, n, to_zero, out=result) + + +def _zero_preserving_modulo_inner_loop( + values: np.ndarray, n: int, to_zero: int, out: np.ndarray +) -> np.ndarray: + """``(values - 1) % n + 1``, but with one specific value mapped to 0. + + This ensures (1) an output value in [0, n] (inclusive), and (2) that + no nonzero values in the input are zero in the output, other than the + ``to_zero`` value. + + Parameters + ---------- + values : np.ndarray + The dividend of the modulo operator. + n : int + The divisor. + to_zero : int + A specific value to map to 0. (Usually, 0 itself.) + out : np.ndarray + Preallocated output array + + Returns + ------- + np.ndarray + The result: 0 for the ``to_zero`` value, ``values % n + 1`` + everywhere else. + """ + for i in prange(values.size): + if values.flat[i] == to_zero: + out.flat[i] = 0 + else: + out.flat[i] = (values.flat[i] - 1) % n + 1 + + return out + + +def _labels_raw_to_texture_direct_numpy( + data: np.ndarray, direct_colormap: 'DirectLabelColormap' +) -> np.ndarray: + """Convert labels data to the data type used in the texture. + + This implementation uses numpy vectorized operations. + + See `_cast_labels_data_to_texture_dtype_direct` for more details. + """ + if direct_colormap.use_selection: + return (data == direct_colormap.selection).astype(np.uint8) + mapper = direct_colormap._array_map + if any(x < 0 for x in direct_colormap.color_dict if x is not None): + half_shape = mapper.shape[0] // 2 - 1 + data = np.clip(data, -half_shape, half_shape) + else: + data = np.clip(data, 0, mapper.shape[0] - 1) + + return mapper[data] + + +def _labels_raw_to_texture_direct_loop( + data: np.ndarray, direct_colormap: 'DirectLabelColormap' +) -> np.ndarray: + """ + Cast direct labels to the minimum type. + + Parameters + ---------- + data : np.ndarray + The input data array. + direct_colormap : DirectLabelColormap + The direct colormap. + + Returns + ------- + np.ndarray + The cast data array. + """ + if direct_colormap.use_selection: + return (data == direct_colormap.selection).astype(np.uint8) + + dkt = direct_colormap._get_typed_dict_mapping(data.dtype) + target_dtype = minimum_dtype_for_labels( + direct_colormap._num_unique_colors + 2 + ) + result_array = np.full_like( + data, MAPPING_OF_UNKNOWN_VALUE, dtype=target_dtype + ) + return _labels_raw_to_texture_direct_inner_loop(data, dkt, result_array) + + +def _labels_raw_to_texture_direct_inner_loop( + data: np.ndarray, dkt: 'typed.Dict', out: np.ndarray +) -> np.ndarray: + """ + Relabel data using typed dict with mapping unknown labels to default value + """ + # The numba typed dict does not provide official Api for + # determine key and value types + for i in prange(data.size): + val = data.flat[i] + if val in dkt: + out.flat[i] = dkt[data.flat[i]] + + return out + + +try: + import numba +except ModuleNotFoundError: + zero_preserving_modulo = zero_preserving_modulo_numpy + labels_raw_to_texture_direct = _labels_raw_to_texture_direct_numpy + prange = range +else: + _zero_preserving_modulo_inner_loop = numba.njit(parallel=True, cache=True)( + _zero_preserving_modulo_inner_loop + ) + zero_preserving_modulo = _zero_preserving_modulo_loop + labels_raw_to_texture_direct = _labels_raw_to_texture_direct_loop + _labels_raw_to_texture_direct_inner_loop = numba.njit( + parallel=True, cache=True + )(_labels_raw_to_texture_direct_inner_loop) + prange = numba.prange # type: ignore [misc] + + del numba diff --git a/napari/utils/colormaps/_tests/test_colormap.py b/napari/utils/colormaps/_tests/test_colormap.py index 16ca87ac80a..6ed4f8b32a3 100644 --- a/napari/utils/colormaps/_tests/test_colormap.py +++ b/napari/utils/colormaps/_tests/test_colormap.py @@ -1,4 +1,5 @@ import importlib +from collections import defaultdict from itertools import product from unittest.mock import patch @@ -8,12 +9,17 @@ from napari._pydantic_compat import ValidationError from napari.utils.color import ColorArray -from napari.utils.colormaps import Colormap, colormap -from napari.utils.colormaps.colormap import ( +from napari.utils.colormaps import Colormap, _accelerated_cmap, colormap +from napari.utils.colormaps._accelerated_cmap import ( MAPPING_OF_UNKNOWN_VALUE, - DirectLabelColormap, _labels_raw_to_texture_direct_numpy, ) +from napari.utils.colormaps.colormap import ( + CyclicLabelColormap, + DirectLabelColormap, + LabelColormapBase, + _normalize_label_colormap, +) from napari.utils.colormaps.colormap_utils import label_colormap @@ -137,16 +143,23 @@ def test_mapped_shape(ndim): ('num', 'dtype'), [(40, np.uint8), (1000, np.uint16), (80000, np.float32)] ) def test_minimum_dtype_for_labels(num, dtype): - assert colormap.minimum_dtype_for_labels(num) == dtype + assert _accelerated_cmap.minimum_dtype_for_labels(num) == dtype @pytest.fixture() def _disable_jit(monkeypatch): + """Fixture to temporarily disable numba JIT during testing. + + This helps to measure coverage and in debugging. *However*, reloading a + module can cause issues with object instance / class relationships, so + the `_accelerated_cmap` module should be as small as possible and contain + no class definitions, only functions. + """ pytest.importorskip('numba') with patch('numba.core.config.DISABLE_JIT', True): - importlib.reload(colormap) + importlib.reload(_accelerated_cmap) yield - importlib.reload(colormap) # revert to original state + importlib.reload(_accelerated_cmap) # revert to original state @pytest.mark.parametrize(('num', 'dtype'), [(40, np.uint8), (1000, np.uint16)]) @@ -222,7 +235,9 @@ def test_direct_label_colormap_selection(direct_label_colormap): @pytest.mark.usefixtures('_disable_jit') def test_cast_direct_labels_to_minimum_type(direct_label_colormap): data = np.arange(15, dtype=np.uint32) - cast = colormap._labels_raw_to_texture_direct(data, direct_label_colormap) + cast = _accelerated_cmap.labels_raw_to_texture_direct( + data, direct_label_colormap + ) label_mapping = ( direct_label_colormap._values_mapping_to_minimum_values_set()[0] ) @@ -270,15 +285,15 @@ def test_test_cast_direct_labels_to_minimum_type_no_jit(num, dtype): cmap.color_dict[None] = np.array([1, 1, 1, 1]) data = np.arange(10, dtype=np.uint32) data[2] = 80005 - cast = colormap._labels_raw_to_texture_direct(data, cmap) + cast = _accelerated_cmap.labels_raw_to_texture_direct(data, cmap) assert cast.dtype == dtype def test_zero_preserving_modulo_naive(): pytest.importorskip('numba') data = np.arange(1000, dtype=np.uint32) - res1 = colormap._zero_preserving_modulo_numpy(data, 49, np.uint8) - res2 = colormap._zero_preserving_modulo(data, 49, np.uint8) + res1 = _accelerated_cmap.zero_preserving_modulo_numpy(data, 49, np.uint8) + res2 = _accelerated_cmap.zero_preserving_modulo(data, 49, np.uint8) npt.assert_array_equal(res1, res2) @@ -332,7 +347,9 @@ def test_label_colormap_using_cache(dtype, monkeypatch): expected = np.array([[0, 0, 0, 0], [1, 0, 0, 1], [0, 1, 0, 1]]) map1 = cmap.map(values) npt.assert_array_equal(map1, expected) - monkeypatch.setattr(colormap, '_zero_preserving_modulo_numpy', None) + monkeypatch.setattr( + _accelerated_cmap, 'zero_preserving_modulo_numpy', None + ) map2 = cmap.map(values) npt.assert_array_equal(map1, map2) @@ -341,7 +358,7 @@ def test_label_colormap_using_cache(dtype, monkeypatch): def test_cast_direct_labels_to_minimum_type_naive(size): pytest.importorskip('numba') data = np.arange(size, dtype=np.uint32) - dtype = colormap.minimum_dtype_for_labels(size) + dtype = _accelerated_cmap.minimum_dtype_for_labels(size) cmap = DirectLabelColormap( color_dict={ None: np.array([1, 1, 1, 1]), @@ -355,8 +372,8 @@ def test_cast_direct_labels_to_minimum_type_naive(size): }, ) cmap.color_dict[None] = np.array([255, 255, 255, 255]) - res1 = colormap._labels_raw_to_texture_direct(data, cmap) - res2 = colormap._labels_raw_to_texture_direct_numpy(data, cmap) + res1 = _accelerated_cmap.labels_raw_to_texture_direct(data, cmap) + res2 = _accelerated_cmap._labels_raw_to_texture_direct_numpy(data, cmap) npt.assert_array_equal(res1, res2) assert res1.dtype == dtype assert res2.dtype == dtype @@ -506,3 +523,28 @@ def test_direct_colormap_negative_values_numpy(): np.array([-1, -2, 5], dtype=np.int8), cmap ) npt.assert_array_equal(res, [0, 1, 0]) + + +@pytest.mark.parametrize( + 'colormap_like', + [ + ['red', 'blue'], + [[1, 0, 0, 1], [0, 0, 1, 1]], + {None: 'transparent', 1: 'red', 2: 'blue'}, + {None: [0, 0, 0, 0], 1: [1, 0, 0, 1], 2: [0, 0, 1, 1]}, + defaultdict(lambda: 'transparent', {1: 'red', 2: 'blue'}), + CyclicLabelColormap(['red', 'blue']), + DirectLabelColormap( + color_dict={None: 'transparent', 1: 'red', 2: 'blue'} + ), + 5, # test ValueError + ], +) +def test_normalize_label_colormap(colormap_like): + if not isinstance(colormap_like, int): + assert isinstance( + _normalize_label_colormap(colormap_like), LabelColormapBase + ) + else: + with pytest.raises(ValueError, match='Unable to interpret'): + _normalize_label_colormap(colormap_like) diff --git a/napari/utils/colormaps/colormap.py b/napari/utils/colormaps/colormap.py index ff05e02cd66..b07ccb8ff18 100644 --- a/napari/utils/colormaps/colormap.py +++ b/napari/utils/colormaps/colormap.py @@ -1,4 +1,5 @@ from collections import defaultdict +from collections.abc import MutableMapping, Sequence from functools import cached_property from typing import ( TYPE_CHECKING, @@ -15,6 +16,7 @@ from napari._pydantic_compat import Field, PrivateAttr, validator from napari.utils.color import ColorArray +from napari.utils.colormaps import _accelerated_cmap as _accel_cmap from napari.utils.colormaps.colorbars import make_colorbar from napari.utils.colormaps.standardize_color import transform_color from napari.utils.compat import StrEnum @@ -26,10 +28,6 @@ if TYPE_CHECKING: from numba import typed -MAPPING_OF_UNKNOWN_VALUE = 0 -# For direct mode we map all unknown values to single value -# for simplicity of implementation we select 0 - class ColormapInterpolationMode(StrEnum): """INTERPOLATION: Interpolation mode for colormaps. @@ -314,7 +312,7 @@ def _data_to_texture( return _cast_labels_data_to_texture_dtype_auto(values, self) def _map_without_cache(self, values) -> np.ndarray: - texture_dtype_values = _zero_preserving_modulo_numpy( + texture_dtype_values = _accel_cmap.zero_preserving_modulo_numpy( values, len(self.colors) - 1, values.dtype, @@ -494,7 +492,9 @@ def map(self, values: Union[np.ndarray, np.integer, int]) -> np.ndarray: if mapper is not None: mapped = mapper[values] else: - values_cast = _labels_raw_to_texture_direct(values, self) + values_cast = _accel_cmap.labels_raw_to_texture_direct( + values, self + ) mapped = self._map_precast(values_cast, apply_selection=True) if self.use_selection: @@ -503,7 +503,7 @@ def map(self, values: Union[np.ndarray, np.integer, int]) -> np.ndarray: def _map_without_cache(self, values: np.ndarray) -> np.ndarray: cmap = self._cmap_without_selection() - cast = _labels_raw_to_texture_direct(values, cmap) + cast = _accel_cmap.labels_raw_to_texture_direct(values, cmap) return self._map_precast(cast, apply_selection=False) def _map_precast(self, values, apply_selection) -> np.ndarray: @@ -581,10 +581,10 @@ def _label_mapping_and_color_dict( ) -> tuple[dict[Optional[int], int], dict[int, np.ndarray]]: color_to_labels: dict[tuple[int, ...], list[Optional[int]]] = {} labels_to_new_labels: dict[Optional[int], int] = { - None: MAPPING_OF_UNKNOWN_VALUE + None: _accel_cmap.MAPPING_OF_UNKNOWN_VALUE } new_color_dict: dict[int, np.ndarray] = { - MAPPING_OF_UNKNOWN_VALUE: self.default_color, + _accel_cmap.MAPPING_OF_UNKNOWN_VALUE: self.default_color, } for label, color in self.color_dict.items(): @@ -632,7 +632,9 @@ def _get_typed_dict_mapping(self, data_dtype: np.dtype) -> 'typed.Dict': from numba import typed, types # num_unique_colors + 2 because we need to map None and background - target_type = minimum_dtype_for_labels(self._num_unique_colors + 2) + target_type = _accel_cmap.minimum_dtype_for_labels( + self._num_unique_colors + 2 + ) dkt = typed.Dict.empty( key_type=getattr(types, data_dtype.name), @@ -663,7 +665,9 @@ def _array_map(self): 'Cannot use numpy implementation for large values of labels ' 'direct colormap. Please install numba.' ) - dtype = minimum_dtype_for_labels(self._num_unique_colors + 2) + dtype = _accel_cmap.minimum_dtype_for_labels( + self._num_unique_colors + 2 + ) label_mapping = self._values_mapping_to_minimum_values_set()[0] # We need 2 + the max value: one because we will be indexing with the @@ -671,7 +675,7 @@ def _array_map(self): # that index and map to the default value, rather than to the max # value in the map. mapper = np.full( - (max_value + 2), MAPPING_OF_UNKNOWN_VALUE, dtype=dtype + (max_value + 2), _accel_cmap.MAPPING_OF_UNKNOWN_VALUE, dtype=dtype ) for key, val in label_mapping.items(): if key is None: @@ -775,14 +779,14 @@ def _cast_labels_data_to_texture_dtype_auto( data_arr = np.atleast_1d(data) num_colors = len(colormap.colors) - 1 - zero_preserving_modulo_func = _zero_preserving_modulo + zero_preserving_modulo_func = _accel_cmap.zero_preserving_modulo if isinstance(data, np.integer): - zero_preserving_modulo_func = _zero_preserving_modulo_numpy + zero_preserving_modulo_func = _accel_cmap.zero_preserving_modulo_numpy - dtype = minimum_dtype_for_labels(num_colors + 1) + dtype = _accel_cmap.minimum_dtype_for_labels(num_colors + 1) if colormap.use_selection: - selection_in_texture = _zero_preserving_modulo_numpy( + selection_in_texture = _accel_cmap.zero_preserving_modulo_numpy( np.array([colormap.selection]), num_colors, dtype ) converted = np.where( @@ -799,103 +803,6 @@ def _cast_labels_data_to_texture_dtype_auto( return np.reshape(converted, original_shape) -def _zero_preserving_modulo_numpy( - values: np.ndarray, n: int, dtype: np.dtype, to_zero: int = 0 -) -> np.ndarray: - """``(values - 1) % n + 1``, but with one specific value mapped to 0. - - This ensures (1) an output value in [0, n] (inclusive), and (2) that - no nonzero values in the input are zero in the output, other than the - ``to_zero`` value. - - Parameters - ---------- - values : np.ndarray - The dividend of the modulo operator. - n : int - The divisor. - dtype : np.dtype - The desired dtype for the output array. - to_zero : int, optional - A specific value to map to 0. (By default, 0 itself.) - - Returns - ------- - np.ndarray - The result: 0 for the ``to_zero`` value, ``values % n + 1`` - everywhere else. - """ - res = ((values - 1) % n + 1).astype(dtype) - res[values == to_zero] = 0 - return res - - -def _zero_preserving_modulo_loop( - values: np.ndarray, n: int, dtype: np.dtype, to_zero: int = 0 -) -> np.ndarray: - """``(values - 1) % n + 1``, but with one specific value mapped to 0. - - This ensures (1) an output value in [0, n] (inclusive), and (2) that - no nonzero values in the input are zero in the output, other than the - ``to_zero`` value. - - Parameters - ---------- - values : np.ndarray - The dividend of the modulo operator. - n : int - The divisor. - dtype : np.dtype - The desired dtype for the output array. - to_zero : int, optional - A specific value to map to 0. (By default, 0 itself.) - - Returns - ------- - np.ndarray - The result: 0 for the ``to_zero`` value, ``values % n + 1`` - everywhere else. - """ - result = np.empty_like(values, dtype=dtype) - # need to preallocate numpy array for asv memory benchmarks - return _zero_preserving_modulo_inner_loop(values, n, to_zero, out=result) - - -def _zero_preserving_modulo_inner_loop( - values: np.ndarray, n: int, to_zero: int, out: np.ndarray -) -> np.ndarray: - """``(values - 1) % n + 1``, but with one specific value mapped to 0. - - This ensures (1) an output value in [0, n] (inclusive), and (2) that - no nonzero values in the input are zero in the output, other than the - ``to_zero`` value. - - Parameters - ---------- - values : np.ndarray - The dividend of the modulo operator. - n : int - The divisor. - to_zero : int - A specific value to map to 0. (Usually, 0 itself.) - out : np.ndarray - Preallocated output array - - Returns - ------- - np.ndarray - The result: 0 for the ``to_zero`` value, ``values % n + 1`` - everywhere else. - """ - for i in prange(values.size): - if values.flat[i] == to_zero: - out.flat[i] = 0 - else: - out.flat[i] = (values.flat[i] - 1) % n + 1 - - return out - - @overload def _cast_labels_data_to_texture_dtype_direct( data: np.ndarray, direct_colormap: DirectLabelColormap @@ -949,89 +856,21 @@ def _cast_labels_data_to_texture_dtype_direct( if isinstance(data, np.integer): mapper = direct_colormap._label_mapping_and_color_dict[0] - target_dtype = minimum_dtype_for_labels( + target_dtype = _accel_cmap.minimum_dtype_for_labels( direct_colormap._num_unique_colors + 2 ) return target_dtype.type( - mapper.get(int(data), MAPPING_OF_UNKNOWN_VALUE) + mapper.get(int(data), _accel_cmap.MAPPING_OF_UNKNOWN_VALUE) ) original_shape = np.shape(data) array_data = np.atleast_1d(data) return np.reshape( - _labels_raw_to_texture_direct(array_data, direct_colormap), + _accel_cmap.labels_raw_to_texture_direct(array_data, direct_colormap), original_shape, ) -def _labels_raw_to_texture_direct_numpy( - data: np.ndarray, direct_colormap: DirectLabelColormap -) -> np.ndarray: - """Convert labels data to the data type used in the texture. - - This implementation uses numpy vectorized operations. - - See `_cast_labels_data_to_texture_dtype_direct` for more details. - """ - if direct_colormap.use_selection: - return (data == direct_colormap.selection).astype(np.uint8) - mapper = direct_colormap._array_map - if any(x < 0 for x in direct_colormap.color_dict if x is not None): - half_shape = mapper.shape[0] // 2 - 1 - data = np.clip(data, -half_shape, half_shape) - else: - data = np.clip(data, 0, mapper.shape[0] - 1) - - return mapper[data] - - -def _labels_raw_to_texture_direct_loop( - data: np.ndarray, direct_colormap: DirectLabelColormap -) -> np.ndarray: - """ - Cast direct labels to the minimum type. - - Parameters - ---------- - data : np.ndarray - The input data array. - direct_colormap : DirectLabelColormap - The direct colormap. - - Returns - ------- - np.ndarray - The cast data array. - """ - if direct_colormap.use_selection: - return (data == direct_colormap.selection).astype(np.uint8) - - dkt = direct_colormap._get_typed_dict_mapping(data.dtype) - target_dtype = minimum_dtype_for_labels( - direct_colormap._num_unique_colors + 2 - ) - result_array = np.full_like( - data, MAPPING_OF_UNKNOWN_VALUE, dtype=target_dtype - ) - return _labels_raw_to_texture_direct_inner_loop(data, dkt, result_array) - - -def _labels_raw_to_texture_direct_inner_loop( - data: np.ndarray, dkt: 'typed.Dict', out: np.ndarray -) -> np.ndarray: - """ - Relabel data using typed dict with mapping unknown labels to default value - """ - # The numba typed dict does not provide official Api for - # determine key and value types - for i in prange(data.size): - val = data.flat[i] - if val in dkt: - out.flat[i] = dkt[data.flat[i]] - - return out - - def _texture_dtype(num_colors: int, dtype: np.dtype) -> np.dtype: """Compute VisPy texture dtype given number of colors and raw data dtype. @@ -1044,44 +883,43 @@ def _texture_dtype(num_colors: int, dtype: np.dtype) -> np.dtype: return np.dtype(np.uint8) if dtype.itemsize == 2: return np.dtype(np.uint16) - return minimum_dtype_for_labels(num_colors) + return _accel_cmap.minimum_dtype_for_labels(num_colors) + +def _normalize_label_colormap( + any_colormap_like, +) -> Union[CyclicLabelColormap, DirectLabelColormap]: + """Convenience function to convert color containers to LabelColormaps. -def minimum_dtype_for_labels(num_colors: int) -> np.dtype: - """Return the minimum texture dtype that can hold given number of colors. + A list of colors or 2D nparray of colors is interpreted as a color cycle + (`CyclicLabelColormap`), and a mapping of colors is interpreted as a direct + color map (`DirectLabelColormap`). Parameters ---------- - num_colors : int - Number of unique colors in the data. + any_colormap_like : Sequence[color], MutableMapping[int, color], ... + An object that can be interpreted as a LabelColormap, including a + LabelColormap directly. Returns ------- - np.dtype - Minimum dtype that can hold the number of colors. + CyclicLabelColormap | DirectLabelColormap + The computed LabelColormap object. """ - if num_colors <= np.iinfo(np.uint8).max: - return np.dtype(np.uint8) - if num_colors <= np.iinfo(np.uint16).max: - return np.dtype(np.uint16) - return np.dtype(np.float32) - - -try: - import numba -except ModuleNotFoundError: - _zero_preserving_modulo = _zero_preserving_modulo_numpy - _labels_raw_to_texture_direct = _labels_raw_to_texture_direct_numpy - prange = range -else: - _zero_preserving_modulo_inner_loop = numba.njit(parallel=True, cache=True)( - _zero_preserving_modulo_inner_loop + if isinstance( + any_colormap_like, (CyclicLabelColormap, DirectLabelColormap) + ): + return any_colormap_like + if isinstance(any_colormap_like, Sequence): + return CyclicLabelColormap(any_colormap_like) + if isinstance(any_colormap_like, MutableMapping): + return DirectLabelColormap(color_dict=any_colormap_like) + if ( + isinstance(any_colormap_like, np.ndarray) + and any_colormap_like.ndim == 2 + and any_colormap_like.shape[1] in (3, 4) + ): + return CyclicLabelColormap(any_colormap_like) + raise ValueError( + f'Unable to interpret as labels colormap: {any_colormap_like}' ) - _zero_preserving_modulo = _zero_preserving_modulo_loop - _labels_raw_to_texture_direct = _labels_raw_to_texture_direct_loop - _labels_raw_to_texture_direct_inner_loop = numba.njit( - parallel=True, cache=True - )(_labels_raw_to_texture_direct_inner_loop) - prange = numba.prange # type: ignore [misc] - - del numba diff --git a/napari/utils/colormaps/colormap_utils.py b/napari/utils/colormaps/colormap_utils.py index 8c53b16b3c4..a8967b5e968 100644 --- a/napari/utils/colormaps/colormap_utils.py +++ b/napari/utils/colormaps/colormap_utils.py @@ -16,13 +16,13 @@ ) from vispy.color.colormap import LUT_len +from napari.utils.colormaps._accelerated_cmap import minimum_dtype_for_labels from napari.utils.colormaps.bop_colors import bopd from napari.utils.colormaps.colormap import ( Colormap, ColormapInterpolationMode, CyclicLabelColormap, DirectLabelColormap, - minimum_dtype_for_labels, ) from napari.utils.colormaps.inverse_colormaps import inverse_cmaps from napari.utils.colormaps.standardize_color import transform_color From 6b9af03b75a359f6e6dc9a2c1a9efce79f978d5a Mon Sep 17 00:00:00 2001 From: Ashley Anderson Date: Tue, 25 Jun 2024 17:06:12 -0400 Subject: [PATCH 08/11] Block zarr 3.0.0a0 on pre tests (#7028) # References and relevant issues closes https://github.com/napari/napari/issues/6997 replaces #6999 (apologies Czaki I cannot push to your branch or PR to your repo - feel free to port these changes to your branch if it works for you) # Description This PR blocks using `zarr==3.0.0rc1` in pre tests by add it to `resources/constraints/version_denylist.txt` and use this file as constraints for pre tests. This also fixes a failure in the --pre macOS-latest/pyqt6 workflow. My suspicion here is that it is not properly waiting for some of the macOS fullscreen animations, and it causes the app/testing environment to lose focus. Unfortunately I could not find a better way to fix it than just waiting. Any signal/condition I waited for did not seem to work. I guess they happen before the animation runs. Initially I narrowed the cause down to this test by running a [modified version of `detect-test-pollution`](https://github.com/aganders3/detect-test-pollution) in CI [on my fork](https://github.com/aganders3/napari/pull/17), and eventually I was able to replicate this locally. I'm still not sure why it's only a problem in macOS-latest/pyqt6. --------- Co-authored-by: Grzegorz Bokota --- .github/workflows/test_prereleases.yml | 6 ++++++ napari/_qt/_qapp_model/_tests/test_view_menu.py | 14 ++++++++++---- resources/constraints/version_denylist.txt | 1 + 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test_prereleases.yml b/.github/workflows/test_prereleases.yml index 7875aecba01..3f1b466b09a 100644 --- a/.github/workflows/test_prereleases.yml +++ b/.github/workflows/test_prereleases.yml @@ -11,6 +11,7 @@ on: pull_request: paths: - '.github/workflows/test_prereleases.yml' + - 'resources/constraints/version_denylist.txt' env: COLUMNS: 120 @@ -42,6 +43,10 @@ jobs: - uses: tlambert03/setup-qt-libs@v1 + - name: Setup Graphviz + uses: ts-graphviz/setup-graphviz@v2 + continue-on-error: true + - name: Install Windows OpenGL if: runner.os == 'Windows' run: | @@ -64,6 +69,7 @@ jobs: BACKEND: ${{ matrix.backend }} COVERAGE: "no_cov" PYVISTA_OFF_SCREEN: True # required for opengl on windows + PIP_CONSTRAINT: resources/constraints/version_denylist.txt # If something goes wrong, we can open an issue in the repo - name: Report Failures diff --git a/napari/_qt/_qapp_model/_tests/test_view_menu.py b/napari/_qt/_qapp_model/_tests/test_view_menu.py index 4faffefafef..df553e47a9d 100644 --- a/napari/_qt/_qapp_model/_tests/test_view_menu.py +++ b/napari/_qt/_qapp_model/_tests/test_view_menu.py @@ -49,22 +49,28 @@ def test_toggle_axes_scale_bar_attr( @skip_local_popups -def test_toggle_fullscreen(make_napari_viewer): +def test_toggle_fullscreen(make_napari_viewer, qtbot): """Test toggle fullscreen action.""" action_id = 'napari.window.view.toggle_fullscreen' app = get_app() viewer = make_napari_viewer(show=True) # Check initial default state (no fullscreen) - assert not viewer.window._qt_window._fullscreen_flag + assert not viewer.window._qt_window.isFullScreen() # Check fullscreen state change app.commands.execute_command(action_id) - assert viewer.window._qt_window._fullscreen_flag + if sys.platform == 'darwin': + # On macOS, wait for the animation to complete + qtbot.wait(250) + assert viewer.window._qt_window.isFullScreen() # Check return to non fullscreen state app.commands.execute_command(action_id) - assert not viewer.window._qt_window._fullscreen_flag + if sys.platform == 'darwin': + # On macOS, wait for the animation to complete + qtbot.wait(250) + assert not viewer.window._qt_window.isFullScreen() @skip_local_focus diff --git a/resources/constraints/version_denylist.txt b/resources/constraints/version_denylist.txt index 7f930fed497..f689ca503ec 100644 --- a/resources/constraints/version_denylist.txt +++ b/resources/constraints/version_denylist.txt @@ -4,3 +4,4 @@ PySide6 != 6.4.3, !=6.5.0, !=6.5.1, !=6.5.1.1, !=6.5.2, != 6.5.3, != 6.6.0, != 6 pytest-json-report pyopengl!=3.1.7 tensorstore!=0.1.38 +zarr!=3.0.0a0 From 95aeea77c2b60bdb380e182eed27135ca0693c7e Mon Sep 17 00:00:00 2001 From: napari-bot <81196843+napari-bot@users.noreply.github.com> Date: Wed, 26 Jun 2024 12:29:55 +0800 Subject: [PATCH 09/11] Update `coverage`, `dask`, `hypothesis`, `npe2`, `psutil`, `scikit-image`, `tensorstore`, `tifffile`, `virtualenv` (#7026) Updated packages: `coverage`, `dask`, `filelock`, `hypothesis`, `importlib-metadata`, `npe2`, `pip`, `psutil`, `pyperclip`, `pyqt6-qt6`, `scikit-image`, `tensorstore`, `tifffile`, `virtualenv` Co-authored-by: napari-bot Co-authored-by: Juan Nunez-Iglesias --- resources/constraints/constraints_py3.10.txt | 28 +++++++-------- .../constraints/constraints_py3.10_docs.txt | 34 +++++++++---------- .../constraints_py3.10_macos_arm.txt | 28 +++++++-------- .../constraints_py3.10_pydantic_1.txt | 30 ++++++++-------- resources/constraints/constraints_py3.11.txt | 28 +++++++-------- .../constraints_py3.11_macos_arm.txt | 28 +++++++-------- .../constraints_py3.11_pydantic_1.txt | 30 ++++++++-------- resources/constraints/constraints_py3.12.txt | 26 +++++++------- .../constraints_py3.12_macos_arm.txt | 26 +++++++------- .../constraints_py3.12_pydantic_1.txt | 28 +++++++-------- resources/constraints/constraints_py3.9.txt | 28 +++++++-------- .../constraints_py3.9_examples.txt | 28 +++++++-------- .../constraints_py3.9_macos_arm.txt | 28 +++++++-------- .../constraints_py3.9_pydantic_1.txt | 30 ++++++++-------- resources/requirements_mypy.txt | 6 ++-- 15 files changed, 203 insertions(+), 203 deletions(-) diff --git a/resources/constraints/constraints_py3.10.txt b/resources/constraints/constraints_py3.10.txt index 4dba43f4948..72bad4ddaf8 100644 --- a/resources/constraints/constraints_py3.10.txt +++ b/resources/constraints/constraints_py3.10.txt @@ -44,13 +44,13 @@ comm==0.2.2 # via ipykernel contourpy==1.2.1 # via matplotlib -coverage==7.5.3 +coverage==7.5.4 # via # napari (napari_repo/pyproject.toml) # pytest-cov cycler==0.12.1 # via matplotlib -dask==2024.6.0 +dask==2024.6.2 # via napari (napari_repo/pyproject.toml) debugpy==1.8.1 # via ipykernel @@ -73,7 +73,7 @@ executing==2.0.1 # via stack-data fasteners==0.19 # via zarr -filelock==3.15.1 +filelock==3.15.4 # via # torch # triton @@ -95,7 +95,7 @@ heapdict==1.0.1 # via cachey hsluv==5.0.4 # via vispy -hypothesis==6.103.2 +hypothesis==6.103.4 # via napari (napari_repo/pyproject.toml) idna==3.7 # via requests @@ -106,7 +106,7 @@ imageio==2.34.1 # scikit-image imagesize==1.4.1 # via sphinx -importlib-metadata==7.1.0 +importlib-metadata==7.2.1 # via # build # dask @@ -196,7 +196,7 @@ networkx==3.3 # via # scikit-image # torch -npe2==0.7.5 +npe2==0.7.6 # via # napari (napari_repo/pyproject.toml) # napari-plugin-manager @@ -291,7 +291,7 @@ pillow==10.3.0 # scikit-image pint==0.24 # via napari (napari_repo/pyproject.toml) -pip==24.0 +pip==24.1 # via napari-plugin-manager platformdirs==4.2.2 # via @@ -310,7 +310,7 @@ pretend==1.0.9 # via napari (napari_repo/pyproject.toml) prompt-toolkit==3.0.47 # via ipython -psutil==5.9.8 +psutil==6.0.0 # via # napari (napari_repo/pyproject.toml) # ipykernel @@ -356,7 +356,7 @@ pyopengl==3.1.6 # napari (napari_repo/pyproject.toml) pyparsing==3.1.2 # via matplotlib -pyperclip==1.8.2 +pyperclip==1.9.0 # via mouseinfo pyproject-hooks==1.1.0 # via build @@ -368,7 +368,7 @@ pyqt5-sip==12.13.0 # via pyqt5 pyqt6==6.7.0 # via napari (napari_repo/pyproject.toml) -pyqt6-qt6==6.7.1 +pyqt6-qt6==6.7.2 # via pyqt6 pyqt6-sip==13.6.0 # via pyqt6 @@ -460,7 +460,7 @@ rpds-py==0.18.1 # via # jsonschema # referencing -scikit-image==0.23.2 +scikit-image==0.24.0 # via napari (napari_repo/pyproject.toml) scipy==1.13.1 # via @@ -508,11 +508,11 @@ sympy==1.12.1 # via torch tabulate==0.9.0 # via numpydoc -tensorstore==0.1.62 +tensorstore==0.1.63 # via # -r napari_repo/resources/constraints/version_denylist.txt # napari (napari_repo/pyproject.toml) -tifffile==2024.5.22 +tifffile==2024.6.18 # via # napari (napari_repo/pyproject.toml) # scikit-image @@ -572,7 +572,7 @@ tzdata==2024.1 # via pandas urllib3==2.2.2 # via requests -virtualenv==20.26.2 +virtualenv==20.26.3 # via napari (napari_repo/pyproject.toml) vispy==0.14.3 # via diff --git a/resources/constraints/constraints_py3.10_docs.txt b/resources/constraints/constraints_py3.10_docs.txt index 2a46bbe2fa7..0f2177b79c1 100644 --- a/resources/constraints/constraints_py3.10_docs.txt +++ b/resources/constraints/constraints_py3.10_docs.txt @@ -57,13 +57,13 @@ comm==0.2.2 # via ipykernel contourpy==1.2.1 # via matplotlib -coverage==7.5.3 +coverage==7.5.4 # via # napari (napari_repo/pyproject.toml) # pytest-cov cycler==0.12.1 # via matplotlib -dask==2024.6.0 +dask==2024.6.2 # via napari (napari_repo/pyproject.toml) debugpy==1.8.1 # via ipykernel @@ -93,7 +93,7 @@ fasteners==0.19 # via zarr fastjsonschema==2.20.0 # via nbformat -filelock==3.15.1 +filelock==3.15.4 # via # torch # triton @@ -119,7 +119,7 @@ heapdict==1.0.1 # via cachey hsluv==5.0.4 # via vispy -hypothesis==6.103.2 +hypothesis==6.103.4 # via napari (napari_repo/pyproject.toml) idna==3.7 # via @@ -134,7 +134,7 @@ imageio-ffmpeg==0.5.1 # via -r docs/requirements.txt imagesize==1.4.1 # via sphinx -importlib-metadata==7.1.0 +importlib-metadata==7.2.1 # via # build # dask @@ -269,7 +269,7 @@ nibabel==5.2.1 # via nilearn nilearn==0.10.4 # via -r resources/constraints/version_denylist_examples.txt -npe2==0.7.5 +npe2==0.7.6 # via # napari (napari_repo/pyproject.toml) # napari-plugin-manager @@ -375,7 +375,7 @@ pillow==10.3.0 # sphinx-gallery pint==0.24 # via napari (napari_repo/pyproject.toml) -pip==24.0 +pip==24.1 # via napari-plugin-manager platformdirs==4.2.2 # via @@ -394,7 +394,7 @@ pretend==1.0.9 # via napari (napari_repo/pyproject.toml) prompt-toolkit==3.0.47 # via ipython -psutil==5.9.8 +psutil==6.0.0 # via # napari (napari_repo/pyproject.toml) # ipykernel @@ -412,7 +412,7 @@ pyautogui==0.9.54 # via napari (napari_repo/pyproject.toml) pyconify==0.1.6 # via superqt -pydantic==1.10.16 +pydantic==1.10.17 # via # -r napari_repo/resources/constraints/pydantic_le_2.txt # napari (napari_repo/pyproject.toml) @@ -444,7 +444,7 @@ pyopengl==3.1.6 # napari (napari_repo/pyproject.toml) pyparsing==3.1.2 # via matplotlib -pyperclip==1.8.2 +pyperclip==1.9.0 # via mouseinfo pyproject-hooks==1.1.0 # via build @@ -456,7 +456,7 @@ pyqt5-sip==12.13.0 # via pyqt5 pyqt6==6.7.0 # via napari (napari_repo/pyproject.toml) -pyqt6-qt6==6.7.1 +pyqt6-qt6==6.7.2 # via pyqt6 pyqt6-sip==13.6.0 # via pyqt6 @@ -553,7 +553,7 @@ rpds-py==0.18.1 # via # jsonschema # referencing -scikit-image==0.23.2 +scikit-image==0.24.0 # via napari (napari_repo/pyproject.toml) scikit-learn==1.5.0 # via nilearn @@ -563,7 +563,7 @@ scipy==1.13.1 # nilearn # scikit-image # scikit-learn -setuptools==70.0.0 +setuptools==70.1.0 # via imageio-ffmpeg shellingham==1.5.4 # via typer @@ -631,7 +631,7 @@ sphinxcontrib-qthelp==1.0.7 # via sphinx sphinxcontrib-serializinghtml==1.1.10 # via sphinx -sqlalchemy==2.0.30 +sqlalchemy==2.0.31 # via jupyter-cache stack-data==0.6.3 # via ipython @@ -648,13 +648,13 @@ tabulate==0.9.0 # via # jupyter-cache # numpydoc -tensorstore==0.1.62 +tensorstore==0.1.63 # via # -r napari_repo/resources/constraints/version_denylist.txt # napari (napari_repo/pyproject.toml) threadpoolctl==3.5.0 # via scikit-learn -tifffile==2024.5.22 +tifffile==2024.6.18 # via # napari (napari_repo/pyproject.toml) # scikit-image @@ -722,7 +722,7 @@ urllib3==2.2.2 # via requests uvicorn==0.30.1 # via sphinx-autobuild -virtualenv==20.26.2 +virtualenv==20.26.3 # via napari (napari_repo/pyproject.toml) vispy==0.14.3 # via diff --git a/resources/constraints/constraints_py3.10_macos_arm.txt b/resources/constraints/constraints_py3.10_macos_arm.txt index 4876f7bff18..94101222e89 100644 --- a/resources/constraints/constraints_py3.10_macos_arm.txt +++ b/resources/constraints/constraints_py3.10_macos_arm.txt @@ -46,13 +46,13 @@ comm==0.2.2 # via ipykernel contourpy==1.2.1 # via matplotlib -coverage==7.5.3 +coverage==7.5.4 # via # napari (napari_repo/pyproject.toml) # pytest-cov cycler==0.12.1 # via matplotlib -dask==2024.6.0 +dask==2024.6.2 # via napari (napari_repo/pyproject.toml) debugpy==1.8.1 # via ipykernel @@ -75,7 +75,7 @@ executing==2.0.1 # via stack-data fasteners==0.19 # via zarr -filelock==3.15.1 +filelock==3.15.4 # via # torch # virtualenv @@ -96,7 +96,7 @@ heapdict==1.0.1 # via cachey hsluv==5.0.4 # via vispy -hypothesis==6.103.2 +hypothesis==6.103.4 # via napari (napari_repo/pyproject.toml) idna==3.7 # via requests @@ -107,7 +107,7 @@ imageio==2.34.1 # scikit-image imagesize==1.4.1 # via sphinx -importlib-metadata==7.1.0 +importlib-metadata==7.2.1 # via # build # dask @@ -197,7 +197,7 @@ networkx==3.3 # via # scikit-image # torch -npe2==0.7.5 +npe2==0.7.6 # via # napari (napari_repo/pyproject.toml) # napari-plugin-manager @@ -260,7 +260,7 @@ pillow==10.3.0 # scikit-image pint==0.24 # via napari (napari_repo/pyproject.toml) -pip==24.0 +pip==24.1 # via napari-plugin-manager platformdirs==4.2.2 # via @@ -279,7 +279,7 @@ pretend==1.0.9 # via napari (napari_repo/pyproject.toml) prompt-toolkit==3.0.47 # via ipython -psutil==5.9.8 +psutil==6.0.0 # via # napari (napari_repo/pyproject.toml) # ipykernel @@ -334,7 +334,7 @@ pyopengl==3.1.6 # napari (napari_repo/pyproject.toml) pyparsing==3.1.2 # via matplotlib -pyperclip==1.8.2 +pyperclip==1.9.0 # via mouseinfo pyproject-hooks==1.1.0 # via build @@ -346,7 +346,7 @@ pyqt5-sip==12.13.0 # via pyqt5 pyqt6==6.7.0 # via napari (napari_repo/pyproject.toml) -pyqt6-qt6==6.7.1 +pyqt6-qt6==6.7.2 # via pyqt6 pyqt6-sip==13.6.0 # via pyqt6 @@ -434,7 +434,7 @@ rpds-py==0.18.1 # referencing rubicon-objc==0.4.9 # via mouseinfo -scikit-image==0.23.2 +scikit-image==0.24.0 # via napari (napari_repo/pyproject.toml) scipy==1.13.1 # via @@ -480,11 +480,11 @@ sympy==1.12.1 # via torch tabulate==0.9.0 # via numpydoc -tensorstore==0.1.62 +tensorstore==0.1.63 # via # -r napari_repo/resources/constraints/version_denylist.txt # napari (napari_repo/pyproject.toml) -tifffile==2024.5.22 +tifffile==2024.6.18 # via # napari (napari_repo/pyproject.toml) # scikit-image @@ -540,7 +540,7 @@ tzdata==2024.1 # via pandas urllib3==2.2.2 # via requests -virtualenv==20.26.2 +virtualenv==20.26.3 # via napari (napari_repo/pyproject.toml) vispy==0.14.3 # via diff --git a/resources/constraints/constraints_py3.10_pydantic_1.txt b/resources/constraints/constraints_py3.10_pydantic_1.txt index 7d0e0585364..ef080fb4409 100644 --- a/resources/constraints/constraints_py3.10_pydantic_1.txt +++ b/resources/constraints/constraints_py3.10_pydantic_1.txt @@ -42,13 +42,13 @@ comm==0.2.2 # via ipykernel contourpy==1.2.1 # via matplotlib -coverage==7.5.3 +coverage==7.5.4 # via # napari (napari_repo/pyproject.toml) # pytest-cov cycler==0.12.1 # via matplotlib -dask==2024.6.0 +dask==2024.6.2 # via napari (napari_repo/pyproject.toml) debugpy==1.8.1 # via ipykernel @@ -71,7 +71,7 @@ executing==2.0.1 # via stack-data fasteners==0.19 # via zarr -filelock==3.15.1 +filelock==3.15.4 # via # torch # triton @@ -93,7 +93,7 @@ heapdict==1.0.1 # via cachey hsluv==5.0.4 # via vispy -hypothesis==6.103.2 +hypothesis==6.103.4 # via napari (napari_repo/pyproject.toml) idna==3.7 # via requests @@ -104,7 +104,7 @@ imageio==2.34.1 # scikit-image imagesize==1.4.1 # via sphinx -importlib-metadata==7.1.0 +importlib-metadata==7.2.1 # via # build # dask @@ -194,7 +194,7 @@ networkx==3.3 # via # scikit-image # torch -npe2==0.7.5 +npe2==0.7.6 # via # napari (napari_repo/pyproject.toml) # napari-plugin-manager @@ -289,7 +289,7 @@ pillow==10.3.0 # scikit-image pint==0.24 # via napari (napari_repo/pyproject.toml) -pip==24.0 +pip==24.1 # via napari-plugin-manager platformdirs==4.2.2 # via @@ -308,7 +308,7 @@ pretend==1.0.9 # via napari (napari_repo/pyproject.toml) prompt-toolkit==3.0.47 # via ipython -psutil==5.9.8 +psutil==6.0.0 # via # napari (napari_repo/pyproject.toml) # ipykernel @@ -326,7 +326,7 @@ pyautogui==0.9.54 # via napari (napari_repo/pyproject.toml) pyconify==0.1.6 # via superqt -pydantic==1.10.16 +pydantic==1.10.17 # via # -r napari_repo/resources/constraints/pydantic_le_2.txt # napari (napari_repo/pyproject.toml) @@ -353,7 +353,7 @@ pyopengl==3.1.6 # napari (napari_repo/pyproject.toml) pyparsing==3.1.2 # via matplotlib -pyperclip==1.8.2 +pyperclip==1.9.0 # via mouseinfo pyproject-hooks==1.1.0 # via build @@ -365,7 +365,7 @@ pyqt5-sip==12.13.0 # via pyqt5 pyqt6==6.7.0 # via napari (napari_repo/pyproject.toml) -pyqt6-qt6==6.7.1 +pyqt6-qt6==6.7.2 # via pyqt6 pyqt6-sip==13.6.0 # via pyqt6 @@ -457,7 +457,7 @@ rpds-py==0.18.1 # via # jsonschema # referencing -scikit-image==0.23.2 +scikit-image==0.24.0 # via napari (napari_repo/pyproject.toml) scipy==1.13.1 # via @@ -505,11 +505,11 @@ sympy==1.12.1 # via torch tabulate==0.9.0 # via numpydoc -tensorstore==0.1.62 +tensorstore==0.1.63 # via # -r napari_repo/resources/constraints/version_denylist.txt # napari (napari_repo/pyproject.toml) -tifffile==2024.5.22 +tifffile==2024.6.18 # via # napari (napari_repo/pyproject.toml) # scikit-image @@ -568,7 +568,7 @@ tzdata==2024.1 # via pandas urllib3==2.2.2 # via requests -virtualenv==20.26.2 +virtualenv==20.26.3 # via napari (napari_repo/pyproject.toml) vispy==0.14.3 # via diff --git a/resources/constraints/constraints_py3.11.txt b/resources/constraints/constraints_py3.11.txt index 3708e12f4ff..3b959a5264c 100644 --- a/resources/constraints/constraints_py3.11.txt +++ b/resources/constraints/constraints_py3.11.txt @@ -44,13 +44,13 @@ comm==0.2.2 # via ipykernel contourpy==1.2.1 # via matplotlib -coverage==7.5.3 +coverage==7.5.4 # via # napari (napari_repo/pyproject.toml) # pytest-cov cycler==0.12.1 # via matplotlib -dask==2024.6.0 +dask==2024.6.2 # via napari (napari_repo/pyproject.toml) debugpy==1.8.1 # via ipykernel @@ -68,7 +68,7 @@ executing==2.0.1 # via stack-data fasteners==0.19 # via zarr -filelock==3.15.1 +filelock==3.15.4 # via # torch # triton @@ -90,7 +90,7 @@ heapdict==1.0.1 # via cachey hsluv==5.0.4 # via vispy -hypothesis==6.103.2 +hypothesis==6.103.4 # via napari (napari_repo/pyproject.toml) idna==3.7 # via requests @@ -101,7 +101,7 @@ imageio==2.34.1 # scikit-image imagesize==1.4.1 # via sphinx -importlib-metadata==7.1.0 +importlib-metadata==7.2.1 # via dask in-n-out==0.2.1 # via app-model @@ -189,7 +189,7 @@ networkx==3.3 # via # scikit-image # torch -npe2==0.7.5 +npe2==0.7.6 # via # napari (napari_repo/pyproject.toml) # napari-plugin-manager @@ -284,7 +284,7 @@ pillow==10.3.0 # scikit-image pint==0.24 # via napari (napari_repo/pyproject.toml) -pip==24.0 +pip==24.1 # via napari-plugin-manager platformdirs==4.2.2 # via @@ -303,7 +303,7 @@ pretend==1.0.9 # via napari (napari_repo/pyproject.toml) prompt-toolkit==3.0.47 # via ipython -psutil==5.9.8 +psutil==6.0.0 # via # napari (napari_repo/pyproject.toml) # ipykernel @@ -349,7 +349,7 @@ pyopengl==3.1.6 # napari (napari_repo/pyproject.toml) pyparsing==3.1.2 # via matplotlib -pyperclip==1.8.2 +pyperclip==1.9.0 # via mouseinfo pyproject-hooks==1.1.0 # via build @@ -361,7 +361,7 @@ pyqt5-sip==12.13.0 # via pyqt5 pyqt6==6.7.0 # via napari (napari_repo/pyproject.toml) -pyqt6-qt6==6.7.1 +pyqt6-qt6==6.7.2 # via pyqt6 pyqt6-sip==13.6.0 # via pyqt6 @@ -451,7 +451,7 @@ rpds-py==0.18.1 # via # jsonschema # referencing -scikit-image==0.23.2 +scikit-image==0.24.0 # via napari (napari_repo/pyproject.toml) scipy==1.13.1 # via @@ -497,11 +497,11 @@ sympy==1.12.1 # via torch tabulate==0.9.0 # via numpydoc -tensorstore==0.1.62 +tensorstore==0.1.63 # via # -r napari_repo/resources/constraints/version_denylist.txt # napari (napari_repo/pyproject.toml) -tifffile==2024.5.22 +tifffile==2024.6.18 # via # napari (napari_repo/pyproject.toml) # scikit-image @@ -553,7 +553,7 @@ tzdata==2024.1 # via pandas urllib3==2.2.2 # via requests -virtualenv==20.26.2 +virtualenv==20.26.3 # via napari (napari_repo/pyproject.toml) vispy==0.14.3 # via diff --git a/resources/constraints/constraints_py3.11_macos_arm.txt b/resources/constraints/constraints_py3.11_macos_arm.txt index 3f65222852d..f50ddf54947 100644 --- a/resources/constraints/constraints_py3.11_macos_arm.txt +++ b/resources/constraints/constraints_py3.11_macos_arm.txt @@ -46,13 +46,13 @@ comm==0.2.2 # via ipykernel contourpy==1.2.1 # via matplotlib -coverage==7.5.3 +coverage==7.5.4 # via # napari (napari_repo/pyproject.toml) # pytest-cov cycler==0.12.1 # via matplotlib -dask==2024.6.0 +dask==2024.6.2 # via napari (napari_repo/pyproject.toml) debugpy==1.8.1 # via ipykernel @@ -70,7 +70,7 @@ executing==2.0.1 # via stack-data fasteners==0.19 # via zarr -filelock==3.15.1 +filelock==3.15.4 # via # torch # virtualenv @@ -91,7 +91,7 @@ heapdict==1.0.1 # via cachey hsluv==5.0.4 # via vispy -hypothesis==6.103.2 +hypothesis==6.103.4 # via napari (napari_repo/pyproject.toml) idna==3.7 # via requests @@ -102,7 +102,7 @@ imageio==2.34.1 # scikit-image imagesize==1.4.1 # via sphinx -importlib-metadata==7.1.0 +importlib-metadata==7.2.1 # via dask in-n-out==0.2.1 # via app-model @@ -190,7 +190,7 @@ networkx==3.3 # via # scikit-image # torch -npe2==0.7.5 +npe2==0.7.6 # via # napari (napari_repo/pyproject.toml) # napari-plugin-manager @@ -253,7 +253,7 @@ pillow==10.3.0 # scikit-image pint==0.24 # via napari (napari_repo/pyproject.toml) -pip==24.0 +pip==24.1 # via napari-plugin-manager platformdirs==4.2.2 # via @@ -272,7 +272,7 @@ pretend==1.0.9 # via napari (napari_repo/pyproject.toml) prompt-toolkit==3.0.47 # via ipython -psutil==5.9.8 +psutil==6.0.0 # via # napari (napari_repo/pyproject.toml) # ipykernel @@ -327,7 +327,7 @@ pyopengl==3.1.6 # napari (napari_repo/pyproject.toml) pyparsing==3.1.2 # via matplotlib -pyperclip==1.8.2 +pyperclip==1.9.0 # via mouseinfo pyproject-hooks==1.1.0 # via build @@ -339,7 +339,7 @@ pyqt5-sip==12.13.0 # via pyqt5 pyqt6==6.7.0 # via napari (napari_repo/pyproject.toml) -pyqt6-qt6==6.7.1 +pyqt6-qt6==6.7.2 # via pyqt6 pyqt6-sip==13.6.0 # via pyqt6 @@ -427,7 +427,7 @@ rpds-py==0.18.1 # referencing rubicon-objc==0.4.9 # via mouseinfo -scikit-image==0.23.2 +scikit-image==0.24.0 # via napari (napari_repo/pyproject.toml) scipy==1.13.1 # via @@ -473,11 +473,11 @@ sympy==1.12.1 # via torch tabulate==0.9.0 # via numpydoc -tensorstore==0.1.62 +tensorstore==0.1.63 # via # -r napari_repo/resources/constraints/version_denylist.txt # napari (napari_repo/pyproject.toml) -tifffile==2024.5.22 +tifffile==2024.6.18 # via # napari (napari_repo/pyproject.toml) # scikit-image @@ -525,7 +525,7 @@ tzdata==2024.1 # via pandas urllib3==2.2.2 # via requests -virtualenv==20.26.2 +virtualenv==20.26.3 # via napari (napari_repo/pyproject.toml) vispy==0.14.3 # via diff --git a/resources/constraints/constraints_py3.11_pydantic_1.txt b/resources/constraints/constraints_py3.11_pydantic_1.txt index 9ff3a3429fa..c90348cff49 100644 --- a/resources/constraints/constraints_py3.11_pydantic_1.txt +++ b/resources/constraints/constraints_py3.11_pydantic_1.txt @@ -42,13 +42,13 @@ comm==0.2.2 # via ipykernel contourpy==1.2.1 # via matplotlib -coverage==7.5.3 +coverage==7.5.4 # via # napari (napari_repo/pyproject.toml) # pytest-cov cycler==0.12.1 # via matplotlib -dask==2024.6.0 +dask==2024.6.2 # via napari (napari_repo/pyproject.toml) debugpy==1.8.1 # via ipykernel @@ -66,7 +66,7 @@ executing==2.0.1 # via stack-data fasteners==0.19 # via zarr -filelock==3.15.1 +filelock==3.15.4 # via # torch # triton @@ -88,7 +88,7 @@ heapdict==1.0.1 # via cachey hsluv==5.0.4 # via vispy -hypothesis==6.103.2 +hypothesis==6.103.4 # via napari (napari_repo/pyproject.toml) idna==3.7 # via requests @@ -99,7 +99,7 @@ imageio==2.34.1 # scikit-image imagesize==1.4.1 # via sphinx -importlib-metadata==7.1.0 +importlib-metadata==7.2.1 # via dask in-n-out==0.2.1 # via app-model @@ -187,7 +187,7 @@ networkx==3.3 # via # scikit-image # torch -npe2==0.7.5 +npe2==0.7.6 # via # napari (napari_repo/pyproject.toml) # napari-plugin-manager @@ -282,7 +282,7 @@ pillow==10.3.0 # scikit-image pint==0.24 # via napari (napari_repo/pyproject.toml) -pip==24.0 +pip==24.1 # via napari-plugin-manager platformdirs==4.2.2 # via @@ -301,7 +301,7 @@ pretend==1.0.9 # via napari (napari_repo/pyproject.toml) prompt-toolkit==3.0.47 # via ipython -psutil==5.9.8 +psutil==6.0.0 # via # napari (napari_repo/pyproject.toml) # ipykernel @@ -319,7 +319,7 @@ pyautogui==0.9.54 # via napari (napari_repo/pyproject.toml) pyconify==0.1.6 # via superqt -pydantic==1.10.16 +pydantic==1.10.17 # via # -r napari_repo/resources/constraints/pydantic_le_2.txt # napari (napari_repo/pyproject.toml) @@ -346,7 +346,7 @@ pyopengl==3.1.6 # napari (napari_repo/pyproject.toml) pyparsing==3.1.2 # via matplotlib -pyperclip==1.8.2 +pyperclip==1.9.0 # via mouseinfo pyproject-hooks==1.1.0 # via build @@ -358,7 +358,7 @@ pyqt5-sip==12.13.0 # via pyqt5 pyqt6==6.7.0 # via napari (napari_repo/pyproject.toml) -pyqt6-qt6==6.7.1 +pyqt6-qt6==6.7.2 # via pyqt6 pyqt6-sip==13.6.0 # via pyqt6 @@ -448,7 +448,7 @@ rpds-py==0.18.1 # via # jsonschema # referencing -scikit-image==0.23.2 +scikit-image==0.24.0 # via napari (napari_repo/pyproject.toml) scipy==1.13.1 # via @@ -494,11 +494,11 @@ sympy==1.12.1 # via torch tabulate==0.9.0 # via numpydoc -tensorstore==0.1.62 +tensorstore==0.1.63 # via # -r napari_repo/resources/constraints/version_denylist.txt # napari (napari_repo/pyproject.toml) -tifffile==2024.5.22 +tifffile==2024.6.18 # via # napari (napari_repo/pyproject.toml) # scikit-image @@ -549,7 +549,7 @@ tzdata==2024.1 # via pandas urllib3==2.2.2 # via requests -virtualenv==20.26.2 +virtualenv==20.26.3 # via napari (napari_repo/pyproject.toml) vispy==0.14.3 # via diff --git a/resources/constraints/constraints_py3.12.txt b/resources/constraints/constraints_py3.12.txt index 06b696e0084..1ef588f451c 100644 --- a/resources/constraints/constraints_py3.12.txt +++ b/resources/constraints/constraints_py3.12.txt @@ -44,13 +44,13 @@ comm==0.2.2 # via ipykernel contourpy==1.2.1 # via matplotlib -coverage==7.5.3 +coverage==7.5.4 # via # napari (napari_repo/pyproject.toml) # pytest-cov cycler==0.12.1 # via matplotlib -dask==2024.6.0 +dask==2024.6.2 # via napari (napari_repo/pyproject.toml) debugpy==1.8.1 # via ipykernel @@ -68,7 +68,7 @@ executing==2.0.1 # via stack-data fasteners==0.19 # via zarr -filelock==3.15.1 +filelock==3.15.4 # via # torch # virtualenv @@ -89,7 +89,7 @@ heapdict==1.0.1 # via cachey hsluv==5.0.4 # via vispy -hypothesis==6.103.2 +hypothesis==6.103.4 # via napari (napari_repo/pyproject.toml) idna==3.7 # via requests @@ -186,7 +186,7 @@ networkx==3.3 # via # scikit-image # torch -npe2==0.7.5 +npe2==0.7.6 # via # napari (napari_repo/pyproject.toml) # napari-plugin-manager @@ -280,7 +280,7 @@ pillow==10.3.0 # scikit-image pint==0.24 # via napari (napari_repo/pyproject.toml) -pip==24.0 +pip==24.1 # via napari-plugin-manager platformdirs==4.2.2 # via @@ -299,7 +299,7 @@ pretend==1.0.9 # via napari (napari_repo/pyproject.toml) prompt-toolkit==3.0.47 # via ipython -psutil==5.9.8 +psutil==6.0.0 # via # napari (napari_repo/pyproject.toml) # ipykernel @@ -345,7 +345,7 @@ pyopengl==3.1.6 # napari (napari_repo/pyproject.toml) pyparsing==3.1.2 # via matplotlib -pyperclip==1.8.2 +pyperclip==1.9.0 # via mouseinfo pyproject-hooks==1.1.0 # via build @@ -357,7 +357,7 @@ pyqt5-sip==12.13.0 # via pyqt5 pyqt6==6.7.0 # via napari (napari_repo/pyproject.toml) -pyqt6-qt6==6.7.1 +pyqt6-qt6==6.7.2 # via pyqt6 pyqt6-sip==13.6.0 # via pyqt6 @@ -437,7 +437,7 @@ rpds-py==0.18.1 # via # jsonschema # referencing -scikit-image==0.23.2 +scikit-image==0.24.0 # via napari (napari_repo/pyproject.toml) scipy==1.13.1 # via @@ -478,11 +478,11 @@ sympy==1.12.1 # via torch tabulate==0.9.0 # via numpydoc -tensorstore==0.1.62 +tensorstore==0.1.63 # via # -r napari_repo/resources/constraints/version_denylist.txt # napari (napari_repo/pyproject.toml) -tifffile==2024.5.22 +tifffile==2024.6.18 # via # napari (napari_repo/pyproject.toml) # scikit-image @@ -531,7 +531,7 @@ tzdata==2024.1 # via pandas urllib3==2.2.2 # via requests -virtualenv==20.26.2 +virtualenv==20.26.3 # via napari (napari_repo/pyproject.toml) vispy==0.14.3 # via diff --git a/resources/constraints/constraints_py3.12_macos_arm.txt b/resources/constraints/constraints_py3.12_macos_arm.txt index e3960bb95b2..c760d6be303 100644 --- a/resources/constraints/constraints_py3.12_macos_arm.txt +++ b/resources/constraints/constraints_py3.12_macos_arm.txt @@ -46,13 +46,13 @@ comm==0.2.2 # via ipykernel contourpy==1.2.1 # via matplotlib -coverage==7.5.3 +coverage==7.5.4 # via # napari (napari_repo/pyproject.toml) # pytest-cov cycler==0.12.1 # via matplotlib -dask==2024.6.0 +dask==2024.6.2 # via napari (napari_repo/pyproject.toml) debugpy==1.8.1 # via ipykernel @@ -70,7 +70,7 @@ executing==2.0.1 # via stack-data fasteners==0.19 # via zarr -filelock==3.15.1 +filelock==3.15.4 # via # torch # virtualenv @@ -91,7 +91,7 @@ heapdict==1.0.1 # via cachey hsluv==5.0.4 # via vispy -hypothesis==6.103.2 +hypothesis==6.103.4 # via napari (napari_repo/pyproject.toml) idna==3.7 # via requests @@ -188,7 +188,7 @@ networkx==3.3 # via # scikit-image # torch -npe2==0.7.5 +npe2==0.7.6 # via # napari (napari_repo/pyproject.toml) # napari-plugin-manager @@ -250,7 +250,7 @@ pillow==10.3.0 # scikit-image pint==0.24 # via napari (napari_repo/pyproject.toml) -pip==24.0 +pip==24.1 # via napari-plugin-manager platformdirs==4.2.2 # via @@ -269,7 +269,7 @@ pretend==1.0.9 # via napari (napari_repo/pyproject.toml) prompt-toolkit==3.0.47 # via ipython -psutil==5.9.8 +psutil==6.0.0 # via # napari (napari_repo/pyproject.toml) # ipykernel @@ -324,7 +324,7 @@ pyopengl==3.1.6 # napari (napari_repo/pyproject.toml) pyparsing==3.1.2 # via matplotlib -pyperclip==1.8.2 +pyperclip==1.9.0 # via mouseinfo pyproject-hooks==1.1.0 # via build @@ -336,7 +336,7 @@ pyqt5-sip==12.13.0 # via pyqt5 pyqt6==6.7.0 # via napari (napari_repo/pyproject.toml) -pyqt6-qt6==6.7.1 +pyqt6-qt6==6.7.2 # via pyqt6 pyqt6-sip==13.6.0 # via pyqt6 @@ -414,7 +414,7 @@ rpds-py==0.18.1 # referencing rubicon-objc==0.4.9 # via mouseinfo -scikit-image==0.23.2 +scikit-image==0.24.0 # via napari (napari_repo/pyproject.toml) scipy==1.13.1 # via @@ -455,11 +455,11 @@ sympy==1.12.1 # via torch tabulate==0.9.0 # via numpydoc -tensorstore==0.1.62 +tensorstore==0.1.63 # via # -r napari_repo/resources/constraints/version_denylist.txt # napari (napari_repo/pyproject.toml) -tifffile==2024.5.22 +tifffile==2024.6.18 # via # napari (napari_repo/pyproject.toml) # scikit-image @@ -506,7 +506,7 @@ tzdata==2024.1 # via pandas urllib3==2.2.2 # via requests -virtualenv==20.26.2 +virtualenv==20.26.3 # via napari (napari_repo/pyproject.toml) vispy==0.14.3 # via diff --git a/resources/constraints/constraints_py3.12_pydantic_1.txt b/resources/constraints/constraints_py3.12_pydantic_1.txt index ab39e15845b..3fa75aeae0f 100644 --- a/resources/constraints/constraints_py3.12_pydantic_1.txt +++ b/resources/constraints/constraints_py3.12_pydantic_1.txt @@ -42,13 +42,13 @@ comm==0.2.2 # via ipykernel contourpy==1.2.1 # via matplotlib -coverage==7.5.3 +coverage==7.5.4 # via # napari (napari_repo/pyproject.toml) # pytest-cov cycler==0.12.1 # via matplotlib -dask==2024.6.0 +dask==2024.6.2 # via napari (napari_repo/pyproject.toml) debugpy==1.8.1 # via ipykernel @@ -66,7 +66,7 @@ executing==2.0.1 # via stack-data fasteners==0.19 # via zarr -filelock==3.15.1 +filelock==3.15.4 # via # torch # virtualenv @@ -87,7 +87,7 @@ heapdict==1.0.1 # via cachey hsluv==5.0.4 # via vispy -hypothesis==6.103.2 +hypothesis==6.103.4 # via napari (napari_repo/pyproject.toml) idna==3.7 # via requests @@ -184,7 +184,7 @@ networkx==3.3 # via # scikit-image # torch -npe2==0.7.5 +npe2==0.7.6 # via # napari (napari_repo/pyproject.toml) # napari-plugin-manager @@ -278,7 +278,7 @@ pillow==10.3.0 # scikit-image pint==0.24 # via napari (napari_repo/pyproject.toml) -pip==24.0 +pip==24.1 # via napari-plugin-manager platformdirs==4.2.2 # via @@ -297,7 +297,7 @@ pretend==1.0.9 # via napari (napari_repo/pyproject.toml) prompt-toolkit==3.0.47 # via ipython -psutil==5.9.8 +psutil==6.0.0 # via # napari (napari_repo/pyproject.toml) # ipykernel @@ -315,7 +315,7 @@ pyautogui==0.9.54 # via napari (napari_repo/pyproject.toml) pyconify==0.1.6 # via superqt -pydantic==1.10.16 +pydantic==1.10.17 # via # -r napari_repo/resources/constraints/pydantic_le_2.txt # napari (napari_repo/pyproject.toml) @@ -342,7 +342,7 @@ pyopengl==3.1.6 # napari (napari_repo/pyproject.toml) pyparsing==3.1.2 # via matplotlib -pyperclip==1.8.2 +pyperclip==1.9.0 # via mouseinfo pyproject-hooks==1.1.0 # via build @@ -354,7 +354,7 @@ pyqt5-sip==12.13.0 # via pyqt5 pyqt6==6.7.0 # via napari (napari_repo/pyproject.toml) -pyqt6-qt6==6.7.1 +pyqt6-qt6==6.7.2 # via pyqt6 pyqt6-sip==13.6.0 # via pyqt6 @@ -434,7 +434,7 @@ rpds-py==0.18.1 # via # jsonschema # referencing -scikit-image==0.23.2 +scikit-image==0.24.0 # via napari (napari_repo/pyproject.toml) scipy==1.13.1 # via @@ -475,11 +475,11 @@ sympy==1.12.1 # via torch tabulate==0.9.0 # via numpydoc -tensorstore==0.1.62 +tensorstore==0.1.63 # via # -r napari_repo/resources/constraints/version_denylist.txt # napari (napari_repo/pyproject.toml) -tifffile==2024.5.22 +tifffile==2024.6.18 # via # napari (napari_repo/pyproject.toml) # scikit-image @@ -527,7 +527,7 @@ tzdata==2024.1 # via pandas urllib3==2.2.2 # via requests -virtualenv==20.26.2 +virtualenv==20.26.3 # via napari (napari_repo/pyproject.toml) vispy==0.14.3 # via diff --git a/resources/constraints/constraints_py3.9.txt b/resources/constraints/constraints_py3.9.txt index 4b090f85d14..759d720b581 100644 --- a/resources/constraints/constraints_py3.9.txt +++ b/resources/constraints/constraints_py3.9.txt @@ -43,13 +43,13 @@ comm==0.2.2 # via ipykernel contourpy==1.2.1 # via matplotlib -coverage==7.5.3 +coverage==7.5.4 # via # napari (napari_repo/pyproject.toml) # pytest-cov cycler==0.12.1 # via matplotlib -dask==2024.6.0 +dask==2024.6.2 # via napari (napari_repo/pyproject.toml) debugpy==1.8.1 # via ipykernel @@ -72,7 +72,7 @@ executing==2.0.1 # via stack-data fasteners==0.19 # via zarr -filelock==3.15.1 +filelock==3.15.4 # via # torch # triton @@ -90,7 +90,7 @@ heapdict==1.0.1 # via cachey hsluv==5.0.4 # via vispy -hypothesis==6.103.2 +hypothesis==6.103.4 # via napari (napari_repo/pyproject.toml) idna==3.7 # via requests @@ -101,7 +101,7 @@ imageio==2.34.1 # scikit-image imagesize==1.4.1 # via sphinx -importlib-metadata==7.1.0 +importlib-metadata==7.2.1 # via # build # dask @@ -195,7 +195,7 @@ networkx==3.2.1 # via # scikit-image # torch -npe2==0.7.5 +npe2==0.7.6 # via # napari (napari_repo/pyproject.toml) # napari-plugin-manager @@ -290,7 +290,7 @@ pillow==10.3.0 # scikit-image pint==0.23 # via napari (napari_repo/pyproject.toml) -pip==24.0 +pip==24.1 # via napari-plugin-manager platformdirs==4.2.2 # via @@ -309,7 +309,7 @@ pretend==1.0.9 # via napari (napari_repo/pyproject.toml) prompt-toolkit==3.0.47 # via ipython -psutil==5.9.8 +psutil==6.0.0 # via # napari (napari_repo/pyproject.toml) # ipykernel @@ -355,7 +355,7 @@ pyopengl==3.1.6 # napari (napari_repo/pyproject.toml) pyparsing==3.1.2 # via matplotlib -pyperclip==1.8.2 +pyperclip==1.9.0 # via mouseinfo pyproject-hooks==1.1.0 # via build @@ -367,7 +367,7 @@ pyqt5-sip==12.13.0 # via pyqt5 pyqt6==6.7.0 # via napari (napari_repo/pyproject.toml) -pyqt6-qt6==6.7.1 +pyqt6-qt6==6.7.2 # via pyqt6 pyqt6-sip==13.6.0 # via pyqt6 @@ -459,7 +459,7 @@ rpds-py==0.18.1 # via # jsonschema # referencing -scikit-image==0.22.0 +scikit-image==0.24.0 # via napari (napari_repo/pyproject.toml) scipy==1.13.1 # via @@ -507,11 +507,11 @@ sympy==1.12.1 # via torch tabulate==0.9.0 # via numpydoc -tensorstore==0.1.62 +tensorstore==0.1.63 # via # -r napari_repo/resources/constraints/version_denylist.txt # napari (napari_repo/pyproject.toml) -tifffile==2024.5.22 +tifffile==2024.6.18 # via # napari (napari_repo/pyproject.toml) # scikit-image @@ -569,7 +569,7 @@ tzdata==2024.1 # via pandas urllib3==2.2.2 # via requests -virtualenv==20.26.2 +virtualenv==20.26.3 # via napari (napari_repo/pyproject.toml) vispy==0.14.3 # via diff --git a/resources/constraints/constraints_py3.9_examples.txt b/resources/constraints/constraints_py3.9_examples.txt index 2a059cfdb04..ab4e4f47923 100644 --- a/resources/constraints/constraints_py3.9_examples.txt +++ b/resources/constraints/constraints_py3.9_examples.txt @@ -43,13 +43,13 @@ comm==0.2.2 # via ipykernel contourpy==1.2.1 # via matplotlib -coverage==7.5.3 +coverage==7.5.4 # via # napari (napari_repo/pyproject.toml) # pytest-cov cycler==0.12.1 # via matplotlib -dask==2024.6.0 +dask==2024.6.2 # via napari (napari_repo/pyproject.toml) debugpy==1.8.1 # via ipykernel @@ -72,7 +72,7 @@ executing==2.0.1 # via stack-data fasteners==0.19 # via zarr -filelock==3.15.1 +filelock==3.15.4 # via # torch # triton @@ -90,7 +90,7 @@ heapdict==1.0.1 # via cachey hsluv==5.0.4 # via vispy -hypothesis==6.103.2 +hypothesis==6.103.4 # via napari (napari_repo/pyproject.toml) idna==3.7 # via requests @@ -101,7 +101,7 @@ imageio==2.34.1 # scikit-image imagesize==1.4.1 # via sphinx -importlib-metadata==7.1.0 +importlib-metadata==7.2.1 # via # build # dask @@ -204,7 +204,7 @@ nibabel==5.2.1 # via nilearn nilearn==0.10.4 # via -r resources/constraints/version_denylist_examples.txt -npe2==0.7.5 +npe2==0.7.6 # via # napari (napari_repo/pyproject.toml) # napari-plugin-manager @@ -307,7 +307,7 @@ pillow==10.3.0 # scikit-image pint==0.23 # via napari (napari_repo/pyproject.toml) -pip==24.0 +pip==24.1 # via napari-plugin-manager platformdirs==4.2.2 # via @@ -326,7 +326,7 @@ pretend==1.0.9 # via napari (napari_repo/pyproject.toml) prompt-toolkit==3.0.47 # via ipython -psutil==5.9.8 +psutil==6.0.0 # via # napari (napari_repo/pyproject.toml) # ipykernel @@ -372,7 +372,7 @@ pyopengl==3.1.6 # napari (napari_repo/pyproject.toml) pyparsing==3.1.2 # via matplotlib -pyperclip==1.8.2 +pyperclip==1.9.0 # via mouseinfo pyproject-hooks==1.1.0 # via build @@ -384,7 +384,7 @@ pyqt5-sip==12.13.0 # via pyqt5 pyqt6==6.7.0 # via napari (napari_repo/pyproject.toml) -pyqt6-qt6==6.7.1 +pyqt6-qt6==6.7.2 # via pyqt6 pyqt6-sip==13.6.0 # via pyqt6 @@ -477,7 +477,7 @@ rpds-py==0.18.1 # via # jsonschema # referencing -scikit-image==0.22.0 +scikit-image==0.24.0 # via napari (napari_repo/pyproject.toml) scikit-learn==1.5.0 # via nilearn @@ -529,13 +529,13 @@ sympy==1.12.1 # via torch tabulate==0.9.0 # via numpydoc -tensorstore==0.1.62 +tensorstore==0.1.63 # via # -r napari_repo/resources/constraints/version_denylist.txt # napari (napari_repo/pyproject.toml) threadpoolctl==3.5.0 # via scikit-learn -tifffile==2024.5.22 +tifffile==2024.6.18 # via # napari (napari_repo/pyproject.toml) # scikit-image @@ -593,7 +593,7 @@ tzdata==2024.1 # via pandas urllib3==2.2.2 # via requests -virtualenv==20.26.2 +virtualenv==20.26.3 # via napari (napari_repo/pyproject.toml) vispy==0.14.3 # via diff --git a/resources/constraints/constraints_py3.9_macos_arm.txt b/resources/constraints/constraints_py3.9_macos_arm.txt index fcf7f673edb..b775c196bc9 100644 --- a/resources/constraints/constraints_py3.9_macos_arm.txt +++ b/resources/constraints/constraints_py3.9_macos_arm.txt @@ -45,13 +45,13 @@ comm==0.2.2 # via ipykernel contourpy==1.2.1 # via matplotlib -coverage==7.5.3 +coverage==7.5.4 # via # napari (napari_repo/pyproject.toml) # pytest-cov cycler==0.12.1 # via matplotlib -dask==2024.6.0 +dask==2024.6.2 # via napari (napari_repo/pyproject.toml) debugpy==1.8.1 # via ipykernel @@ -74,7 +74,7 @@ executing==2.0.1 # via stack-data fasteners==0.19 # via zarr -filelock==3.15.1 +filelock==3.15.4 # via # torch # virtualenv @@ -91,7 +91,7 @@ heapdict==1.0.1 # via cachey hsluv==5.0.4 # via vispy -hypothesis==6.103.2 +hypothesis==6.103.4 # via napari (napari_repo/pyproject.toml) idna==3.7 # via requests @@ -102,7 +102,7 @@ imageio==2.34.1 # scikit-image imagesize==1.4.1 # via sphinx -importlib-metadata==7.1.0 +importlib-metadata==7.2.1 # via # build # dask @@ -196,7 +196,7 @@ networkx==3.2.1 # via # scikit-image # torch -npe2==0.7.5 +npe2==0.7.6 # via # napari (napari_repo/pyproject.toml) # napari-plugin-manager @@ -259,7 +259,7 @@ pillow==10.3.0 # scikit-image pint==0.23 # via napari (napari_repo/pyproject.toml) -pip==24.0 +pip==24.1 # via napari-plugin-manager platformdirs==4.2.2 # via @@ -278,7 +278,7 @@ pretend==1.0.9 # via napari (napari_repo/pyproject.toml) prompt-toolkit==3.0.47 # via ipython -psutil==5.9.8 +psutil==6.0.0 # via # napari (napari_repo/pyproject.toml) # ipykernel @@ -333,7 +333,7 @@ pyopengl==3.1.6 # napari (napari_repo/pyproject.toml) pyparsing==3.1.2 # via matplotlib -pyperclip==1.8.2 +pyperclip==1.9.0 # via mouseinfo pyproject-hooks==1.1.0 # via build @@ -345,7 +345,7 @@ pyqt5-sip==12.13.0 # via pyqt5 pyqt6==6.7.0 # via napari (napari_repo/pyproject.toml) -pyqt6-qt6==6.7.1 +pyqt6-qt6==6.7.2 # via pyqt6 pyqt6-sip==13.6.0 # via pyqt6 @@ -433,7 +433,7 @@ rpds-py==0.18.1 # referencing rubicon-objc==0.4.9 # via mouseinfo -scikit-image==0.22.0 +scikit-image==0.24.0 # via napari (napari_repo/pyproject.toml) scipy==1.13.1 # via @@ -479,11 +479,11 @@ sympy==1.12.1 # via torch tabulate==0.9.0 # via numpydoc -tensorstore==0.1.62 +tensorstore==0.1.63 # via # -r napari_repo/resources/constraints/version_denylist.txt # napari (napari_repo/pyproject.toml) -tifffile==2024.5.22 +tifffile==2024.6.18 # via # napari (napari_repo/pyproject.toml) # scikit-image @@ -537,7 +537,7 @@ tzdata==2024.1 # via pandas urllib3==2.2.2 # via requests -virtualenv==20.26.2 +virtualenv==20.26.3 # via napari (napari_repo/pyproject.toml) vispy==0.14.3 # via diff --git a/resources/constraints/constraints_py3.9_pydantic_1.txt b/resources/constraints/constraints_py3.9_pydantic_1.txt index eab06f39df1..23276a0e17f 100644 --- a/resources/constraints/constraints_py3.9_pydantic_1.txt +++ b/resources/constraints/constraints_py3.9_pydantic_1.txt @@ -41,13 +41,13 @@ comm==0.2.2 # via ipykernel contourpy==1.2.1 # via matplotlib -coverage==7.5.3 +coverage==7.5.4 # via # napari (napari_repo/pyproject.toml) # pytest-cov cycler==0.12.1 # via matplotlib -dask==2024.6.0 +dask==2024.6.2 # via napari (napari_repo/pyproject.toml) debugpy==1.8.1 # via ipykernel @@ -70,7 +70,7 @@ executing==2.0.1 # via stack-data fasteners==0.19 # via zarr -filelock==3.15.1 +filelock==3.15.4 # via # torch # triton @@ -88,7 +88,7 @@ heapdict==1.0.1 # via cachey hsluv==5.0.4 # via vispy -hypothesis==6.103.2 +hypothesis==6.103.4 # via napari (napari_repo/pyproject.toml) idna==3.7 # via requests @@ -99,7 +99,7 @@ imageio==2.34.1 # scikit-image imagesize==1.4.1 # via sphinx -importlib-metadata==7.1.0 +importlib-metadata==7.2.1 # via # build # dask @@ -193,7 +193,7 @@ networkx==3.2.1 # via # scikit-image # torch -npe2==0.7.5 +npe2==0.7.6 # via # napari (napari_repo/pyproject.toml) # napari-plugin-manager @@ -288,7 +288,7 @@ pillow==10.3.0 # scikit-image pint==0.23 # via napari (napari_repo/pyproject.toml) -pip==24.0 +pip==24.1 # via napari-plugin-manager platformdirs==4.2.2 # via @@ -307,7 +307,7 @@ pretend==1.0.9 # via napari (napari_repo/pyproject.toml) prompt-toolkit==3.0.47 # via ipython -psutil==5.9.8 +psutil==6.0.0 # via # napari (napari_repo/pyproject.toml) # ipykernel @@ -325,7 +325,7 @@ pyautogui==0.9.54 # via napari (napari_repo/pyproject.toml) pyconify==0.1.6 # via superqt -pydantic==1.10.16 +pydantic==1.10.17 # via # -r napari_repo/resources/constraints/pydantic_le_2.txt # napari (napari_repo/pyproject.toml) @@ -352,7 +352,7 @@ pyopengl==3.1.6 # napari (napari_repo/pyproject.toml) pyparsing==3.1.2 # via matplotlib -pyperclip==1.8.2 +pyperclip==1.9.0 # via mouseinfo pyproject-hooks==1.1.0 # via build @@ -364,7 +364,7 @@ pyqt5-sip==12.13.0 # via pyqt5 pyqt6==6.7.0 # via napari (napari_repo/pyproject.toml) -pyqt6-qt6==6.7.1 +pyqt6-qt6==6.7.2 # via pyqt6 pyqt6-sip==13.6.0 # via pyqt6 @@ -456,7 +456,7 @@ rpds-py==0.18.1 # via # jsonschema # referencing -scikit-image==0.22.0 +scikit-image==0.24.0 # via napari (napari_repo/pyproject.toml) scipy==1.13.1 # via @@ -504,11 +504,11 @@ sympy==1.12.1 # via torch tabulate==0.9.0 # via numpydoc -tensorstore==0.1.62 +tensorstore==0.1.63 # via # -r napari_repo/resources/constraints/version_denylist.txt # napari (napari_repo/pyproject.toml) -tifffile==2024.5.22 +tifffile==2024.6.18 # via # napari (napari_repo/pyproject.toml) # scikit-image @@ -565,7 +565,7 @@ tzdata==2024.1 # via pandas urllib3==2.2.2 # via requests -virtualenv==20.26.2 +virtualenv==20.26.3 # via napari (napari_repo/pyproject.toml) vispy==0.14.3 # via diff --git a/resources/requirements_mypy.txt b/resources/requirements_mypy.txt index 13435b205f9..4e988603b84 100644 --- a/resources/requirements_mypy.txt +++ b/resources/requirements_mypy.txt @@ -26,7 +26,7 @@ mypy==1.10.0 # via -r napari_repo/resources/requirements_mypy.in mypy-extensions==1.0.0 # via mypy -npe2==0.7.5 +npe2==0.7.6 # via -r napari_repo/resources/requirements_mypy.in numpy==2.0.0 # via -r napari_repo/resources/requirements_mypy.in @@ -54,7 +54,7 @@ pyproject-hooks==1.1.0 # via build pyqt6==6.7.0 # via -r napari_repo/resources/requirements_mypy.in -pyqt6-qt6==6.7.1 +pyqt6-qt6==6.7.2 # via pyqt6 pyqt6-sip==13.6.0 # via pyqt6 @@ -81,7 +81,7 @@ typer==0.12.3 # via npe2 types-pyyaml==6.0.12.20240311 # via -r napari_repo/resources/requirements_mypy.in -types-requests==2.32.0.20240602 +types-requests==2.32.0.20240622 # via -r napari_repo/resources/requirements_mypy.in types-setuptools==70.0.0.20240524 # via -r napari_repo/resources/requirements_mypy.in From 739e049caa364204cbcc857417d4d8fccd4768e8 Mon Sep 17 00:00:00 2001 From: Peter Sobolewski <76622105+psobolewskiPhD@users.noreply.github.com> Date: Wed, 26 Jun 2024 00:30:33 -0400 Subject: [PATCH 10/11] [UI/UX] Updated polygon button icon for Shapes & Labels regular polygon tool (#7019) # References and relevant issues partially adresses https://github.com/napari/napari/issues/6489 # Description In this PR I replace the triangle icon used for the polygon tool with a new one that mimics the font-awesome draw-polygon icon: https://fontawesome.com/v5/icons/draw-polygon?f=classic&s=light But uses the proportions of the existing napari icons (and is svg). Then this new icon is used for both Shapes and Labels polygon tools because they share a behavior (stopping behavior aside for the time being). Here's what the new Shapes toolbar looks like: image And the new Labels toolbar: image I confirmed visually that the icon looks fine in light and dark theme. --- napari/_qt/qt_resources/styles/01_buttons.qss | 2 +- napari/resources/icons/polygon.svg | 36 ++++++++++++------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/napari/_qt/qt_resources/styles/01_buttons.qss b/napari/_qt/qt_resources/styles/01_buttons.qss index db51fdbd3ab..e605dea21e3 100644 --- a/napari/_qt/qt_resources/styles/01_buttons.qss +++ b/napari/_qt/qt_resources/styles/01_buttons.qss @@ -126,7 +126,7 @@ QtModeRadioButton[mode="polygon"]::indicator { } QtModeRadioButton[mode="labels_polygon"]::indicator { - image: url("theme_{{ id }}:/polygon_lasso.svg"); + image: url("theme_{{ id }}:/polygon.svg"); } QtModeRadioButton[mode="polygon_lasso"]::indicator { diff --git a/napari/resources/icons/polygon.svg b/napari/resources/icons/polygon.svg index e1cd3aa3065..93b343818dd 100644 --- a/napari/resources/icons/polygon.svg +++ b/napari/resources/icons/polygon.svg @@ -1,14 +1,24 @@ - - - - - - - - - + + + + Layer 1 + + + + + + + + + + + + + + + + + + + From ed160d5eb266eb5259b0766e80a10267fdcde484 Mon Sep 17 00:00:00 2001 From: Peter Sobolewski <76622105+psobolewskiPhD@users.noreply.github.com> Date: Wed, 26 Jun 2024 00:31:24 -0400 Subject: [PATCH 11/11] [Maint] take pyqt6 out of experimental, restrict to >6.5 (#6937) # References and relevant issues Closes: https://github.com/napari/napari/issues/5820 # Description This PR drops the `experimental` for PyQt6 and sets the minimum supported to >6.5 , which is needed to fix the various qt6 issues. I've been using PyQt6 for ~2 years now and I think it's a good time with 0.5.0 to take it out of experimental. --- .github/workflows/upgrade_test_constraints.yml | 2 +- pyproject.toml | 4 ++-- tools/check_updated_packages.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/upgrade_test_constraints.yml b/.github/workflows/upgrade_test_constraints.yml index e0e21e2ee48..cbd4a403ba4 100644 --- a/.github/workflows/upgrade_test_constraints.yml +++ b/.github/workflows/upgrade_test_constraints.yml @@ -165,7 +165,7 @@ jobs: # # --extra pyqt5 etc - names of extra sections from pyproject.toml that should be checked for the dependencies list (maybe we could create a super extra section to collect them all in) flags+=" --extra pyqt5" - flags+=" --extra pyqt6_experimental" + flags+=" --extra pyqt6" flags+=" --extra pyside2" flags+=" --extra pyside6_experimental" flags+=" --extra testing" diff --git a/pyproject.toml b/pyproject.toml index 1f3af819f39..7feb77bb0a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,8 +97,8 @@ pyside2 = [ pyside6_experimental = [ "PySide6 < 6.5 ; python_version < '3.12'" ] -pyqt6_experimental = [ - "PyQt6", +pyqt6 = [ + "PyQt6 > 6.5", "PyQt6 != 6.6.1 ; platform_system == 'Darwin'" ] pyside = [ diff --git a/tools/check_updated_packages.py b/tools/check_updated_packages.py index fa2cc53ab71..3d8e485af51 100644 --- a/tools/check_updated_packages.py +++ b/tools/check_updated_packages.py @@ -141,7 +141,7 @@ def calc_only_direct_updates( packages = ( metadata['dependencies'] + optional_dependencies['pyqt5'] - + optional_dependencies['pyqt6_experimental'] + + optional_dependencies['pyqt6'] + optional_dependencies['pyside2'] + optional_dependencies['pyside6_experimental'] + optional_dependencies['testing']