Display 3D skeletons in napari#

There are two ways to display 3D skeletons in napari. First, we demonstrate how to use the napari Shapes layer to draw Paths for each skeleton path. As of napari 0.4.14, however, 3D interactivity in the Shapes layer is not ideal, so we also demonstrate how to use the Labels layer.

Using the Shapes layer#

The napari Shapes layer allows displaying of 2D and 3D paths, and coloring them by tabular properties, which makes it ideal for displaying skeletons, and the measurements provided by skan.

import napari
import numpy as np
import skan
from skimage.data import binary_blobs
from skimage.morphology import skeletonize
import scipy.ndimage as ndi

Note

We use synthetic data here. If you have a cool 3D skeleton dataset that you’d like to show off in these docs, let us know!

blobs = binary_blobs(64, volume_fraction=0.3, n_dim=3)
binary_skeleton = skeletonize(blobs)
skeleton = skan.Skeleton(binary_skeleton)
all_paths = [
        skeleton.path_coordinates(i)
        for i in range(skeleton.n_paths)
        ]
paths_table = skan.summarize(skeleton)
paths_table['path-id'] = np.arange(skeleton.n_paths)

First, we color by random path ID, showing each path in a distinct color using the matplotlib “tab10” qualitative palette. (Coloring by path ID directly results in “bands” of nearby paths receiving the same color.)

paths_table['random-path-id'] = np.random.default_rng().permutation(skeleton.n_paths)
viewer = napari.Viewer(ndisplay=3)

skeleton_layer = viewer.add_shapes(
        all_paths,
        shape_type='path',
        properties=paths_table,
        edge_width=0.5,
        edge_color='random-path-id',
        edge_colormap='tab10',
)
WARNING: QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '/tmp/runtime-runner'

We can also demonstrate that most of these branches are in one skeleton, with a few stragglers around the edges, by coloring by skeleton ID:

skeleton_layer.edge_color = 'skeleton-id'
# for now, we need to set the face color as well
skeleton_layer.face_color = 'skeleton-id'

Finally, we can color the paths by a numerical property, such as their length.

skeleton_layer.edge_color = 'branch-distance'
skeleton_layer.edge_colormap = 'viridis'
# for now, we need to set the face color as well
skeleton_layer.face_color = 'branch-distance'
skeleton_layer.face_colormap = 'viridis'

Using the Labels layer#

We can also visualize the pixels of the skeleton as a Labels layer, with each path ID appearing as a different label. The downside with this approach is that junction pixels are arbitrarily assigned to one of the branches incident on that junction. Therefore, removing that branch would cause the junction pixel to be removed, which could incorrectly disconnect the skeleton at that point.

However, the rich 3D interactivity of the labels layer does allow prunning of branches in 3D, which could be extremely useful for manual curation of the skeleton, as long as propert care is taken in downstream processing of the edits.

labels = np.asarray(skeleton)

viewer2 = napari.view_labels(
        labels,
        properties=paths_table,
        opacity=1,
        ndisplay=3,
        )
WARNING: Error drawing visual <Volume at 0x7fca20d29f40>
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/app/backends/_qt.py:903, in CanvasBackendDesktop.paintGL(self)
    901 # (0, 0, self.width(), self.height()))
    902 self._vispy_canvas.set_current()
--> 903 self._vispy_canvas.events.draw(region=None)
    905 # Clear the alpha channel with QOpenGLWidget (Qt >= 5.4), otherwise the
    906 # window is translucent behind non-opaque objects.
    907 # Reference:  MRtrix3/mrtrix3#266
    908 if QT5_NEW_API or PYSIDE6_API or PYQT6_API:

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/util/event.py:453, in EventEmitter.__call__(self, *args, **kwargs)
    450 if self._emitting > 1:
    451     raise RuntimeError('EventEmitter loop detected!')
--> 453 self._invoke_callback(cb, event)
    454 if event.blocked:
    455     break

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/util/event.py:471, in EventEmitter._invoke_callback(self, cb, event)
    469     cb(event)
    470 except Exception:
--> 471     _handle_exception(self.ignore_callback_errors,
    472                       self.print_callback_errors,
    473                       self, cb_event=(cb, event))

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/util/event.py:469, in EventEmitter._invoke_callback(self, cb, event)
    467 def _invoke_callback(self, cb, event):
    468     try:
--> 469         cb(event)
    470     except Exception:
    471         _handle_exception(self.ignore_callback_errors,
    472                           self.print_callback_errors,
    473                           self, cb_event=(cb, event))

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/scene/canvas.py:218, in SceneCanvas.on_draw(self, event)
    215 # Now that a draw event is going to be handled, open up the
    216 # scheduling of further updates
    217 self._update_pending = False
--> 218 self._draw_scene()

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/scene/canvas.py:277, in SceneCanvas._draw_scene(self, bgcolor)
    275     bgcolor = self._bgcolor
    276 self.context.clear(color=bgcolor, depth=True)
--> 277 self.draw_visual(self.scene)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/scene/canvas.py:315, in SceneCanvas.draw_visual(self, visual, event)
    313         else:
    314             if hasattr(node, 'draw'):
--> 315                 node.draw()
    316                 prof.mark(str(node))
    317 else:

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/scene/visuals.py:103, in VisualNode.draw(self)
    101 if self.picking and not self.interactive:
    102     return
--> 103 self._visual_superclass.draw(self)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/visuals/visual.py:451, in Visual.draw(self)
    449 self._configure_gl_state()
    450 try:
--> 451     self._program.draw(self._vshare.draw_mode,
    452                        self._vshare.index_buffer)
    453 except Exception:
    454     logger.warning("Error drawing visual %r" % self)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/visuals/shaders/program.py:102, in ModularProgram.draw(self, *args, **kwargs)
    100 self.build_if_needed()
    101 self.update_variables()
--> 102 Program.draw(self, *args, **kwargs)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/program.py:526, in Program.draw(self, mode, indices, check_error)
    522     raise TypeError("Invalid index: %r (must be IndexBuffer)" %
    523                     indices)
    525 # Process GLIR commands
--> 526 canvas.context.flush_commands()

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/context.py:172, in GLContext.flush_commands(self, event)
    170         fbo = 0
    171     self.shared.parser.parse([('CURRENT', 0, fbo)])
--> 172 self.glir.flush(self.shared.parser)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/glir.py:582, in GlirQueue.flush(self, parser)
    580 def flush(self, parser):
    581     """Flush all current commands to the GLIR interpreter."""
--> 582     self._shared.flush(parser)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/glir.py:504, in _GlirQueueShare.flush(self, parser)
    502     show = self._verbose if isinstance(self._verbose, str) else None
    503     self.show(show)
--> 504 parser.parse(self._filter(self.clear(), parser))

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/glir.py:822, in GlirParser.parse(self, commands)
    819     self._objects.pop(id_)
    821 for command in commands:
--> 822     self._parse(command)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/glir.py:792, in GlirParser._parse(self, command)
    790     ob.set_attribute(*args)
    791 elif cmd == 'DATA':  # VertexBuffer, IndexBuffer, Texture, Shader
--> 792     ob.set_data(*args)
    793 elif cmd == 'SIZE':  # VertexBuffer, IndexBuffer,
    794     ob.set_size(*args)  # Texture[1D, 2D, 3D], RenderBuffer

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/glir.py:931, in GlirShader.set_data(self, offset, code)
    929 errors = gl.glGetShaderInfoLog(self._handle)
    930 errormsg = self._get_error(code, errors, 4)
--> 931 raise RuntimeError("Shader compilation error in %s:\n%s" %
    932                    (self._target, errormsg))

