Is it possible to map an image texture onto a mesh (a sphere in my case) using pyqtgraph
?
I have looked at the examples, found the sphere mesh:
import pyqtgraph as pg
import pyqtgraph.opengl as gl
app = pg.mkQApp("GLMeshItem Example")
w = gl.GLViewWidget()
w.show()
w.setWindowTitle('pyqtgraph example: GLMeshItem')
w.setCameraPosition(distance=4)
import numpy as np
md = gl.MeshData.sphere(rows=32, cols=64)
colors = np.ones((md.faceCount(), 4), dtype=float)
colors[::2, 0] = 0
colors[:, 1] = np.linspace(0, 1, colors.shape[0])
md.setFaceColors(colors)
m3 = gl.GLMeshItem(meshdata=md, smooth=False)
w.addItem(m3)
if __name__ == '__main__':
pg.exec()
I would like to map a png/jpg image (or np.ndarray
of rgba colors) as a texture onto the sphere. An example would be an image of the world, to create a 3D globe.
I could only find how to set vertex colors, but making a sphere with 1000x2000 vertices is problematic. I have looked at shaders, but did not find a solution (only a limited set of pre-defined shaders).
Other libraries using opengl can do this, see for example https://github.com/saiduc/PyOpenGLobe
Thanks!
So in the end, the solution I found was to copy the implementation of GlImageItem and make the vertices for the sphere manually.
from OpenGL.GL import * # noqa
import numpy as np
import pyqtgraph as pg
import pyqtgraph.opengl as gl
from pyqtgraph.opengl.GLGraphicsItem import GLGraphicsItem
from scipy.ndimage import gaussian_filter
def to_xyz(phi, theta):
theta = np.pi - theta
r = 10.0
xpos = r * np.sin(theta) * np.cos(phi)
ypos = r * np.sin(theta) * np.sin(phi)
zpos = r * np.cos(theta)
return xpos, ypos, zpos
class GLTexturedSphereItem(GLGraphicsItem):
def __init__(self, data, smooth=False, glOptions="translucent", parentItem=None):
"""
============== =======================================================================================
**Arguments:**
data Volume data to be rendered. *Must* be 3D numpy array (x, y, RGBA) with dtype=ubyte.
(See functions.makeRGBA)
smooth (bool) If True, the volume slices are rendered with linear interpolation
============== =======================================================================================
"""
self.smooth = smooth
self._needUpdate = False
super().__init__(parentItem=parentItem)
self.setData(data)
self.setGLOptions(glOptions)
self.texture = None
def initializeGL(self):
if self.texture is not None:
return
glEnable(GL_TEXTURE_2D)
self.texture = glGenTextures(1)
def setData(self, data):
self.data = data
self._needUpdate = True
self.update()
def _updateTexture(self):
glBindTexture(GL_TEXTURE_2D, self.texture)
if self.smooth:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
else:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER)
# glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER)
shape = self.data.shape
## Test texture dimensions first
glTexImage2D(
GL_PROXY_TEXTURE_2D,
0,
GL_RGBA,
shape[0],
shape[1],
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
None,
)
if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) == 0:
raise Exception(
"OpenGL failed to create 2D texture (%dx%d); too large for this hardware."
% shape[:2]
)
data = np.ascontiguousarray(self.data.transpose((1, 0, 2)))
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RGBA,
shape[0],
shape[1],
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
data,
)
glDisable(GL_TEXTURE_2D)
def paint(self):
if self._needUpdate:
self._updateTexture()
self._needUpdate = False
glEnable(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, self.texture)
self.setupGLState()
glColor4f(1, 1, 1, 1)
theta = np.linspace(0, np.pi, 32, dtype="float32")
phi = np.linspace(0, 2 * np.pi, 64, dtype="float32")
t_n = theta / np.pi
p_n = phi / (2 * np.pi)
glBegin(GL_QUADS)
for j in range(len(theta) - 1):
for i in range(len(phi) - 1):
xyz_nw = to_xyz(phi[i], theta[j])
xyz_sw = to_xyz(phi[i], theta[j + 1])
xyz_se = to_xyz(phi[i + 1], theta[j + 1])
xyz_ne = to_xyz(phi[i + 1], theta[j])
glTexCoord2f(p_n[i], t_n[j])
glVertex3f(xyz_nw[0], xyz_nw[1], xyz_nw[2])
glTexCoord2f(p_n[i], t_n[j + 1])
glVertex3f(xyz_sw[0], xyz_sw[1], xyz_sw[2])
glTexCoord2f(p_n[i + 1], t_n[j + 1])
glVertex3f(xyz_se[0], xyz_se[1], xyz_se[2])
glTexCoord2f(p_n[i + 1], t_n[j])
glVertex3f(xyz_ne[0], xyz_ne[1], xyz_ne[2])
glEnd()
glDisable(GL_TEXTURE_2D)
app = pg.mkQApp("GLImageItem Example")
w = gl.GLViewWidget()
w.show()
w.setWindowTitle("pyqtgraph example: GLImageItem")
w.setCameraPosition(distance=200)
a = np.zeros((256, 256))
N = 100
x = (np.random.random(N) * 256).astype(int)
y = (np.random.random(N) * 256).astype(int)
a[y, x] = 10000
smooth = gaussian_filter(a, sigma=5, mode="wrap").reshape(256, 256, 1)
smooth = (smooth / smooth.max() * 255).astype(np.uint8)
smooth = np.broadcast_to(smooth, (256, 256, 4)).copy()
smooth[..., 3] = 255
smooth[..., 0] += 50
v1 = GLTexturedSphereItem(np.clip(smooth, 0, 255))
w.addItem(v1)
if __name__ == "__main__":
pg.exec()