Added matplotlib exporter

Updates to MeshData class (this is still not tested)
This commit is contained in:
Luke Campagnola 2012-03-23 22:13:41 -04:00
parent c814499bee
commit f6da6e2fd0
8 changed files with 292 additions and 150 deletions

View File

@ -72,6 +72,8 @@ class ExportDialog(QtGui.QWidget):
def exportItemChanged(self, item, prev):
if item is None:
return
if item.gitem is self.scene:
newBounds = self.scene.views()[0].viewRect()
else:
@ -105,7 +107,10 @@ class ExportDialog(QtGui.QWidget):
expClass = self.exporterClasses[str(item.text())]
exp = expClass(item=self.ui.itemTree.currentItem().gitem)
params = exp.parameters()
self.ui.paramTree.setParameters(params)
if params is None:
self.ui.paramTree.clear()
else:
self.ui.paramTree.setParameters(params)
self.currentExporter = exp
def exportClicked(self):

View File

@ -57,7 +57,7 @@ renamePyc(path)
## don't import the more complex systems--canvas, parametertree, flowchart, dockarea
## these must be imported separately.
def importAll(path):
def importAll(path, excludes=()):
d = os.path.join(os.path.split(__file__)[0], path)
files = []
for f in os.listdir(d):
@ -67,6 +67,8 @@ def importAll(path):
files.append(f[:-3])
for modName in files:
if modName in excludes:
continue
mod = __import__(path+"."+modName, globals(), locals(), fromlist=['*'])
if hasattr(mod, '__all__'):
names = mod.__all__
@ -77,7 +79,7 @@ def importAll(path):
globals()[k] = getattr(mod, k)
importAll('graphicsItems')
importAll('widgets')
importAll('widgets', excludes=['MatplotlibWidget'])
from imageview import *
from WidgetGroup import *

74
exporters/Matplotlib.py Normal file
View File

@ -0,0 +1,74 @@
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, QtCore
from Exporter import Exporter
__all__ = ['MatplotlibExporter']
class MatplotlibExporter(Exporter):
Name = "Matplotlib Window"
windows = []
def __init__(self, item):
Exporter.__init__(self, item)
def parameters(self):
return None
def export(self, fileName=None):
if isinstance(self.item, pg.PlotItem):
mpw = MatplotlibWindow()
MatplotlibExporter.windows.append(mpw)
fig = mpw.getFigure()
ax = fig.add_subplot(111)
ax.clear()
#ax.grid(True)
for item in self.item.curves:
x, y = item.getData()
opts = item.opts
pen = pg.mkPen(opts['pen'])
if pen.style() == QtCore.Qt.NoPen:
linestyle = ''
else:
linestyle = '-'
color = tuple([c/255. for c in pg.colorTuple(pen.color())])
symbol = opts['symbol']
if symbol == 't':
symbol = '^'
symbolPen = pg.mkPen(opts['symbolPen'])
symbolBrush = pg.mkBrush(opts['symbolBrush'])
markeredgecolor = tuple([c/255. for c in pg.colorTuple(symbolPen.color())])
markerfacecolor = tuple([c/255. for c in pg.colorTuple(symbolBrush.color())])
if opts['fillLevel'] is not None and opts['fillBrush'] is not None:
fillBrush = pg.mkBrush(opts['fillBrush'])
fillcolor = tuple([c/255. for c in pg.colorTuple(fillBrush.color())])
ax.fill_between(x=x, y1=y, y2=opts['fillLevel'], facecolor=fillcolor)
ax.plot(x, y, marker=symbol, color=color, linewidth=pen.width(), linestyle=linestyle, markeredgecolor=markeredgecolor, markerfacecolor=markerfacecolor)
xr, yr = self.item.viewRange()
ax.set_xbound(*xr)
ax.set_ybound(*yr)
mpw.draw()
else:
raise Exception("Matplotlib export currently only works with plot items")
class MatplotlibWindow(QtGui.QMainWindow):
def __init__(self):
import pyqtgraph.widgets.MatplotlibWidget
QtGui.QMainWindow.__init__(self)
self.mpl = pyqtgraph.widgets.MatplotlibWidget.MatplotlibWidget()
self.setCentralWidget(self.mpl)
self.show()
def __getattr__(self, attr):
return getattr(self.mpl, attr)
def closeEvent(self, ev):
MatplotlibExporter.windows.remove(self)

