Performance enhancements

- HistogramLUTItem avoids using lookup table if possible
 - GradientEditorItem has a method to ask whether the gradient is trivial (can be applied without the use of a lookup table)
 - ROI, LinearRegionItem, InfiniteLine no longer redraw for every mouse movement
This commit is contained in:
Luke Campagnola 2012-04-04 09:29:35 -04:00
parent 33b09dfa23
commit 78d4bc0838
12 changed files with 120 additions and 21 deletions

View File

@ -18,10 +18,10 @@ if 'linux' in sys.platform: ## linux has numerous bugs in opengl implementation
elif 'darwin' in sys.platform: ## openGL greatly speeds up display on mac elif 'darwin' in sys.platform: ## openGL greatly speeds up display on mac
useOpenGL = True useOpenGL = True
else: else:
useOpenGL = True ## on windows there's a more even performance / bugginess tradeoff. useOpenGL = False ## on windows there's a more even performance / bugginess tradeoff.
CONFIG_OPTIONS = { CONFIG_OPTIONS = {
'useOpenGL': None, ## by default, this is platform-dependent (see widgets/GraphicsView). Set to True or False to explicitly enable/disable opengl. 'useOpenGL': useOpenGL, ## by default, this is platform-dependent (see widgets/GraphicsView). Set to True or False to explicitly enable/disable opengl.
'leftButtonPan': True ## if false, left button drags a rubber band for zooming in viewbox 'leftButtonPan': True ## if false, left button drags a rubber band for zooming in viewbox
} }

View File

@ -1,14 +1,14 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
## Add path to library (just for examples; you do not need this) ## Add path to library (just for examples; you do not need this)
import sys, os, time import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
from pyqtgraph.Qt import QtGui, QtCore from pyqtgraph.Qt import QtGui, QtCore
import numpy as np import numpy as np
import pyqtgraph as pg import pyqtgraph as pg
from pyqtgraph.ptime import time
#QtGui.QApplication.setGraphicsSystem('raster') #QtGui.QApplication.setGraphicsSystem('raster')
app = QtGui.QApplication([]) app = QtGui.QApplication([])
#mw = QtGui.QMainWindow() #mw = QtGui.QMainWindow()
@ -18,15 +18,22 @@ p = pg.plot()
p.setRange(QtCore.QRectF(0, -10, 5000, 20)) p.setRange(QtCore.QRectF(0, -10, 5000, 20))
p.setLabel('bottom', 'Index', units='B') p.setLabel('bottom', 'Index', units='B')
curve = p.plot() curve = p.plot()
#curve.setFillBrush((0, 0, 100, 100))
#curve.setFillLevel(0)
#lr = pg.LinearRegionItem([100, 4900])
#p.addItem(lr)
data = np.random.normal(size=(50,5000)) data = np.random.normal(size=(50,5000))
ptr = 0 ptr = 0
lastTime = time.time() lastTime = time()
fps = None fps = None
def update(): def update():
global curve, data, ptr, p, lastTime, fps global curve, data, ptr, p, lastTime, fps
curve.setData(data[ptr%10]) curve.setData(data[ptr%10])
ptr += 1 ptr += 1
now = time.time() now = time()
dt = now - lastTime dt = now - lastTime
lastTime = now lastTime = now
if fps is None: if fps is None:

View File

@ -25,6 +25,8 @@ win.show()
ui.maxSpin1.setOpts(value=255, step=1) ui.maxSpin1.setOpts(value=255, step=1)
ui.minSpin1.setOpts(value=0, step=1) ui.minSpin1.setOpts(value=0, step=1)
#ui.graphicsView.useOpenGL() ## buggy, but you can try it if you need extra speed.
vb = pg.ViewBox() vb = pg.ViewBox()
ui.graphicsView.setCentralItem(vb) ui.graphicsView.setCentralItem(vb)
vb.setAspectLocked() vb.setAspectLocked()

View File

@ -467,7 +467,19 @@ class GradientEditorItem(TickSliderItem):
return table return table
def isLookupTrivial(self):
"""Return true if the gradient has exactly two stops in it: black at 0.0 and white at 1.0"""
ticks = self.listTicks()
if len(ticks) != 2:
return False
if ticks[0][1] != 0.0 or ticks[1][1] != 1.0:
return False
c1 = fn.colorTuple(ticks[0][0].color)
c2 = fn.colorTuple(ticks[1][0].color)
if c1 != (0,0,0,255) or c2 != (255,255,255,255):
return False
return True
def mouseReleaseEvent(self, ev): def mouseReleaseEvent(self, ev):
TickSliderItem.mouseReleaseEvent(self, ev) TickSliderItem.mouseReleaseEvent(self, ev)

View File

