We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Removing and readding a 3D image layer is broken
## 🐛 Bug

Re-adding a previously removed 3D image layer results in the following error and a failure to add the layer:

```python
ValueError: interpolation must be one of bessel, bicubic, bilinear, blackman, catrom, gaussian, hamming, hanning, hermite, kaiser, lanczos, mitchell, nearest, quadric, sinc, spline16, spline36
```

This only happens with *3D images*. 2D images are fine.

As appropriate, `git bisect` tells me that it was a regression introduced by *me*, over at #3333 :P

## To Reproduce

```python
import napari
import numpy as np

v = napari.Viewer(ndisplay=3)
img = np.random.rand(100, 100, 100)
il = napari.layers.Image(img)
v.add_layer(il)
# everything is fine so far
```
```python
v.layers.remove(il)
v.add_layer(il)
# error!
```
Removing and readding a 3D image layer is broken
## 🐛 Bug

Re-adding a previously removed 3D image layer results in the following error and a failure to add the layer:

```python
ValueError: interpolation must be one of bessel, bicubic, bilinear, blackman, catrom, gaussian, hamming, hanning, hermite, kaiser, lanczos, mitchell, nearest, quadric, sinc, spline16, spline36
```

This only happens with *3D images*. 2D images are fine.

As appropriate, `git bisect` tells me that it was a regression introduced by *me*, over at #3333 :P

## To Reproduce