View File

@ -409,12 +409,12 @@ def affineSlice(data, shape, origin, vectors, axes, **kargs):
def makeARGB(data, lut=None, levels=None, useRGBA=False):
def makeARGB(data, lut=None, levels=None):
"""
Convert a 2D or 3D array into an ARGB array suitable for building QImages
Will optionally do scaling and/or table lookups to determine final colors.
Returns the ARGB array (values 0-255) and a boolean indicating whether there is alpha channel data.
Returns the ARGB array and a boolean indicating whether there is alpha channel data.
Arguments:
data - 2D or 3D numpy array of int/float types
@ -433,8 +433,6 @@ def makeARGB(data, lut=None, levels=None, useRGBA=False):
Lookup tables can be built using GradientWidget.
levels - List [min, max]; optionally rescale data before converting through the
lookup table. rescaled = (data-min) * len(lut) / (max-min)
useRGBA - If True, the data is returned in RGBA order. The default is
False, which returns in BGRA order for use with QImage.
"""
@ -582,11 +580,8 @@ def makeARGB(data, lut=None, levels=None, useRGBA=False):
prof.mark('4')
if useRGBA:
order = [0,1,2,3] ## array comes out RGBA
else:
order = [2,1,0,3] ## for some reason, the colors line up as BGR in the final image.
order = [2,1,0,3] ## for some reason, the colors line up as BGR in the final image.
if data.shape[2] == 1:
for i in xrange(3):
imgData[..., order[i]] = data[..., 0]
@ -737,85 +732,7 @@ def rescaleData(data, scale, offset):
#return facets
def isocurve(data, level):
"""
Generate isocurve from 2D data using marching squares algorithm.
*data* 2D numpy array of scalar values
*level* The level at which to generate an isosurface
This function is SLOW; plenty of room for optimization here.
"""
sideTable = [
[],
[0,1],
[1,2],
[0,2],
[0,3],
[1,3],
[0,1,2,3],
[2,3],
[2,3],
[0,1,2,3],
[1,3],
[0,3],
[0,2],
[1,2],
[0,1],
[]
]
edgeKey=[
[(0,1),(0,0)],
[(0,0), (1,0)],
[(1,0), (1,1)],
[(1,1), (0,1)]
]
lines = []
## mark everything below the isosurface level
mask = data < level
### make four 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), dtype=object)
slices = [slice(0,-1), slice(1,None)]
for i in [0,1]:
for j in [0,1]:
fields[i,j] = mask[slices[i], slices[j]]
#vertIndex = i - 2*j*i + 3*j + 4*k ## this is just to match Bourk's vertex numbering scheme
vertIndex = i+2*j
#print i,j,k," : ", fields[i,j,k], 2**vertIndex
index += fields[i,j] * 2**vertIndex
#print index
#print index
## add lines
for i in xrange(index.shape[0]): # data x-axis
for j in xrange(index.shape[1]): # data y-axis
sides = sideTable[index[i,j]]
for l in range(0, len(sides), 2): ## faces for this grid cell
edges = sides[l:l+2]
pts = []
for m in [0,1]: # points in this face
p1 = edgeKey[edges[m]][0] # p1, p2 are points at either side of an edge
p2 = edgeKey[edges[m]][1]
v1 = data[i+p1[0], j+p1[1]] # v1 and v2 are the values at p1 and p2
v2 = data[i+p2[0], j+p2[1]]
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
)
pts.append(p)
lines.append(pts)
return lines ## a list of pairs of points
def isosurface(data, level):
"""
@ -1193,55 +1110,55 @@ def isosurface(data, level):
return facets
## code has moved to opengl/MeshData.py
#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), ...]
#"""
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)
#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)
### 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)
### 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))
### 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
#return avgNorms, faces

