Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix direct colormap #6461

Merged
merged 9 commits into from Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
94 changes: 77 additions & 17 deletions napari/_qt/_tests/test_qt_viewer.py
Expand Up @@ -2,13 +2,14 @@
import os
import weakref
from dataclasses import dataclass
from typing import List
from typing import List, Tuple
from unittest import mock

import numpy as np
import numpy.testing
import pytest
from imageio import imread
from pytestqt.qtbot import QtBot
from qtpy.QtGui import QGuiApplication
from qtpy.QtWidgets import QMessageBox
from scipy import ndimage as ndi
Expand All @@ -23,7 +24,7 @@
)
from napari._vispy._tests.utils import vispy_image_scene_size
from napari.components.viewer_model import ViewerModel
from napari.layers import Points
from napari.layers import Labels, Points
from napari.settings import get_settings
from napari.utils.interactions import mouse_press_callbacks
from napari.utils.theme import available_themes
Expand Down Expand Up @@ -666,16 +667,50 @@ def test_create_non_empty_viewer_model(qtbot):
gc.collect()


def _update_data(
layer: Labels, label: int, qtbot: QtBot, qt_viewer: QtViewer
) -> Tuple[np.ndarray, np.ndarray]:
"""Change layer data and return color of label and middle pixel of screenshot."""
layer.data = np.full((2, 2), label, dtype=np.uint64)
layer.selected_label = label

qtbot.wait(50) # wait for .update() to be called on QtColorBox from Qt

color_box_color = qt_viewer.controls.widgets[layer].colorBox.color
screenshot = qt_viewer.screenshot(flash=False)
shape = np.array(screenshot.shape[:2])
middle_pixel = screenshot[tuple(shape // 2)]

return color_box_color, middle_pixel


@pytest.fixture()
def qt_viewer_with_controls(qtbot):
qt_viewer = QtViewer(viewer=ViewerModel())
qt_viewer.show()
qt_viewer.controls.show()
yield qt_viewer
qt_viewer.controls.hide()
qt_viewer.controls.close()
qt_viewer.hide()
qt_viewer.close()
qtbot.wait(50)


@skip_local_popups
@skip_on_win_ci
def test_label_colors_matching_widget(qtbot, make_napari_viewer):
@pytest.mark.parametrize("use_selection", [False])
def test_label_colors_matching_widget(
qtbot, qt_viewer_with_controls, use_selection
):
"""Make sure the rendered label colors match the QtColorBox widget."""
viewer = make_napari_viewer(show=True)

# XXX TODO: this unstable! Seed = 0 fails, for example. This is due to numerical
# imprecision in random colormap on gpu vs cpu
np.random.seed(1)
data = np.ones((2, 2), dtype=np.uint64)
layer = viewer.add_labels(data)
layer = qt_viewer_with_controls.viewer.add_labels(data)
layer.show_selected_label = use_selection
layer.opacity = 1.0 # QtColorBox & single layer are blending differently

test_colors = np.concatenate(
Expand All @@ -687,22 +722,47 @@ def test_label_colors_matching_widget(qtbot, make_napari_viewer):

for label in test_colors:
# Change color & selected color to the same label
layer.data = np.full((2, 2), label, dtype=np.uint64)
layer.selected_label = label
color_box_color, middle_pixel = _update_data(
layer, label, qtbot, qt_viewer_with_controls
)

assert np.allclose(color_box_color, middle_pixel, atol=1), label
# there is a difference of rounding between the QtColorBox and the screenshot

qtbot.wait(
100
) # wait for .update() to be called on QtColorBox from Qt

color_box_color = viewer.window._qt_viewer.controls.widgets[
layer
].colorBox.color
screenshot = viewer.window.screenshot(flash=False, canvas_only=True)
shape = np.array(screenshot.shape[:2])
middle_pixel = screenshot[tuple(shape // 2)]
@skip_local_popups
@skip_on_win_ci
@pytest.mark.parametrize("use_selection", [True, False])
def test_label_colors_matching_widget_direct(
qtbot, qt_viewer_with_controls, use_selection
):
"""Make sure the rendered label colors match the QtColorBox widget."""
data = np.ones((2, 2), dtype=np.uint64)
layer = qt_viewer_with_controls.viewer.add_labels(data)
layer.show_selected_label = use_selection
layer.opacity = 1.0 # QtColorBox & single layer are blending differently
layer.color = {
0: "transparent",
1: "yellow",
3: "blue",
8: "red",
1000: "green",
None: "white",
}

test_colors = (1, 2, 3, 8, 1000, 50)

color_box_color, middle_pixel = _update_data(
layer, 0, qtbot, qt_viewer_with_controls
)
assert np.allclose([0, 0, 0, 255], middle_pixel)

for label in test_colors:
# Change color & selected color to the same label
color_box_color, middle_pixel = _update_data(
layer, label, qtbot, qt_viewer_with_controls
)
assert np.allclose(color_box_color, middle_pixel, atol=1), label
# there is a difference of rounding between the QtColorBox and the screenshot


def test_axes_labels(make_napari_viewer):
Expand Down
7 changes: 4 additions & 3 deletions napari/_vispy/layers/labels.py
Expand Up @@ -161,7 +161,7 @@
self.glsl_map = (
auto_lookup_shader.replace('$color_map_size', str(len(colors)))
.replace('$use_selection', str(use_selection).lower())
.replace('$selection', str(selection))
.replace('$selection', str(selection + 1))
.replace('$scale', str(scale))
)

Expand Down Expand Up @@ -357,7 +357,7 @@


def build_textures_from_dict(
color_dict: Dict[float, ColorTuple],
color_dict: Dict[Optional[float], ColorTuple],
empty_val=_UNSET,
shape=None,
use_selection=False,
Expand Down Expand Up @@ -401,7 +401,7 @@
if use_selection:
keys = np.full((1, 1), selection, dtype=vispy_texture_dtype)
values = np.zeros((1, 1, 4), dtype=vispy_texture_dtype)
values[0, 0] = color_dict[selection]
values[0, 0] = color_dict.get(selection, color_dict[None])

Check warning on line 404 in napari/_vispy/layers/labels.py

View check run for this annotation

Codecov / codecov/patch

napari/_vispy/layers/labels.py#L404

Added line #L404 was not covered by tests
return keys, values, False

if empty_val is _UNSET:
Expand Down Expand Up @@ -524,6 +524,7 @@
interpolation='nearest',
)
self.node.shared_program['LUT_shape'] = key_texture.shape
self.node.shared_program['color_count'] = len(color_dict)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this only silence openGL warning

else:
self.node.cmap = VispyColormap(*colormap)

Expand Down