merge from acq4

This commit is contained in:
Luke Campagnola 2013-01-30 15:59:48 -05:00
commit c7574f9adc
7 changed files with 252 additions and 64 deletions

View File

@ -27,6 +27,7 @@ from pyqtgraph import getConfigOption
import numpy as np
import decimal, re
import ctypes
import sys, struct
try:
import scipy.ndimage
@ -1041,6 +1042,97 @@ def colorToAlpha(data, color):
def arrayToQPath(x, y, connect='all'):
"""Convert an array of x,y coordinats to QPath as efficiently as possible.
The *connect* argument may be 'all', indicating that each point should be
connected to the next; 'pairs', indicating that each pair of points
should be connected, or an array of int32 values (0 or 1) indicating
connections.
"""
## Create all vertices in path. The method used below creates a binary format so that all
## vertices can be read in at once. This binary format may change in future versions of Qt,
## so the original (slower) method is left here for emergencies:
#path.moveTo(x[0], y[0])
#for i in range(1, y.shape[0]):
# path.lineTo(x[i], y[i])
## Speed this up using >> operator
## Format is:
## numVerts(i4) 0(i4)
## x(f8) y(f8) 0(i4) <-- 0 means this vertex does not connect
## x(f8) y(f8) 1(i4) <-- 1 means this vertex connects to the previous vertex
## ...
## 0(i4)
##
## All values are big endian--pack using struct.pack('>d') or struct.pack('>i')
path = QtGui.QPainterPath()
#prof = debug.Profiler('PlotCurveItem.generatePath', disabled=True)
if sys.version_info[0] == 2: ## So this is disabled for python 3... why??
n = x.shape[0]
# create empty array, pad with extra space on either end
arr = np.empty(n+2, dtype=[('x', '>f8'), ('y', '>f8'), ('c', '>i4')])
# write first two integers
#prof.mark('allocate empty')
arr.data[12:20] = struct.pack('>ii', n, 0)
#prof.mark('pack header')
# Fill array with vertex values
arr[1:-1]['x'] = x
arr[1:-1]['y'] = y
# decide which points are connected by lines
if connect == 'pairs':
connect = np.empty((n/2,2), dtype=np.int32)
connect[:,0] = 1
connect[:,1] = 0
connect = connect.flatten()
if connect == 'all':
arr[1:-1]['c'] = 1
elif isinstance(connect, np.ndarray):
arr[1:-1]['c'] = connect
else:
raise Exception('connect argument must be "all", "pairs", or array')
#prof.mark('fill array')
# write last 0
lastInd = 20*(n+1)
arr.data[lastInd:lastInd+4] = struct.pack('>i', 0)
#prof.mark('footer')
# create datastream object and stream into path
buf = QtCore.QByteArray(arr.data[12:lastInd+4]) # I think one unnecessary copy happens here
#prof.mark('create buffer')
ds = QtCore.QDataStream(buf)
#prof.mark('create datastream')
ds >> path
#prof.mark('load')
#prof.finish()
else:
## This does exactly the same as above, but less efficiently (and more simply).
path.moveTo(x[0], y[0])
if connect == 'all':
for i in range(1, y.shape[0]):
path.lineTo(x[i], y[i])
elif connect == 'pairs':
for i in range(1, y.shape[0]):
if i%2 == 0:
path.lineTo(x[i], y[i])
else:
path.moveTo(x[i], y[i])
elif isinstance(connect, np.ndarray):
for i in range(1, y.shape[0]):
if connect[i] == 1:
path.lineTo(x[i], y[i])
else:
path.moveTo(x[i], y[i])
else:
raise Exception('connect argument must be "all", "pairs", or array')
return path
#def isosurface(data, level):
#"""
#Generate isosurface from volumetric data using marching tetrahedra algorithm.

View File

