OpenGL scenegraph updates

- volumetric rendering
 - isosurfaces, mesh rendering
 - basic transformation and parent/child functionality
This commit is contained in:
Luke Campagnola 2012-03-09 12:38:15 -05:00
parent 269374ef84
commit 920fd9333e
15 changed files with 1150 additions and 135 deletions

67
examples/GLMeshItem.py Normal file
View File

@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
## This example uses the isosurface function to convert a scalar field
## (a hydrogen orbital) into a mesh for 3D display.
## Add path to library (just for examples; you do not need this)
import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg
import pyqtgraph.opengl as gl
app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.show()
g = gl.GLGridItem()
g.scale(2,2,1)
w.addItem(g)
import numpy as np
def psi(i, j, k, offset=(25, 25, 50)):
x = i-offset[0]
y = j-offset[1]
z = k-offset[2]
th = np.arctan2(z, (x**2+y**2)**0.5)
phi = np.arctan2(y, x)
r = (x**2 + y**2 + z **2)**0.5
a0 = 1
#ps = (1./81.) * (2./np.pi)**0.5 * (1./a0)**(3/2) * (6 - r/a0) * (r/a0) * np.exp(-r/(3*a0)) * np.cos(th)
ps = (1./81.) * 1./(6.*np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * np.exp(-r/(3*a0)) * (3 * np.cos(th)**2 - 1)
return ps
#return ((1./81.) * (1./np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * (r/a0) * np.exp(-r/(3*a0)) * np.sin(th) * np.cos(th) * np.exp(2 * 1j * phi))**2
print "Generating scalar field.."
data = np.abs(np.fromfunction(psi, (50,50,100)))
#data = np.fromfunction(lambda i,j,k: np.sin(0.2*((i-25)**2+(j-15)**2+k**2)**0.5), (50,50,50));
print "Generating isosurface.."
faces = pg.isosurface(data, data.max()/4.)
m = gl.GLMeshItem(faces)
w.addItem(m)
m.translate(-25, -25, -50)
#data = np.zeros((5,5,5))
#data[2,2,1:4] = 1
#data[2,1:4,2] = 1
#data[1:4,2,2] = 1
#tr.translate(-2.5, -2.5, 0)
#data = np.ones((2,2,2))
#data[0, 1, 0] = 0
#faces = pg.isosurface(data, 0.5)
#m = gl.GLMeshItem(faces)
#w.addItem(m)
#m.setTransform(tr)
## Start Qt event loop unless running in interactive mode.
if sys.flags.interactive != 1:
app.exec_()

View File

@ -8,12 +8,21 @@ import pyqtgraph.opengl as gl
app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.opts['distance'] = 20
w.show()
ax = gl.GLAxisItem()
ax.setSize(5,5,5)
w.addItem(ax)
b = gl.GLBoxItem()
w.addItem(b)
v = gl.GLVolumeItem()
w.addItem(v)
ax2 = gl.GLAxisItem()
ax2.setParentItem(b)
b.translate(1,1,1)
## Start Qt event loop unless running in interactive mode.
if sys.flags.interactive != 1:
app.exec_()

57
examples/GLVolumeItem.py Normal file
View File

@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
## Add path to library (just for examples; you do not need this)
import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl
app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.opts['distance'] = 200
w.show()
#b = gl.GLBoxItem()
#w.addItem(b)
g = gl.GLGridItem()
g.scale(10, 10, 1)
w.addItem(g)
import numpy as np
## Hydrogen electron probability density
def psi(i, j, k, offset=(50,50,100)):
x = i-offset[0]
y = j-offset[1]
z = k-offset[2]
th = np.arctan2(z, (x**2+y**2)**0.5)
phi = np.arctan2(y, x)
r = (x**2 + y**2 + z **2)**0.5
a0 = 2
#ps = (1./81.) * (2./np.pi)**0.5 * (1./a0)**(3/2) * (6 - r/a0) * (r/a0) * np.exp(-r/(3*a0)) * np.cos(th)
ps = (1./81.) * 1./(6.*np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * np.exp(-r/(3*a0)) * (3 * np.cos(th)**2 - 1)
return ps
#return ((1./81.) * (1./np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * (r/a0) * np.exp(-r/(3*a0)) * np.sin(th) * np.cos(th) * np.exp(2 * 1j * phi))**2
data = np.fromfunction(psi, (100,100,200))
positive = np.log(np.clip(data, 0, data.max())**2)
negative = np.log(np.clip(-data, 0, -data.min())**2)
d2 = np.empty(data.shape + (4,), dtype=np.ubyte)
d2[..., 0] = positive * (255./positive.max())
d2[..., 1] = negative * (255./negative.max())
d2[..., 2] = d2[...,1]
d2[..., 3] = d2[..., 0]*0.3 + d2[..., 1]*0.3
d2[..., 3] = (d2[..., 3].astype(float) / 255.) **2 * 255
v = gl.GLVolumeItem(d2)
v.translate(-50,-50,-100)
w.addItem(v)
## Start Qt event loop unless running in interactive mode.
if sys.flags.interactive != 1:
app.exec_()

View File

@ -67,6 +67,21 @@ y = np.sin(np.linspace(0, 10, 1000)) + np.random.normal(size=1000, scale=0.1)
p7.plot(y, fillLevel=-0.3, brush=(50,50,200,100))
x2 = np.linspace(-100, 100, 1000)
data2 = np.sin(x2) / x2
p8 = win.addPlot(title="Region Selection")
p8.plot(data2, pen=(255,255,255,200))
lr = pg.LinearRegionItem([400,700])
lr.setZValue(-10)
p8.addItem(lr)
p9 = win.addPlot(title="Zoom on selected region")
p9.plot(data2)
def update():
p9.setXRange(*lr.getRegion())
lr.sigRegionChanged.connect(update)
update()
## Start Qt event loop unless running in interactive mode.
if sys.flags.interactive != 1:
app.exec_()

View File

@ -120,6 +120,18 @@ def siEval(s):
return v * 1000**n
class Color(QtGui.QColor):
def __init__(self, *args):
QtGui.QColor.__init__(self, mkColor(*args))
def glColor(self):
"""Return (r,g,b,a) normalized for use in opengl"""
return (self.red()/255., self.green()/255., self.blue()/255., self.alpha()/255.)
def __getitem__(self, ind):
return (self.red, self.green, self.blue, self.alpha)[ind]()
def mkColor(*args):
"""
Convenience function for constructing QColor from a variety of argument types. Accepted arguments are:
@ -632,4 +644,526 @@ def rescaleData(data, scale, offset):
data = newData.reshape(data.shape)
return data
#def isosurface(data, level):
#"""
#Generate isosurface from volumetric data using marching tetrahedra algorithm.
#See Paul Bourke, "Polygonising a Scalar Field Using Tetrahedrons" (http://local.wasp.uwa.edu.au/~pbourke/geometry/polygonise/)
#*data* 3D numpy array of scalar values
#*level* The level at which to generate an isosurface
#"""
#facets = []
### mark everything below the isosurface level
#mask = data < level
#### make eight sub-fields
#fields = np.empty((2,2,2), dtype=object)
#slices = [slice(0,-1), slice(1,None)]
#for i in [0,1]:
#for j in [0,1]:
#for k in [0,1]:
#fields[i,j,k] = mask[slices[i], slices[j], slices[k]]
### split each cell into 6 tetrahedra
### these all have the same 'orienation'; points 1,2,3 circle
### clockwise around point 0
#tetrahedra = [
#[(0,1,0), (1,1,1), (0,1,1), (1,0,1)],
#[(0,1,0), (0,1,1), (0,0,1), (1,0,1)],
#[(0,1,0), (0,0,1), (0,0,0), (1,0,1)],
#[(0,1,0), (0,0,0), (1,0,0), (1,0,1)],
#[(0,1,0), (1,0,0), (1,1,0), (1,0,1)],
#[(0,1,0), (1,1,0), (1,1,1), (1,0,1)]
#]
### each tetrahedron will be assigned an index
### which determines how to generate its facets.
### this structure is:
### facets[index][facet1, facet2, ...]
### where each facet is triangular and its points are each
### interpolated between two points on the tetrahedron
### facet = [(p1a, p1b), (p2a, p2b), (p3a, p3b)]
### facet points always circle clockwise if you are looking
### at them from below the isosurface.
#indexFacets = [
#[], ## all above
#[[(0,1), (0,2), (0,3)]], # 0 below
#[[(1,0), (1,3), (1,2)]], # 1 below
#[[(0,2), (1,3), (1,2)], [(0,2), (0,3), (1,3)]], # 0,1 below
#[[(2,0), (2,1), (2,3)]], # 2 below
#[[(0,3), (1,2), (2,3)], [(0,3), (0,1), (1,2)]], # 0,2 below
#[[(1,0), (2,3), (2,0)], [(1,0), (1,3), (2,3)]], # 1,2 below
#[[(3,0), (3,1), (3,2)]], # 3 above
#[[(3,0), (3,2), (3,1)]], # 3 below
#[[(1,0), (2,0), (2,3)], [(1,0), (2,3), (1,3)]], # 0,3 below
#[[(0,3), (2,3), (1,2)], [(0,3), (1,2), (0,1)]], # 1,3 below
#[[(2,0), (2,3), (2,1)]], # 0,1,3 below
#[[(0,2), (1,2), (1,3)], [(0,2), (1,3), (0,3)]], # 2,3 below
#[[(1,0), (1,2), (1,3)]], # 0,2,3 below
#[[(0,1), (0,3), (0,2)]], # 1,2,3 below
#[] ## all below
#]
#for tet in tetrahedra:
### get the 4 fields for this tetrahedron
#tetFields = [fields[c] for c in tet]
### generate an index for each grid cell
#index = tetFields[0] + tetFields[1]*2 + tetFields[2]*4 + tetFields[3]*8
### add facets
#for i in xrange(index.shape[0]): # data x-axis
#for j in xrange(index.shape[1]): # data y-axis
#for k in xrange(index.shape[2]): # data z-axis
#for f in indexFacets[index[i,j,k]]: # faces to generate for this tet
#pts = []
#for l in [0,1,2]: # points in this face
#p1 = tet[f[l][0]] # tet corner 1
#p2 = tet[f[l][1]] # tet corner 2
#pts.append([(p1[x]+p2[x])*0.5+[i,j,k][x]+0.5 for x in [0,1,2]]) ## interpolate between tet corners
#facets.append(pts)
#return facets
def isosurface(data, level):
"""
Generate isosurface from volumetric data using marching tetrahedra algorithm.
See Paul Bourke, "Polygonising a Scalar Field"
(http://local.wasp.uwa.edu.au/~pbourke/geometry/polygonise/)
*data* 3D numpy array of scalar values
*level* The level at which to generate an isosurface
This function is SLOW; plenty of room for optimization here.
"""
## map from grid cell index to edge index.
## grid cell index tells us which corners are below the isosurface,
## edge index tells us which edges are cut by the isosurface.
## (Data stolen from Bourk; see above.)
edgeTable = [
0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c,
0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c,
0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,
0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c,
0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac,
0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,
0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c,
0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,
0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc,
0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c,
0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,
0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc ,
0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,
0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc,
0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c,
0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,
0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc,
0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,
0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c,
0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460,
0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac,
0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0,
0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c,
0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230,
0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c,
0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190,
0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c,
0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 ]
## Table of triangles to use for filling each grid cell.
## Each set of three integers tells us which three edges to
## draw a triangle between.
## (Data stolen from Bourk; see above.)
triTable = [
[],
[0, 8, 3],
[0, 1, 9],
[1, 8, 3, 9, 8, 1],
[1, 2, 10],
[0, 8, 3, 1, 2, 10],
[9, 2, 10, 0, 2, 9],
[2, 8, 3, 2, 10, 8, 10, 9, 8],
[3, 11, 2],
[0, 11, 2, 8, 11, 0],
[1, 9, 0, 2, 3, 11],
[1, 11, 2, 1, 9, 11, 9, 8, 11],
[3, 10, 1, 11, 10, 3],
[0, 10, 1, 0, 8, 10, 8, 11, 10],
[3, 9, 0, 3, 11, 9, 11, 10, 9],
[9, 8, 10, 10, 8, 11],
[4, 7, 8],
[4, 3, 0, 7, 3, 4],
[0, 1, 9, 8, 4, 7],
[4, 1, 9, 4, 7, 1, 7, 3, 1],
[1, 2, 10, 8, 4, 7],
[3, 4, 7, 3, 0, 4, 1, 2, 10],
[9, 2, 10, 9, 0, 2, 8, 4, 7],
[2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4],
[8, 4, 7, 3, 11, 2],
[11, 4, 7, 11, 2, 4, 2, 0, 4],
[9, 0, 1, 8, 4, 7, 2, 3, 11],
[4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1],
[3, 10, 1, 3, 11, 10, 7, 8, 4],
[1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4],
[4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3],
[4, 7, 11, 4, 11, 9, 9, 11, 10],
[9, 5, 4],
[9, 5, 4, 0, 8, 3],
[0, 5, 4, 1, 5, 0],
[8, 5, 4, 8, 3, 5, 3, 1, 5],
[1, 2, 10, 9, 5, 4],
[3, 0, 8, 1, 2, 10, 4, 9, 5],
[5, 2, 10, 5, 4, 2, 4, 0, 2],
[2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8],
[9, 5, 4, 2, 3, 11],
[0, 11, 2, 0, 8, 11, 4, 9, 5],
[0, 5, 4, 0, 1, 5, 2, 3, 11],
[2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5],
[10, 3, 11, 10, 1, 3, 9, 5, 4],
[4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10],
[5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3],
[5, 4, 8, 5, 8, 10, 10, 8, 11],
[9, 7, 8, 5, 7, 9],
[9, 3, 0, 9, 5, 3, 5, 7, 3],
[0, 7, 8, 0, 1, 7, 1, 5, 7],
[1, 5, 3, 3, 5, 7],
[9, 7, 8, 9, 5, 7, 10, 1, 2],
[10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3],
[8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2],
[2, 10, 5, 2, 5, 3, 3, 5, 7],
[7, 9, 5, 7, 8, 9, 3, 11, 2],
[9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11],
[2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7],
[11, 2, 1, 11, 1, 7, 7, 1, 5],
[9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11],
[5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0],
[11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0],
[11, 10, 5, 7, 11, 5],
[10, 6, 5],
[0, 8, 3, 5, 10, 6],
[9, 0, 1, 5, 10, 6],
[1, 8, 3, 1, 9, 8, 5, 10, 6],
[1, 6, 5, 2, 6, 1],
[1, 6, 5, 1, 2, 6, 3, 0, 8],
[9, 6, 5, 9, 0, 6, 0, 2, 6],
[5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8],
[2, 3, 11, 10, 6, 5],
[11, 0, 8, 11, 2, 0, 10, 6, 5],
[0, 1, 9, 2, 3, 11, 5, 10, 6],
[5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11],
[6, 3, 11, 6, 5, 3, 5, 1, 3],
[0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6],
[3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9],
[6, 5, 9, 6, 9, 11, 11, 9, 8],
[5, 10, 6, 4, 7, 8],
[4, 3, 0, 4, 7, 3, 6, 5, 10],
[1, 9, 0, 5, 10, 6, 8, 4, 7],
[10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4],
[6, 1, 2, 6, 5, 1, 4, 7, 8],
[1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7],
[8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6],
[7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9],
[3, 11, 2, 7, 8, 4, 10, 6, 5],
[5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11],
[0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6],
[9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6],
[8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6],
[5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11],
[0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7],
[6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9],
[10, 4, 9, 6, 4, 10],
[4, 10, 6, 4, 9, 10, 0, 8, 3],
[10, 0, 1, 10, 6, 0, 6, 4, 0],
[8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10],
[1, 4, 9, 1, 2, 4, 2, 6, 4],
[3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4],
[0, 2, 4, 4, 2, 6],
[8, 3, 2, 8, 2, 4, 4, 2, 6],
[10, 4, 9, 10, 6, 4, 11, 2, 3],
[0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6],
[3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10],
[6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1],
[9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3],
[8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1],
[3, 11, 6, 3, 6, 0, 0, 6, 4],
[6, 4, 8, 11, 6, 8],
[7, 10, 6, 7, 8, 10, 8, 9, 10],
[0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10],
[10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0],
[10, 6, 7, 10, 7, 1, 1, 7, 3],
[1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7],
[2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9],
[7, 8, 0, 7, 0, 6, 6, 0, 2],
[7, 3, 2, 6, 7, 2],
[2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7],
[2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7],
[1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11],
[11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1],
[8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6],
[0, 9, 1, 11, 6, 7],
[7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0],
[7, 11, 6],
[7, 6, 11],
[3, 0, 8, 11, 7, 6],
[0, 1, 9, 11, 7, 6],
[8, 1, 9, 8, 3, 1, 11, 7, 6],
[10, 1, 2, 6, 11, 7],
[1, 2, 10, 3, 0, 8, 6, 11, 7],
[2, 9, 0, 2, 10, 9, 6, 11, 7],
[6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8],
[7, 2, 3, 6, 2, 7],
[7, 0, 8, 7, 6, 0, 6, 2, 0],
[2, 7, 6, 2, 3, 7, 0, 1, 9],
[1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6],
[10, 7, 6, 10, 1, 7, 1, 3, 7],
[10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8],
[0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7],
[7, 6, 10, 7, 10, 8, 8, 10, 9],
[6, 8, 4, 11, 8, 6],
[3, 6, 11, 3, 0, 6, 0, 4, 6],
[8, 6, 11, 8, 4, 6, 9, 0, 1],
[9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6],
[6, 8, 4, 6, 11, 8, 2, 10, 1],
[1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6],
[4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9],
[10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3],
[8, 2, 3, 8, 4, 2, 4, 6, 2],
[0, 4, 2, 4, 6, 2],
[1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8],
[1, 9, 4, 1, 4, 2, 2, 4, 6],
[8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1],
[10, 1, 0, 10, 0, 6, 6, 0, 4],
[4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3],
[10, 9, 4, 6, 10, 4],
[4, 9, 5, 7, 6, 11],
[0, 8, 3, 4, 9, 5, 11, 7, 6],
[5, 0, 1, 5, 4, 0, 7, 6, 11],
[11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5],
[9, 5, 4, 10, 1, 2, 7, 6, 11],
[6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5],
[7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2],
[3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6],
[7, 2, 3, 7, 6, 2, 5, 4, 9],
[9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7],
[3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0],
[6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8],
[9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7],
[1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4],
[4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10],
[7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10],
[6, 9, 5, 6, 11, 9, 11, 8, 9],
[3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5],
[0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11],
[6, 11, 3, 6, 3, 5, 5, 3, 1],
[1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6],
[0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10],
[11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5],
[6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3],
[5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2],
[9, 5, 6, 9, 6, 0, 0, 6, 2],
[1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8],
[1, 5, 6, 2, 1, 6],
[1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6],
[10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0],
[0, 3, 8, 5, 6, 10],
[10, 5, 6],
[11, 5, 10, 7, 5, 11],
[11, 5, 10, 11, 7, 5, 8, 3, 0],
[5, 11, 7, 5, 10, 11, 1, 9, 0],
[10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1],
[11, 1, 2, 11, 7, 1, 7, 5, 1],
[0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11],
[9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7],
[7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2],
[2, 5, 10, 2, 3, 5, 3, 7, 5],
[8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5],
[9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2],
[9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2],
[1, 3, 5, 3, 7, 5],
[0, 8, 7, 0, 7, 1, 1, 7, 5],
[9, 0, 3, 9, 3, 5, 5, 3, 7],
[9, 8, 7, 5, 9, 7],
[5, 8, 4, 5, 10, 8, 10, 11, 8],
[5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0],
[0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5],
[10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4],
[2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8],
[0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11],
[0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5],
[9, 4, 5, 2, 11, 3],
[2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4],
[5, 10, 2, 5, 2, 4, 4, 2, 0],
[3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9],
[5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2],
[8, 4, 5, 8, 5, 3, 3, 5, 1],
[0, 4, 5, 1, 0, 5],
[8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5],
[9, 4, 5],
[4, 11, 7, 4, 9, 11, 9, 10, 11],
[0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11],
[1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11],
[3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4],
[4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2],
[9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3],
[11, 7, 4, 11, 4, 2, 2, 4, 0],
[11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4],
[2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9],
[9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7],
[3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10],
[1, 10, 2, 8, 7, 4],
[4, 9, 1, 4, 1, 7, 7, 1, 3],
[4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1],
[4, 0, 3, 7, 4, 3],
[4, 8, 7],
[9, 10, 8, 10, 11, 8],
[3, 0, 9, 3, 9, 11, 11, 9, 10],
[0, 1, 10, 0, 10, 8, 8, 10, 11],
[3, 1, 10, 11, 3, 10],
[1, 2, 11, 1, 11, 9, 9, 11, 8],
[3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9],
[0, 2, 11, 8, 0, 11],
[3, 2, 11],
[2, 3, 8, 2, 8, 10, 10, 8, 9],
[9, 10, 2, 0, 9, 2],
[2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8],
[1, 10, 2],
[1, 3, 8, 9, 1, 8],
[0, 9, 1],
[0, 3, 8],
[]
]
## translation between edge index and
## the vertex indexes that bound the edge
edgeKey = [
[(0,0,0), (1,0,0)],
[(1,0,0), (1,1,0)],
[(1,1,0), (0,1,0)],
[(0,1,0), (0,0,0)],
[(0,0,1), (1,0,1)],
[(1,0,1), (1,1,1)],
[(1,1,1), (0,1,1)],
[(0,1,1), (0,0,1)],
[(0,0,0), (0,0,1)],
[(1,0,0), (1,0,1)],
[(1,1,0), (1,1,1)],
[(0,1,0), (0,1,1)],
]
facets = []
## mark everything below the isosurface level
mask = data < level
### make eight sub-fields and compute indexes for grid cells
index = np.zeros([x-1 for x in data.shape], dtype=np.ubyte)
fields = np.empty((2,2,2), dtype=object)
slices = [slice(0,-1), slice(1,None)]
for i in [0,1]:
for j in [0,1]:
for k in [0,1]:
fields[i,j,k] = mask[slices[i], slices[j], slices[k]]
vertIndex = i - 2*j*i + 3*j + 4*k ## this is just to match Bourk's vertex numbering scheme
#print i,j,k," : ", fields[i,j,k], 2**vertIndex
index += fields[i,j,k] * 2**vertIndex
#print index
#print index
## add facets
for i in xrange(index.shape[0]): # data x-axis
for j in xrange(index.shape[1]): # data y-axis
for k in xrange(index.shape[2]): # data z-axis
tris = triTable[index[i,j,k]]
for l in range(0, len(tris), 3): ## faces for this grid cell
edges = tris[l:l+3]
pts = []
for m in [0,1,2]: # points in this face
p1 = edgeKey[edges[m]][0]
p2 = edgeKey[edges[m]][1]
v1 = data[i+p1[0], j+p1[1], k+p1[2]]
v2 = data[i+p2[0], j+p2[1], k+p2[2]]
f = (level-v1) / (v2-v1)
fi = 1.0 - f
p = ( ## interpolate between corners
p1[0]*fi + p2[0]*f + i + 0.5,
p1[1]*fi + p2[1]*f + j + 0.5,
p1[2]*fi + p2[2]*f + k + 0.5
)
pts.append(p)
facets.append(pts)
return facets
def meshNormals(data):
"""
Return list of normal vectors and list of faces which reference the normals
data must be list of triangles; each triangle is a list of three points
[ [(x,y,z), (x,y,z), (x,y,z)], ...]
Return values are
normals: [(x,y,z), ...]
faces: [(n1, n2, n3), ...]
"""
normals = []
points = {}
for i, face in enumerate(data):
## compute face normal
pts = [QtGui.QVector3D(*x) for x in face]
norm = QtGui.QVector3D.crossProduct(pts[1]-pts[0], pts[2]-pts[0])
normals.append(norm)
## remember each point was associated with this normal
for p in face:
p = tuple(map(lambda x: np.round(x, 8), p))
if p not in points:
points[p] = []
points[p].append(i)
## compute averages
avgLookup = {}
avgNorms = []
for k,v in points.iteritems():
norms = [normals[i] for i in v]
a = norms[0]
if len(v) > 1:
for n in norms[1:]:
a = a + n
a = a / len(v)
avgLookup[k] = len(avgNorms)
avgNorms.append(a)
## generate return array
faces = []
for i, face in enumerate(data):
f = []
for p in face:
p = tuple(map(lambda x: np.round(x, 8), p))
f.append(avgLookup[p])
faces.append(tuple(f))
return avgNorms, faces

View File

@ -297,7 +297,18 @@ class AxisItem(GraphicsWidget):
pw = 10 ** (np.floor(np.log10(dif))-1)
scaledIntervals = intervals * pw
scaledTickCounts = dif / scaledIntervals
i1 = np.argwhere(scaledTickCounts < optimalTickCount)[0,0]
try:
i1 = np.argwhere(scaledTickCounts < optimalTickCount)[0,0]
except:
print "AxisItem can't determine tick spacing:"
print "scaledTickCounts", scaledTickCounts
print "optimalTickCount", optimalTickCount
print "dif", dif
print "scaledIntervals", scaledIntervals
print "intervals", intervals
print "pw", pw
print "pixelSpacing", pixelSpacing
i1 = 1
distBetweenIntervals = (optimalTickCount-scaledTickCounts[i1]) / (scaledTickCounts[i1-1]-scaledTickCounts[i1])

View File

@ -6,6 +6,8 @@ class GLGraphicsItem(QtCore.QObject):
self.__parent = None
self.__view = None
self.__children = set()
self.__transform = QtGui.QMatrix4x4()
self.__visible = True
self.setParentItem(parentItem)
self.setDepthValue(0)
@ -16,6 +18,11 @@ class GLGraphicsItem(QtCore.QObject):
item.__children.add(self)
self.__parent = item
if self.__parent is not None and self.view() is not self.__parent.view():
if self.view() is not None:
self.view().removeItem(self)
self.__parent.view().addItem(self)
def parentItem(self):
return self.__parent
@ -42,6 +49,69 @@ class GLGraphicsItem(QtCore.QObject):
"""Return the depth value of this item. See setDepthValue for mode information."""
return self.__depthValue
def setTransform(self, tr):
self.__transform = tr
self.update()
def applyTransform(self, tr, local):
"""
Multiply this object's transform by *tr*.
If local is True, then *tr* is multiplied on the right of the current transform:
newTransform = transform * tr
If local is False, then *tr* is instead multiplied on the left:
newTransform = tr * transform
"""
if local:
self.setTransform(self.transform() * tr)
else:
self.setTransform(tr * self.transform())
def transform(self):
return self.__transform
def translate(self, dx, dy, dz, local=False):
"""
Translate the object by (*dx*, *dy*, *dz*) in its parent's coordinate system.
If *local* is True, then translation takes place in local coordinates.
"""
tr = QtGui.QMatrix4x4()
tr.translate(dx, dy, dz)
self.applyTransform(tr, local=local)
def rotate(self, angle, x, y, z, local=False):
"""
Rotate the object around the axis specified by (x,y,z).
*angle* is in degrees.
"""
tr = QtGui.QMatrix4x4()
tr.rotate(angle, x, y, z)
self.applyTransform(tr, local=local)
def scale(self, x, y, z, local=True):
"""
Scale the object by (*dx*, *dy*, *dz*) in its local coordinate system.
If *local* is False, then scale takes place in the parent's coordinates.
"""
tr = QtGui.QMatrix4x4()
tr.scale(x, y, z)
self.applyTransform(tr, local=local)
def hide(self):
self.setVisible(False)
def show(self):
self.setVisible(True)
def setVisible(self, vis):
self.__visible = vis
self.update()
def visible(self):
return self.__visible
def initializeGL(self):
"""
Called after an item is added to a GLViewWidget.
@ -57,4 +127,15 @@ class GLGraphicsItem(QtCore.QObject):
but the caller will take care of pushing/popping.
"""
pass
def update(self):
v = self.view()
if v is None:
return
v.updateGL()
def mapFromParent(self, point):
tr = self.transform()
if tr is None:
return point
return tr.inverted()[0].map(point)

View File

@ -33,14 +33,15 @@ class GLViewWidget(QtOpenGL.QGLWidget):
#print "set view", item, self, item.view()
self.updateGL()
def removeItem(self, item):
self.items.remove(item)
item._setView(None)
self.updateGL()
def initializeGL(self):
glClearColor(0.0, 0.0, 0.0, 0.0)
glEnable(GL_DEPTH_TEST)
glEnable( GL_ALPHA_TEST )
self.resizeGL(self.width(), self.height())
self.generateAxes()
#self.generatePoints()
def resizeGL(self, w, h):
glViewport(0, 0, w, h)
@ -75,15 +76,7 @@ class GLViewWidget(QtOpenGL.QGLWidget):
def paintGL(self):
self.setProjection()
self.setModelview()
glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT )
glDisable( GL_DEPTH_TEST )
#print "draw list:", self.axisList
glCallList(self.axisList) ## draw axes
#glCallList(self.pointList)
#self.drawPoints()
#self.drawAxes()
self.drawItemTree()
def drawItemTree(self, item=None):
@ -94,14 +87,19 @@ class GLViewWidget(QtOpenGL.QGLWidget):
items.append(item)
items.sort(lambda a,b: cmp(a.depthValue(), b.depthValue()))
for i in items:
if not i.visible():
continue
if i is item:
i.paint()
else:
glMatrixMode(GL_MODELVIEW)
glPushMatrix()
i.paint()
tr = i.transform()
a = np.array(tr.copyDataTo()).reshape((4,4))
glMultMatrixf(a.transpose())
self.drawItemTree(i)
glMatrixMode(GL_MODELVIEW)
glPopMatrix()
else:
self.drawItemTree(i)
def cameraPosition(self):
@ -118,65 +116,7 @@ class GLViewWidget(QtOpenGL.QGLWidget):
)
return pos
def generateAxes(self):
self.axisList = glGenLists(1)
glNewList(self.axisList, GL_COMPILE)
#glShadeModel(GL_FLAT)
#glFrontFace(GL_CCW)
#glEnable( GL_LIGHT_MODEL_TWO_SIDE )
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glEnable( GL_BLEND )
glEnable( GL_ALPHA_TEST )
#glAlphaFunc( GL_ALWAYS,0.5 )
glEnable( GL_POINT_SMOOTH )
glDisable( GL_DEPTH_TEST )
glBegin( GL_LINES )
glColor4f(1, 1, 1, .3)
for x in range(-10, 11):
glVertex3f(x, -10, 0)
glVertex3f(x, 10, 0)
for y in range(-10, 11):
glVertex3f(-10, y, 0)
glVertex3f( 10, y, 0)
glColor4f(0, 1, 0, .6) # z is green
glVertex3f(0, 0, 0)
glVertex3f(0, 0, 5)
glColor4f(1, 1, 0, .6) # y is yellow
glVertex3f(0, 0, 0)
glVertex3f(0, 5, 0)
glColor4f(0, 0, 1, .6) # x is blue
glVertex3f(0, 0, 0)
glVertex3f(5, 0, 0)
glEnd()
glEndList()
def generatePoints(self):
self.pointList = glGenLists(1)
glNewList(self.pointList, GL_COMPILE)
width = 7
alpha = 0.02
n = 40
glPointSize( width )
glBegin(GL_POINTS)
for x in range(-n, n+1):
r = (n-x)/(2.*n)
glColor4f(r, r, r, alpha)
for y in range(-n, n+1):
for z in range(-n, n+1):
glVertex3f(x, y, z)
glEnd()
glEndList()
def mousePressEvent(self, ev):
self.mousePos = ev.pos()

