Plot cube using pyqtgraph in python

无人久伴 提交于 2020-05-15 08:46:07

问题


I would like to plot moving cubes using pyqtgraph in python (update their positions based on given dataset). I have a sample code as follow, but I don't know how to plot cube instead of the sphere used in the code. Also, I'm very confused that why this code doesn't work for the second time running? I must close my spyder then open it again or restart kernel to run the code for the second time running. (win10, python3.7 spyder3.3.1)

from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl
import numpy as np

app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.opts['distance'] = 20
w.show()
w.setWindowTitle('pyqtgraph example: GLScatterPlotItem')

g = gl.GLGridItem()
w.addItem(g)


##
##  First example is a set of points with pxMode=False
##  These demonstrate the ability to have points with real size down to a very small scale 
## 
pos = np.empty((53, 3))
size = np.empty((53))
color = np.empty((53, 4))
pos[0] = (1,0,0); size[0] = 0.5;   color[0] = (1.0, 0.0, 0.0, 0.5)
pos[1] = (0,1,0); size[1] = 0.2;   color[1] = (0.0, 0.0, 1.0, 0.5)
pos[2] = (0,0,1); size[2] = 2./3.; color[2] = (0.0, 1.0, 0.0, 0.5)

z = 0.5
d = 6.0
for i in range(3,53):
    pos[i] = (0,0,z)
    size[i] = 2./d
    color[i] = (0.0, 1.0, 0.0, 0.5)
    z *= 0.5
    d *= 2.0

sp1 = gl.GLScatterPlotItem(pos=pos, size=size, color=color, pxMode=False)
sp1.translate(5,5,0)
w.addItem(sp1)


##
##  Second example shows a volume of points with rapidly updating color
##  and pxMode=True
##

pos = np.random.random(size=(100000,3))
pos *= [10,-10,10]
pos[0] = (0,0,0)
color = np.ones((pos.shape[0], 4))
d2 = (pos**2).sum(axis=1)**0.5
size = np.random.random(size=pos.shape[0])*10
sp2 = gl.GLScatterPlotItem(pos=pos, color=(1,1,1,1), size=size)
phase = 0.

w.addItem(sp2)


##
##  Third example shows a grid of points with rapidly updating position
##  and pxMode = False
##

pos3 = np.zeros((100,100,3))
pos3[:,:,:2] = np.mgrid[:100, :100].transpose(1,2,0) * [-0.1,0.1]
pos3 = pos3.reshape(10000,3)
d3 = (pos3**2).sum(axis=1)**0.5

sp3 = gl.GLScatterPlotItem(pos=pos3, color=(1,1,1,.3), size=0.1, pxMode=False)

w.addItem(sp3)


def update():
    ## update volume colors
    global phase, sp2, d2
    s = -np.cos(d2*2+phase)
    color = np.empty((len(d2),4), dtype=np.float32)
    color[:,3] = np.clip(s * 0.1, 0, 1)
    color[:,0] = np.clip(s * 3.0, 0, 1)
    color[:,1] = np.clip(s * 1.0, 0, 1)
    color[:,2] = np.clip(s ** 3, 0, 1)
    sp2.setData(color=color)
    phase -= 0.1

    ## update surface positions and colors
    global sp3, d3, pos3
    z = -np.cos(d3*2+phase)
    pos3[:,2] = z
    color = np.empty((len(d3),4), dtype=np.float32)
    color[:,3] = 0.3
    color[:,0] = np.clip(z * 3.0, 0, 1)
    color[:,1] = np.clip(z * 1.0, 0, 1)
    color[:,2] = np.clip(z ** 3, 0, 1)
    sp3.setData(pos=pos3, color=color)

t = QtCore.QTimer()
t.timeout.connect(update)
t.start(50)


## Start Qt event loop unless running in interactive mode.
if __name__ == '__main__':
    import sys
    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtGui.QApplication.instance().exec_()

回答1:


Imports:

from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl
import numpy as np

Okay first of all, seemingly you cannot run two QApplications at the same time. So instead of

app = QtGui.QApplication([])