```python
import napari
import numpy as np

v = napari.Viewer(ndisplay=3)
img = np.random.rand(100, 100, 100)
il = napari.layers.Image(img)
v.add_layer(il)
# everything is fine so far
```
```python
v.layers.remove(il)
v.add_layer(il)
# error!
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `napari/_vispy/layers/image.py`
Content:
```
1 import warnings
2 
3 import numpy as np
4 from vispy.color import Colormap as VispyColormap
5 from vispy.scene.node import Node
6 
7 from ...utils.translations import trans
8 from ..utils.gl import fix_data_dtype
9 from ..visuals.image import Image as ImageNode
10 from ..visuals.volume import Volume as VolumeNode
11 from .base import VispyBaseLayer
12 
13 
14 class ImageLayerNode:
15     def __init__(self, custom_node: Node = None):
16         self._custom_node = custom_node
17         self._image_node = ImageNode(None, method='auto')
18         self._volume_node = VolumeNode(
19             np.zeros((1, 1, 1), dtype=np.float32), clim=[0, 1]
20         )
21 
22     def get_node(self, ndisplay: int) -> Node:
23 
24         # Return custom node if we have one.
25         if self._custom_node is not None:
26             return self._custom_node
27 
28         # Return Image or Volume node based on 2D or 3D.
29         if ndisplay == 2:
30             return self._image_node
31         return self._volume_node
32 
33 
34 class VispyImageLayer(VispyBaseLayer):
35     def __init__(self, layer, node=None):
36 
37         # Use custom node from caller, or our standard image/volume nodes.
38         self._layer_node = ImageLayerNode(node)
39 
40         # Default to 2D (image) node.
41         super().__init__(layer, self._layer_node.get_node(2))
42 
43         self._array_like = True
44 
45         self.layer.events.rendering.connect(self._on_rendering_change)
46         self.layer.events.interpolation.connect(self._on_interpolation_change)
47         self.layer.events.colormap.connect(self._on_colormap_change)
48         self.layer.events.contrast_limits.connect(
49             self._on_contrast_limits_change
50         )
51         self.layer.events.gamma.connect(self._on_gamma_change)
52         self.layer.events.iso_threshold.connect(self._on_iso_threshold_change)
53         self.layer.events.attenuation.connect(self._on_attenuation_change)
54         self.layer.experimental_slicing_plane.events.enabled.connect(
55             self._on_experimental_slicing_plane_enabled_change
56         )
57         self.layer.experimental_slicing_plane.events.position.connect(
58             self._on_experimental_slicing_plane_position_change
59         )
60         self.layer.experimental_slicing_plane.events.thickness.connect(
61             self._on_experimental_slicing_plane_thickness_change
62         )
63         self.layer.experimental_slicing_plane.events.normal.connect(
64             self._on_experimental_slicing_plane_normal_change
65         )
66 
67         self.reset()
68         self._on_data_change()
69 
70     def _on_display_change(self, data=None):
71 
72         parent = self.node.parent
73         self.node.parent = None
74 
75         self.node = self._layer_node.get_node(self.layer._ndisplay)
76 
77         if data is None:
78             data = np.zeros((1,) * self.layer._ndisplay)
79 
80         if self.layer._empty:
81             self.node.visible = False
82         else:
83             self.node.visible = self.layer.visible
84 
85         if self.layer.loaded:
86             self.node.set_data(data)
87 
88         self.node.parent = parent
89         self.node.order = self.order
90         self.reset()
91 
92     def _on_data_change(self):
93         if not self.layer.loaded:
94             # Do nothing if we are not yet loaded. Calling astype below could
95             # be very expensive. Lets not do it until our data has been loaded.
96             return
97 
98         self._set_node_data(self.node, self.layer._data_view)
99 
100     def _set_node_data(self, node, data):
101         """Our self.layer._data_view has been updated, update our node."""
102 
103         data = fix_data_dtype(data)
104 
105         if self.layer._ndisplay == 3 and self.layer.ndim == 2:
106             data = np.expand_dims(data, axis=0)
107 
108         # Check if data exceeds MAX_TEXTURE_SIZE and downsample
109         if self.MAX_TEXTURE_SIZE_2D is not None and self.layer._ndisplay == 2:
110             data = self.downsample_texture(data, self.MAX_TEXTURE_SIZE_2D)
111         elif (
112             self.MAX_TEXTURE_SIZE_3D is not None and self.layer._ndisplay == 3
113         ):
114             data = self.downsample_texture(data, self.MAX_TEXTURE_SIZE_3D)
115 
116         # Check if ndisplay has changed current node type needs updating
117         if (
118             self.layer._ndisplay == 3 and not isinstance(node, VolumeNode)
119         ) or (self.layer._ndisplay == 2 and not isinstance(node, ImageNode)):
120             self._on_display_change(data)
121         else:
122             node.set_data(data)
123 
124         if self.layer._empty:
125             node.visible = False
126         else:
127             node.visible = self.layer.visible
128 
129         # Call to update order of translation values with new dims:
130         self._on_matrix_change()
131         node.update()
132 
133     def _on_interpolation_change(self):
134         self.node.interpolation = self.layer.interpolation
135 
136     def _on_rendering_change(self):
137         if isinstance(self.node, VolumeNode):
138             self.node.method = self.layer.rendering
139             self._on_attenuation_change()
140             self._on_iso_threshold_change()
141 
142     def _on_colormap_change(self):
143         self.node.cmap = VispyColormap(*self.layer.colormap)
144 
145     def _on_contrast_limits_change(self):
146         self.node.clim = self.layer.contrast_limits
147 
148     def _on_gamma_change(self):
149         if len(self.node.shared_program.frag._set_items) > 0:
150             self.node.gamma = self.layer.gamma
151 
152     def _on_iso_threshold_change(self):
153         if isinstance(self.node, VolumeNode):
154             self.node.threshold = self.layer.iso_threshold
155 
156     def _on_attenuation_change(self):
157         if isinstance(self.node, VolumeNode):
158             self.node.attenuation = self.layer.attenuation
159 
160     def _on_experimental_slicing_plane_enabled_change(self):
161         if isinstance(self.node, VolumeNode):
162             if self.layer.experimental_slicing_plane.enabled is True:
163                 raycasting_mode = 'plane'
164             else:
165                 raycasting_mode = 'volume'
166             self.node.raycasting_mode = raycasting_mode
167 
168     def _on_experimental_slicing_plane_thickness_change(self):
169         if isinstance(self.node, VolumeNode):
170             self.node.plane_thickness = (
171                 self.layer.experimental_slicing_plane.thickness
172             )
173 
174     def _on_experimental_slicing_plane_position_change(self):
175         if isinstance(self.node, VolumeNode):
176             self.node.plane_position = (
177                 self.layer.experimental_slicing_plane.position
178             )
179 
180     def _on_experimental_slicing_plane_normal_change(self):
181         if isinstance(self.node, VolumeNode):
182             self.node.plane_normal = (
183                 self.layer.experimental_slicing_plane.normal
184             )
185 
186     def reset(self, event=None):
187         super().reset()
188         self._on_interpolation_change()
189         self._on_colormap_change()
190         self._on_contrast_limits_change()
191         self._on_gamma_change()
192         self._on_rendering_change()
193         self._on_experimental_slicing_plane_enabled_change()
194         self._on_experimental_slicing_plane_position_change()
195         self._on_experimental_slicing_plane_normal_change()
196         self._on_experimental_slicing_plane_thickness_change()
197 
198     def downsample_texture(self, data, MAX_TEXTURE_SIZE):
199         """Downsample data based on maximum allowed texture size.
200 
201         Parameters
202         ----------
203         data : array
204             Data to be downsampled if needed.
205         MAX_TEXTURE_SIZE : int
206             Maximum allowed texture size.
207 
208         Returns
209         -------
210         data : array
211             Data that now fits inside texture.
212         """
213         if np.any(np.greater(data.shape, MAX_TEXTURE_SIZE)):
214             if self.layer.multiscale:
215                 raise ValueError(
216                     trans._(
217                         "Shape of in dividual tiles in multiscale {shape} cannot exceed GL_MAX_TEXTURE_SIZE {texture_size}. Rendering is currently in {ndisplay}D mode.",
218                         deferred=True,
219                         shape=data.shape,
220                         texture_size=MAX_TEXTURE_SIZE,
221                         ndisplay=self.layer._ndisplay,
222                     )
223                 )
224             warnings.warn(
225                 trans._(
226                     "data shape {shape} exceeds GL_MAX_TEXTURE_SIZE {texture_size} in at least one axis and will be downsampled. Rendering is currently in {ndisplay}D mode.",
227                     deferred=True,
228                     shape=data.shape,
229                     texture_size=MAX_TEXTURE_SIZE,
230                     ndisplay=self.layer._ndisplay,
231                 )
232             )
233             downsample = np.ceil(
234                 np.divide(data.shape, MAX_TEXTURE_SIZE)
235             ).astype(int)
236             scale = np.ones(self.layer.ndim)
237             for i, d in enumerate(self.layer._dims_displayed):
238                 scale[d] = downsample[i]
239             self.layer._transforms['tile2data'].scale = scale
240             self._on_matrix_change()
241             slices = tuple(slice(None, None, ds) for ds in downsample)
242             data = data[slices]
243         return data
244
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/napari/_vispy/layers/image.py b/napari/_vispy/layers/image.py
--- a/napari/_vispy/layers/image.py
+++ b/napari/_vispy/layers/image.py
@@ -64,18 +64,21 @@
             self._on_experimental_slicing_plane_normal_change
         )
 