25
opengl/MeshData.py Normal file
View File

@ -0,0 +1,25 @@
class MeshData(object):
"""
Class for storing 3D mesh data. May contain:
- list of vertex locations
- list of edges
- list of triangles
- colors per vertex, edge, or tri
- normals per vertex or tri
"""
def __init__(self ...):
def generateFaceNormals(self):
def generateVertexNormals(self):
"""
Assigns each vertex the average of its connected face normals.
If face normals have not been computed yet, then generateFaceNormals will be called.
"""
def reverseNormals(self):

View File

@ -0,0 +1,51 @@
from OpenGL.GL import *
from .. GLGraphicsItem import GLGraphicsItem
from pyqtgraph import QtGui
__all__ = ['GLAxisItem']
class GLAxisItem(GLGraphicsItem):
def __init__(self, size=None):
GLGraphicsItem.__init__(self)
if size is None:
size = QtGui.QVector3D(1,1,1)
self.setSize(size=size)
def setSize(self, x=None, y=None, z=None, size=None):
"""
Set the size of the axes (in its local coordinate system; this does not affect the transform)
Arguments can be x,y,z or size=QVector3D().
"""
if size is not None:
x = size.x()
y = size.y()
z = size.z()
self.__size = [x,y,z]
self.update()
def size(self):
return self.__size[:]
def paint(self):
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glEnable( GL_BLEND )
glEnable( GL_ALPHA_TEST )
glEnable( GL_POINT_SMOOTH )
#glDisable( GL_DEPTH_TEST )
glBegin( GL_LINES )
x,y,z = self.size()
glColor4f(0, 1, 0, .6) # z is green
glVertex3f(0, 0, 0)
glVertex3f(0, 0, z)
glColor4f(1, 1, 0, .6) # y is yellow
glVertex3f(0, 0, 0)
glVertex3f(0, y, 0)
glColor4f(0, 0, 1, .6) # x is blue
glVertex3f(0, 0, 0)
glVertex3f(x, 0, 0)
glEnd()