View File

@ -98,7 +98,7 @@ class PlotDataItem(GraphicsObject):
'pen': (200,200,200),
'shadowPen': None,
'fillLevel': None,
'brush': None,
'fillBrush': None,
'symbol': None,
'symbolSize': 10,
@ -165,10 +165,13 @@ class PlotDataItem(GraphicsObject):
#self.update()
self.updateItems()
def setBrush(self, *args, **kargs):
def setFillBrush(self, *args, **kargs):
brush = fn.mkBrush(*args, **kargs)
self.opts['brush'] = brush
self.opts['fillBrush'] = brush
self.updateItems()
def setBrush(self, *args, **kargs):
return self.setFillBrush(*args, **kargs)
def setFillLevel(self, level):
self.opts['fillLevel'] = level
@ -268,6 +271,9 @@ class PlotDataItem(GraphicsObject):
if 'symbol' not in kargs and ('symbolPen' in kargs or 'symbolBrush' in kargs or 'symbolSize' in kargs):
kargs['symbol'] = 'o'
if 'brush' in kargs:
kargs['fillBrush'] = kargs['brush']
for k in self.opts.keys():
if k in kargs:
self.opts[k] = kargs[k]
@ -313,8 +319,8 @@ class PlotDataItem(GraphicsObject):
#c.scene().removeItem(c)
curveArgs = {}
for k in ['pen', 'shadowPen', 'fillLevel', 'brush']:
curveArgs[k] = self.opts[k]
for k,v in [('pen','pen'), ('shadowPen','shadowPen'), ('fillLevel','fillLevel'), ('fillBrush', 'brush')]:
curveArgs[v] = self.opts[k]
scatterArgs = {}
for k,v in [('symbolPen','pen'), ('symbolBrush','brush'), ('symbol','symbol'), ('symbolSize', 'size')]:

View File

@ -146,11 +146,11 @@ class PlotItem(GraphicsWidget):
## Wrap a few methods from viewBox
for m in [
'setXRange', 'setYRange', 'setXLink', 'setYLink',
'setRange', 'autoRange', 'viewRect', 'setMouseEnabled',
'enableAutoRange', 'disableAutoRange', 'setAspectLocked']:
setattr(self, m, getattr(self.vb, m))
#for m in [
#'setXRange', 'setYRange', 'setXLink', 'setYLink',
#'setRange', 'autoRange', 'viewRect', 'setMouseEnabled',
#'enableAutoRange', 'disableAutoRange', 'setAspectLocked']:
#setattr(self, m, getattr(self.vb, m))
self.items = []
self.curves = []
@ -296,6 +296,8 @@ class PlotItem(GraphicsWidget):
#QtGui.QGraphicsWidget.paint(self, *args)
#prof.finish()
def __getattr__(self, attr): ## wrap ms
return getattr(self.vb, attr)
def close(self):
#print "delete", self

View File

