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',
)

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 0x7f8db42bb010>
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/site-packages/vispy/gloo/glir.py:794, in GlirParser._parse(self, command)
    792     ob.set_data(*args)
    793 elif cmd == 'SIZE':  # VertexBuffer, IndexBuffer,
--> 794     ob.set_size(*args)  # Texture[1D, 2D, 3D], RenderBuffer
    795 elif cmd == 'ATTACH':  # FrameBuffer, Program
    796     ob.attach(*args)

File /opt/hostedtoolcache/Python/3.10.12/x64/lib/python3.10/site-packages/vispy/gloo/glir.py:1631, in GlirTexture3D.set_size(self, shape, format, internalformat)
   1629 self.activate()
   1630 self._shape_formats = shape, format, internalformat
-> 1631 glTexImage3D(self._target, 0, internalformat, format,
   1632              gl.GL_BYTE, shape[:3])

File /opt/hostedtoolcache/Python/3.10.12/x64/lib/python3.10/site-packages/vispy/gloo/glir.py:1576, in glTexImage3D(target, level, internalformat, format, type, pixels)
   1574 def glTexImage3D(target, level, internalformat, format, type, pixels):
   1575     # Import from PyOpenGL
-> 1576     _gl = _check_pyopengl_3D()
   1577     border = 0
   1578     assert isinstance(pixels, (tuple, list))  # the only way we use this now

File /opt/hostedtoolcache/Python/3.10.12/x64/lib/python3.10/site-packages/vispy/gloo/glir.py:1568, in _check_pyopengl_3D()
   1566 USE_TEX_3D = True
   1567 try:
-> 1568     import OpenGL.GL as _gl
   1569 except ImportError:
   1570     raise ImportError('PyOpenGL is required for 3D texture support')

File /opt/hostedtoolcache/Python/3.10.12/x64/lib/python3.10/site-packages/OpenGL/GL/__init__.py:4
      2 # early import of our modules to prevent import loops...
      3 from OpenGL import error as _error
----> 4 from OpenGL.GL.VERSION.GL_1_1 import *
      5 from OpenGL.GL.pointers import *
      6 from OpenGL.GL.images import *

File /opt/hostedtoolcache/Python/3.10.12/x64/lib/python3.10/site-packages/OpenGL/GL/VERSION/GL_1_1.py:14
     12 import ctypes
     13 from OpenGL.raw.GL import _types, _glgets
---> 14 from OpenGL.raw.GL.VERSION.GL_1_1 import *
     15 from OpenGL.raw.GL.VERSION.GL_1_1 import _EXTENSION_NAME
     17 def glInitGl11VERSION():

File /opt/hostedtoolcache/Python/3.10.12/x64/lib/python3.10/site-packages/OpenGL/raw/GL/VERSION/GL_1_1.py:7
      5 # End users want this...
      6 from OpenGL.raw.GL._types import *
----> 7 from OpenGL.raw.GL import _errors
      8 from OpenGL.constant import Constant as _C
      9 # Spec mixes constants from 1.0 and 1.1

File /opt/hostedtoolcache/Python/3.10.12/x64/lib/python3.10/site-packages/OpenGL/raw/GL/_errors.py:4
      2 from OpenGL.error import _ErrorChecker
      3 if _ErrorChecker:
----> 4     _error_checker = _ErrorChecker( _p, _p.GL.glGetError )
      5 else:
      6     _error_checker = None

AttributeError: 'NoneType' object has no attribute 'glGetError'
WARNING: Error drawing visual <Volume at 0x7f8db42bb010>
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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 131 because it does not exist
WARNING: Error drawing visual <Volume at 0x7f8db42bb010>
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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 131 because it does not exist
WARNING: Error drawing visual <Volume at 0x7f8db42bb010>
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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 131 because it does not exist
WARNING: Error drawing visual <Volume at 0x7f8db42bb010>
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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 131 because it does not exist
WARNING: Error drawing visual <Volume at 0x7f8db42bb010>
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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 131 because it does not exist
WARNING: Error drawing visual <Volume at 0x7f8db42bb010>
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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.10.12/x64/lib/python3.10/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 131 because it does not exist