View File

@ -1,9 +1,42 @@
from OpenGL.GL import *
from .. GLGraphicsItem import GLGraphicsItem
from pyqtgraph.Qt import QtGui
import pyqtgraph as pg
__all__ = ['GLBoxItem']
class GLBoxItem(GLGraphicsItem):
def __init__(self, size=None, color=None):
GLGraphicsItem.__init__(self)
if size is None:
size = QtGui.QVector3D(1,1,1)
self.setSize(size=size)
if color is None:
color = (255,255,255,80)
self.setColor(color)
def setSize(self, x=None, y=None, z=None, size=None):
"""
Set the size of the box (in its local coordinate system; this does not affect the transform)
Arguments can be x,y,z or size=QVector3D().
"""
if size is not None:
x = size.x()
y = size.y()
z = size.z()
self.__size = [x,y,z]
self.update()
def size(self):
return self.__size[:]
def setColor(self, *args):
"""Set the color of the box. Arguments are the same as those accepted by functions.mkColor()"""
self.__color = pg.Color(*args)
def color(self):
return self.__color
def paint(self):
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glEnable( GL_BLEND )
@ -13,34 +46,34 @@ class GLBoxItem(GLGraphicsItem):
glDisable( GL_DEPTH_TEST )
glBegin( GL_LINES )
glColor4f(1, 1, 1, .3)
w = 10
glVertex3f(-w, -w, -w)
glVertex3f(-w, -w, w)
glVertex3f( w, -w, -w)
glVertex3f( w, -w, w)
glVertex3f(-w, w, -w)
glVertex3f(-w, w, w)
glVertex3f( w, w, -w)
glVertex3f( w, w, w)
glColor4f(*self.color().glColor())
x,y,z = self.size()
glVertex3f(0, 0, 0)
glVertex3f(0, 0, z)
glVertex3f(x, 0, 0)
glVertex3f(x, 0, z)
glVertex3f(0, y, 0)
glVertex3f(0, y, z)
glVertex3f(x, y, 0)
glVertex3f(x, y, z)
glVertex3f(-w, -w, -w)
glVertex3f(-w, w, -w)
glVertex3f( w, -w, -w)
glVertex3f( w, w, -w)
glVertex3f(-w, -w, w)
glVertex3f(-w, w, w)
glVertex3f( w, -w, w)
glVertex3f( w, w, w)
glVertex3f(0, 0, 0)
glVertex3f(0, y, 0)
glVertex3f(x, 0, 0)
glVertex3f(x, y, 0)
glVertex3f(0, 0, z)
glVertex3f(0, y, z)
glVertex3f(x, 0, z)
glVertex3f(x, y, z)
glVertex3f(-w, -w, -w)
glVertex3f( w, -w, -w)
glVertex3f(-w, w, -w)
glVertex3f( w, w, -w)
glVertex3f(-w, -w, w)
glVertex3f( w, -w, w)
glVertex3f(-w, w, w)
glVertex3f( w, w, w)
glVertex3f(0, 0, 0)
glVertex3f(x, 0, 0)
glVertex3f(0, y, 0)
glVertex3f(x, y, 0)
glVertex3f(0, 0, z)
glVertex3f(x, 0, z)
glVertex3f(0, y, z)
glVertex3f(x, y, z)
glEnd()

