Repository: napari/napari

Issue:
Colormap in 3D broken by Image Layer Event Handler
## 🐛 Bug

Changing the colormap in 3D doesn't cause the colormap for the actual data to update. The thumbnail does update. This was likely introduced in #1376. Changing colormap in 2D still works fine.


Discussion:

Resolving patch:
diff --git a/napari/_vispy/vispy_image_layer.py b/napari/_vispy/vispy_image_layer.py
--- a/napari/_vispy/vispy_image_layer.py
+++ b/napari/_vispy/vispy_image_layer.py
@@ -159,6 +159,13 @@ def _on_colormap_change(self, colormap):
             node_cmap = Colormap(cmap[np.linspace(0, 1, 256) ** self._gamma])
         else:
             node_cmap = cmap
+        # Following should be added to cmap setter in VisPy volume visual
+        if isinstance(self.node, VolumeNode):
+            self.node.view_program['texture2D_LUT'] = (
+                node_cmap.texture_lut()
+                if (hasattr(node_cmap, 'texture_lut'))
+                else None
+            )
         self.node.cmap = node_cmap
 
     def _on_contrast_limits_change(self, contrast_limits):
@@ -192,6 +199,11 @@ def _on_gamma_change(self, gamma):
         else:
             cmap = self._raw_cmap
         self._gamma = gamma
+        # Following should be added to cmap setter in VisPy volume visual
+        if isinstance(self.node, VolumeNode):
+            self.node.view_program['texture2D_LUT'] = (
+                cmap.texture_lut() if (hasattr(cmap, 'texture_lut')) else None
+            )
         self.node.cmap = cmap
 
     def _on_iso_threshold_change(self, iso_threshold):


Test patch:
diff --git a/napari/_tests/test_with_screenshots.py b/napari/_tests/test_with_screenshots.py
new file mode 100644
--- /dev/null
+++ b/napari/_tests/test_with_screenshots.py
@@ -0,0 +1,68 @@
+import numpy as np
+import os
+import sys
+import pytest
+
+
+@pytest.mark.skipif(
+    sys.platform.startswith('win') or not os.getenv("CI"),
+    reason='Screenshot tests are not supported on napari windows CI.',
+)
+def test_changing_image_colormap(make_test_viewer):
+    """Test changing colormap changes rendering."""
+    viewer = make_test_viewer(show=True)
+
+    data = np.ones((20, 20, 20))
+    layer = viewer.add_image(data, contrast_limits=[0, 1])
+
+    screenshot = viewer.screenshot(canvas_only=True)
+    center = tuple(np.round(np.divide(screenshot.shape[:2], 2)).astype(int))
+    np.testing.assert_almost_equal(screenshot[center], [255, 255, 255, 255])
+
+    layer.colormap = 'red'
+    screenshot = viewer.screenshot(canvas_only=True)
+    np.testing.assert_almost_equal(screenshot[center], [255, 0, 0, 255])
+
+    viewer.dims.ndisplay = 3
+    screenshot = viewer.screenshot(canvas_only=True)
+    np.testing.assert_almost_equal(screenshot[center], [255, 0, 0, 255])
+
+    layer.colormap = 'blue'
+    screenshot = viewer.screenshot(canvas_only=True)
+    np.testing.assert_almost_equal(screenshot[center], [0, 0, 255, 255])
+
+    viewer.dims.ndisplay = 2
+    screenshot = viewer.screenshot(canvas_only=True)
+    np.testing.assert_almost_equal(screenshot[center], [0, 0, 255, 255])
+
+
+@pytest.mark.skipif(
+    sys.platform.startswith('win') or not os.getenv("CI"),
+    reason='Screenshot tests are not supported on napari windows CI.',
+)
+def test_changing_image_gamma(make_test_viewer):
+    """Test changing gamma changes rendering."""
+    viewer = make_test_viewer(show=True)
+
+    data = np.ones((20, 20, 20))
+    layer = viewer.add_image(data, contrast_limits=[0, 2])
+
+    screenshot = viewer.screenshot(canvas_only=True)
+    center = tuple(np.round(np.divide(screenshot.shape[:2], 2)).astype(int))
+    assert screenshot[center + (0,)] == 128
+
+    layer.gamma = 0.1
+    screenshot = viewer.screenshot(canvas_only=True)
+    assert screenshot[center + (0,)] > 230
+
+    viewer.dims.ndisplay = 3
+    screenshot = viewer.screenshot(canvas_only=True)
+    assert screenshot[center + (0,)] > 230
+
+    layer.gamma = 1.9
+    screenshot = viewer.screenshot(canvas_only=True)
+    assert screenshot[center + (0,)] < 80
+
+    viewer.dims.ndisplay = 2
+    screenshot = viewer.screenshot(canvas_only=True)
+    assert screenshot[center + (0,)] < 80