Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
97fe213
silx.gui.plot.backends: Add pygfx/WGPU backend for 2D plotting
physwkim Jun 26, 2026
7912658
silx.gui.plot.test: Add tests for the pygfx 2D backend
physwkim Jun 26, 2026
13d98a7
examples: Add pygfx 2D backend examples
physwkim Jun 26, 2026
7267dcb
Apply ruff format
physwkim Jun 26, 2026
9ae9171
Allow pygfx backend under Wayland sessions via XWayland
physwkim Jun 29, 2026
4adfbf5
Plot: pygfx backend support native Wayland via bitmap present
physwkim Jun 29, 2026
8f3ffa2
silx.gui.plot: Drop "wgpu" backend alias, keep only "pygfx"
physwkim Jul 7, 2026
5c2bd00
silx.gui.plot.backends: Remove unused GPU compute code from pygfx bac…
physwkim Jul 7, 2026
576c26c
silx.gui.plot.backends: Remove unused _PygfxImageItem.updateData
physwkim Jul 7, 2026
91999a7
silx.gui.plot.backends: Use shared aspect-ratio helpers in pygfx backend
physwkim Jul 7, 2026
e501099
silx.gui.plot.backends: Clamp bogus screen DPI in pygfx backend
physwkim Jul 7, 2026
51acf7b
silx.gui.plot.backends: Save pygfx graph via shared saveImageToFile
physwkim Jul 7, 2026
1339b02
silx.gui.plot.backends: Colormap images via Colormap.applyToData
physwkim Jul 7, 2026
3fd670f
silx.gui.plot.backends: Add PAL copyright and reviewers to pygfx authors
physwkim Jul 7, 2026
d8ee520
silx.gui.plot.backends: Drop "~3x faster" claim from PRESENT_METHOD doc
physwkim Jul 7, 2026
add2224
silx.conftest: Ignore pygfx NumPy 2.5 shape-setting DeprecationWarning
physwkim Jul 7, 2026
e957e20
CI: Run pygfx tests on Linux via lavapipe software Vulkan
physwkim Jul 7, 2026
88bca12
CI: Add temporary diagnostics for pygfx/lavapipe crash
physwkim Jul 7, 2026
234c3a5
CI: Fix pygfx/lavapipe SIGABRT via 24-bit xvfb display
physwkim Jul 7, 2026
9759b39
Merge branch 'main' into pygfx-2d-backend-pr
physwkim Jul 7, 2026
0483a4f
CI: Apply 24-bit xvfb via PYTEST_ADDOPTS
physwkim Jul 7, 2026
5e63bb8
silx.gui.plot.backends: Restrict wgpu to Primary backends to fix gl->…
physwkim Jul 7, 2026
d326fdd
CI: Skip pygfx tests on Windows (no software wgpu adapter)
physwkim Jul 7, 2026
fbc55d6
silx.gui.plot: Drop DISPLAY/WAYLAND_DISPLAY guard from pygfx backend …
physwkim Jul 9, 2026
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
3 changes: 2 additions & 1 deletion .github/actions/setup-system/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ runs:
# libegl1-mesa: Required by Qt xcb platform plugin
# libgl1-mesa-glx: For OpenGL
# xserver-xorg-video-dummy: For OpenGL
# mesa-vulkan-drivers (lavapipe), libgl1-mesa-dri (llvmpipe): software rendering for pygfx/wgpu
# libxkbcommon-x11-0, ..: needed for Qt plugins
- name: Install system packages
if: runner.os == 'Linux'
shell: bash
run: |
sudo apt-get update
sudo apt-get install ocl-icd-opencl-dev xvfb libegl1 libglx-mesa0 xserver-xorg-video-dummy libxkbcommon-x11-0 libxkbcommon0 libxkbcommon-dev libxcb-icccm4 libxcb-image0 libxcb-shm0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-render0 libxcb-shape0 libxcb-sync1 libxcb-xfixes0 libxcb-xinerama0 libxcb-xkb1 libxcb-cursor0 libxcb1
sudo apt-get install ocl-icd-opencl-dev xvfb libegl1 libglx-mesa0 mesa-vulkan-drivers libgl1-mesa-dri xserver-xorg-video-dummy libxkbcommon-x11-0 libxkbcommon0 libxkbcommon-dev libxcb-icccm4 libxcb-image0 libxcb-shm0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-render0 libxcb-shape0 libxcb-sync1 libxcb-xfixes0 libxcb-xinerama0 libxcb-xkb1 libxcb-cursor0 libxcb1

- name: Setup Intel OpenCL ICD
if: runner.os == 'Linux'
Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,21 @@ jobs:
QT_API: PySide6
with_opencl: true
with_gl_test: true
with_pygfx_test: true

- os: macos-latest
python-version: "3.14"
QT_API: PyQt6
with_opencl: false
with_gl_test: false
with_pygfx_test: true

- os: windows-latest
python-version: "3.10"
QT_API: PyQt5
with_opencl: false
with_gl_test: true
with_pygfx_test: false # No Vulkan/DX12 software driver on Windows CI

steps:
- uses: actions/checkout@v5
Expand Down Expand Up @@ -87,5 +90,14 @@ jobs:
QT_API: ${{ matrix.QT_API }}
SILX_OPENCL: ${{ matrix.with_opencl && 'True' || 'False' }}
WITH_GL_TEST: ${{ matrix.with_gl_test && 'True' || 'False' }}
WITH_PYGFX_TEST: ${{ matrix.with_pygfx_test && 'True' || 'False' }}
# run_tests() runs pytest against the installed silx, so the source
# pyproject.toml is not the pytest configfile and its ini options are
# ignored. Pass the xvfb color depth as a pytest ini override here so
# it actually reaches the run: pytest-xvfb defaults to a 16-bit
# display, on which lavapipe advertises no Vulkan surface formats and
# the pygfx/wgpu swapchain aborts (wgpuSurfaceConfigure fatal panic).
# 24-bit (like real desktops / the macOS runner) exposes BGRA8.
PYTEST_ADDOPTS: "-o xvfb_colordepth=24"
run: |
python -c "import silx.test, sys; sys.exit(silx.test.run_tests(verbosity=3, args=['--qt-binding=${{ matrix.QT_API }}']))"
51 changes: 51 additions & 0 deletions examples/pygfx_backend/01_basic_curves.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""Basic curve plotting with pygfx backend.

Demonstrates: multiple curves, colors, line widths, symbols, fill, legend.
"""

import numpy
from silx.gui import qt
from silx.gui.plot import Plot1D


def main():
app = qt.QApplication([])

plot = Plot1D(backend="pygfx")
plot.setWindowTitle("pygfx - Basic Curves")
plot.setGraphTitle("Trigonometric Functions")
plot.setGraphXLabel("X")
plot.setGraphYLabel("Y")

x = numpy.linspace(0, 4 * numpy.pi, 500)

# Solid line
plot.addCurve(x, numpy.sin(x), legend="sin(x)", color="blue", linewidth=2)
# Dashed line with symbols
plot.addCurve(
x[::20],
numpy.cos(x[::20]),
legend="cos(x)",
color="red",
linewidth=1.5,
linestyle="--",
symbol="o",
)
# Filled curve
plot.addCurve(
x,
0.5 * numpy.sin(2 * x),
legend="0.5*sin(2x)",
color="green",
linewidth=1,
fill=True,
)

plot.setActiveCurveHandling(False)
plot.resetZoom()
plot.show()
app.exec()


if __name__ == "__main__":
main()
52 changes: 52 additions & 0 deletions examples/pygfx_backend/02_image_display.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Image display with pygfx backend.

Demonstrates: 2D image with colormap, RGBA image, origin/scale, colorbar.
"""

import numpy
from silx.gui import qt
from silx.gui.plot import Plot2D


def main():
app = qt.QApplication([])