View File

@ -0,0 +1,48 @@
from OpenGL.GL import *
from .. GLGraphicsItem import GLGraphicsItem
from pyqtgraph import QtGui
__all__ = ['GLGridItem']
class GLGridItem(GLGraphicsItem):
def __init__(self, size=None, color=None):
GLGraphicsItem.__init__(self)
if size is None:
size = QtGui.QVector3D(1,1,1)
self.setSize(size=size)
def setSize(self, x=None, y=None, z=None, size=None):
"""
Set the size of the axes (in its local coordinate system; this does not affect the transform)
Arguments can be x,y,z or size=QVector3D().
"""
if size is not None:
x = size.x()
y = size.y()
z = size.z()
self.__size = [x,y,z]
self.update()
def size(self):
return self.__size[:]
def paint(self):
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glEnable( GL_BLEND )
glEnable( GL_ALPHA_TEST )
glEnable( GL_POINT_SMOOTH )
#glDisable( GL_DEPTH_TEST )
glBegin( GL_LINES )
x,y,z = self.size()
glColor4f(1, 1, 1, .3)
for x in range(-10, 11):
glVertex3f(x, -10, 0)
glVertex3f(x, 10, 0)
for y in range(-10, 11):
glVertex3f(-10, y, 0)
glVertex3f( 10, y, 0)
glEnd()

