From 78d4bc08380a335ac7f8888bb2c00c03ba30393b Mon Sep 17 00:00:00 2001 From: Luke Campagnola <> Date: Wed, 4 Apr 2012 09:29:35 -0400 Subject: [PATCH] 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 --- __init__.py | 4 +-- examples/PlotSpeedTest.py | 15 +++++++--- examples/VideoSpeedTest.py | 2 ++ graphicsItems/GradientEditorItem.py | 14 ++++++++- graphicsItems/HistogramLUTItem.py | 44 +++++++++++++++++++++++------ graphicsItems/ImageItem.py | 3 ++ graphicsItems/InfiniteLine.py | 11 ++++++++ graphicsItems/LinearRegionItem.py | 16 ++++++++++- graphicsItems/PlotCurveItem.py | 2 ++ graphicsItems/PlotDataItem.py | 9 ++++-- graphicsItems/ROI.py | 15 ++++++++-- widgets/GraphicsView.py | 6 ++++ 12 files changed, 120 insertions(+), 21 deletions(-) diff --git a/__init__.py b/__init__.py index 398aa020..5c260d1a 100644 --- a/__init__.py +++ b/__init__.py @@ -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 useOpenGL = True 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 = { - '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 } diff --git a/examples/PlotSpeedTest.py b/examples/PlotSpeedTest.py index b695bd86..212734a1 100644 --- a/examples/PlotSpeedTest.py +++ b/examples/PlotSpeedTest.py @@ -1,14 +1,14 @@ #!/usr/bin/python # -*- coding: utf-8 -*- ## 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__), '..', '..')) from pyqtgraph.Qt import QtGui, QtCore import numpy as np import pyqtgraph as pg - +from pyqtgraph.ptime import time #QtGui.QApplication.setGraphicsSystem('raster') app = QtGui.QApplication([]) #mw = QtGui.QMainWindow() @@ -18,15 +18,22 @@ p = pg.plot() p.setRange(QtCore.QRectF(0, -10, 5000, 20)) p.setLabel('bottom', 'Index', units='B') 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)) ptr = 0 -lastTime = time.time() +lastTime = time() fps = None def update(): global curve, data, ptr, p, lastTime, fps curve.setData(data[ptr%10]) ptr += 1 - now = time.time() + now = time() dt = now - lastTime lastTime = now if fps is None: diff --git a/examples/VideoSpeedTest.py b/examples/VideoSpeedTest.py index 49d4c715..57d8aacc 100644 --- a/examples/VideoSpeedTest.py +++ b/examples/VideoSpeedTest.py @@ -25,6 +25,8 @@ win.show() ui.maxSpin1.setOpts(value=255, 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() ui.graphicsView.setCentralItem(vb) vb.setAspectLocked() diff --git a/graphicsItems/GradientEditorItem.py b/graphicsItems/GradientEditorItem.py index d3eaaf86..c06995c7 100644 --- a/graphicsItems/GradientEditorItem.py +++ b/graphicsItems/GradientEditorItem.py @@ -467,7 +467,19 @@ class GradientEditorItem(TickSliderItem): 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): TickSliderItem.mouseReleaseEvent(self, ev) diff --git a/graphicsItems/HistogramLUTItem.py b/graphicsItems/HistogramLUTItem.py index 19599720..d51bbd10 100644 --- a/graphicsItems/HistogramLUTItem.py +++ b/graphicsItems/HistogramLUTItem.py @@ -15,17 +15,30 @@ from GridItem import * from pyqtgraph.Point import Point import pyqtgraph.functions as fn import numpy as np +import pyqtgraph.debug as debug __all__ = ['HistogramLUTItem'] 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) sigLevelsChanged = 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) self.lut = None self.imageItem = None @@ -61,6 +74,8 @@ class HistogramLUTItem(GraphicsWidget): self.vb.sigRangeChanged.connect(self.viewRangeChanged) self.plot = PlotDataItem() self.plot.rotate(90) + self.fillHistogram(fillHistogram) + self.vb.addItem(self.plot) self.autoHistogramRange() @@ -68,7 +83,13 @@ class HistogramLUTItem(GraphicsWidget): self.setImageItem(image) #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): #return QtCore.QSizeF(115, 200) @@ -129,21 +150,24 @@ class HistogramLUTItem(GraphicsWidget): def gradientChanged(self): 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 #if self.imageItem is not None: #self.imageItem.setLookupTable(self.gradient.getLookupTable(512)) 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 img.dtype == np.uint8: n = 256 else: n = 512 if self.lut is None: - self.lut = self.gradient.getLookupTable(n) + self.lut = self.gradient.getLookupTable(n, alpha=alpha) return self.lut def regionChanged(self): @@ -159,17 +183,19 @@ class HistogramLUTItem(GraphicsWidget): self.update() def imageChanged(self, autoLevel=False, autoRange=False): + prof = debug.Profiler('HistogramLUTItem.imageChanged', disabled=True) h = self.imageItem.getHistogram() + prof.mark('get histogram') if h[0] is None: return - self.plot.setData(*h, fillLevel=0.0, brush=(100, 100, 200)) + self.plot.setData(*h) + prof.mark('set plot') if autoLevel: mn = h[0][0] mx = h[0][-1] self.region.setRegion([mn, mx]) - #self.updateRange() - #if autoRange: - #self.updateRange() + prof.mark('set region') + prof.finish() def getLevels(self): return self.region.getRegion() diff --git a/graphicsItems/ImageItem.py b/graphicsItems/ImageItem.py index 89bd6b64..423183ab 100644 --- a/graphicsItems/ImageItem.py +++ b/graphicsItems/ImageItem.py @@ -224,10 +224,13 @@ class ImageItem(GraphicsObject): return if self.qimage is None: self.render() + prof.mark('render QImage') if self.paintMode is not None: p.setCompositionMode(self.paintMode) + prof.mark('set comp mode') p.drawImage(QtCore.QPointF(0,0), self.qimage) + prof.mark('p.drawImage') if self.border is not None: p.setPen(self.border) p.drawRect(self.boundingRect()) diff --git a/graphicsItems/InfiniteLine.py b/graphicsItems/InfiniteLine.py index cffbeb52..a886876b 100644 --- a/graphicsItems/InfiniteLine.py +++ b/graphicsItems/InfiniteLine.py @@ -35,6 +35,7 @@ class InfiniteLine(UIGraphicsItem): self.maxRange = bounds self.moving = False self.setMovable(movable) + self.mouseHovering = False self.p = [0, 0] self.setAngle(angle) if pos is None: @@ -222,6 +223,16 @@ class InfiniteLine(UIGraphicsItem): def hoverEvent(self, ev): 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) else: self.currentPen = self.pen diff --git a/graphicsItems/LinearRegionItem.py b/graphicsItems/LinearRegionItem.py index 1b546cb7..bdffd075 100644 --- a/graphicsItems/LinearRegionItem.py +++ b/graphicsItems/LinearRegionItem.py @@ -2,6 +2,7 @@ from pyqtgraph.Qt import QtGui, QtCore from UIGraphicsItem import UIGraphicsItem from InfiniteLine import InfiniteLine import pyqtgraph.functions as fn +import pyqtgraph.debug as debug __all__ = ['LinearRegionItem'] @@ -24,6 +25,7 @@ class LinearRegionItem(UIGraphicsItem): self.bounds = QtCore.QRectF() self.blockLineSignal = False self.moving = False + self.mouseHovering = False if orientation == LinearRegionItem.Horizontal: self.lines = [ @@ -94,9 +96,11 @@ class LinearRegionItem(UIGraphicsItem): return br.normalized() def paint(self, p, *args): + #prof = debug.Profiler('LinearRegionItem.paint') UIGraphicsItem.paint(self, p, *args) p.setBrush(self.currentBrush) p.drawRect(self.boundingRect()) + #prof.finish() def dataBounds(self, axis, frac=1.0): if axis == self.orientation: @@ -197,13 +201,23 @@ class LinearRegionItem(UIGraphicsItem): def hoverEvent(self, ev): 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.setAlpha(c.alpha() * 2) self.currentBrush = fn.mkBrush(c) else: self.currentBrush = self.brush self.update() - + #def hoverEnterEvent(self, ev): #print "rgn hover enter" #ev.ignore() diff --git a/graphicsItems/PlotCurveItem.py b/graphicsItems/PlotCurveItem.py index 12ac044c..bc3629d2 100644 --- a/graphicsItems/PlotCurveItem.py +++ b/graphicsItems/PlotCurveItem.py @@ -352,7 +352,9 @@ class PlotCurveItem(GraphicsObject): p2.closeSubpath() self.fillPath = p2 + prof.mark('generate fill path') p.fillPath(self.fillPath, self.opts['brush']) + prof.mark('draw fill path') ## Copy pens and apply alpha adjustment diff --git a/graphicsItems/PlotDataItem.py b/graphicsItems/PlotDataItem.py index 1938cd50..22c62d1a 100644 --- a/graphicsItems/PlotDataItem.py +++ b/graphicsItems/PlotDataItem.py @@ -11,6 +11,7 @@ from ScatterPlotItem import ScatterPlotItem import numpy as np import scipy import pyqtgraph.functions as fn +import pyqtgraph.debug as debug class PlotDataItem(GraphicsObject): """GraphicsItem for displaying plot curves, scatter plots, or both.""" @@ -215,7 +216,7 @@ class PlotDataItem(GraphicsObject): """ #self.clear() - + prof = debug.Profiler('PlotDataItem.setData (0x%x)' % id(self), disabled=True) y = None x = None if len(args) == 1: @@ -262,7 +263,7 @@ class PlotDataItem(GraphicsObject): if 'y' in kargs: y = kargs['y'] - + prof.mark('interpret data') ## pull in all style arguments. ## 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.xDisp = None self.yDisp = None + prof.mark('set data') self.updateItems() + prof.mark('update items') view = self.getViewBox() if view is not None: view.itemBoundsChanged(self) ## inform view so it can update its range if it wants self.sigPlotChanged.emit(self) + prof.mark('emit') + prof.finish() def updateItems(self): diff --git a/graphicsItems/ROI.py b/graphicsItems/ROI.py index a81b4c13..5c5f930a 100644 --- a/graphicsItems/ROI.py +++ b/graphicsItems/ROI.py @@ -55,7 +55,7 @@ class ROI(GraphicsObject): self.rotateAllowed = True self.freeHandleMoved = False ## keep track of whether free handles have moved since last change signal was emitted. - + self.mouseHovering = False if pen is None: pen = (255, 255, 255) self.setPen(pen) @@ -334,11 +334,22 @@ class ROI(GraphicsObject): def hoverEvent(self, ev): 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) + 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: self.currentPen = self.pen self.update() + def mouseDragEvent(self, ev): if ev.isStart(): diff --git a/widgets/GraphicsView.py b/widgets/GraphicsView.py index 3d3be2da..92a5d5e4 100644 --- a/widgets/GraphicsView.py +++ b/widgets/GraphicsView.py @@ -16,6 +16,7 @@ from FileDialog import FileDialog from pyqtgraph.GraphicsScene import GraphicsScene import numpy as np import pyqtgraph.functions as fn +import pyqtgraph.debug as debug import pyqtgraph __all__ = ['GraphicsView'] @@ -395,6 +396,11 @@ class GraphicsView(QtGui.QGraphicsView): #self.pev = 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): """Return vector with the length and width of one view pixel in scene coordinates"""