mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-09-06 16:40:56 +02:00
117 lines
4.4 KiB
Python
117 lines
4.4 KiB
Python
import numpy as np
|
|
import pyqtgraph as pg
|
|
import pytest
|
|
from qtpy.QtOpenGLWidgets import QOpenGLWidget
|
|
from qtpy.QtWidgets import QLabel, QVBoxLayout, QWidget
|
|
|
|
from bec_widgets.utils import gpu_acceleration
|
|
from bec_widgets.utils.gpu_acceleration import ENV_VAR, grab_widget, opengl_available
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _reset_opengl_probe(monkeypatch):
|
|
"""Keep the cached context probe and pyqtgraph's global config out of other tests."""
|
|
# hold on to the real cached function: monkeypatch may swap the module
|
|
# attribute for a stub, and it is only restored after this fixture resumes
|
|
probe = gpu_acceleration.opengl_info
|
|
probe.cache_clear()
|
|
monkeypatch.delenv(ENV_VAR, raising=False)
|
|
previous = pg.getConfigOption("useOpenGL")
|
|
yield
|
|
pg.setConfigOption("useOpenGL", previous)
|
|
probe.cache_clear()
|
|
|
|
|
|
def _fake_renderer(monkeypatch, renderer: str | None):
|
|
info = None if renderer is None else {"vendor": "v", "renderer": renderer, "version": "4.1"}
|
|
monkeypatch.setattr(gpu_acceleration, "opengl_info", lambda: info)
|
|
|
|
|
|
def test_opengl_available_requires_opt_in(monkeypatch):
|
|
_fake_renderer(monkeypatch, "NVIDIA GeForce RTX 3090")
|
|
assert opengl_available(requested=True) is True
|
|
assert opengl_available(requested=False) is False
|
|
|
|
|
|
def test_opengl_refused_without_context(monkeypatch):
|
|
_fake_renderer(monkeypatch, None)
|
|
assert opengl_available(requested=True) is False
|
|
|
|
|
|
@pytest.mark.parametrize("renderer", ["llvmpipe (LLVM 15.0.7, 256 bits)", "softpipe", "SWRast"])
|
|
def test_opengl_refused_on_software_renderer(monkeypatch, renderer):
|
|
"""A remote/X-forwarded session must stay on the raster viewport."""
|
|
_fake_renderer(monkeypatch, renderer)
|
|
assert opengl_available(requested=True) is False
|
|
|
|
|
|
def test_env_var_forces_opengl_on_software_renderer(monkeypatch):
|
|
_fake_renderer(monkeypatch, "llvmpipe (LLVM 15.0.7, 256 bits)")
|
|
monkeypatch.setenv(ENV_VAR, "1")
|
|
assert opengl_available(requested=True) is True
|
|
# forcing on also overrides a widget that did not ask for it
|
|
assert opengl_available(requested=False) is True
|
|
|
|
|
|
def test_env_var_disables_opengl(monkeypatch):
|
|
_fake_renderer(monkeypatch, "NVIDIA GeForce RTX 3090")
|
|
monkeypatch.setenv(ENV_VAR, "0")
|
|
assert opengl_available(requested=True) is False
|
|
|
|
|
|
def test_unrecognised_env_var_falls_back_to_auto(monkeypatch):
|
|
_fake_renderer(monkeypatch, "NVIDIA GeForce RTX 3090")
|
|
monkeypatch.setenv(ENV_VAR, "maybe")
|
|
assert opengl_available(requested=True) is True
|
|
|
|
|
|
def _non_background_fraction(pixmap) -> float:
|
|
"""Fraction of pixels differing from the most common colour."""
|
|
image = pixmap.toImage()
|
|
buffer = np.frombuffer(image.constBits(), dtype=np.uint8)
|
|
arr = buffer.reshape(image.height(), image.bytesPerLine() // 4, 4)
|
|
flat = arr[:, : image.width(), :3].reshape(-1, 3)
|
|
colours, counts = np.unique(flat, axis=0, return_counts=True)
|
|
return float(np.any(flat != colours[counts.argmax()], axis=1).mean())
|
|
|
|
|
|
def _plot_host(qtbot, use_opengl: bool):
|
|
pg.setConfigOption("useOpenGL", use_opengl)
|
|
host = QWidget()
|
|
layout = QVBoxLayout(host)
|
|
layout.addWidget(QLabel("scan 42"))
|
|
view = pg.GraphicsLayoutWidget()
|
|
layout.addWidget(view)
|
|
plot = view.addPlot()
|
|
x = np.arange(5_000, dtype=np.float64)
|
|
plot.addItem(pg.PlotDataItem(x, np.sin(x * 0.01), pen=pg.mkPen("r", width=2)))
|
|
plot.enableAutoRange(False)
|
|
plot.setXRange(0, 5_000)
|
|
plot.setYRange(-1.5, 1.5)
|
|
host.resize(640, 460)
|
|
qtbot.addWidget(host)
|
|
host.show()
|
|
qtbot.waitExposed(host)
|
|
return host, view
|
|
|
|
|
|
def test_grab_widget_matches_plain_grab_without_opengl(qtbot):
|
|
host, view = _plot_host(qtbot, use_opengl=False)
|
|
assert not isinstance(view.viewport(), QOpenGLWidget)
|
|
assert _non_background_fraction(grab_widget(host)) == pytest.approx(
|
|
_non_background_fraction(host.grab())
|
|
)
|
|
|
|
|
|
def test_grab_widget_recovers_plot_on_opengl_viewport(qtbot):
|
|
"""QWidget.grab() alone returns a blank plot area over an OpenGL viewport."""
|
|
host, view = _plot_host(qtbot, use_opengl=True)
|
|
if not isinstance(view.viewport(), QOpenGLWidget):
|
|
pytest.skip("no OpenGL viewport available in this environment")
|
|
|
|
plain = _non_background_fraction(host.grab())
|
|
composited = _non_background_fraction(grab_widget(host))
|
|
assert composited > plain
|
|
# the plot fills most of the host, so a correct capture is far from empty
|
|
assert composited > 0.05
|