RuntimeError: Shader compilation error in GL_FRAGMENT_SHADER:
    on line 201: warning: `view_ray' used uninitialized
      vec3 V = normalize(view_ray);
    on line 240: warning: `view_ray' used uninitialized
      vec3 L = normalize(view_ray);  //lightDirs[i];
    on line 331: warning: `view_ray' used uninitialized
      vec3 V = normalize(view_ray);
    on line 371: warning: `view_ray' used uninitialized
      vec3 L = normalize(view_ray);  //lightDirs[i];
    on line 494: error: `surface_point' undeclared
      surface_point = iloc * u_shape;
    on line 494: error: value of type vec3 cannot be assigned to variable of type error
      surface_point = iloc * u_shape;
    on line 495: error: `surface_found' undeclared
      surface_found = true;
    on line 495: error: value of type bool cannot be assigned to variable of type error
      surface_found = true;
    on line 514: error: `surface_found' undeclared
      if (surface_found == false) {
    on line 514: error: operands of `==' must have the same type
      if (surface_found == false) {
WARNING: Error drawing visual <Volume at 0x7fca20d29f40>
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/app/backends/_qt.py:903, in CanvasBackendDesktop.paintGL(self)
    901 # (0, 0, self.width(), self.height()))
    902 self._vispy_canvas.set_current()
--> 903 self._vispy_canvas.events.draw(region=None)
    905 # Clear the alpha channel with QOpenGLWidget (Qt >= 5.4), otherwise the
    906 # window is translucent behind non-opaque objects.
    907 # Reference:  MRtrix3/mrtrix3#266
    908 if QT5_NEW_API or PYSIDE6_API or PYQT6_API:

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/util/event.py:453, in EventEmitter.__call__(self, *args, **kwargs)
    450 if self._emitting > 1:
    451     raise RuntimeError('EventEmitter loop detected!')
--> 453 self._invoke_callback(cb, event)
    454 if event.blocked:
    455     break

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/util/event.py:471, in EventEmitter._invoke_callback(self, cb, event)
    469     cb(event)
    470 except Exception:
--> 471     _handle_exception(self.ignore_callback_errors,
    472                       self.print_callback_errors,
    473                       self, cb_event=(cb, event))

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/util/event.py:469, in EventEmitter._invoke_callback(self, cb, event)
    467 def _invoke_callback(self, cb, event):
    468     try:
--> 469         cb(event)
    470     except Exception:
    471         _handle_exception(self.ignore_callback_errors,
    472                           self.print_callback_errors,
    473                           self, cb_event=(cb, event))

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/scene/canvas.py:218, in SceneCanvas.on_draw(self, event)
    215 # Now that a draw event is going to be handled, open up the
    216 # scheduling of further updates
    217 self._update_pending = False
--> 218 self._draw_scene()

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/scene/canvas.py:277, in SceneCanvas._draw_scene(self, bgcolor)
    275     bgcolor = self._bgcolor
    276 self.context.clear(color=bgcolor, depth=True)
--> 277 self.draw_visual(self.scene)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/scene/canvas.py:315, in SceneCanvas.draw_visual(self, visual, event)
    313         else:
    314             if hasattr(node, 'draw'):
--> 315                 node.draw()
    316                 prof.mark(str(node))
    317 else:

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/scene/visuals.py:103, in VisualNode.draw(self)
    101 if self.picking and not self.interactive:
    102     return
--> 103 self._visual_superclass.draw(self)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/visuals/visual.py:451, in Visual.draw(self)
    449 self._configure_gl_state()
    450 try:
--> 451     self._program.draw(self._vshare.draw_mode,
    452                        self._vshare.index_buffer)
    453 except Exception:
    454     logger.warning("Error drawing visual %r" % self)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/visuals/shaders/program.py:102, in ModularProgram.draw(self, *args, **kwargs)
    100 self.build_if_needed()
    101 self.update_variables()
--> 102 Program.draw(self, *args, **kwargs)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/program.py:526, in Program.draw(self, mode, indices, check_error)
    522     raise TypeError("Invalid index: %r (must be IndexBuffer)" %
    523                     indices)
    525 # Process GLIR commands
--> 526 canvas.context.flush_commands()

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/context.py:172, in GLContext.flush_commands(self, event)
    170         fbo = 0
    171     self.shared.parser.parse([('CURRENT', 0, fbo)])
--> 172 self.glir.flush(self.shared.parser)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/glir.py:582, in GlirQueue.flush(self, parser)
    580 def flush(self, parser):
    581     """Flush all current commands to the GLIR interpreter."""
--> 582     self._shared.flush(parser)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/glir.py:504, in _GlirQueueShare.flush(self, parser)
    502     show = self._verbose if isinstance(self._verbose, str) else None
    503     self.show(show)
--> 504 parser.parse(self._filter(self.clear(), parser))

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/glir.py:822, in GlirParser.parse(self, commands)
    819     self._objects.pop(id_)
    821 for command in commands:
--> 822     self._parse(command)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/glir.py:778, in GlirParser._parse(self, command)
    776 if ob is None:
    777     if id_ not in self._invalid_objects:
--> 778         raise RuntimeError('Cannot %s object %i because it '
    779                            'does not exist' % (cmd, id_))
    780     return
    781 # Triage over command. Order of commands is set so most
    782 # common ones occur first.

RuntimeError: Cannot SIZE object 118 because it does not exist
WARNING: Error drawing visual <Volume at 0x7fca20d29f40>
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/app/backends/_qt.py:903, in CanvasBackendDesktop.paintGL(self)
    901 # (0, 0, self.width(), self.height()))
    902 self._vispy_canvas.set_current()
--> 903 self._vispy_canvas.events.draw(region=None)
    905 # Clear the alpha channel with QOpenGLWidget (Qt >= 5.4), otherwise the
    906 # window is translucent behind non-opaque objects.
    907 # Reference:  MRtrix3/mrtrix3#266
    908 if QT5_NEW_API or PYSIDE6_API or PYQT6_API:

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/util/event.py:453, in EventEmitter.__call__(self, *args, **kwargs)
    450 if self._emitting > 1:
    451     raise RuntimeError('EventEmitter loop detected!')
--> 453 self._invoke_callback(cb, event)
    454 if event.blocked:
    455     break

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/util/event.py:471, in EventEmitter._invoke_callback(self, cb, event)
    469     cb(event)
    470 except Exception:
--> 471     _handle_exception(self.ignore_callback_errors,
    472                       self.print_callback_errors,
    473                       self, cb_event=(cb, event))

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/util/event.py:469, in EventEmitter._invoke_callback(self, cb, event)
    467 def _invoke_callback(self, cb, event):
    468     try:
--> 469         cb(event)
    470     except Exception:
    471         _handle_exception(self.ignore_callback_errors,
    472                           self.print_callback_errors,
    473                           self, cb_event=(cb, event))

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/scene/canvas.py:218, in SceneCanvas.on_draw(self, event)
    215 # Now that a draw event is going to be handled, open up the
    216 # scheduling of further updates
    217 self._update_pending = False
--> 218 self._draw_scene()

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/scene/canvas.py:277, in SceneCanvas._draw_scene(self, bgcolor)
    275     bgcolor = self._bgcolor
    276 self.context.clear(color=bgcolor, depth=True)
--> 277 self.draw_visual(self.scene)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/scene/canvas.py:315, in SceneCanvas.draw_visual(self, visual, event)
    313         else:
    314             if hasattr(node, 'draw'):
--> 315                 node.draw()
    316                 prof.mark(str(node))
    317 else:

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/scene/visuals.py:103, in VisualNode.draw(self)
    101 if self.picking and not self.interactive:
    102     return
--> 103 self._visual_superclass.draw(self)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/visuals/visual.py:451, in Visual.draw(self)
    449 self._configure_gl_state()
    450 try:
--> 451     self._program.draw(self._vshare.draw_mode,
    452                        self._vshare.index_buffer)
    453 except Exception:
    454     logger.warning("Error drawing visual %r" % self)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/visuals/shaders/program.py:102, in ModularProgram.draw(self, *args, **kwargs)
    100 self.build_if_needed()
    101 self.update_variables()
--> 102 Program.draw(self, *args, **kwargs)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/program.py:526, in Program.draw(self, mode, indices, check_error)
    522     raise TypeError("Invalid index: %r (must be IndexBuffer)" %
    523                     indices)
    525 # Process GLIR commands
--> 526 canvas.context.flush_commands()

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/context.py:172, in GLContext.flush_commands(self, event)
    170         fbo = 0
    171     self.shared.parser.parse([('CURRENT', 0, fbo)])
--> 172 self.glir.flush(self.shared.parser)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/glir.py:582, in GlirQueue.flush(self, parser)
    580 def flush(self, parser):
    581     """Flush all current commands to the GLIR interpreter."""
--> 582     self._shared.flush(parser)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/glir.py:504, in _GlirQueueShare.flush(self, parser)
    502     show = self._verbose if isinstance(self._verbose, str) else None
    503     self.show(show)
--> 504 parser.parse(self._filter(self.clear(), parser))

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/glir.py:822, in GlirParser.parse(self, commands)
    819     self._objects.pop(id_)
    821 for command in commands:
--> 822     self._parse(command)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/glir.py:778, in GlirParser._parse(self, command)
    776 if ob is None:
    777     if id_ not in self._invalid_objects:
--> 778         raise RuntimeError('Cannot %s object %i because it '
    779                            'does not exist' % (cmd, id_))
    780     return
    781 # Triage over command. Order of commands is set so most
    782 # common ones occur first.

RuntimeError: Cannot SIZE object 118 because it does not exist
WARNING: Error drawing visual <Volume at 0x7fca20d29f40>
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/app/backends/_qt.py:903, in CanvasBackendDesktop.paintGL(self)
    901 # (0, 0, self.width(), self.height()))
    902 self._vispy_canvas.set_current()
--> 903 self._vispy_canvas.events.draw(region=None)
    905 # Clear the alpha channel with QOpenGLWidget (Qt >= 5.4), otherwise the
    906 # window is translucent behind non-opaque objects.
    907 # Reference:  MRtrix3/mrtrix3#266
    908 if QT5_NEW_API or PYSIDE6_API or PYQT6_API:

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/util/event.py:453, in EventEmitter.__call__(self, *args, **kwargs)
    450 if self._emitting > 1:
    451     raise RuntimeError('EventEmitter loop detected!')
--> 453 self._invoke_callback(cb, event)
    454 if event.blocked:
    455     break

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/util/event.py:471, in EventEmitter._invoke_callback(self, cb, event)
    469     cb(event)
    470 except Exception:
--> 471     _handle_exception(self.ignore_callback_errors,
    472                       self.print_callback_errors,
    473                       self, cb_event=(cb, event))

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/util/event.py:469, in EventEmitter._invoke_callback(self, cb, event)
    467 def _invoke_callback(self, cb, event):
    468     try:
--> 469         cb(event)
    470     except Exception:
    471         _handle_exception(self.ignore_callback_errors,
    472                           self.print_callback_errors,
    473                           self, cb_event=(cb, event))

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/scene/canvas.py:218, in SceneCanvas.on_draw(self, event)
    215 # Now that a draw event is going to be handled, open up the
    216 # scheduling of further updates
    217 self._update_pending = False
--> 218 self._draw_scene()

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/scene/canvas.py:277, in SceneCanvas._draw_scene(self, bgcolor)
    275     bgcolor = self._bgcolor
    276 self.context.clear(color=bgcolor, depth=True)
--> 277 self.draw_visual(self.scene)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/scene/canvas.py:315, in SceneCanvas.draw_visual(self, visual, event)
    313         else:
    314             if hasattr(node, 'draw'):
--> 315                 node.draw()
    316                 prof.mark(str(node))
    317 else:

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/scene/visuals.py:103, in VisualNode.draw(self)
    101 if self.picking and not self.interactive:
    102     return
--> 103 self._visual_superclass.draw(self)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/visuals/visual.py:451, in Visual.draw(self)
    449 self._configure_gl_state()
    450 try:
--> 451     self._program.draw(self._vshare.draw_mode,
    452                        self._vshare.index_buffer)
    453 except Exception:
    454     logger.warning("Error drawing visual %r" % self)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/visuals/shaders/program.py:102, in ModularProgram.draw(self, *args, **kwargs)
    100 self.build_if_needed()
    101 self.update_variables()
--> 102 Program.draw(self, *args, **kwargs)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/program.py:526, in Program.draw(self, mode, indices, check_error)
    522     raise TypeError("Invalid index: %r (must be IndexBuffer)" %
    523                     indices)
    525 # Process GLIR commands
--> 526 canvas.context.flush_commands()

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/context.py:172, in GLContext.flush_commands(self, event)
    170         fbo = 0
    171     self.shared.parser.parse([('CURRENT', 0, fbo)])
--> 172 self.glir.flush(self.shared.parser)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/glir.py:582, in GlirQueue.flush(self, parser)
    580 def flush(self, parser):
    581     """Flush all current commands to the GLIR interpreter."""
--> 582     self._shared.flush(parser)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/glir.py:504, in _GlirQueueShare.flush(self, parser)
    502     show = self._verbose if isinstance(self._verbose, str) else None
    503     self.show(show)
--> 504 parser.parse(self._filter(self.clear(), parser))

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/glir.py:822, in GlirParser.parse(self, commands)
    819     self._objects.pop(id_)
    821 for command in commands:
--> 822     self._parse(command)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/glir.py:778, in GlirParser._parse(self, command)
    776 if ob is None:
    777     if id_ not in self._invalid_objects:
--> 778         raise RuntimeError('Cannot %s object %i because it '
    779                            'does not exist' % (cmd, id_))
    780     return
    781 # Triage over command. Order of commands is set so most
    782 # common ones occur first.

RuntimeError: Cannot SIZE object 118 because it does not exist
WARNING: Error drawing visual <Volume at 0x7fca20d29f40>
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/app/backends/_qt.py:903, in CanvasBackendDesktop.paintGL(self)
    901 # (0, 0, self.width(), self.height()))
    902 self._vispy_canvas.set_current()
--> 903 self._vispy_canvas.events.draw(region=None)
    905 # Clear the alpha channel with QOpenGLWidget (Qt >= 5.4), otherwise the
    906 # window is translucent behind non-opaque objects.
    907 # Reference:  MRtrix3/mrtrix3#266
    908 if QT5_NEW_API or PYSIDE6_API or PYQT6_API:

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/util/event.py:453, in EventEmitter.__call__(self, *args, **kwargs)
    450 if self._emitting > 1:
    451     raise RuntimeError('EventEmitter loop detected!')
--> 453 self._invoke_callback(cb, event)
    454 if event.blocked:
    455     break

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/util/event.py:471, in EventEmitter._invoke_callback(self, cb, event)
    469     cb(event)
    470 except Exception:
--> 471     _handle_exception(self.ignore_callback_errors,
    472                       self.print_callback_errors,
    473                       self, cb_event=(cb, event))

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/util/event.py:469, in EventEmitter._invoke_callback(self, cb, event)
    467 def _invoke_callback(self, cb, event):
    468     try:
--> 469         cb(event)
    470     except Exception:
    471         _handle_exception(self.ignore_callback_errors,
    472                           self.print_callback_errors,
    473                           self, cb_event=(cb, event))

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/scene/canvas.py:218, in SceneCanvas.on_draw(self, event)
    215 # Now that a draw event is going to be handled, open up the
    216 # scheduling of further updates
    217 self._update_pending = False
--> 218 self._draw_scene()

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/scene/canvas.py:277, in SceneCanvas._draw_scene(self, bgcolor)
    275     bgcolor = self._bgcolor
    276 self.context.clear(color=bgcolor, depth=True)
--> 277 self.draw_visual(self.scene)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/scene/canvas.py:315, in SceneCanvas.draw_visual(self, visual, event)
    313         else:
    314             if hasattr(node, 'draw'):
--> 315                 node.draw()
    316                 prof.mark(str(node))
    317 else:

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/scene/visuals.py:103, in VisualNode.draw(self)
    101 if self.picking and not self.interactive:
    102     return
--> 103 self._visual_superclass.draw(self)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/visuals/visual.py:451, in Visual.draw(self)
    449 self._configure_gl_state()
    450 try:
--> 451     self._program.draw(self._vshare.draw_mode,
    452                        self._vshare.index_buffer)
    453 except Exception:
    454     logger.warning("Error drawing visual %r" % self)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/visuals/shaders/program.py:102, in ModularProgram.draw(self, *args, **kwargs)
    100 self.build_if_needed()
    101 self.update_variables()
--> 102 Program.draw(self, *args, **kwargs)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/program.py:526, in Program.draw(self, mode, indices, check_error)
    522     raise TypeError("Invalid index: %r (must be IndexBuffer)" %
    523                     indices)
    525 # Process GLIR commands
--> 526 canvas.context.flush_commands()

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/context.py:172, in GLContext.flush_commands(self, event)
    170         fbo = 0
    171     self.shared.parser.parse([('CURRENT', 0, fbo)])
--> 172 self.glir.flush(self.shared.parser)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/glir.py:582, in GlirQueue.flush(self, parser)
    580 def flush(self, parser):
    581     """Flush all current commands to the GLIR interpreter."""
--> 582     self._shared.flush(parser)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/glir.py:504, in _GlirQueueShare.flush(self, parser)
    502     show = self._verbose if isinstance(self._verbose, str) else None
    503     self.show(show)
--> 504 parser.parse(self._filter(self.clear(), parser))

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/glir.py:822, in GlirParser.parse(self, commands)
    819     self._objects.pop(id_)
    821 for command in commands:
--> 822     self._parse(command)

File /opt/hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/vispy/gloo/glir.py:778, in GlirParser._parse(self, command)
    776 if ob is None:
    777     if id_ not in self._invalid_objects:
--> 778         raise RuntimeError('Cannot %s object %i because it '
    779                            'does not exist' % (cmd, id_))
    780     return
    781 # Triage over command. Order of commands is set so most
    782 # common ones occur first.

RuntimeError: Cannot SIZE object 118 because it does not exist