@ -8,18 +8,117 @@ class MeshData(object):
- normals per vertex or tri
"""
def __init__(self ...):
def generateFaceNormals(self):
def __init__(self):
self.vertexes = []
self.edges = None
self.faces = []
self.vertexFaces = None ## maps vertex ID to a list of face IDs
self.vertexNormals = None
self.faceNormals = None
self.vertexColors = None
self.edgeColors = None
self.faceColors = None
def setFaces(self, faces, vertexes=None):
"""
Set the faces in this data set.
Data may be provided either as an Nx3x3 list of floats (9 float coordinate values per face)
*faces* = [ [(x, y, z), (x, y, z), (x, y, z)], ... ]
or as an Nx3 list of ints (vertex integers) AND an Mx3 list of floats (3 float coordinate values per vertex)
*faces* = [ (p1, p2, p3), ... ]
*vertexes* = [ (x, y, z), ... ]
"""
if vertexes is None:
self._setUnindexedFaces(self, faces)
else:
self._setIndexedFaces(self, faces)
def _setUnindexedFaces(self, faces):
verts = {}
self.faces = []
self.vertexes = []
self.vertexFaces = []
self.faceNormals = None
self.vertexNormals = None
for face in faces:
inds = []
for pt in face:
pt2 = tuple([int(x*1e14) for x in pt]) ## quantize to be sure that nearly-identical points will be merged
index = verts.get(pt2, None)
if index is None:
self.vertexes.append(tuple(pt))
self.vertexFaces.append([])
index = len(self.vertexes)-1
verts[pt2] = index
self.vertexFaces[index].append(face)
inds.append(index)
self.faces.append(tuple(inds))
def generateVertexNormals(self):
def _setIndexedFaces(self, faces, vertexes):
self.vertexes = vertexes
self.faces = faces
self.edges = None
self.vertexFaces = None
self.faceNormals = None
self.vertexNormals = None
def getVertexFaces(self):
"""
Return list mapping each vertex index to a list of face indexes that use the vertex.
"""
if self.vertexFaces is None:
self.vertexFaces = [[]] * len(self.vertexes)
for i, face in enumerate(self.faces):
for ind in face:
if len(self.vertexFaces[ind]) == 0:
self.vertexFaces[ind] = [] ## need a unique/empty list to fill
self.vertexFaces[ind].append(i)
return self.vertexFaces
def getFaceNormals(self):
"""
Computes and stores normal of each face.
"""
if self.faceNormals is None:
self.faceNormals = []
for i, face in enumerate(self.faces):
## compute face normal
pts = [QtGui.QVector3D(*self.vertexes[vind]) for vind in face]
norm = QtGui.QVector3D.crossProduct(pts[1]-pts[0], pts[2]-pts[0])
self.faceNormals.append(norm)
return self.faceNormals
def getVertexNormals(self):
"""
Assigns each vertex the average of its connected face normals.
If face normals have not been computed yet, then generateFaceNormals will be called.
"""
if self.vertexNormals is None:
faceNorms = self.getFaceNormals()
vertFaces = self.getVertexFaces()
self.vertexNormals = []
for vindex in xrange(len(self.vertexes)):
norms = [faceNorms[findex] for findex in vertFaces[vindex]]
if len(norms) == 0:
norm = QtGui.QVector3D()
else:
norm = reduce(QtGui.QVector3D.__add__, facenorms) / float(len(norms))
self.vertexNormals.append(norm)
return self.vertexNormals
def reverseNormals(self):
"""
Reverses the direction of all normal vectors.
"""
pass
def generateEdgesFromFaces(self):
"""
Generate a set of edges by listing all the edges of faces and removing any duplicates.
Useful for displaying wireframe meshes.
"""
pass

View File

@ -0,0 +1,37 @@
from pyqtgraph.Qt import QtGui, QtCore
import matplotlib
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
from matplotlib.figure import Figure
class MatplotlibWidget(QtGui.QWidget):
"""
Implements a Matplotlib figure inside a QWidget.
Use getFigure() and redraw() to interact with matplotlib.
Example::
mw = MatplotlibWidget()
subplot = mw.getFigure().add_subplot(111)
subplot.plot(x,y)
mw.draw()
"""
def __init__(self, size=(5.0, 4.0), dpi=100):
QtGui.QWidget.__init__(self)
self.fig = Figure(size, dpi=dpi)
self.canvas = FigureCanvas(self.fig)
self.canvas.setParent(self)
self.toolbar = NavigationToolbar(self.canvas, self)
self.vbox = QtGui.QVBoxLayout()
self.vbox.addWidget(self.toolbar)
self.vbox.addWidget(self.canvas)
self.setLayout(self.vbox)
def getFigure(self):
return self.fig
def draw(self):
self.canvas.draw()