+        # display_change is special (like data_change) because it requires a self.reset()
+        # this means that we have to call it manually. Also, it must be called before reset
+        # in order to set the appropriate node first
+        self._on_display_change()
         self.reset()
         self._on_data_change()
 
     def _on_display_change(self, data=None):
-
         parent = self.node.parent
         self.node.parent = None
 
         self.node = self._layer_node.get_node(self.layer._ndisplay)
 
         if data is None:
-            data = np.zeros((1,) * self.layer._ndisplay)
+            data = np.zeros((1,) * self.layer._ndisplay, dtype=np.float32)
 
         if self.layer._empty:
             self.node.visible = False

{"golden_diff": "diff --git a/napari/_vispy/layers/image.py b/napari/_vispy/layers/image.py\n--- a/napari/_vispy/layers/image.py\n+++ b/napari/_vispy/layers/image.py\n@@ -64,18 +64,21 @@\n             self._on_experimental_slicing_plane_normal_change\n         )\n \n+        # display_change is special (like data_change) because it requires a self.reset()\n+        # this means that we have to call it manually. Also, it must be called before reset\n+        # in order to set the appropriate node first\n+        self._on_display_change()\n         self.reset()\n         self._on_data_change()\n \n     def _on_display_change(self, data=None):\n-\n         parent = self.node.parent\n         self.node.parent = None\n \n         self.node = self._layer_node.get_node(self.layer._ndisplay)\n \n         if data is None:\n-            data = np.zeros((1,) * self.layer._ndisplay)\n+            data = np.zeros((1,) * self.layer._ndisplay, dtype=np.float32)\n \n         if self.layer._empty:\n             self.node.visible = False\n", "issue": "Removing and readding a 3D image layer is broken\n## \ud83d\udc1b Bug\r\n\r\nRe-adding a previously removed 3D image layer results in the following error and a failure to add the layer:\r\n\r\n```python\r\nValueError: interpolation must be one of bessel, bicubic, bilinear, blackman, catrom, gaussian, hamming, hanning, hermite, kaiser, lanczos, mitchell, nearest, quadric, sinc, spline16, spline36\r\n```\r\n\r\nThis only happens with *3D images*. 2D images are fine.\r\n\r\nAs appropriate, `git bisect` tells me that it was a regression introduced by *me*, over at #3333 :P\r\n\r\n## To Reproduce\r\n\r\n```python\r\nimport napari\r\nimport numpy as np\r\n\r\nv = napari.Viewer(ndisplay=3)\r\nimg = np.random.rand(100, 100, 100)\r\nil = napari.layers.Image(img)\r\nv.add_layer(il)\r\n# everything is fine so far\r\n```\r\n```python\r\nv.layers.remove(il)\r\nv.add_layer(il)\r\n# error!\r\n```\nRemoving and readding a 3D image layer is broken\n## \ud83d\udc1b Bug\r\n\r\nRe-adding a previously removed 3D image layer results in the following error and a failure to add the layer:\r\n\r\n```python\r\nValueError: interpolation must be one of bessel, bicubic, bilinear, blackman, catrom, gaussian, hamming, hanning, hermite, kaiser, lanczos, mitchell, nearest, quadric, sinc, spline16, spline36\r\n```\r\n\r\nThis only happens with *3D images*. 2D images are fine.\r\n\r\nAs appropriate, `git bisect` tells me that it was a regression introduced by *me*, over at #3333 :P\r\n\r\n## To Reproduce\r\n\r\n```python\r\nimport napari\r\nimport numpy as np\r\n\r\nv = napari.Viewer(ndisplay=3)\r\nimg = np.random.rand(100, 100, 100)\r\nil = napari.layers.Image(img)\r\nv.add_layer(il)\r\n# everything is fine so far\r\n```\r\n```python\r\nv.layers.remove(il)\r\nv.add_layer(il)\r\n# error!\r\n```\n", "before_files": [{"content": "import warnings\n\nimport numpy as np\nfrom vispy.color import Colormap as VispyColormap\nfrom vispy.scene.node import Node\n\nfrom ...utils.translations import trans\nfrom ..utils.gl import fix_data_dtype\nfrom ..visuals.image import Image as ImageNode\nfrom ..visuals.volume import Volume as VolumeNode\nfrom .base import VispyBaseLayer\n\n\nclass ImageLayerNode:\n    def __init__(self, custom_node: Node = None):\n        self._custom_node = custom_node\n        self._image_node = ImageNode(None, method='auto')\n        self._volume_node = VolumeNode(\n            np.zeros((1, 1, 1), dtype=np.float32), clim=[0, 1]\n        )\n\n    def get_node(self, ndisplay: int) -> Node:\n\n        # Return custom node if we have one.\n        if self._custom_node is not None:\n            return self._custom_node\n\n        # Return Image or Volume node based on 2D or 3D.\n        if ndisplay == 2:\n            return self._image_node\n        return self._volume_node\n\n\nclass VispyImageLayer(VispyBaseLayer):\n    def __init__(self, layer, node=None):\n\n        # Use custom node from caller, or our standard image/volume nodes.\n        self._layer_node = ImageLayerNode(node)\n\n        # Default to 2D (image) node.\n        super().__init__(layer, self._layer_node.get_node(2))\n\n        self._array_like = True\n\n        self.layer.events.rendering.connect(self._on_rendering_change)\n        self.layer.events.interpolation.connect(self._on_interpolation_change)\n        self.layer.events.colormap.connect(self._on_colormap_change)\n        self.layer.events.contrast_limits.connect(\n            self._on_contrast_limits_change\n        )\n        self.layer.events.gamma.connect(self._on_gamma_change)\n        self.layer.events.iso_threshold.connect(self._on_iso_threshold_change)\n        self.layer.events.attenuation.connect(self._on_attenuation_change)\n        self.layer.experimental_slicing_plane.events.enabled.connect(\n            self._on_experimental_slicing_plane_enabled_change\n        )\n        self.layer.experimental_slicing_plane.events.position.connect(\n            self._on_experimental_slicing_plane_position_change\n        )\n        self.layer.experimental_slicing_plane.events.thickness.connect(\n            self._on_experimental_slicing_plane_thickness_change\n        )\n        self.layer.experimental_slicing_plane.events.normal.connect(\n            self._on_experimental_slicing_plane_normal_change\n        )\n\n        self.reset()\n        self._on_data_change()\n\n    def _on_display_change(self, data=None):\n\n        parent = self.node.parent\n        self.node.parent = None\n\n        self.node = self._layer_node.get_node(self.layer._ndisplay)\n\n        if data is None:\n            data = np.zeros((1,) * self.layer._ndisplay)\n\n        if self.layer._empty:\n            self.node.visible = False\n        else:\n            self.node.visible = self.layer.visible\n\n        if self.layer.loaded:\n            self.node.set_data(data)\n\n        self.node.parent = parent\n        self.node.order = self.order\n        self.reset()\n\n    def _on_data_change(self):\n        if not self.layer.loaded:\n            # Do nothing if we are not yet loaded. Calling astype below could\n            # be very expensive. Lets not do it until our data has been loaded.\n            return\n\n        self._set_node_data(self.node, self.layer._data_view)\n\n    def _set_node_data(self, node, data):\n        \"\"\"Our self.layer._data_view has been updated, update our node.\"\"\"\n\n        data = fix_data_dtype(data)\n\n        if self.layer._ndisplay == 3 and self.layer.ndim == 2:\n            data = np.expand_dims(data, axis=0)\n\n        # Check if data exceeds MAX_TEXTURE_SIZE and downsample\n        if self.MAX_TEXTURE_SIZE_2D is not None and self.layer._ndisplay == 2:\n            data = self.downsample_texture(data, self.MAX_TEXTURE_SIZE_2D)\n        elif (\n            self.MAX_TEXTURE_SIZE_3D is not None and self.layer._ndisplay == 3\n        ):\n            data = self.downsample_texture(data, self.MAX_TEXTURE_SIZE_3D)\n\n        # Check if ndisplay has changed current node type needs updating\n        if (\n            self.layer._ndisplay == 3 and not isinstance(node, VolumeNode)\n        ) or (self.layer._ndisplay == 2 and not isinstance(node, ImageNode)):\n            self._on_display_change(data)\n        else:\n            node.set_data(data)\n\n        if self.layer._empty:\n            node.visible = False\n        else:\n            node.visible = self.layer.visible\n\n        # Call to update order of translation values with new dims:\n        self._on_matrix_change()\n        node.update()\n\n    def _on_interpolation_change(self):\n        self.node.interpolation = self.layer.interpolation\n\n    def _on_rendering_change(self):\n        if isinstance(self.node, VolumeNode):\n            self.node.method = self.layer.rendering\n            self._on_attenuation_change()\n            self._on_iso_threshold_change()\n\n    def _on_colormap_change(self):\n        self.node.cmap = VispyColormap(*self.layer.colormap)\n\n    def _on_contrast_limits_change(self):\n        self.node.clim = self.layer.contrast_limits\n\n    def _on_gamma_change(self):\n        if len(self.node.shared_program.frag._set_items) > 0:\n            self.node.gamma = self.layer.gamma\n\n    def _on_iso_threshold_change(self):\n        if isinstance(self.node, VolumeNode):\n            self.node.threshold = self.layer.iso_threshold\n\n    def _on_attenuation_change(self):\n        if isinstance(self.node, VolumeNode):\n            self.node.attenuation = self.layer.attenuation\n\n    def _on_experimental_slicing_plane_enabled_change(self):\n        if isinstance(self.node, VolumeNode):\n            if self.layer.experimental_slicing_plane.enabled is True:\n                raycasting_mode = 'plane'\n            else:\n                raycasting_mode = 'volume'\n            self.node.raycasting_mode = raycasting_mode\n\n    def _on_experimental_slicing_plane_thickness_change(self):\n        if isinstance(self.node, VolumeNode):\n            self.node.plane_thickness = (\n                self.layer.experimental_slicing_plane.thickness\n            )\n\n    def _on_experimental_slicing_plane_position_change(self):\n        if isinstance(self.node, VolumeNode):\n            self.node.plane_position = (\n                self.layer.experimental_slicing_plane.position\n            )\n\n    def _on_experimental_slicing_plane_normal_change(self):\n        if isinstance(self.node, VolumeNode):\n            self.node.plane_normal = (\n                self.layer.experimental_slicing_plane.normal\n            )\n\n    def reset(self, event=None):\n        super().reset()\n        self._on_interpolation_change()\n        self._on_colormap_change()\n        self._on_contrast_limits_change()\n        self._on_gamma_change()\n        self._on_rendering_change()\n        self._on_experimental_slicing_plane_enabled_change()\n        self._on_experimental_slicing_plane_position_change()\n        self._on_experimental_slicing_plane_normal_change()\n        self._on_experimental_slicing_plane_thickness_change()\n\n    def downsample_texture(self, data, MAX_TEXTURE_SIZE):\n        \"\"\"Downsample data based on maximum allowed texture size.\n\n        Parameters\n        ----------\n        data : array\n            Data to be downsampled if needed.\n        MAX_TEXTURE_SIZE : int\n            Maximum allowed texture size.\n\n        Returns\n        -------\n        data : array\n            Data that now fits inside texture.\n        \"\"\"\n        if np.any(np.greater(data.shape, MAX_TEXTURE_SIZE)):\n            if self.layer.multiscale:\n                raise ValueError(\n                    trans._(\n                        \"Shape of in dividual tiles in multiscale {shape} cannot exceed GL_MAX_TEXTURE_SIZE {texture_size}. Rendering is currently in {ndisplay}D mode.\",\n                        deferred=True,\n                        shape=data.shape,\n                        texture_size=MAX_TEXTURE_SIZE,\n                        ndisplay=self.layer._ndisplay,\n                    )\n                )\n            warnings.warn(\n                trans._(\n                    \"data shape {shape} exceeds GL_MAX_TEXTURE_SIZE {texture_size} in at least one axis and will be downsampled. Rendering is currently in {ndisplay}D mode.\",\n                    deferred=True,\n                    shape=data.shape,\n                    texture_size=MAX_TEXTURE_SIZE,\n                    ndisplay=self.layer._ndisplay,\n                )\n            )\n            downsample = np.ceil(\n                np.divide(data.shape, MAX_TEXTURE_SIZE)\n            ).astype(int)\n            scale = np.ones(self.layer.ndim)\n            for i, d in enumerate(self.layer._dims_displayed):\n                scale[d] = downsample[i]\n            self.layer._transforms['tile2data'].scale = scale\n            self._on_matrix_change()\n            slices = tuple(slice(None, None, ds) for ds in downsample)\n            data = data[slices]\n        return data\n", "path": "napari/_vispy/layers/image.py"}], "after_files": [{"content": "import warnings\n\nimport numpy as np\nfrom vispy.color import Colormap as VispyColormap\nfrom vispy.scene.node import Node\n\nfrom ...utils.translations import trans\nfrom ..utils.gl import fix_data_dtype\nfrom ..visuals.image import Image as ImageNode\nfrom ..visuals.volume import Volume as VolumeNode\nfrom .base import VispyBaseLayer\n\n\nclass ImageLayerNode:\n    def __init__(self, custom_node: Node = None):\n        self._custom_node = custom_node\n        self._image_node = ImageNode(None, method='auto')\n        self._volume_node = VolumeNode(\n            np.zeros((1, 1, 1), dtype=np.float32), clim=[0, 1]\n        )\n\n    def get_node(self, ndisplay: int) -> Node:\n\n        # Return custom node if we have one.\n        if self._custom_node is not None:\n            return self._custom_node\n\n        # Return Image or Volume node based on 2D or 3D.\n        if ndisplay == 2:\n            return self._image_node\n        return self._volume_node\n\n\nclass VispyImageLayer(VispyBaseLayer):\n    def __init__(self, layer, node=None):\n\n        # Use custom node from caller, or our standard image/volume nodes.\n        self._layer_node = ImageLayerNode(node)\n\n        # Default to 2D (image) node.\n        super().__init__(layer, self._layer_node.get_node(2))\n\n        self._array_like = True\n\n        self.layer.events.rendering.connect(self._on_rendering_change)\n        self.layer.events.interpolation.connect(self._on_interpolation_change)\n        self.layer.events.colormap.connect(self._on_colormap_change)\n        self.layer.events.contrast_limits.connect(\n            self._on_contrast_limits_change\n        )\n        self.layer.events.gamma.connect(self._on_gamma_change)\n        self.layer.events.iso_threshold.connect(self._on_iso_threshold_change)\n        self.layer.events.attenuation.connect(self._on_attenuation_change)\n        self.layer.experimental_slicing_plane.events.enabled.connect(\n            self._on_experimental_slicing_plane_enabled_change\n        )\n        self.layer.experimental_slicing_plane.events.position.connect(\n            self._on_experimental_slicing_plane_position_change\n        )\n        self.layer.experimental_slicing_plane.events.thickness.connect(\n            self._on_experimental_slicing_plane_thickness_change\n        )\n        self.layer.experimental_slicing_plane.events.normal.connect(\n            self._on_experimental_slicing_plane_normal_change\n        )\n\n        # display_change is special (like data_change) because it requires a self.reset()\n        # this means that we have to call it manually. Also, it must be called before reset\n        # in order to set the appropriate node first\n        self._on_display_change()\n        self.reset()\n        self._on_data_change()\n\n    def _on_display_change(self, data=None):\n        parent = self.node.parent\n        self.node.parent = None\n\n        self.node = self._layer_node.get_node(self.layer._ndisplay)\n\n        if data is None:\n            data = np.zeros((1,) * self.layer._ndisplay, dtype=np.float32)\n\n        if self.layer._empty:\n            self.node.visible = False\n        else:\n            self.node.visible = self.layer.visible\n\n        if self.layer.loaded:\n            self.node.set_data(data)\n\n        self.node.parent = parent\n        self.node.order = self.order\n        self.reset()\n\n    def _on_data_change(self):\n        if not self.layer.loaded:\n            # Do nothing if we are not yet loaded. Calling astype below could\n            # be very expensive. Lets not do it until our data has been loaded.\n            return\n\n        self._set_node_data(self.node, self.layer._data_view)\n\n    def _set_node_data(self, node, data):\n        \"\"\"Our self.layer._data_view has been updated, update our node.\"\"\"\n\n        data = fix_data_dtype(data)\n\n        if self.layer._ndisplay == 3 and self.layer.ndim == 2:\n            data = np.expand_dims(data, axis=0)\n\n        # Check if data exceeds MAX_TEXTURE_SIZE and downsample\n        if self.MAX_TEXTURE_SIZE_2D is not None and self.layer._ndisplay == 2:\n            data = self.downsample_texture(data, self.MAX_TEXTURE_SIZE_2D)\n        elif (\n            self.MAX_TEXTURE_SIZE_3D is not None and self.layer._ndisplay == 3\n        ):\n            data = self.downsample_texture(data, self.MAX_TEXTURE_SIZE_3D)\n\n        # Check if ndisplay has changed current node type needs updating\n        if (\n            self.layer._ndisplay == 3 and not isinstance(node, VolumeNode)\n        ) or (self.layer._ndisplay == 2 and not isinstance(node, ImageNode)):\n            self._on_display_change(data)\n        else:\n            node.set_data(data)\n\n        if self.layer._empty:\n            node.visible = False\n        else:\n            node.visible = self.layer.visible\n\n        # Call to update order of translation values with new dims:\n        self._on_matrix_change()\n        node.update()\n\n    def _on_interpolation_change(self):\n        self.node.interpolation = self.layer.interpolation\n\n    def _on_rendering_change(self):\n        if isinstance(self.node, VolumeNode):\n            self.node.method = self.layer.rendering\n            self._on_attenuation_change()\n            self._on_iso_threshold_change()\n\n    def _on_colormap_change(self):\n        self.node.cmap = VispyColormap(*self.layer.colormap)\n\n    def _on_contrast_limits_change(self):\n        self.node.clim = self.layer.contrast_limits\n\n    def _on_gamma_change(self):\n        if len(self.node.shared_program.frag._set_items) > 0:\n            self.node.gamma = self.layer.gamma\n\n    def _on_iso_threshold_change(self):\n        if isinstance(self.node, VolumeNode):\n            self.node.threshold = self.layer.iso_threshold\n\n    def _on_attenuation_change(self):\n        if isinstance(self.node, VolumeNode):\n            self.node.attenuation = self.layer.attenuation\n\n    def _on_experimental_slicing_plane_enabled_change(self):\n        if isinstance(self.node, VolumeNode):\n            if self.layer.experimental_slicing_plane.enabled is True:\n                raycasting_mode = 'plane'\n            else:\n                raycasting_mode = 'volume'\n            self.node.raycasting_mode = raycasting_mode\n\n    def _on_experimental_slicing_plane_thickness_change(self):\n        if isinstance(self.node, VolumeNode):\n            self.node.plane_thickness = (\n                self.layer.experimental_slicing_plane.thickness\n            )\n\n    def _on_experimental_slicing_plane_position_change(self):\n        if isinstance(self.node, VolumeNode):\n            self.node.plane_position = (\n                self.layer.experimental_slicing_plane.position\n            )\n\n    def _on_experimental_slicing_plane_normal_change(self):\n        if isinstance(self.node, VolumeNode):\n            self.node.plane_normal = (\n                self.layer.experimental_slicing_plane.normal\n            )\n\n    def reset(self, event=None):\n        super().reset()\n        self._on_interpolation_change()\n        self._on_colormap_change()\n        self._on_contrast_limits_change()\n        self._on_gamma_change()\n        self._on_rendering_change()\n        self._on_experimental_slicing_plane_enabled_change()\n        self._on_experimental_slicing_plane_position_change()\n        self._on_experimental_slicing_plane_normal_change()\n        self._on_experimental_slicing_plane_thickness_change()\n\n    def downsample_texture(self, data, MAX_TEXTURE_SIZE):\n        \"\"\"Downsample data based on maximum allowed texture size.\n\n        Parameters\n        ----------\n        data : array\n            Data to be downsampled if needed.\n        MAX_TEXTURE_SIZE : int\n            Maximum allowed texture size.\n\n        Returns\n        -------\n        data : array\n            Data that now fits inside texture.\n        \"\"\"\n        if np.any(np.greater(data.shape, MAX_TEXTURE_SIZE)):\n            if self.layer.multiscale:\n                raise ValueError(\n                    trans._(\n                        \"Shape of in dividual tiles in multiscale {shape} cannot exceed GL_MAX_TEXTURE_SIZE {texture_size}. Rendering is currently in {ndisplay}D mode.\",\n                        deferred=True,\n                        shape=data.shape,\n                        texture_size=MAX_TEXTURE_SIZE,\n                        ndisplay=self.layer._ndisplay,\n                    )\n                )\n            warnings.warn(\n                trans._(\n                    \"data shape {shape} exceeds GL_MAX_TEXTURE_SIZE {texture_size} in at least one axis and will be downsampled. Rendering is currently in {ndisplay}D mode.\",\n                    deferred=True,\n                    shape=data.shape,\n                    texture_size=MAX_TEXTURE_SIZE,\n                    ndisplay=self.layer._ndisplay,\n                )\n            )\n            downsample = np.ceil(\n                np.divide(data.shape, MAX_TEXTURE_SIZE)\n            ).astype(int)\n            scale = np.ones(self.layer.ndim)\n            for i, d in enumerate(self.layer._dims_displayed):\n                scale[d] = downsample[i]\n            self.layer._transforms['tile2data'].scale = scale\n            self._on_matrix_change()\n            slices = tuple(slice(None, None, ds) for ds in downsample)\n            data = data[slices]\n        return data\n", "path": "napari/_vispy/layers/image.py"}]}