View File

@ -0,0 +1,93 @@
from OpenGL.GL import *
from .. GLGraphicsItem import GLGraphicsItem
from pyqtgraph.Qt import QtGui
import pyqtgraph as pg
from .. import shaders
import numpy as np
__all__ = ['GLMeshItem']
class GLMeshItem(GLGraphicsItem):
def __init__(self, faces):
self.faces = faces
self.normals, self.faceNormals = pg.meshNormals(faces)
GLGraphicsItem.__init__(self)
def initializeGL(self):
#balloonVertexShader = shaders.compileShader("""
#varying vec3 normal;
#void main() {
#normal = normalize(gl_NormalMatrix * gl_Normal);
#//vec4 color = normal;
#//normal.w = min(color.w + 2.0 * color.w * pow(normal.x*normal.x + normal.y*normal.y, 2.0), 1.0);
#gl_FrontColor = gl_Color;
#gl_BackColor = gl_Color;
#gl_Position = ftransform();
#}""", GL_VERTEX_SHADER)
#balloonFragmentShader = shaders.compileShader("""
#varying vec3 normal;
#void main() {
#vec4 color = gl_Color;
#color.w = min(color.w + 2.0 * color.w * pow(normal.x*normal.x + normal.y*normal.y, 5.0), 1.0);
#gl_FragColor = color;
#}""", GL_FRAGMENT_SHADER)
#self.shader = shaders.compileProgram(balloonVertexShader, balloonFragmentShader)
self.shader = shaders.getShader('balloon')
l = glGenLists(1)
self.triList = l
glNewList(l, GL_COMPILE)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glEnable( GL_BLEND )
glEnable( GL_ALPHA_TEST )
#glAlphaFunc( GL_ALWAYS,0.5 )
glEnable( GL_POINT_SMOOTH )
glDisable( GL_DEPTH_TEST )
glColor4f(1, 1, 1, .1)
glBegin( GL_TRIANGLES )
for i, f in enumerate(self.faces):
pts = [QtGui.QVector3D(*x) for x in f]
if pts[0] is None:
print f
continue
#norm = QtGui.QVector3D.crossProduct(pts[1]-pts[0], pts[2]-pts[0])
for j in [0,1,2]:
norm = self.normals[self.faceNormals[i][j]]
glNormal3f(norm.x(), norm.y(), norm.z())
#j = (i+1) % 3
glVertex3f(*f[j])
glEnd()
glEndList()
l = glGenLists(1)
self.meshList = l
glNewList(l, GL_COMPILE)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glEnable( GL_BLEND )
glEnable( GL_ALPHA_TEST )
#glAlphaFunc( GL_ALWAYS,0.5 )
glEnable( GL_POINT_SMOOTH )
glEnable( GL_DEPTH_TEST )
glColor4f(1, 1, 1, .3)
glBegin( GL_LINES )
for f in self.faces:
for i in [0,1,2]:
j = (i+1) % 3
glVertex3f(*f[i])
glVertex3f(*f[j])
glEnd()
glEndList()
def paint(self):
shaders.glUseProgram(self.shader)
glCallList(self.triList)
shaders.glUseProgram(0)
#glCallList(self.meshList)