@ -58,13 +58,14 @@ class AxisItem(GraphicsWidget):
self.labelUnitPrefix=''
self.labelStyle = {}
self.logMode = False
self.tickFont = None
self.textHeight = 18
self.tickLength = maxTickLength
self._tickLevels = None ## used to override the automatic ticking system with explicit ticks
self.scale = 1.0
self.autoScale = True
self.setRange(0, 1)
self.setPen(pen)
@ -72,12 +73,12 @@ class AxisItem(GraphicsWidget):
self._linkedView = None
if linkView is not None:
self.linkToView(linkView)
self.showLabel(False)
self.grid = False
#self.setCacheMode(self.DeviceCoordinateCache)
def close(self):
self.scene().removeItem(self.label)
self.label = None
@ -100,6 +101,14 @@ class AxisItem(GraphicsWidget):
self.picture = None
self.update()
def setTickFont(self, font):
self.tickFont = font
self.picture = None
self.prepareGeometryChange()
## Need to re-allocate space depending on font size?
self.update()
def resizeEvent(self, ev=None):
#s = self.size()
@ -311,14 +320,21 @@ class AxisItem(GraphicsWidget):
if linkedView is None or self.grid is False:
rect = self.mapRectFromParent(self.geometry())
## extend rect if ticks go in negative direction
## also extend to account for text that flows past the edges
if self.orientation == 'left':
rect.setRight(rect.right() - min(0,self.tickLength))
#rect.setRight(rect.right() - min(0,self.tickLength))
#rect.setTop(rect.top() - 15)
#rect.setBottom(rect.bottom() + 15)
rect = rect.adjusted(0, -15, -min(0,self.tickLength), 15)
elif self.orientation == 'right':
rect.setLeft(rect.left() + min(0,self.tickLength))
#rect.setLeft(rect.left() + min(0,self.tickLength))
rect = rect.adjusted(min(0,self.tickLength), -15, 0, 15)
elif self.orientation == 'top':
rect.setBottom(rect.bottom() - min(0,self.tickLength))
#rect.setBottom(rect.bottom() - min(0,self.tickLength))
rect = rect.adjusted(-15, 0, 15, -min(0,self.tickLength))
elif self.orientation == 'bottom':
rect.setTop(rect.top() + min(0,self.tickLength))
#rect.setTop(rect.top() + min(0,self.tickLength))
rect = rect.adjusted(-15, min(0,self.tickLength), 15, 0)
return rect
else:
return self.mapRectFromParent(self.geometry()) | linkedView.mapRectToItem(self, linkedView.boundingRect())
@ -647,6 +663,9 @@ class AxisItem(GraphicsWidget):
prof.mark('draw ticks')
## Draw text until there is no more room (or no more text)
if self.tickFont is not None:
p.setFont(self.tickFont)
textRects = []
for i in range(len(tickLevels)):
## Get the list of strings to display for this level

109
graphicsItems/GraphItem.py Normal file
View File

@ -0,0 +1,109 @@
from .. import functions as fn
from GraphicsObject import GraphicsObject
from ScatterPlotItem import ScatterPlotItem
import pyqtgraph as pg
import numpy as np
__all__ = ['GraphItem']
class GraphItem(GraphicsObject):
"""A GraphItem displays graph information (as in 'graph theory', not 'graphics') as
a set of nodes connected by lines.
"""
def __init__(self, **kwds):
GraphicsObject.__init__(self)
self.scatter = ScatterPlotItem()
self.scatter.setParentItem(self)
self.adjacency = None
self.pos = None
self.picture = None
self.pen = 'default'
self.setData(**kwds)
def setData(self, **kwds):
"""
Change the data displayed by the graph.
============ =========================================================
Arguments
pos (N,2) array of the positions of each node in the graph
adj (M,2) array of connection data. Each row contains indexes
of two nodes that are connected.
pen The pen to use when drawing lines between connected
nodes. May be one of:
* QPen
* a single argument to pass to pg.mkPen
* a record array of length M
with fields (red, green, blue, alpha, width).
* None (to disable connection drawing)
* 'default' to use the default foreground color.
symbolPen The pen used for drawing nodes.
**opts All other keyword arguments are given to ScatterPlotItem
to affect the appearance of nodes (symbol, size, brush,
etc.)
============ =========================================================
"""
if 'adj' in kwds:
self.adjacency = kwds.pop('adj')
assert self.adjacency.dtype.kind in 'iu'
self.picture = None
if 'pos' in kwds:
self.pos = kwds['pos']
self.picture = None
if 'pen' in kwds:
self.setPen(kwds.pop('pen'))
self.picture = None
if 'symbolPen' in kwds:
kwds['pen'] = kwds.pop('symbolPen')
self.scatter.setData(**kwds)
self.informViewBoundsChanged()
def setPen(self, pen):
self.pen = pen
self.picture = None
def generatePicture(self):
self.picture = pg.QtGui.QPicture()
if self.pen is None or self.pos is None or self.adjacency is None:
return
p = pg.QtGui.QPainter(self.picture)
try:
pts = self.pos[self.adjacency]
pen = self.pen
if isinstance(pen, np.ndarray):
lastPen = None
for i in range(pts.shape[0]):
pen = self.pen[i]
if np.any(pen != lastPen):
lastPen = pen
if pen.dtype.fields is None:
p.setPen(pg.mkPen(color=(pen[0], pen[1], pen[2], pen[3]), width=1))
else:
p.setPen(pg.mkPen(color=(pen['red'], pen['green'], pen['blue'], pen['alpha']), width=pen['width']))
p.drawLine(pg.QtCore.QPointF(*pts[i][0]), pg.QtCore.QPointF(*pts[i][1]))
else:
if pen == 'default':
pen = pg.getConfigOption('foreground')
p.setPen(pg.mkPen(pen))
pts = pts.reshape((pts.shape[0]*pts.shape[1], pts.shape[2]))
path = fn.arrayToQPath(x=pts[:,0], y=pts[:,1], connect='pairs')
p.drawPath(path)
finally:
p.end()
def paint(self, p, *args):
if self.picture == None:
self.generatePicture()
self.picture.play(p)
def boundingRect(self):
return self.scatter.boundingRect()

View File

@ -202,7 +202,7 @@ class GraphicsItem(object):
## check global cache
key = (dt.m11(), dt.m21(), dt.m31(), dt.m12(), dt.m22(), dt.m32(), dt.m31(), dt.m32())
pv = self._pixelVectorGlobalCache.get(key, None)
if pv is not None:
if direction is None and pv is not None:
self._pixelVectorCache = [dt, pv]
return tuple(map(Point,pv)) ## return a *copy*