@ -15,17 +15,30 @@ from GridItem import *
from pyqtgraph.Point import Point from pyqtgraph.Point import Point
import pyqtgraph.functions as fn import pyqtgraph.functions as fn
import numpy as np import numpy as np
import pyqtgraph.debug as debug
__all__ = ['HistogramLUTItem'] __all__ = ['HistogramLUTItem']
class HistogramLUTItem(GraphicsWidget): class HistogramLUTItem(GraphicsWidget):
"""
This is a graphicsWidget which provides controls for adjusting the display of an image.
Includes:
- Image histogram
- Movable region over histogram to select black/white levels
- Gradient editor to define color lookup table for single-channel images
"""
sigLookupTableChanged = QtCore.Signal(object) sigLookupTableChanged = QtCore.Signal(object)
sigLevelsChanged = QtCore.Signal(object) sigLevelsChanged = QtCore.Signal(object)
sigLevelChangeFinished = QtCore.Signal(object) sigLevelChangeFinished = QtCore.Signal(object)
def __init__(self, image=None): def __init__(self, image=None, fillHistogram=True):
"""
If *image* (ImageItem) is provided, then the control will be automatically linked to the image and changes to the control will be immediately reflected in the image's appearance.
By default, the histogram is rendered with a fill. For performance, set *fillHistogram* = False.
"""
GraphicsWidget.__init__(self) GraphicsWidget.__init__(self)
self.lut = None self.lut = None
self.imageItem = None self.imageItem = None
@ -61,6 +74,8 @@ class HistogramLUTItem(GraphicsWidget):
self.vb.sigRangeChanged.connect(self.viewRangeChanged) self.vb.sigRangeChanged.connect(self.viewRangeChanged)
self.plot = PlotDataItem() self.plot = PlotDataItem()
self.plot.rotate(90) self.plot.rotate(90)
self.fillHistogram(fillHistogram)
self.vb.addItem(self.plot) self.vb.addItem(self.plot)
self.autoHistogramRange() self.autoHistogramRange()
@ -68,7 +83,13 @@ class HistogramLUTItem(GraphicsWidget):
self.setImageItem(image) self.setImageItem(image)
#self.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding) #self.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding)
def fillHistogram(self, fill=True, level=0.0, color=(100, 100, 200)):
if fill:
self.plot.setFillLevel(level)
self.plot.setFillBrush(color)
else:
self.plot.setFillLevel(None)
#def sizeHint(self, *args): #def sizeHint(self, *args):
#return QtCore.QSizeF(115, 200) #return QtCore.QSizeF(115, 200)
@ -129,21 +150,24 @@ class HistogramLUTItem(GraphicsWidget):
def gradientChanged(self): def gradientChanged(self):
if self.imageItem is not None: if self.imageItem is not None:
self.imageItem.setLookupTable(self.getLookupTable) ## send function pointer, not the result if self.gradient.isLookupTrivial():
self.imageItem.setLookupTable(None) #lambda x: x.astype(np.uint8))
else:
self.imageItem.setLookupTable(self.getLookupTable) ## send function pointer, not the result
self.lut = None self.lut = None
#if self.imageItem is not None: #if self.imageItem is not None:
#self.imageItem.setLookupTable(self.gradient.getLookupTable(512)) #self.imageItem.setLookupTable(self.gradient.getLookupTable(512))
self.sigLookupTableChanged.emit(self) self.sigLookupTableChanged.emit(self)
def getLookupTable(self, img=None, n=None): def getLookupTable(self, img=None, n=None, alpha=False):
if n is None: if n is None:
if img.dtype == np.uint8: if img.dtype == np.uint8:
n = 256 n = 256
else: else:
n = 512 n = 512
if self.lut is None: if self.lut is None:
self.lut = self.gradient.getLookupTable(n) self.lut = self.gradient.getLookupTable(n, alpha=alpha)
return self.lut return self.lut
def regionChanged(self): def regionChanged(self):
@ -159,17 +183,19 @@ class HistogramLUTItem(GraphicsWidget):
self.update() self.update()
def imageChanged(self, autoLevel=False, autoRange=False): def imageChanged(self, autoLevel=False, autoRange=False):
prof = debug.Profiler('HistogramLUTItem.imageChanged', disabled=True)
h = self.imageItem.getHistogram() h = self.imageItem.getHistogram()
prof.mark('get histogram')
if h[0] is None: if h[0] is None:
return return
self.plot.setData(*h, fillLevel=0.0, brush=(100, 100, 200)) self.plot.setData(*h)
prof.mark('set plot')
if autoLevel: if autoLevel:
mn = h[0][0] mn = h[0][0]
mx = h[0][-1] mx = h[0][-1]
self.region.setRegion([mn, mx]) self.region.setRegion([mn, mx])
#self.updateRange() prof.mark('set region')
#if autoRange: prof.finish()
#self.updateRange()
def getLevels(self): def getLevels(self):
return self.region.getRegion() return self.region.getRegion()