View File

@ -6,23 +6,28 @@ import numpy as np
__all__ = ['GLVolumeItem']
class GLVolumeItem(GLGraphicsItem):
def __init__(self, data, sliceDensity=1, smooth=True):
self.sliceDensity = sliceDensity
self.smooth = smooth
self.data = data
GLGraphicsItem.__init__(self)
def initializeGL(self):
n = 128
self.data = np.random.randint(0, 255, size=4*n**3).astype(np.uint8).reshape((n,n,n,4))
self.data[...,3] *= 0.1
for i in range(n):
self.data[i,:,:,0] = i*256./n
glEnable(GL_TEXTURE_3D)
self.texture = glGenTextures(1)
glBindTexture(GL_TEXTURE_3D, self.texture)
#glTexImage3D( GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, void *data );
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
if self.smooth:
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
else:
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER)
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER)
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER)
#glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_BORDER_COLOR, ) ## black/transparent by default
glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, n, n, n, 0, GL_RGBA, GL_UNSIGNED_BYTE, self.data)
shape = self.data.shape
glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, shape[0], shape[1], shape[2], 0, GL_RGBA, GL_UNSIGNED_BYTE, self.data.transpose((2,1,0,3)))
glDisable(GL_TEXTURE_3D)
self.lists = {}
@ -40,14 +45,16 @@ class GLVolumeItem(GLGraphicsItem):
glEnable(GL_TEXTURE_3D)
glBindTexture(GL_TEXTURE_3D, self.texture)
glDisable(GL_DEPTH_TEST)
glEnable(GL_DEPTH_TEST)
#glDisable(GL_CULL_FACE)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glEnable( GL_BLEND )
glEnable( GL_ALPHA_TEST )
glColor4f(1,1,1,1)
view = self.view()
cam = view.cameraPosition()
center = QtGui.QVector3D(*[x/2. for x in self.data.shape[:3]])
cam = self.mapFromParent(view.cameraPosition()) - center
cam = np.array([cam.x(), cam.y(), cam.z()])
ax = np.argmax(abs(cam))
d = 1 if cam[ax] > 0 else -1
@ -55,7 +62,6 @@ class GLVolumeItem(GLGraphicsItem):
glDisable(GL_TEXTURE_3D)
def drawVolume(self, ax, d):
slices = 256
N = 5
imax = [0,1,2]
@ -63,31 +69,35 @@ class GLVolumeItem(GLGraphicsItem):
tp = [[0,0,0],[0,0,0],[0,0,0],[0,0,0]]
vp = [[0,0,0],[0,0,0],[0,0,0],[0,0,0]]
tp[0][imax[0]] = 0
tp[0][imax[1]] = 0
tp[1][imax[0]] = 1
tp[1][imax[1]] = 0
tp[2][imax[0]] = 1
tp[2][imax[1]] = 1
tp[3][imax[0]] = 0
tp[3][imax[1]] = 1
nudge = [0.5/x for x in self.data.shape]
tp[0][imax[0]] = 0+nudge[imax[0]]
tp[0][imax[1]] = 0+nudge[imax[1]]
tp[1][imax[0]] = 1-nudge[imax[0]]
tp[1][imax[1]] = 0+nudge[imax[1]]
tp[2][imax[0]] = 1-nudge[imax[0]]
tp[2][imax[1]] = 1-nudge[imax[1]]
tp[3][imax[0]] = 0+nudge[imax[0]]
tp[3][imax[1]] = 1-nudge[imax[1]]
vp[0][imax[0]] = -N
vp[0][imax[1]] = -N
vp[1][imax[0]] = N
vp[1][imax[1]] = -N
vp[2][imax[0]] = N
vp[2][imax[1]] = N
vp[3][imax[0]] = -N
vp[3][imax[1]] = N
vp[0][imax[0]] = 0
vp[0][imax[1]] = 0
vp[1][imax[0]] = self.data.shape[imax[0]]
vp[1][imax[1]] = 0
vp[2][imax[0]] = self.data.shape[imax[0]]
vp[2][imax[1]] = self.data.shape[imax[1]]
vp[3][imax[0]] = 0
vp[3][imax[1]] = self.data.shape[imax[1]]
slices = self.data.shape[ax] * self.sliceDensity
r = range(slices)
if d == -1:
r = r[::-1]
glBegin(GL_QUADS)
tzVals = np.linspace(nudge[ax], 1.0-nudge[ax], slices)
vzVals = np.linspace(0, self.data.shape[ax], slices)
for i in r:
z = float(i)/(slices-1.)
w = float(i)*10./(slices-1.) - 5.
z = tzVals[i]
w = vzVals[i]
tp[0][ax] = z
tp[1][ax] = z

