napari: OpenGL errors when using multiscale image layers and shape layers together

πŸ› Bug

When adding a shape layer and subsequently a multi-scale image, I encounter vispy errors.

This happens to me with napari 0.4.12 and the current master. It does not happen with 0.4.11 when using the same vispy version (0.9.4) as in the napari versions where I observe the error.

To Reproduce

Steps to reproduce the behavior:

  1. Open an napari ipython terminal in 0.4.12 or newer
  2. Paste the following code snippet:
import numpy as np
viewer.add_shapes(data=[np.array([[1500., 4500.], [4500., 4500.], [4500., 1500.], [1500., 1500.]])], shape_type=['polygon'])
viewer.add_image([np.eye(1024), np.eye(512), np.eye(256)])

Observe this stack trace (note that the same error will be re-raised multiple times when zooming after the initial error message is dismissed). Note that I don’t actually observe any unusual visuals, both the shape and the image are rendered.

WARNING: Error drawing visual <Image at 0x7f76b177d5b0>
16:28:15 WARNING Error drawing visual <Image at 0x7f76b177d5b0>
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
~/miniconda3/envs/spacem_ht/lib/python3.8/site-packages/vispy/app/backends/_qt.py in paintGL(self=<vispy.app.backends._qt.CanvasBackendDesktop object>)
    895         # (0, 0, self.width(), self.height()))
    896         self._vispy_canvas.set_current()
--> 897         self._vispy_canvas.events.draw(region=None)
        self._vispy_canvas.events.draw = <vispy.util.event.EventEmitter object at 0x7f76e43f3d30>
        global region = undefined
    898 
    899         # Clear the alpha channel with QOpenGLWidget (Qt >= 5.4), otherwise the

~/miniconda3/envs/spacem_ht/lib/python3.8/site-packages/vispy/util/event.py in __call__(self=<vispy.util.event.EventEmitter object>, *args=(), **kwargs={'region': None})
    451                     raise RuntimeError('EventEmitter loop detected!')
    452 
--> 453                 self._invoke_callback(cb, event)
        self._invoke_callback = <bound method EventEmitter._invoke_callback of <vispy.util.event.EventEmitter object at 0x7f76e43f3d30>>
        cb = <bound method SceneCanvas.on_draw of <VispyCanvas (PyQt5) at 0x7f76f8021730>>
        event = <DrawEvent blocked=False handled=False native=None region=None source=None sources=[] type=draw>
    454                 if event.blocked:
    455                     break

~/miniconda3/envs/spacem_ht/lib/python3.8/site-packages/vispy/util/event.py in _invoke_callback(self=<vispy.util.event.EventEmitter object>, cb=<bound method SceneCanvas.on_draw of <VispyCanvas (PyQt5)>>, event=<DrawEvent blocked=False handled=False native=None region=None source=None sources=[] type=draw>)
    469             cb(event)
    470         except Exception:
--> 471             _handle_exception(self.ignore_callback_errors,
        global _handle_exception = <function _handle_exception at 0x7f770bbf5940>
        self.ignore_callback_errors = False
        self.print_callback_errors = 'reminders'
        self = <vispy.util.event.EventEmitter object at 0x7f76e43f3d30>
        global cb_event = undefined
        cb = <bound method SceneCanvas.on_draw of <VispyCanvas (PyQt5) at 0x7f76f8021730>>
        event = <DrawEvent blocked=False handled=False native=None region=None source=None sources=[] type=draw>
    472                               self.print_callback_errors,
    473                               self, cb_event=(cb, event))

~/miniconda3/envs/spacem_ht/lib/python3.8/site-packages/vispy/util/event.py in _invoke_callback(self=<vispy.util.event.EventEmitter object>, cb=<bound method SceneCanvas.on_draw of <VispyCanvas (PyQt5)>>, event=<DrawEvent blocked=False handled=False native=None region=None source=None sources=[] type=draw>)
    467     def _invoke_callback(self, cb, event):
    468         try:
--> 469             cb(event)
        cb = <bound method SceneCanvas.on_draw of <VispyCanvas (PyQt5) at 0x7f76f8021730>>
        event = <DrawEvent blocked=False handled=False native=None region=None source=None sources=[] type=draw>
    470         except Exception:
    471             _handle_exception(self.ignore_callback_errors,

~/miniconda3/envs/spacem_ht/lib/python3.8/site-packages/vispy/scene/canvas.py in on_draw(self=<VispyCanvas (PyQt5)>, event=<DrawEvent blocked=False handled=False native=None region=None source=None sources=[] type=draw>)
    216         # scheduling of further updates
    217         self._update_pending = False
--> 218         self._draw_scene()
        self._draw_scene = <bound method SceneCanvas._draw_scene of <VispyCanvas (PyQt5) at 0x7f76f8021730>>
    219 
    220     def render(self, region=None, size=None, bgcolor=None, crop=None, alpha=True):

~/miniconda3/envs/spacem_ht/lib/python3.8/site-packages/vispy/scene/canvas.py in _draw_scene(self=<VispyCanvas (PyQt5)>, bgcolor=<class 'numpy.ndarray'> (4,) float32)
    275             bgcolor = self._bgcolor
    276         self.context.clear(color=bgcolor, depth=True)
--> 277         self.draw_visual(self.scene)
        self.draw_visual = <bound method SceneCanvas.draw_visual of <VispyCanvas (PyQt5) at 0x7f76f8021730>>
        self.scene = <SubScene at 0x7f76e43f3a90>
    278 
    279     def draw_visual(self, visual, event=None):

~/miniconda3/envs/spacem_ht/lib/python3.8/site-packages/vispy/scene/canvas.py in draw_visual(self=<VispyCanvas (PyQt5)>, visual=<SubScene>, event=None)
    313                         else:
    314                             if hasattr(node, 'draw'):
--> 315                                 node.draw()
        node.draw = <bound method VisualNode.draw of <Image at 0x7f76b177d5b0>>
    316                                 prof.mark(str(node))
    317                 else:

~/miniconda3/envs/spacem_ht/lib/python3.8/site-packages/vispy/scene/visuals.py in draw(self=<Image>)
     97         if self.picking and not self.interactive:
     98             return
---> 99         self._visual_superclass.draw(self)
        self._visual_superclass.draw = <function Visual.draw at 0x7f77050853a0>
        self = <Image at 0x7f76b177d5b0>
    100 
    101 

~/miniconda3/envs/spacem_ht/lib/python3.8/site-packages/vispy/visuals/visual.py in draw(self=<Image>)
    449         self._configure_gl_state()
    450         try:
--> 451             self._program.draw(self._vshare.draw_mode,
        self._program.draw = <bound method ModularProgram.draw of <vispy.visuals.shaders.program.ModularProgram object at 0x7f76b16898e0>>
        self._vshare.draw_mode = 'triangles'
        self._vshare.index_buffer = None
    452                                self._vshare.index_buffer)
    453         except Exception:

~/miniconda3/envs/spacem_ht/lib/python3.8/site-packages/vispy/visuals/shaders/program.py in draw(self=<vispy.visuals.shaders.program.ModularProgram object>, *args=('triangles', None), **kwargs={})
    100         self.build_if_needed()
    101         self.update_variables()
--> 102         Program.draw(self, *args, **kwargs)
        global Program.draw = <function Program.draw at 0x7f773a1138b0>
        self = <vispy.visuals.shaders.program.ModularProgram object at 0x7f76b16898e0>
        args = ('triangles', None)
        kwargs = {}
    103 
    104     def build_if_needed(self):

~/miniconda3/envs/spacem_ht/lib/python3.8/site-packages/vispy/gloo/program.py in draw(self=<vispy.visuals.shaders.program.ModularProgram object>, mode='triangles', indices=None, check_error=True)
    524 
    525         # Process GLIR commands
--> 526         canvas.context.flush_commands()
        canvas.context.flush_commands = <bound method GLContext.flush_commands of <GLContext at 0x7f76e43f3b80>>

~/miniconda3/envs/spacem_ht/lib/python3.8/site-packages/vispy/gloo/context.py in flush_commands(self=<GLContext>, event=None)
    170                 fbo = 0
    171             self.shared.parser.parse([('CURRENT', 0, fbo)])
--> 172         self.glir.flush(self.shared.parser)
        self.glir.flush = <bound method GlirQueue.flush of <vispy.gloo.glir.GlirQueue object at 0x7f76e43f3ee0>>
        self.shared.parser = <vispy.gloo.glir.GlirParser object at 0x7f76e43f3e20>
    173 
    174     def set_viewport(self, *args):

~/miniconda3/envs/spacem_ht/lib/python3.8/site-packages/vispy/gloo/glir.py in flush(self=<vispy.gloo.glir.GlirQueue object>, parser=<vispy.gloo.glir.GlirParser object>)
    577     def flush(self, parser):
    578         """Flush all current commands to the GLIR interpreter."""
--> 579         self._shared.flush(parser)
        self._shared.flush = <bound method _GlirQueueShare.flush of <vispy.gloo.glir._GlirQueueShare object at 0x7f76e43f3f10>>
        parser = <vispy.gloo.glir.GlirParser object at 0x7f76e43f3e20>
    580 
    581 

~/miniconda3/envs/spacem_ht/lib/python3.8/site-packages/vispy/gloo/glir.py in flush(self=<vispy.gloo.glir._GlirQueueShare object>, parser=<vispy.gloo.glir.GlirParser object>)
    499             show = self._verbose if isinstance(self._verbose, str) else None
    500             self.show(show)
--> 501         parser.parse(self._filter(self.clear(), parser))
        parser.parse = <bound method GlirParser.parse of <vispy.gloo.glir.GlirParser object at 0x7f76e43f3e20>>
        self._filter = <bound method _GlirQueueShare._filter of <vispy.gloo.glir._GlirQueueShare object at 0x7f76e43f3f10>>
        self.clear = <bound method _GlirQueueShare.clear of <vispy.gloo.glir._GlirQueueShare object at 0x7f76e43f3f10>>
        parser = <vispy.gloo.glir.GlirParser object at 0x7f76e43f3e20>
    502 
    503     def _filter(self, commands, parser):

~/miniconda3/envs/spacem_ht/lib/python3.8/site-packages/vispy/gloo/glir.py in parse(self=<vispy.gloo.glir.GlirParser object>, commands=[('FUNC', 'glDisable', 'cull_face'), ('FUNC', 'glEnable', 'depth_test'), ('FUNC', 'glEnable', 'blend'), ('FUNC', 'glBlendFuncSeparate', 'src_alpha', 'one_minus_src_alpha', 'zero', 'one'), ('CREATE', 50, 'Program'), ('CREATE', 73, 'VertexShader'), ('DATA', 73, 0, 'uniform mat4 u_matrix;\n\nvec4 affine_transform_ma...sform_map_chain_10(vec4(a_position, 0., 1.));\n}\n\n'), ('ATTACH', 50, 73), ('CREATE', 74, 'FragmentShader'), ('DATA', 74, 0, 'uniform float u_alpha;\n\nvoid apply_alpha() {\n   ...ord));\n\n    apply_alpha();\npicking_filter();\n\n}\n\n'), ('ATTACH', 50, 74), ('LINK', 50), ('DELETE', 73), ('DELETE', 74), ('UNIFORM', 50, 'u_alpha', 'float', <class 'numpy.ndarray'> (1,) float32), ('UNIFORM', 50, 'u_clim', 'vec2', <class 'numpy.ndarray'> (2,) float32), ('UNIFORM', 50, 'u_enabled', 'int', <class 'numpy.ndarray'> (1,) int32), ('UNIFORM', 50, 'u_gamma', 'float', <class 'numpy.ndarray'> (1,) float32), ('UNIFORM', 50, 'u_id_color', 'vec4', <class 'numpy.ndarray'> (4,) float32), ('UNIFORM', 50, 'u_matrix', 'mat4', <class 'numpy.ndarray'> (16,) float32), ...])
    817 
    818         for command in commands:
--> 819             self._parse(command)
        self._parse = <bound method GlirParser._parse of <vispy.gloo.glir.GlirParser object at 0x7f76e43f3e20>>
        command = ('DRAW', 50, 'triangles', (0, 6))
    820 
    821     def get_object(self, id_):

~/miniconda3/envs/spacem_ht/lib/python3.8/site-packages/vispy/gloo/glir.py in _parse(self=<vispy.gloo.glir.GlirParser object>, command=('DRAW', 50, 'triangles', (0, 6)))
    779             # common ones occur first.
    780             if cmd == 'DRAW':  # Program
--> 781                 ob.draw(*args)
        ob.draw = <bound method GlirProgram.draw of <GlirProgram 50 at 0x7f76b16899d0>>
        args = ('triangles', (0, 6))
    782             elif cmd == 'TEXTURE':  # Program
    783                 ob.set_texture(*args)

~/miniconda3/envs/spacem_ht/lib/python3.8/site-packages/vispy/gloo/glir.py in draw(self=<GlirProgram 50>, mode='triangles', selection=(0, 6))
   1316             raise RuntimeError('Cannot draw program if code has not been set')
   1317         # Init
-> 1318         gl.check_error('Check before draw')
        global gl.check_error = <function check_error at 0x7f773a49fc10>
   1319         try:
   1320             mode = as_enum(mode)

~/miniconda3/envs/spacem_ht/lib/python3.8/site-packages/vispy/gloo/gl/__init__.py in check_error(when='Check before draw')
    202         err.errors = errors
    203         err.err = errors[-1]  # pyopengl compat
--> 204         raise err
        err = RuntimeError('OpenGL got errors (Check before draw): GL_INVALID_VALUE')
    205 
    206 

RuntimeError: OpenGL got errors (Check before draw): GL_INVALID_VALUE

Expected behavior

I would expect to be able to use both multi-scale images and shape layers without error.

Environment

napari: 0.4.13.dev150+g7e4da147
Platform: Linux-5.11.0-41-generic-x86_64-with-glibc2.17
System: Ubuntu 20.04.2 LTS
Python: 3.8.12 (default, Oct 12 2021, 13:49:34) [GCC 7.5.0]
Qt: 5.15.2
PyQt5: 5.15.6
NumPy: 1.21.5
SciPy: 1.7.3
Dask: 2021.06.2
VisPy: 0.9.4

OpenGL:
- GL version: 4.6.0 NVIDIA 460.91.03
- MAX_TEXTURE_SIZE: 32768

Screens:
- screen 1: resolution 1920x1200, scale 1.0
- screen 2: resolution 3840x2160, scale 1.0

Plugins:
- console: 0.0.4
- napari-ome-zarr: 0.2.1.dev6+gb626109
- scikit-image: 0.4.13.dev150+g7e4da147

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 1
  • Comments: 16 (16 by maintainers)

Most upvoted comments

To me, a 1024x1024 array should have corners[[0,0],[1023,1023]] if this is supposed to be [[y_topleft, x_topleft], [y_bottom_right, x_bottom_right]]. But maybe the representation is [[y_topleft, x_topleft], [height, width]] ?

extent.world was definitely changed to incorporate the full height, width. I need to check how extent.data is being used various places to see if changing that one was a mistake or if we just need to keep it as is, but update some other places like the corner calculation that assume the [0, 1023] convention.

Yeah, prior to 04d4898, I see corners=[[1023 805], [1023 1023]] which is also extent 0 on the first axis. This does not result in the OpenGL error being raised though. So maybe that error is not really due to the 0 extent itself, but is just related to the corner point getting set to something out of bounds.

I had come across this separate issue with OpenGL + multiscale previously: #3567

Thanks for reporting this @VolkerH, I can take a look. We definitely changed extent.world (to include the pixel element width) and also made it so that the Dims.range are now half-open. I don’t recall if we intentionally changed _extent_data, but can check. It sounds like there is something else going on, though.