View File

@ -224,10 +224,13 @@ class ImageItem(GraphicsObject):
return return
if self.qimage is None: if self.qimage is None:
self.render() self.render()
prof.mark('render QImage')
if self.paintMode is not None: if self.paintMode is not None:
p.setCompositionMode(self.paintMode) p.setCompositionMode(self.paintMode)
prof.mark('set comp mode')
p.drawImage(QtCore.QPointF(0,0), self.qimage) p.drawImage(QtCore.QPointF(0,0), self.qimage)
prof.mark('p.drawImage')
if self.border is not None: if self.border is not None:
p.setPen(self.border) p.setPen(self.border)
p.drawRect(self.boundingRect()) p.drawRect(self.boundingRect())

View File

@ -35,6 +35,7 @@ class InfiniteLine(UIGraphicsItem):
self.maxRange = bounds self.maxRange = bounds
self.moving = False self.moving = False
self.setMovable(movable) self.setMovable(movable)
self.mouseHovering = False
self.p = [0, 0] self.p = [0, 0]
self.setAngle(angle) self.setAngle(angle)
if pos is None: if pos is None:
@ -222,6 +223,16 @@ class InfiniteLine(UIGraphicsItem):
def hoverEvent(self, ev): def hoverEvent(self, ev):
if (not ev.isExit()) and self.movable and ev.acceptDrags(QtCore.Qt.LeftButton): if (not ev.isExit()) and self.movable and ev.acceptDrags(QtCore.Qt.LeftButton):
self.setMouseHover(True)
else:
self.setMouseHover(False)
def setMouseHover(self, hover):
## Inform the item that the mouse is(not) hovering over it
if self.mouseHovering == hover:
return
self.mouseHovering = hover
if hover:
self.currentPen = fn.mkPen(255, 0,0) self.currentPen = fn.mkPen(255, 0,0)
else: else:
self.currentPen = self.pen self.currentPen = self.pen

View File