41
opengl/shaders.py Normal file
View File

@ -0,0 +1,41 @@
from OpenGL.GL import *
from OpenGL.GL import shaders
## For centralizing and managing vertex/fragment shader programs.
Shaders = {
'balloon': ( ## increases fragment alpha as the normal turns orthogonal to the view
"""
varying vec3 normal;
void main() {
normal = normalize(gl_NormalMatrix * gl_Normal);
//vec4 color = normal;
//normal.w = min(color.w + 2.0 * color.w * pow(normal.x*normal.x + normal.y*normal.y, 2.0), 1.0);
gl_FrontColor = gl_Color;
gl_BackColor = gl_Color;
gl_Position = ftransform();
}
""",
"""
varying vec3 normal;
void main() {
vec4 color = gl_Color;
color.w = min(color.w + 2.0 * color.w * pow(normal.x*normal.x + normal.y*normal.y, 5.0), 1.0);
gl_FragColor = color;
}
"""
),
}
CompiledShaders = {}
def getShader(name):
global Shaders, CompiledShaders
if name not in CompiledShaders:
vshader, fshader = Shaders[name]
vcomp = shaders.compileShader(vshader, GL_VERTEX_SHADER)
fcomp = shaders.compileShader(fshader, GL_FRAGMENT_SHADER)
prog = shaders.compileProgram(vcomp, fcomp)
CompiledShaders[name] = prog, vcomp, fcomp
return CompiledShaders[name][0]