Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ wxpython = ["pyconify>=0.2.1", "wxpython >=4.2.2"]

# Supported Canavs backends
vispy = ["vispy>=0.14.3", "pyopengl >=3.1"]
pygfx = ["pygfx==0.9.0", "rendercanvas ==2.0.1", "wgpu==0.19.3"]
pygfx = ["pygfx~=0.16", "rendercanvas~=2.6", "wgpu~=0.31"]

# ready to go bundles with pygfx
qt = ["ndv[pygfx,pyqt]", "imageio[tifffile] >=2.20"]
Expand Down
1 change: 1 addition & 0 deletions src/ndv/views/_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ def process_events() -> None:

def run_app() -> None:
"""Start the active GUI application event loop."""
print("running")
ndv_app().run()


Expand Down
7 changes: 4 additions & 3 deletions src/ndv/views/_pygfx/_array_canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,6 @@ def __init__(self, viewer_model: ArrayViewerModel) -> None:
self._disconnect_mouse_events = filter_mouse_events(self._canvas, self)

self._renderer = pygfx.renderers.WgpuRenderer(self._canvas)
self._renderer.blend_mode = "additive"

self._scene = pygfx.Scene()
self._camera: pygfx.Camera | None = None
Expand Down Expand Up @@ -451,7 +450,7 @@ def add_image(self, data: np.ndarray | None = None) -> PyGFXImageHandle:
image = pygfx.Image(
pygfx.Geometry(grid=tex),
# depth_test=False for additive-like blending
pygfx.ImageBasicMaterial(depth_test=False),
pygfx.ImageBasicMaterial(depth_test=False, alpha_mode="add"),
)
self._scene.add(image)

Expand All @@ -476,7 +475,9 @@ def add_volume(self, data: np.ndarray | None = None) -> PyGFXImageHandle:
vol = pygfx.Volume(
pygfx.Geometry(grid=tex),
# depth_test=False for additive-like blending
pygfx.VolumeRayMaterial(interpolation="nearest", depth_test=False),
pygfx.VolumeRayMaterial(
interpolation="nearest", depth_test=False, alpha_mode="add"
),
)
self._scene.add(vol)

Expand Down
2 changes: 0 additions & 2 deletions src/ndv/views/_pygfx/_histogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,6 @@ def __init__(self, *, vertical: bool = False) -> None:

self._renderer = pygfx.renderers.WgpuRenderer(self._canvas)

self._renderer.blend_mode = "ordered1"

# Note that we split the view up into multiple scenes, each with their
# own camera and renderer.
#
Expand Down
18 changes: 13 additions & 5 deletions src/ndv/views/_pygfx/_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,19 @@ def sizeHint(self) -> QSize:

return rendercanvas.jupyter.JupyterRenderCanvas # type: ignore[no-any-return]
if frontend == GuiFrontend.WX:
# ...still not working
# import rendercanvas.wx
# return rendercanvas.wx.WxRenderWidget
from wgpu.gui.wx import WxWgpuCanvas
import rendercanvas.wx
import wx

return WxWgpuCanvas # type: ignore[no-any-return]
class WxRenderWidget(rendercanvas.wx.WxRenderWidget):
"""Ensure the widget always has a parent to avoid segfaults."""

def __init__(self, *args: object, **kwargs: object) -> None:
if "parent" not in kwargs and (not args or args[0] is None):
# wx.Window segfaults on Reparent if created without
# a parent, so use a temporary hidden frame.
kwargs["parent"] = wx.Frame(None)
super().__init__(*args, **kwargs)

return WxRenderWidget

raise ValueError(f"Unsupported frontend: {frontend}") # pragma: no cover
13 changes: 10 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,22 @@ def _catch_qt_leaks(request: FixtureRequest, qapp: QApplication) -> Iterator[Non
# if the test failed, don't worry about checking widgets
if request.session.testsfailed - failures_before:
return
allow: list[type] = []
try:
from vispy.app.backends._qt import CanvasBackendDesktop

allow: tuple[type, ...] = (CanvasBackendDesktop,)
allow.append(CanvasBackendDesktop)
except (ImportError, RuntimeError):
allow = ()
pass
try:
import rendercanvas.qt

allow.append(rendercanvas.qt.QRenderWidget)
except (ImportError, RuntimeError):
pass

# This is a known widget that is not cleaned up properly
remaining = [w for w in qapp.topLevelWidgets() if not isinstance(w, allow)]
remaining = [w for w in qapp.topLevelWidgets() if not isinstance(w, tuple(allow))]
if len(remaining) > nbefore:
test_node = request.node

Expand Down
7 changes: 7 additions & 0 deletions tests/views/_pygfx/test_histogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ def test_interaction() -> None:
# gamma=2,
)
histogram = PyGFXHistogramCanvas()
# Ensure the canvas has a real size (rendercanvas >=2.6 returns (1, 1)
# before the widget is shown). Qt's show() triggers a resize, but for
# other backends we force it via _size_info directly.
if hasattr(histogram._canvas, "show"):
histogram._canvas.show()
else:
histogram._canvas._size_info.set_physical_size(640, 480, 1.0)
histogram.set_range(x=(0, 10), y=(0, 1))
histogram.model = model
left, right = 0, 10
Expand Down
Loading