@ -2,6 +2,7 @@ from pyqtgraph.Qt import QtGui, QtCore
from UIGraphicsItem import UIGraphicsItem from UIGraphicsItem import UIGraphicsItem
from InfiniteLine import InfiniteLine from InfiniteLine import InfiniteLine
import pyqtgraph.functions as fn import pyqtgraph.functions as fn
import pyqtgraph.debug as debug
__all__ = ['LinearRegionItem'] __all__ = ['LinearRegionItem']
@ -24,6 +25,7 @@ class LinearRegionItem(UIGraphicsItem):
self.bounds = QtCore.QRectF() self.bounds = QtCore.QRectF()
self.blockLineSignal = False self.blockLineSignal = False
self.moving = False self.moving = False
self.mouseHovering = False
if orientation == LinearRegionItem.Horizontal: if orientation == LinearRegionItem.Horizontal:
self.lines = [ self.lines = [
@ -94,9 +96,11 @@ class LinearRegionItem(UIGraphicsItem):
return br.normalized() return br.normalized()
def paint(self, p, *args): def paint(self, p, *args):
#prof = debug.Profiler('LinearRegionItem.paint')
UIGraphicsItem.paint(self, p, *args) UIGraphicsItem.paint(self, p, *args)
p.setBrush(self.currentBrush) p.setBrush(self.currentBrush)
p.drawRect(self.boundingRect()) p.drawRect(self.boundingRect())
#prof.finish()
def dataBounds(self, axis, frac=1.0): def dataBounds(self, axis, frac=1.0):
if axis == self.orientation: if axis == self.orientation:
@ -197,13 +201,23 @@ class LinearRegionItem(UIGraphicsItem):
def hoverEvent(self, ev): def hoverEvent(self, ev):
if (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton): if (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton):
self.setMouseHover(True)
else:
self.setMouseHover(False)
def setMouseHover(self, hover):
## Inform the item that the mouse is(not) hovering over it
if self.mouseHovering == hover:
return
self.mouseHovering = hover
if hover:
c = self.brush.color() c = self.brush.color()
c.setAlpha(c.alpha() * 2) c.setAlpha(c.alpha() * 2)
self.currentBrush = fn.mkBrush(c) self.currentBrush = fn.mkBrush(c)
else: else:
self.currentBrush = self.brush self.currentBrush = self.brush
self.update() self.update()
#def hoverEnterEvent(self, ev): #def hoverEnterEvent(self, ev):
#print "rgn hover enter" #print "rgn hover enter"
#ev.ignore() #ev.ignore()

View File

@ -352,7 +352,9 @@ class PlotCurveItem(GraphicsObject):
p2.closeSubpath() p2.closeSubpath()
self.fillPath = p2 self.fillPath = p2
prof.mark('generate fill path')
p.fillPath(self.fillPath, self.opts['brush']) p.fillPath(self.fillPath, self.opts['brush'])
prof.mark('draw fill path')
## Copy pens and apply alpha adjustment ## Copy pens and apply alpha adjustment

View File

@ -11,6 +11,7 @@ from ScatterPlotItem import ScatterPlotItem
import numpy as np import numpy as np
import scipy import scipy
import pyqtgraph.functions as fn import pyqtgraph.functions as fn
import pyqtgraph.debug as debug
class PlotDataItem(GraphicsObject): class PlotDataItem(GraphicsObject):
"""GraphicsItem for displaying plot curves, scatter plots, or both.""" """GraphicsItem for displaying plot curves, scatter plots, or both."""
@ -215,7 +216,7 @@ class PlotDataItem(GraphicsObject):
""" """
#self.clear() #self.clear()
prof = debug.Profiler('PlotDataItem.setData (0x%x)' % id(self), disabled=True)
y = None y = None
x = None x = None
if len(args) == 1: if len(args) == 1:
@ -262,7 +263,7 @@ class PlotDataItem(GraphicsObject):
if 'y' in kargs: if 'y' in kargs:
y = kargs['y'] y = kargs['y']
prof.mark('interpret data')
## pull in all style arguments. ## pull in all style arguments.
## Use self.opts to fill in anything not present in kargs. ## Use self.opts to fill in anything not present in kargs.
@ -305,12 +306,16 @@ class PlotDataItem(GraphicsObject):
self.yData = y.view(np.ndarray) self.yData = y.view(np.ndarray)
self.xDisp = None self.xDisp = None
self.yDisp = None self.yDisp = None
prof.mark('set data')
self.updateItems() self.updateItems()
prof.mark('update items')
view = self.getViewBox() view = self.getViewBox()
if view is not None: if view is not None:
view.itemBoundsChanged(self) ## inform view so it can update its range if it wants view.itemBoundsChanged(self) ## inform view so it can update its range if it wants
self.sigPlotChanged.emit(self) self.sigPlotChanged.emit(self)
prof.mark('emit')
prof.finish()
def updateItems(self): def updateItems(self):

View File

@ -55,7 +55,7 @@ class ROI(GraphicsObject):
self.rotateAllowed = True self.rotateAllowed = True
self.freeHandleMoved = False ## keep track of whether free handles have moved since last change signal was emitted. self.freeHandleMoved = False ## keep track of whether free handles have moved since last change signal was emitted.
self.mouseHovering = False
if pen is None: if pen is None:
pen = (255, 255, 255) pen = (255, 255, 255)
self.setPen(pen) self.setPen(pen)
@ -334,11 +334,22 @@ class ROI(GraphicsObject):
def hoverEvent(self, ev): def hoverEvent(self, ev):
if self.translatable and (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton): if self.translatable and (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton):
self.currentPen = fn.mkPen(255, 255, 0) self.setMouseHover(True)
self.sigHoverEvent.emit(self) self.sigHoverEvent.emit(self)
else:
self.setMouseHover(False)
def setMouseHover(self, hover):
## Inform the ROI that the mouse is(not) hovering over it
if self.mouseHovering == hover:
return
self.mouseHovering = hover
if hover:
self.currentPen = fn.mkPen(255, 255, 0)
else: else:
self.currentPen = self.pen self.currentPen = self.pen
self.update() self.update()
def mouseDragEvent(self, ev): def mouseDragEvent(self, ev):
if ev.isStart(): if ev.isStart():

View File

@ -16,6 +16,7 @@ from FileDialog import FileDialog
from pyqtgraph.GraphicsScene import GraphicsScene from pyqtgraph.GraphicsScene import GraphicsScene
import numpy as np import numpy as np
import pyqtgraph.functions as fn import pyqtgraph.functions as fn
import pyqtgraph.debug as debug
import pyqtgraph import pyqtgraph
__all__ = ['GraphicsView'] __all__ = ['GraphicsView']
@ -395,6 +396,11 @@ class GraphicsView(QtGui.QGraphicsView):
#self.pev = pev #self.pev = pev
#self.currentItem.mouseMoveEvent(pev) #self.currentItem.mouseMoveEvent(pev)
#def paintEvent(self, ev):
#prof = debug.Profiler('GraphicsView.paintEvent (0x%x)' % id(self))
#QtGui.QGraphicsView.paintEvent(self, ev)
#prof.finish()
def pixelSize(self): def pixelSize(self):
"""Return vector with the length and width of one view pixel in scene coordinates""" """Return vector with the length and width of one view pixel in scene coordinates"""