# --- Plot2D with colormap image ---
plot = Plot2D(backend="pygfx")
plot.setWindowTitle("pygfx - Image Display")
plot.setGraphTitle("2D Gaussian + Noise")
plot.setGraphXLabel("X")
plot.setGraphYLabel("Y")

# Generate a 2D Gaussian
size = 256
x = numpy.linspace(-3, 3, size)
y = numpy.linspace(-3, 3, size)
xx, yy = numpy.meshgrid(x, y)
image = numpy.exp(-(xx**2 + yy**2)) + 0.1 * numpy.random.random((size, size))

plot.getDefaultColormap().setName("viridis")
plot.addImage(image, origin=(-3, -3), scale=(6 / size, 6 / size))
plot.setKeepDataAspectRatio(True)
plot.resetZoom()
plot.show()

# --- RGBA image window ---
plot2 = Plot2D(backend="pygfx")
plot2.setWindowTitle("pygfx - RGBA Image")
plot2.setGraphTitle("RGBA Gradient")

rgba = numpy.zeros((200, 300, 4), dtype=numpy.uint8)
rgba[:, :, 0] = numpy.linspace(0, 255, 300)[numpy.newaxis, :] # R gradient
rgba[:, :, 1] = numpy.linspace(0, 255, 200)[:, numpy.newaxis] # G gradient
rgba[:, :, 2] = 128
rgba[:, :, 3] = 255
plot2.addImage(rgba)
plot2.resetZoom()
plot2.show()

app.exec()


if __name__ == "__main__":
main()
35 changes: 35 additions & 0 deletions examples/pygfx_backend/03_scatter_plot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Scatter plot with pygfx backend.

Demonstrates: scatter points with colormap values, symbol sizes.
"""

import numpy
from silx.gui import qt
from silx.gui.plot import Plot1D


def main():
app = qt.QApplication([])

plot = Plot1D(backend="pygfx")
plot.setWindowTitle("pygfx - Scatter Plot")
plot.setGraphTitle("Random Scatter with Colormap")
plot.setGraphXLabel("X")
plot.setGraphYLabel("Y")
plot.getDefaultColormap().setName("plasma")

numpy.random.seed(42)
n = 200
x = numpy.random.randn(n)
y = numpy.random.randn(n)
value = numpy.sqrt(x**2 + y**2) # distance from origin

plot.addScatter(x, y, value, legend="distance", symbol="o")
plot.setKeepDataAspectRatio(True)
plot.resetZoom()
plot.show()
app.exec()


if __name__ == "__main__":
main()
67 changes: 67 additions & 0 deletions examples/pygfx_backend/04_line_styles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""Line styles and symbols with pygfx backend.

Demonstrates: all line styles (solid, dashed, dash-dot, dotted),
various symbols, line widths, gap colors.
"""

import numpy
from silx.gui import qt
from silx.gui.plot import PlotWidget


def main():
app = qt.QApplication([])

plot = PlotWidget(backend="pygfx")
plot.setWindowTitle("pygfx - Line Styles & Symbols")
plot.setGraphTitle("Line Styles and Symbols")
plot.setGraphXLabel("X")
plot.setGraphYLabel("Y")

x = numpy.linspace(0, 10, 100)

# Line styles
styles = [
("-", "solid"),
("--", "dashed"),
("-.", "dash-dot"),
(":", "dotted"),
]
for i, (style, name) in enumerate(styles):
y = numpy.sin(x) + i * 2.5
plot.addCurve(x, y, legend=name, linestyle=style, linewidth=2, symbol="")

# Gap color example
y = numpy.sin(x) + 10
plot.addCurve(
x,
y,
legend="dashed+gapcolor",
linestyle="--",
linewidth=2,
symbol="",
color="blue",
)

# Symbols (only those supported by silx SymbolMixIn)
symbols = ["o", ".", "+", "x", "d", "s", ",", "|", "_"]
x_sym = numpy.linspace(0, 10, 30)
for i, sym in enumerate(symbols):
y_sym = numpy.cos(x_sym) + 15 + i * 1.5
plot.addCurve(
x_sym,
y_sym,
legend=f"sym '{sym}'",
symbol=sym,
linestyle=" ",
color=f"C{i % 10}",
)

