I have a simple GUI onto which I am plotting images. I create Widgets
for text outputs and arrays plots, and I would like to add one for 3D visualtion using glumpy
, say to show this example from the glumpy documentation.
What I would like is an "extra" slot on which to have the glumpy output:
I saw for example in this GitHub thread that people were referring to PyQt5 and glumpy integration, but I only see snippets of code and noting that works as a standalone example.
Also, it seems glumpy is already using PyQt5 in the backend (here), but I don't understand this well enough to know if and how I can access it a posteriori?
This is my MWE:
from PyQt5 import QtGui, QtCore
import pyqtgraph as pg
import sys
width = 1000
height = 500
class layout():
def setup(self, window):
self.window = window
self.window.resize(width, height)
self.centralwidget = QtGui.QWidget(self.window)
self.horizontallayout = QtGui.QHBoxLayout(self.centralwidget)
self.window.setCentralWidget(self.centralwidget)
self.dialogue = QtGui.QTextEdit()
self.horizontallayout.addWidget(self.dialogue)
self.plot = pg.GraphicsLayoutWidget(self.window)
self.horizontallayout.addWidget(self.plot)
self.plot1 = self.plot.addPlot(colspan=1)
class Window(pg.Qt.QtGui.QMainWindow, layout):
def __init__(self, shot = None):
super(Window, self).__init__()
self.setup(self)
self.show()
if __name__ == '__main__':
app = pg.Qt.QtGui.QApplication([])
Window()
sys.exit(app.exec_())
You have to get the internal QGLWidget through the "_native_window" attribute. The following example is based on the official example geometry-surface.py .
import sys
from PyQt5 import QtGui, QtCore
import pyqtgraph as pg
import numpy as np
from glumpy import app as glumpy_app, gl, gloo, data, library
from glumpy.geometry import primitives
from glumpy.transforms import Trackball
width = 1000
height = 500
glumpy_app.use("qt5")
vertex = """
#include "misc/spatial-filters.frag"
uniform float height;
uniform sampler2D data;
uniform vec2 data_shape;
attribute vec3 position;
attribute vec2 texcoord;
varying vec3 v_position;
varying vec2 v_texcoord;
void main()
{
float z = height*Bicubic(data, data_shape, texcoord).r;
gl_Position = <transform>;
v_texcoord = texcoord;
v_position = vec3(position.xy, z);
}
"""
fragment = """
#include "misc/spatial-filters.frag"
uniform mat4 model;
uniform mat4 view;
uniform mat4 normal;
uniform sampler2D texture;
uniform float height;
uniform vec4 color;
uniform sampler2D data;
uniform vec2 data_shape;
uniform vec3 light_color[3];
uniform vec3 light_position[3];
varying vec3 v_position;
varying vec2 v_texcoord;
float lighting(vec3 v_normal, vec3 light_position)
{
// Calculate normal in world coordinates
vec3 n = normalize(normal * vec4(v_normal,1.0)).xyz;
// Calculate the location of this fragment (pixel) in world coordinates
vec3 position = vec3(view * model * vec4(v_position, 1));
// Calculate the vector from this pixels surface to the light source
vec3 surface_to_light = light_position - position;
// Calculate the cosine of the angle of incidence (brightness)
float brightness = dot(n, surface_to_light) /
(length(surface_to_light) * length(n));
brightness = max(min(brightness,1.0),0.0);
return brightness;
}
void main()
{
mat4 model = <transform.trackball_model>;
// Extract data value
float value = Bicubic(data, data_shape, v_texcoord).r;
// Compute surface normal using neighbour values
float hx0 = height*Bicubic(data, data_shape, v_texcoord+vec2(+1,0)/data_shape).r;
float hx1 = height*Bicubic(data, data_shape, v_texcoord+vec2(-1,0)/data_shape).r;
float hy0 = height*Bicubic(data, data_shape, v_texcoord+vec2(0,+1)/data_shape).r;
float hy1 = height*Bicubic(data, data_shape, v_texcoord+vec2(0,-1)/data_shape).r;
vec3 dx = vec3(2.0/data_shape.x,0.0,hx0-hx1);
vec3 dy = vec3(0.0,2.0/data_shape.y,hy0-hy1);
vec3 v_normal = normalize(cross(dx,dy));
// Map value to rgb color
float c = 0.6 + 0.4*texture2D(texture, v_texcoord).r;
vec4 l1 = vec4(light_color[0] * lighting(v_normal, light_position[0]), 1);
vec4 l2 = vec4(light_color[1] * lighting(v_normal, light_position[1]), 1);
vec4 l3 = vec4(light_color[2] * lighting(v_normal, light_position[2]), 1);
gl_FragColor = color * vec4(c,c,c,1) * (0.5 + 0.5*(l1+l2+l3));
} """
def func3(x, y):
return (1 - x / 2 + x ** 5 + y ** 3) * np.exp(-(x ** 2) - y ** 2)
class layout:
def setup(self, window):
self.window = window
self.window.resize(width, height)
self.dialogue = QtGui.QTextEdit()
self.plot = pg.GraphicsLayoutWidget(self.window)
self.plot1 = self.plot.addPlot(colspan=1)
self.glumpy_window = glumpy_app.Window(color=(1, 1, 1, 1))
self.glumpy_window._native_window
self.centralwidget = QtGui.QWidget(self.window)
self.horizontallayout = QtGui.QHBoxLayout(self.centralwidget)
self.window.setCentralWidget(self.centralwidget)
self.horizontallayout.addWidget(self.dialogue, stretch=1)
self.horizontallayout.addWidget(self.plot, stretch=1)
self.horizontallayout.addWidget(self.glumpy_window._native_window, stretch=1)
class Window(pg.Qt.QtGui.QMainWindow, layout):
def __init__(self, shot=None):
super(Window, self).__init__()
self.setup(self)
n = 64
self.surface = gloo.Program(vertex, fragment)
self.vertices, self.s_indices = primitives.plane(2.0, n=n)
self.surface.bind(self.vertices)
I = []
for i in range(n):
I.append(i)
for i in range(1, n):
I.append(n - 1 + i * n)
for i in range(n - 1):
I.append(n * n - 1 - i)
for i in range(n - 1):
I.append(n * (n - 1) - i * n)
self.b_indices = np.array(I, dtype=np.uint32).view(gloo.IndexBuffer)
x = np.linspace(-2.0, 2.0, 32).astype(np.float32)
y = np.linspace(-2.0, 2.0, 32).astype(np.float32)
X, Y = np.meshgrid(x, y)
Z = func3(X, Y)
self.surface["data"] = (Z - Z.min()) / (Z.max() - Z.min())
self.surface["data"].interpolation = gl.GL_NEAREST
self.surface["data_shape"] = Z.shape[1], Z.shape[0]
self.surface["u_kernel"] = data.get("spatial-filters.npy")
self.surface["u_kernel"].interpolation = gl.GL_LINEAR
self.surface["texture"] = data.checkerboard(32, 24)
self.transform = Trackball("vec4(position.xy, z, 1.0)")
self.surface["transform"] = self.transform
self.glumpy_window.attach(self.transform)
T = (Z - Z.min()) / (Z.max() - Z.min())
self.surface["height"] = 0.75
self.surface["light_position[0]"] = 3, 0, 0 + 5
self.surface["light_position[1]"] = 0, 3, 0 + 5
self.surface["light_position[2]"] = -3, -3, +5
self.surface["light_color[0]"] = 1, 0, 0
self.surface["light_color[1]"] = 0, 1, 0
self.surface["light_color[2]"] = 0, 0, 1
phi, theta = -45, 0
self.time = 0
self.glumpy_window.set_handler("on_init", self.on_init)
self.glumpy_window.set_handler("on_draw", self.on_draw)
def on_init(self):
gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
gl.glPolygonOffset(1, 1)
gl.glEnable(gl.GL_LINE_SMOOTH)
gl.glLineWidth(2.5)
def on_draw(self, dt):
self.time += dt
self.glumpy_window.clear()
self.surface["data"]
gl.glDisable(gl.GL_BLEND)
gl.glEnable(gl.GL_DEPTH_TEST)
gl.glEnable(gl.GL_POLYGON_OFFSET_FILL)
self.surface["color"] = 1, 1, 1, 1
self.surface.draw(gl.GL_TRIANGLES, self.s_indices)
gl.glDisable(gl.GL_POLYGON_OFFSET_FILL)
gl.glEnable(gl.GL_BLEND)
gl.glDepthMask(gl.GL_FALSE)
self.surface["color"] = 0, 0, 0, 1
self.surface.draw(gl.GL_LINE_LOOP, self.b_indices)
gl.glDepthMask(gl.GL_TRUE)
model = self.surface["transform"]["model"].reshape(4, 4)
view = self.surface["transform"]["view"].reshape(4, 4)
self.surface["view"] = view
self.surface["model"] = model
self.surface["normal"] = np.array(np.matrix(np.dot(view, model)).I.T)
self.surface["height"] = 0.75 * np.cos(self.time)
def showEvent(self, event):
super().showEvent(event)
self.glumpy_window.dispatch_event("on_resize", *self.glumpy_window.get_size())
def closeEvent(self, event):
super().closeEvent(event)
self.glumpy_window.close()
if __name__ == "__main__":
app = pg.Qt.QtGui.QApplication([])
w = Window()
w.show()
glumpy_app.run()