write

app = QtGui.QApplication.instance()
if app is None:
    app = QtGui.QApplication([])

This will only create a new instance instead of a new Application in case one already exists.

Now to mesh drawing with pyqtgraph. You have to define the vertexes of your mesh. For a cube these are the eight corners:

vertexes = np.array([[1, 0, 0], #0
                     [0, 0, 0], #1
                     [0, 1, 0], #2
                     [0, 0, 1], #3
                     [1, 1, 0], #4
                     [1, 1, 1], #5
                     [0, 1, 1], #6
                     [1, 0, 1]])#7

These are just the positions of the corners in cartesian coordinates.

Now we have to define faces. These are triangles so we need 12 of those for a cube. We give the corners of the triangles in the array positions of the vertexes that shall span the triangle:

faces = np.array([[1,0,7], [1,3,7],
                  [1,2,4], [1,0,4],
                  [1,2,6], [1,3,6],
                  [0,4,5], [0,7,5],
                  [2,4,5], [2,6,5],
                  [3,6,5], [3,7,5]])

There may very well be an algorithm to it; for now I just wrote it by hand; maybe I will update later.

Now we have to define colors of the faces. We need RGBA values for every face; I just made it red:

colors = np.array([[1,0,0,1] for i in range(12)])

With this we can create a GLMeshItem. I draw the edges in black so you can see the triangles:

cube = gl.GLMeshItem(vertexes=vertexes, faces=faces, faceColors=colors,
                     drawEdges=True, edgeColor=(0, 0, 0, 1))

Now we just need to add the item and start the mainloop:

w.addItem(cube)

if __name__ == '__main__':
    import sys
    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtGui.QApplication.instance().exec_()

You can move the cube with:

cube.translate(x,y,z)

It will automatically update. And of course you can have several cube instances in a list.

EDIT I actually found an ugly algorithm. It uses the idea, that at least one coordinate must always be the same if the triangles are on the surface of the cube. New code is:

from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl
import numpy as np
import itertools

app = QtGui.QApplication.instance()
if app is None:
    app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.opts['distance'] = 20
w.show()
w.setWindowTitle('A cube')

vertexes = np.array(list(itertools.product(range(2),repeat=3)))

faces = []

for i in range(2):
    temp = np.where(vertexes==i)
    for j in range(3):
        temp2 = temp[0][np.where(temp[1]==j)]
        for k in range(2):
            faces.append([temp2[0],temp2[1+k],temp2[3]])

faces = np.array(faces)

colors = np.array([[1,0,0,1] for i in range(12)])


cube = gl.GLMeshItem(vertexes=vertexes, faces=faces, faceColors=colors,
                     drawEdges=True, edgeColor=(0, 0, 0, 1))

w.addItem(cube)

if __name__ == '__main__':
    import sys
    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtGui.QApplication.instance().exec_()

EDIT 2 The error in pyqtgraph is because of internal zero division. If you go to the file MeshData.py you will find a function vertexNormals. I changed the first if statement to:

if self._vertexNormals is None:
    faceNorms = self.faceNormals()
    vertFaces = self.vertexFaces()
    self._vertexNormals = np.empty(self._vertexes.shape, dtype=float)
    for vindex in xrange(self._vertexes.shape[0]):
        faces = vertFaces[vindex]
        if len(faces) == 0:
            self._vertexNormals[vindex] = (0,0,0)
            continue
        norms = faceNorms[faces]  ## get all face normals
        norm = norms.sum(axis=0)       ## sum normals
        if all(norm==0):
            self._vertexNormals[vindex] = norm
            continue
        #norm /= (norm**2).sum()**0.5  ## and re-normalize
        np.true_divide(norm, (norm**2).sum()**0.5, out=norm, casting='unsafe')
        self._vertexNormals[vindex] = norm

Now it doesn't throw any errors. This is because:

  • I don't use seemingly deprecated /=
  • I just continue if the vector is a zero vector


来源:https://stackoverflow.com/questions/53136301/plot-cube-using-pyqtgraph-in-python

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!