plot.setActiveCurveHandling(False)
plot.resetZoom()
plot.show()
app.exec()


if __name__ == "__main__":
main()
57 changes: 57 additions & 0 deletions examples/pygfx_backend/05_markers_shapes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""Markers and shapes with pygfx backend.

Demonstrates: point markers with text, x/y markers, shapes (rectangle, polyline).
"""

import numpy
from silx.gui import qt
from silx.gui.plot import PlotWidget
from silx.gui.plot import items


def main():
app = qt.QApplication([])

plot = PlotWidget(backend="pygfx")
plot.setWindowTitle("pygfx - Markers & Shapes")
plot.setGraphTitle("Markers, Text Labels and Shapes")

# Background image for visual reference
size = 100
xx, yy = numpy.meshgrid(numpy.linspace(0, 1, size), numpy.linspace(0, 1, size))
image = numpy.sin(10 * xx) * numpy.cos(10 * yy)
plot.addImage(image, origin=(0, 0), scale=(100 / size, 100 / size))
plot.getDefaultColormap().setName("gray")

# Point markers with text
plot.addMarker(20, 80, legend="marker1", text="Point A", color="red", symbol="o")
plot.addMarker(50, 60, legend="marker2", text="Point B", color="blue", symbol="d")
plot.addMarker(80, 80, legend="marker3", text="Point C", color="green", symbol="s")

# Horizontal and vertical markers
plot.addXMarker(30, legend="x_marker", text="X=30", color="yellow")
plot.addYMarker(40, legend="y_marker", text="Y=40", color="cyan")

# Rectangle shape
rect = items.Shape("rectangle")
rect.setPoints(numpy.array([(10, 10), (45, 45)]))
rect.setColor("red")
rect.setLineWidth(2)
plot.addItem(rect)

# Polyline shape
poly = items.Shape("polylines")
poly.setPoints(numpy.array([(55, 10), (70, 40), (85, 15), (95, 35)]))
poly.setColor("green")
poly.setLineWidth(2)
plot.addItem(poly)

plot.setGraphXLimits(-5, 105)
plot.setGraphYLimits(-5, 105)
plot.resetZoom()
plot.show()
app.exec()


if __name__ == "__main__":
main()
63 changes: 63 additions & 0 deletions examples/pygfx_backend/06_error_bars.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""Error bars with pygfx backend.

Demonstrates: curves with x and y error bars.
"""

import numpy
from silx.gui import qt
from silx.gui.plot import Plot1D


def main():
app = qt.QApplication([])

plot = Plot1D(backend="pygfx")
plot.setWindowTitle("pygfx - Error Bars")
plot.setGraphTitle("Curves with Error Bars")
plot.setGraphXLabel("X")
plot.setGraphYLabel("Y")

x = numpy.linspace(0, 10, 30)

# Symmetric Y errors
y1 = numpy.sin(x)
yerr1 = 0.1 + 0.1 * numpy.abs(numpy.sin(x))
plot.addCurve(
x,
y1,
legend="sym Y error",
color="blue",
symbol="o",
yerror=yerr1,
linewidth=1.5,
)

# Asymmetric Y errors
y2 = numpy.cos(x) + 3
yerr_low = 0.2 * numpy.ones_like(x)
yerr_high = 0.5 * numpy.abs(numpy.cos(x))
plot.addCurve(
x,
y2,
legend="asym Y error",
color="red",
symbol="s",
yerror=numpy.array([yerr_low, yerr_high]),
linewidth=1.5,
)

# X errors
y3 = 0.5 * x - 1.5
xerr = 0.3 * numpy.ones_like(x)
plot.addCurve(
x, y3, legend="X error", color="green", symbol="d", xerror=xerr, linewidth=1.5
)

plot.setActiveCurveHandling(False)
plot.resetZoom()
plot.show()
app.exec()


if __name__ == "__main__":
main()
Loading
Loading