View File

@ -249,26 +249,6 @@ class PlotCurveItem(GraphicsObject):
prof.finish()
def generatePath(self, x, y):
prof = debug.Profiler('PlotCurveItem.generatePath', disabled=True)
path = QtGui.QPainterPath()
## Create all vertices in path. The method used below creates a binary format so that all
## vertices can be read in at once. This binary format may change in future versions of Qt,
## so the original (slower) method is left here for emergencies:
#path.moveTo(x[0], y[0])
#for i in range(1, y.shape[0]):
# path.lineTo(x[i], y[i])
## Speed this up using >> operator
## Format is:
## numVerts(i4) 0(i4)
## x(f8) y(f8) 0(i4) <-- 0 means this vertex does not connect
## x(f8) y(f8) 1(i4) <-- 1 means this vertex connects to the previous vertex
## ...
## 0(i4)
##
## All values are big endian--pack using struct.pack('>d') or struct.pack('>i')
if self.opts['stepMode']:
## each value in the x/y arrays generates 2 points.
x2 = np.empty((len(x),2), dtype=x.dtype)
@ -286,41 +266,8 @@ class PlotCurveItem(GraphicsObject):
y = y2.reshape(y2.size)[1:-1]
y[0] = self.opts['fillLevel']
y[-1] = self.opts['fillLevel']
if sys.version_info[0] == 2: ## So this is disabled for python 3... why??
n = x.shape[0]
# create empty array, pad with extra space on either end
arr = np.empty(n+2, dtype=[('x', '>f8'), ('y', '>f8'), ('c', '>i4')])
# write first two integers
prof.mark('allocate empty')
arr.data[12:20] = struct.pack('>ii', n, 0)
prof.mark('pack header')
# Fill array with vertex values
arr[1:-1]['x'] = x
arr[1:-1]['y'] = y
arr[1:-1]['c'] = 1
prof.mark('fill array')
# write last 0
lastInd = 20*(n+1)
arr.data[lastInd:lastInd+4] = struct.pack('>i', 0)
prof.mark('footer')
# create datastream object and stream into path
buf = QtCore.QByteArray(arr.data[12:lastInd+4]) # I think one unnecessary copy happens here
prof.mark('create buffer')
ds = QtCore.QDataStream(buf)
prof.mark('create datastream')
ds >> path
prof.mark('load')
prof.finish()
else:
path.moveTo(x[0], y[0])
for i in range(1, y.shape[0]):
path.lineTo(x[i], y[i])
path = fn.arrayToQPath(x, y, connect='all')
return path

View File

@ -1054,6 +1054,21 @@ class PlotItem(GraphicsWidget):
"""
self.getAxis(axis).setLabel(text=text, units=units, **args)
def setLabels(self, **kwds):
"""
Convenience function allowing multiple labels and/or title to be set in one call.
Keyword arguments can be 'title', 'left', 'bottom', 'right', or 'top'.
Values may be strings or a tuple of arguments to pass to setLabel.
"""
for k,v in kwds.items():
if k == 'title':
self.setTitle(v)
else:
if isinstance(v, basestring):
v = (v,)
self.setLabel(k, *v)
def showLabel(self, axis, show=True):
"""
Show or hide one of the plot's axis labels (the axis itself will be unaffected).

View File

@ -620,9 +620,12 @@ class ScatterPlotItem(GraphicsObject):
if frac >= 1.0:
## increase size of bounds based on spot size and pen width
px = self.pixelLength(Point(1, 0) if ax == 0 else Point(0, 1)) ## determine length of pixel along this axis
#px = self.pixelLength(Point(1, 0) if ax == 0 else Point(0, 1)) ## determine length of pixel along this axis
px = self.pixelVectors()[ax]
if px is None:
px = 0
else:
px = px.length()
minIndex = np.argmin(d)
maxIndex = np.argmax(d)
minVal = d[minIndex]
@ -668,6 +671,8 @@ class ScatterPlotItem(GraphicsObject):
pts[1] = self.data['y']
pts = fn.transformCoordinates(tr, pts)
self.fragments = []
pts = np.clip(pts, -2**31, 2**31) ## prevent Qt segmentation fault.
## Still won't be able to render correctly, though.
for i in xrange(len(self.data)):
rec = self.data[i]
pos = QtCore.QPointF(pts[0,i], pts[1,i])
@ -680,8 +685,10 @@ class ScatterPlotItem(GraphicsObject):
self.invalidate()
def paint(self, p, *args):
#p.setPen(fn.mkPen('r'))
#p.drawRect(self.boundingRect())
if self._exportOpts is not False:
aa = self._exportOpts.get('antialias', True)
scale = self._exportOpts.get('resolutionScale', 1.0) ## exporting to image; pixel resolution may have changed
@ -728,7 +735,6 @@ class ScatterPlotItem(GraphicsObject):
p2.end()
self.picture.play(p)
def points(self):
for rec in self.data: