Profilers controllable via PYQTGRAPHPROFILE.

A new function profiling system is implemented.  Most importantly, this
allows one to profile various internal functions directly by setting the
`PYQTGRAPHPROFILE` environment variable to a comma separated list of
function and method names, e.g.

    PYQTGRAPHPROFILE=functions.makeARGB,ImageItem.render \
        python -mexamples

Specifically, items in `PYQTGRAPHPROFILE` must be of the form
`classname.methodname` or `dotted_module_name.functionname`, with the
initial "pyqtgraph." stripped from the dotted module name.

Moreover, the overhead of inactive profilers has been kept minimal: an
introspective check of the caller's name (only if `PYQTGRAPHPROFILE` is
set) and a trivial function (not method) call per profiler call.

The new profilers rely on `sys._getframe` to find the caller's name,
although the previous system (passing the caller's name explicitely)
could certainly have been kept instead.

Finally the API of profilers has been changed: register a
profiling point simply by calling the profiler, and profilers are
automatically flushed on garbage collection.  See the docstring of
`pyqtgraph.debug.Profiler` for more details.
This commit is contained in:
Antony Lee 2013-11-26 22:16:13 -08:00
parent c1f72b29c6
commit f136b33033
13 changed files with 203 additions and 214 deletions

View File

@ -5,6 +5,8 @@ Copyright 2010 Luke Campagnola
Distributed under MIT/X11 license. See license.txt for more infomation. Distributed under MIT/X11 license. See license.txt for more infomation.
""" """
from __future__ import print_function
import sys, traceback, time, gc, re, types, weakref, inspect, os, cProfile import sys, traceback, time, gc, re, types, weakref, inspect, os, cProfile
from . import ptime from . import ptime
from numpy import ndarray from numpy import ndarray
@ -365,83 +367,98 @@ class GarbageWatcher(object):
return self.objs[item] return self.objs[item]
class Profiler:
class Profiler(object):
"""Simple profiler allowing measurement of multiple time intervals. """Simple profiler allowing measurement of multiple time intervals.
Arguments:
msg: message to print at start and finish of profiling By default, profilers are disabled. To enable profiling, set the
disabled: If true, profiler does nothing (so you can leave it in place) environment variable `PYQTGRAPHPROFILE` to a comma-separated list of
delayed: If true, all messages are printed after call to finish() fully-qualified names of profiled functions.
(this can result in more accurate time step measurements)
globalDelay: if True, all nested profilers delay printing until the top level finishes Calling a profiler registers a message (defaulting to an increasing
counter) that contains the time elapsed since the last call. When the
profiler is about to be garbage-collected, the messages are passed to the
outer profiler if one is running, or printed to stdout otherwise.
If `delayed` is set to False, messages are immediately printed instead.
Example: Example:
prof = Profiler('Function') def function(...):
profiler = Profiler()
... do stuff ... ... do stuff ...
prof.mark('did stuff') profiler('did stuff')
... do other stuff ... ... do other stuff ...
prof.mark('did other stuff') profiler('did other stuff')
prof.finish() # profiler is garbage-collected and flushed at function end
If this function is a method of class C, setting `PYQTGRAPHPROFILE` to
"C.function" (without the module name) will enable this profiler.
For regular functions, use the qualified name of the function, stripping
only the initial "pyqtgraph." prefix from the module.
""" """
depth = 0
msgs = []
def __init__(self, msg="Profiler", disabled=False, delayed=True, globalDelay=True): _profilers = os.environ.get("PYQTGRAPHPROFILE", "")
self.disabled = disabled _depth = 0
if disabled: _msgs = []
return
self.markCount = 0 if _profilers:
self.finished = False _profilers = _profilers.split(",")
self.depth = Profiler.depth def __new__(cls, delayed=True):
Profiler.depth += 1 """Optionally create a new profiler based on caller's qualname.
if not globalDelay: """
self.msgs = [] # determine the qualified name of the caller function
self.delayed = delayed caller_frame = sys._getframe(1)
self.msg = " "*self.depth + msg try:
msg2 = self.msg + " >>> Started" caller_object_type = type(caller_frame.f_locals["self"])
if self.delayed: except KeyError: # we are in a regular function
self.msgs.append(msg2) qualifier = caller_frame.f_globals["__name__"].split(".", 1)[1]
else: # we are in a method
qualifier = caller_object_type.__name__
func_qualname = qualifier + "." + caller_frame.f_code.co_name
if func_qualname not in cls._profilers: # don't do anything
return lambda msg=None: None
# create an actual profiling object
cls._depth += 1
obj = super(Profiler, cls).__new__(cls)
obj._name = func_qualname
obj._delayed = delayed
obj._markCount = 0
obj._firstTime = obj._lastTime = ptime.time()
obj._newMsg("> Entering " + func_qualname)
return obj
else: else:
print(msg2) def __new__(cls, delayed=True):
self.t0 = ptime.time() return lambda msg=None: None
self.t1 = self.t0
def mark(self, msg=None):
if self.disabled:
return
def __call__(self, msg=None):
"""Register or print a new message with timing information.
"""
if msg is None: if msg is None:
msg = str(self.markCount) msg = str(self._markCount)
self.markCount += 1 self._markCount += 1
newTime = ptime.time()
self._newMsg(
msg + ": " + str((newTime - self._lastTime) * 1000) + "ms")
self._lastTime = newTime
t1 = ptime.time() def _newMsg(self, msg):
msg2 = " "+self.msg+" "+msg+" "+"%gms" % ((t1-self.t1)*1000) msg = " " * (self._depth - 1) + msg
if self.delayed: if self._delayed:
self.msgs.append(msg2) self._msgs.append(msg)
else:
print(msg2)
self.t1 = ptime.time() ## don't measure time it took to print
def finish(self, msg=None):
if self.disabled or self.finished:
return
if msg is not None:
self.mark(msg)
t1 = ptime.time()
msg = self.msg + ' <<< Finished, total time: %gms' % ((t1-self.t0)*1000)
if self.delayed:
self.msgs.append(msg)
if self.depth == 0:
for line in self.msgs:
print(line)
Profiler.msgs = []
else: else:
print(msg) print(msg)
Profiler.depth = self.depth
self.finished = True
def __del__(self):
"""Add a final message; flush the message list if no parent profiler.
"""
self._newMsg("< Exiting " + self._name + ", total time: " +
str((ptime.time() - self._firstTime) * 1000) + "ms")
type(self)._depth -= 1
if not self._depth and self._msgs:
print("\n".join(self._msgs))
type(self)._msgs = []
def profile(code, name='profile_run', sort='cumulative', num=30): def profile(code, name='profile_run', sort='cumulative', num=30):

View File

@ -156,7 +156,7 @@ def _generateItemSvg(item, nodes=None, root=None):
## ##
## Both 2 and 3 can be addressed by drawing all items in world coordinates. ## Both 2 and 3 can be addressed by drawing all items in world coordinates.
prof = pg.debug.Profiler('generateItemSvg %s' % str(item), disabled=True) profiler = pg.debug.Profiler()
if nodes is None: ## nodes maps all node IDs to their XML element. if nodes is None: ## nodes maps all node IDs to their XML element.
## this allows us to ensure all elements receive unique names. ## this allows us to ensure all elements receive unique names.
@ -235,12 +235,12 @@ def _generateItemSvg(item, nodes=None, root=None):
print(doc.toxml()) print(doc.toxml())
raise raise
prof.mark('render') profiler('render')
## Get rid of group transformation matrices by applying ## Get rid of group transformation matrices by applying
## transformation to inner coordinates ## transformation to inner coordinates
correctCoordinates(g1, item) correctCoordinates(g1, item)
prof.mark('correct') profiler('correct')
## make sure g1 has the transformation matrix ## make sure g1 has the transformation matrix
#m = (tr.m11(), tr.m12(), tr.m21(), tr.m22(), tr.m31(), tr.m32()) #m = (tr.m11(), tr.m12(), tr.m21(), tr.m22(), tr.m31(), tr.m32())
#g1.setAttribute('transform', "matrix(%f,%f,%f,%f,%f,%f)" % m) #g1.setAttribute('transform', "matrix(%f,%f,%f,%f,%f,%f)" % m)
@ -290,7 +290,7 @@ def _generateItemSvg(item, nodes=None, root=None):
childGroup = g1.ownerDocument.createElement('g') childGroup = g1.ownerDocument.createElement('g')
childGroup.setAttribute('clip-path', 'url(#%s)' % clip) childGroup.setAttribute('clip-path', 'url(#%s)' % clip)
g1.appendChild(childGroup) g1.appendChild(childGroup)
prof.mark('clipping') profiler('clipping')
## Add all child items as sub-elements. ## Add all child items as sub-elements.
childs.sort(key=lambda c: c.zValue()) childs.sort(key=lambda c: c.zValue())
@ -299,8 +299,7 @@ def _generateItemSvg(item, nodes=None, root=None):
if cg is None: if cg is None:
continue continue
childGroup.appendChild(cg) ### this isn't quite right--some items draw below their parent (good enough for now) childGroup.appendChild(cg) ### this isn't quite right--some items draw below their parent (good enough for now)
prof.mark('children') profiler('children')
prof.finish()
return g1 return g1
def correctCoordinates(node, item): def correctCoordinates(node, item):

View File

@ -775,7 +775,7 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
is BGRA). is BGRA).
============ ================================================================================== ============ ==================================================================================
""" """
prof = debug.Profiler('functions.makeARGB', disabled=True) profile = debug.Profiler()
if lut is not None and not isinstance(lut, np.ndarray): if lut is not None and not isinstance(lut, np.ndarray):
lut = np.array(lut) lut = np.array(lut)
@ -795,7 +795,7 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
print(levels) print(levels)
raise Exception("levels argument must be 1D or 2D.") raise Exception("levels argument must be 1D or 2D.")
prof.mark('1') profile()
if scale is None: if scale is None:
if lut is not None: if lut is not None:
@ -822,8 +822,8 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
if minVal == maxVal: if minVal == maxVal:
maxVal += 1e-16 maxVal += 1e-16
data = rescaleData(data, scale/(maxVal-minVal), minVal, dtype=int) data = rescaleData(data, scale/(maxVal-minVal), minVal, dtype=int)
prof.mark('2')
profile()
## apply LUT if given ## apply LUT if given
if lut is not None: if lut is not None:
@ -831,13 +831,13 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
else: else:
if data.dtype is not np.ubyte: if data.dtype is not np.ubyte:
data = np.clip(data, 0, 255).astype(np.ubyte) data = np.clip(data, 0, 255).astype(np.ubyte)
prof.mark('3')
profile()
## copy data into ARGB ordered array ## copy data into ARGB ordered array
imgData = np.empty(data.shape[:2]+(4,), dtype=np.ubyte) imgData = np.empty(data.shape[:2]+(4,), dtype=np.ubyte)
prof.mark('4') profile()
if useRGBA: if useRGBA:
order = [0,1,2,3] ## array comes out RGBA order = [0,1,2,3] ## array comes out RGBA
@ -857,7 +857,7 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
for i in range(0, data.shape[2]): for i in range(0, data.shape[2]):
imgData[..., i] = data[..., order[i]] imgData[..., i] = data[..., order[i]]
prof.mark('5') profile()
if data.ndim == 2 or data.shape[2] == 3: if data.ndim == 2 or data.shape[2] == 3:
alpha = False alpha = False
@ -865,9 +865,7 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
else: else:
alpha = True alpha = True
prof.mark('6') profile()
prof.finish()
return imgData, alpha return imgData, alpha
@ -898,7 +896,7 @@ def makeQImage(imgData, alpha=None, copy=True, transpose=True):
=========== =================================================================== =========== ===================================================================
""" """
## create QImage from buffer ## create QImage from buffer
prof = debug.Profiler('functions.makeQImage', disabled=True) profile = debug.Profiler()
## If we didn't explicitly specify alpha, check the array shape. ## If we didn't explicitly specify alpha, check the array shape.
if alpha is None: if alpha is None:
@ -923,6 +921,8 @@ def makeQImage(imgData, alpha=None, copy=True, transpose=True):
if transpose: if transpose:
imgData = imgData.transpose((1, 0, 2)) ## QImage expects the row/column order to be opposite imgData = imgData.transpose((1, 0, 2)) ## QImage expects the row/column order to be opposite
profile()
if not imgData.flags['C_CONTIGUOUS']: if not imgData.flags['C_CONTIGUOUS']:
if copy is False: if copy is False:
extra = ' (try setting transpose=False)' if transpose else '' extra = ' (try setting transpose=False)' if transpose else ''
@ -963,11 +963,10 @@ def makeQImage(imgData, alpha=None, copy=True, transpose=True):
#except AttributeError: ## happens when image data is non-contiguous #except AttributeError: ## happens when image data is non-contiguous
#buf = imgData.data #buf = imgData.data
#prof.mark('1') #profiler()
#qimage = QtGui.QImage(buf, imgData.shape[1], imgData.shape[0], imgFormat) #qimage = QtGui.QImage(buf, imgData.shape[1], imgData.shape[0], imgFormat)
#prof.mark('2') #profiler()
#qimage.data = imgData #qimage.data = imgData
#prof.finish()
#return qimage #return qimage
def imageToArray(img, copy=False, transpose=True): def imageToArray(img, copy=False, transpose=True):
@ -1087,16 +1086,16 @@ def arrayToQPath(x, y, connect='all'):
path = QtGui.QPainterPath() path = QtGui.QPainterPath()
#prof = debug.Profiler('PlotCurveItem.generatePath', disabled=True) #profiler = debug.Profiler()
n = x.shape[0] n = x.shape[0]
# create empty array, pad with extra space on either end # create empty array, pad with extra space on either end
arr = np.empty(n+2, dtype=[('x', '>f8'), ('y', '>f8'), ('c', '>i4')]) arr = np.empty(n+2, dtype=[('x', '>f8'), ('y', '>f8'), ('c', '>i4')])
# write first two integers # write first two integers
#prof.mark('allocate empty') #profiler('allocate empty')
byteview = arr.view(dtype=np.ubyte) byteview = arr.view(dtype=np.ubyte)
byteview[:12] = 0 byteview[:12] = 0
byteview.data[12:20] = struct.pack('>ii', n, 0) byteview.data[12:20] = struct.pack('>ii', n, 0)
#prof.mark('pack header') #profiler('pack header')
# Fill array with vertex values # Fill array with vertex values
arr[1:-1]['x'] = x arr[1:-1]['x'] = x
arr[1:-1]['y'] = y arr[1:-1]['y'] = y
@ -1117,11 +1116,11 @@ def arrayToQPath(x, y, connect='all'):
else: else:
raise Exception('connect argument must be "all", "pairs", or array') raise Exception('connect argument must be "all", "pairs", or array')
#prof.mark('fill array') #profiler('fill array')
# write last 0 # write last 0
lastInd = 20*(n+1) lastInd = 20*(n+1)
byteview.data[lastInd:lastInd+4] = struct.pack('>i', 0) byteview.data[lastInd:lastInd+4] = struct.pack('>i', 0)
#prof.mark('footer') #profiler('footer')
# create datastream object and stream into path # create datastream object and stream into path
## Avoiding this method because QByteArray(str) leaks memory in PySide ## Avoiding this method because QByteArray(str) leaks memory in PySide
@ -1132,13 +1131,11 @@ def arrayToQPath(x, y, connect='all'):
buf = QtCore.QByteArray.fromRawData(path.strn) buf = QtCore.QByteArray.fromRawData(path.strn)
except TypeError: except TypeError:
buf = QtCore.QByteArray(bytes(path.strn)) buf = QtCore.QByteArray(bytes(path.strn))
#prof.mark('create buffer') #profiler('create buffer')
ds = QtCore.QDataStream(buf) ds = QtCore.QDataStream(buf)
ds >> path ds >> path
#prof.mark('load') #profiler('load')
#prof.finish()
return path return path
@ -1865,7 +1862,7 @@ def isosurface(data, level):
faces = np.empty((totFaces, 3), dtype=np.uint32) faces = np.empty((totFaces, 3), dtype=np.uint32)
ptr = 0 ptr = 0
#import debug #import debug
#p = debug.Profiler('isosurface', disabled=False) #p = debug.Profiler()
## this helps speed up an indexing operation later on ## this helps speed up an indexing operation later on
cs = np.array(cutEdges.strides)//cutEdges.itemsize cs = np.array(cutEdges.strides)//cutEdges.itemsize
@ -1877,32 +1874,32 @@ def isosurface(data, level):
for i in range(1,6): for i in range(1,6):
### expensive: ### expensive:
#p.mark('1') #profiler()
cells = np.argwhere(nFaces == i) ## all cells which require i faces (argwhere is expensive) cells = np.argwhere(nFaces == i) ## all cells which require i faces (argwhere is expensive)
#p.mark('2') #profiler()
if cells.shape[0] == 0: if cells.shape[0] == 0:
continue continue
#cellInds = index[(cells*ins[np.newaxis,:]).sum(axis=1)] #cellInds = index[(cells*ins[np.newaxis,:]).sum(axis=1)]
cellInds = index[cells[:,0], cells[:,1], cells[:,2]] ## index values of cells to process for this round cellInds = index[cells[:,0], cells[:,1], cells[:,2]] ## index values of cells to process for this round
#p.mark('3') #profiler()
### expensive: ### expensive:
verts = faceShiftTables[i][cellInds] verts = faceShiftTables[i][cellInds]
#p.mark('4') #profiler()
verts[...,:3] += cells[:,np.newaxis,np.newaxis,:] ## we now have indexes into cutEdges verts[...,:3] += cells[:,np.newaxis,np.newaxis,:] ## we now have indexes into cutEdges
verts = verts.reshape((verts.shape[0]*i,)+verts.shape[2:]) verts = verts.reshape((verts.shape[0]*i,)+verts.shape[2:])
#p.mark('5') #profiler()
### expensive: ### expensive:
#print verts.shape #print verts.shape
verts = (verts * cs[np.newaxis, np.newaxis, :]).sum(axis=2) verts = (verts * cs[np.newaxis, np.newaxis, :]).sum(axis=2)
#vertInds = cutEdges[verts[...,0], verts[...,1], verts[...,2], verts[...,3]] ## and these are the vertex indexes we want. #vertInds = cutEdges[verts[...,0], verts[...,1], verts[...,2], verts[...,3]] ## and these are the vertex indexes we want.
vertInds = cutEdges[verts] vertInds = cutEdges[verts]
#p.mark('6') #profiler()
nv = vertInds.shape[0] nv = vertInds.shape[0]
#p.mark('7') #profiler()
faces[ptr:ptr+nv] = vertInds #.reshape((nv, 3)) faces[ptr:ptr+nv] = vertInds #.reshape((nv, 3))
#p.mark('8') #profiler()
ptr += nv ptr += nv
return vertexes, faces return vertexes, faces

View File

@ -404,25 +404,22 @@ class AxisItem(GraphicsWidget):
return self.mapRectFromParent(self.geometry()) | linkedView.mapRectToItem(self, linkedView.boundingRect()) return self.mapRectFromParent(self.geometry()) | linkedView.mapRectToItem(self, linkedView.boundingRect())
def paint(self, p, opt, widget): def paint(self, p, opt, widget):
prof = debug.Profiler('AxisItem.paint', disabled=True) profiler = debug.Profiler()
if self.picture is None: if self.picture is None:
try: try:
picture = QtGui.QPicture() picture = QtGui.QPicture()
painter = QtGui.QPainter(picture) painter = QtGui.QPainter(picture)
specs = self.generateDrawSpecs(painter) specs = self.generateDrawSpecs(painter)
prof.mark('generate specs') profiler('generate specs')
if specs is not None: if specs is not None:
self.drawPicture(painter, *specs) self.drawPicture(painter, *specs)
prof.mark('draw picture') profiler('draw picture')
finally: finally:
painter.end() painter.end()
self.picture = picture self.picture = picture
#p.setRenderHint(p.Antialiasing, False) ## Sometimes we get a segfault here ??? #p.setRenderHint(p.Antialiasing, False) ## Sometimes we get a segfault here ???
#p.setRenderHint(p.TextAntialiasing, True) #p.setRenderHint(p.TextAntialiasing, True)
self.picture.play(p) self.picture.play(p)
prof.finish()
def setTicks(self, ticks): def setTicks(self, ticks):
"""Explicitly determine which ticks to display. """Explicitly determine which ticks to display.
@ -626,7 +623,7 @@ class AxisItem(GraphicsWidget):
be drawn, then generates from this a set of drawing commands to be be drawn, then generates from this a set of drawing commands to be
interpreted by drawPicture(). interpreted by drawPicture().
""" """
prof = debug.Profiler("AxisItem.generateDrawSpecs", disabled=True) profiler = debug.Profiler()
#bounds = self.boundingRect() #bounds = self.boundingRect()
bounds = self.mapRectFromParent(self.geometry()) bounds = self.mapRectFromParent(self.geometry())
@ -706,7 +703,7 @@ class AxisItem(GraphicsWidget):
xMin = min(xRange) xMin = min(xRange)
xMax = max(xRange) xMax = max(xRange)
prof.mark('init') profiler('init')
tickPositions = [] # remembers positions of previously drawn ticks tickPositions = [] # remembers positions of previously drawn ticks
@ -744,7 +741,7 @@ class AxisItem(GraphicsWidget):
color.setAlpha(lineAlpha) color.setAlpha(lineAlpha)
tickPen.setColor(color) tickPen.setColor(color)
tickSpecs.append((tickPen, Point(p1), Point(p2))) tickSpecs.append((tickPen, Point(p1), Point(p2)))
prof.mark('compute ticks') profiler('compute ticks')
## This is where the long axis line should be drawn ## This is where the long axis line should be drawn
@ -857,7 +854,7 @@ class AxisItem(GraphicsWidget):
#p.setPen(self.pen()) #p.setPen(self.pen())
#p.drawText(rect, textFlags, vstr) #p.drawText(rect, textFlags, vstr)
textSpecs.append((rect, textFlags, vstr)) textSpecs.append((rect, textFlags, vstr))
prof.mark('compute text') profiler('compute text')
## update max text size if needed. ## update max text size if needed.
self._updateMaxTextSize(textSize2) self._updateMaxTextSize(textSize2)
@ -865,7 +862,7 @@ class AxisItem(GraphicsWidget):
return (axisSpec, tickSpecs, textSpecs) return (axisSpec, tickSpecs, textSpecs)
def drawPicture(self, p, axisSpec, tickSpecs, textSpecs): def drawPicture(self, p, axisSpec, tickSpecs, textSpecs):
prof = debug.Profiler("AxisItem.drawPicture", disabled=True) profiler = debug.Profiler()
p.setRenderHint(p.Antialiasing, False) p.setRenderHint(p.Antialiasing, False)
p.setRenderHint(p.TextAntialiasing, True) p.setRenderHint(p.TextAntialiasing, True)
@ -880,7 +877,7 @@ class AxisItem(GraphicsWidget):
for pen, p1, p2 in tickSpecs: for pen, p1, p2 in tickSpecs:
p.setPen(pen) p.setPen(pen)
p.drawLine(p1, p2) p.drawLine(p1, p2)
prof.mark('draw ticks') profiler('draw ticks')
## Draw all text ## Draw all text
if self.tickFont is not None: if self.tickFont is not None:
@ -889,9 +886,7 @@ class AxisItem(GraphicsWidget):
for rect, flags, text in textSpecs: for rect, flags, text in textSpecs:
p.drawText(rect, flags, text) p.drawText(rect, flags, text)
#p.drawRect(rect) #p.drawRect(rect)
profiler('draw text')
prof.mark('draw text')
prof.finish()
def show(self): def show(self):

View File

@ -184,19 +184,18 @@ 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) profiler = debug.Profiler()
h = self.imageItem.getHistogram() h = self.imageItem.getHistogram()
prof.mark('get histogram') profiler('get histogram')
if h[0] is None: if h[0] is None:
return return
self.plot.setData(*h) self.plot.setData(*h)
prof.mark('set plot') profiler('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])
prof.mark('set region') profiler('set region')
prof.finish()
def getLevels(self): def getLevels(self):
return self.region.getRegion() return self.region.getRegion()

View File

@ -188,7 +188,7 @@ class ImageItem(GraphicsObject):
border Sets the pen used when drawing the image border. Default is None. border Sets the pen used when drawing the image border. Default is None.
================= ========================================================================= ================= =========================================================================
""" """
prof = debug.Profiler('ImageItem.setImage', disabled=True) profile = debug.Profiler()
gotNewData = False gotNewData = False
if image is None: if image is None:
@ -202,7 +202,7 @@ class ImageItem(GraphicsObject):
self.prepareGeometryChange() self.prepareGeometryChange()
self.informViewBoundsChanged() self.informViewBoundsChanged()
prof.mark('1') profile()
if autoLevels is None: if autoLevels is None:
if 'levels' in kargs: if 'levels' in kargs:
@ -218,23 +218,22 @@ class ImageItem(GraphicsObject):
mn = 0 mn = 0
mx = 255 mx = 255
kargs['levels'] = [mn,mx] kargs['levels'] = [mn,mx]
prof.mark('2')
profile()
self.setOpts(update=False, **kargs) self.setOpts(update=False, **kargs)
prof.mark('3')
profile()
self.qimage = None self.qimage = None
self.update() self.update()
prof.mark('4')
profile()
if gotNewData: if gotNewData:
self.sigImageChanged.emit() self.sigImageChanged.emit()
prof.finish()
def updateImage(self, *args, **kargs): def updateImage(self, *args, **kargs):
## used for re-rendering qimage from self.image. ## used for re-rendering qimage from self.image.
@ -250,7 +249,7 @@ class ImageItem(GraphicsObject):
def render(self): def render(self):
prof = debug.Profiler('ImageItem.render', disabled=True) profile = debug.Profiler()
if self.image is None or self.image.size == 0: if self.image is None or self.image.size == 0:
return return
if isinstance(self.lut, collections.Callable): if isinstance(self.lut, collections.Callable):
@ -262,28 +261,25 @@ class ImageItem(GraphicsObject):
argb, alpha = fn.makeARGB(self.image.transpose((1, 0, 2)[:self.image.ndim]), lut=lut, levels=self.levels) argb, alpha = fn.makeARGB(self.image.transpose((1, 0, 2)[:self.image.ndim]), lut=lut, levels=self.levels)
self.qimage = fn.makeQImage(argb, alpha, transpose=False) self.qimage = fn.makeQImage(argb, alpha, transpose=False)
prof.finish()
def paint(self, p, *args): def paint(self, p, *args):
prof = debug.Profiler('ImageItem.paint', disabled=True) profile = debug.Profiler()
if self.image is None: if self.image is None:
return return
if self.qimage is None: if self.qimage is None:
self.render() self.render()
if self.qimage is None: if self.qimage is None:
return return
prof.mark('render QImage') profile('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') profile('set comp mode')
p.drawImage(QtCore.QPointF(0,0), self.qimage) p.drawImage(QtCore.QPointF(0,0), self.qimage)
prof.mark('p.drawImage') profile('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())
prof.finish()
def save(self, fileName, *args): def save(self, fileName, *args):
"""Save this image to file. Note that this saves the visible image (after scale/color changes), not the original data.""" """Save this image to file. Note that this saves the visible image (after scale/color changes), not the original data."""

View File

@ -140,12 +140,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') profiler = debug.Profiler()
UIGraphicsItem.paint(self, p, *args) UIGraphicsItem.paint(self, p, *args)
p.setBrush(self.currentBrush) p.setBrush(self.currentBrush)
p.setPen(fn.mkPen(None)) p.setPen(fn.mkPen(None))
p.drawRect(self.boundingRect()) p.drawRect(self.boundingRect())
#prof.finish()
def dataBounds(self, axis, frac=1.0, orthoRange=None): def dataBounds(self, axis, frac=1.0, orthoRange=None):
if axis == self.orientation: if axis == self.orientation:

View File

@ -281,7 +281,7 @@ class PlotCurveItem(GraphicsObject):
self.updateData(*args, **kargs) self.updateData(*args, **kargs)
def updateData(self, *args, **kargs): def updateData(self, *args, **kargs):
prof = debug.Profiler('PlotCurveItem.updateData', disabled=True) profiler = debug.Profiler()
if len(args) == 1: if len(args) == 1:
kargs['y'] = args[0] kargs['y'] = args[0]
@ -304,7 +304,7 @@ class PlotCurveItem(GraphicsObject):
if 'complex' in str(data.dtype): if 'complex' in str(data.dtype):
raise Exception("Can not plot complex data types.") raise Exception("Can not plot complex data types.")
prof.mark("data checks") profiler("data checks")
#self.setCacheMode(QtGui.QGraphicsItem.NoCache) ## Disabling and re-enabling the cache works around a bug in Qt 4.6 causing the cached results to display incorrectly #self.setCacheMode(QtGui.QGraphicsItem.NoCache) ## Disabling and re-enabling the cache works around a bug in Qt 4.6 causing the cached results to display incorrectly
## Test this bug with test_PlotWidget and zoom in on the animated plot ## Test this bug with test_PlotWidget and zoom in on the animated plot
@ -314,7 +314,7 @@ class PlotCurveItem(GraphicsObject):
self.yData = kargs['y'].view(np.ndarray) self.yData = kargs['y'].view(np.ndarray)
self.xData = kargs['x'].view(np.ndarray) self.xData = kargs['x'].view(np.ndarray)
prof.mark('copy') profiler('copy')
if 'stepMode' in kargs: if 'stepMode' in kargs:
self.opts['stepMode'] = kargs['stepMode'] self.opts['stepMode'] = kargs['stepMode']
@ -346,12 +346,11 @@ class PlotCurveItem(GraphicsObject):
self.opts['antialias'] = kargs['antialias'] self.opts['antialias'] = kargs['antialias']
prof.mark('set') profiler('set')
self.update() self.update()
prof.mark('update') profiler('update')
self.sigPlotChanged.emit(self) self.sigPlotChanged.emit(self)
prof.mark('emit') profiler('emit')
prof.finish()
def generatePath(self, x, y): def generatePath(self, x, y):
if self.opts['stepMode']: if self.opts['stepMode']:
@ -387,7 +386,7 @@ class PlotCurveItem(GraphicsObject):
@pg.debug.warnOnException ## raising an exception here causes crash @pg.debug.warnOnException ## raising an exception here causes crash
def paint(self, p, opt, widget): def paint(self, p, opt, widget):
prof = debug.Profiler('PlotCurveItem.paint '+str(id(self)), disabled=True) profiler = debug.Profiler()
if self.xData is None: if self.xData is None:
return return
@ -405,7 +404,7 @@ class PlotCurveItem(GraphicsObject):
self.fillPath = None self.fillPath = None
path = self.path path = self.path
prof.mark('generate path') profiler('generate path')
if self._exportOpts is not False: if self._exportOpts is not False:
aa = self._exportOpts.get('antialias', True) aa = self._exportOpts.get('antialias', True)
@ -426,9 +425,9 @@ class PlotCurveItem(GraphicsObject):
p2.closeSubpath() p2.closeSubpath()
self.fillPath = p2 self.fillPath = p2
prof.mark('generate fill path') profiler('generate fill path')
p.fillPath(self.fillPath, self.opts['brush']) p.fillPath(self.fillPath, self.opts['brush'])
prof.mark('draw fill path') profiler('draw fill path')
sp = fn.mkPen(self.opts['shadowPen']) sp = fn.mkPen(self.opts['shadowPen'])
cp = fn.mkPen(self.opts['pen']) cp = fn.mkPen(self.opts['pen'])
@ -451,10 +450,9 @@ class PlotCurveItem(GraphicsObject):
p.drawPath(path) p.drawPath(path)
p.setPen(cp) p.setPen(cp)
p.drawPath(path) p.drawPath(path)
prof.mark('drawPath') profiler('drawPath')
#print "Render hints:", int(p.renderHints()) #print "Render hints:", int(p.renderHints())
prof.finish()
#p.setPen(QtGui.QPen(QtGui.QColor(255,0,0))) #p.setPen(QtGui.QPen(QtGui.QColor(255,0,0)))
#p.drawRect(self.boundingRect()) #p.drawRect(self.boundingRect())

View File

@ -333,7 +333,7 @@ class PlotDataItem(GraphicsObject):
See :func:`__init__() <pyqtgraph.PlotDataItem.__init__>` for details; it accepts the same arguments. See :func:`__init__() <pyqtgraph.PlotDataItem.__init__>` for details; it accepts the same arguments.
""" """
#self.clear() #self.clear()
prof = debug.Profiler('PlotDataItem.setData (0x%x)' % id(self), disabled=True) profiler = debug.Profiler()
y = None y = None
x = None x = None
if len(args) == 1: if len(args) == 1:
@ -383,7 +383,7 @@ class PlotDataItem(GraphicsObject):
if 'y' in kargs: if 'y' in kargs:
y = kargs['y'] y = kargs['y']
prof.mark('interpret data') profiler('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.
@ -432,10 +432,10 @@ class PlotDataItem(GraphicsObject):
self.xClean = self.yClean = None self.xClean = self.yClean = None
self.xDisp = None self.xDisp = None
self.yDisp = None self.yDisp = None
prof.mark('set data') profiler('set data')
self.updateItems() self.updateItems()
prof.mark('update items') profiler('update items')
self.informViewBoundsChanged() self.informViewBoundsChanged()
#view = self.getViewBox() #view = self.getViewBox()
@ -443,9 +443,7 @@ class PlotDataItem(GraphicsObject):
#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') profiler('emit')
prof.finish()
def updateItems(self): def updateItems(self):

View File

@ -339,9 +339,8 @@ class PlotItem(GraphicsWidget):
self.ctrl.gridAlphaSlider.setValue(v) self.ctrl.gridAlphaSlider.setValue(v)
#def paint(self, *args): #def paint(self, *args):
#prof = debug.Profiler('PlotItem.paint', disabled=True) #prof = debug.Profiler()
#QtGui.QGraphicsWidget.paint(self, *args) #QtGui.QGraphicsWidget.paint(self, *args)
#prof.finish()
## bad idea. ## bad idea.
#def __getattr__(self, attr): ## wrap ms #def __getattr__(self, attr): ## wrap ms

View File

@ -219,7 +219,7 @@ class ScatterPlotItem(GraphicsObject):
""" """
Accepts the same arguments as setData() Accepts the same arguments as setData()
""" """
prof = debug.Profiler('ScatterPlotItem.__init__', disabled=True) profiler = debug.Profiler()
GraphicsObject.__init__(self) GraphicsObject.__init__(self)
self.picture = None # QPicture used for rendering when pxmode==False self.picture = None # QPicture used for rendering when pxmode==False
@ -240,10 +240,9 @@ class ScatterPlotItem(GraphicsObject):
self.setBrush(100,100,150, update=False) self.setBrush(100,100,150, update=False)
self.setSymbol('o', update=False) self.setSymbol('o', update=False)
self.setSize(7, update=False) self.setSize(7, update=False)
prof.mark('1') profiler()
self.setData(*args, **kargs) self.setData(*args, **kargs)
prof.mark('setData') profiler('setData')
prof.finish()
#self.setCacheMode(self.DeviceCoordinateCache) #self.setCacheMode(self.DeviceCoordinateCache)

View File

@ -1205,7 +1205,7 @@ class ViewBox(GraphicsWidget):
[[xmin, xmax], [ymin, ymax]] [[xmin, xmax], [ymin, ymax]]
Values may be None if there are no specific bounds for an axis. Values may be None if there are no specific bounds for an axis.
""" """
prof = debug.Profiler('updateAutoRange', disabled=True) profiler = debug.Profiler()
if items is None: if items is None:
items = self.addedItems items = self.addedItems
@ -1282,7 +1282,7 @@ class ViewBox(GraphicsWidget):
range[0] = [min(bounds.left(), range[0][0]), max(bounds.right(), range[0][1])] range[0] = [min(bounds.left(), range[0][0]), max(bounds.right(), range[0][1])]
else: else:
range[0] = [bounds.left(), bounds.right()] range[0] = [bounds.left(), bounds.right()]
prof.mark('2') profiler()
#print "range", range #print "range", range
@ -1307,9 +1307,6 @@ class ViewBox(GraphicsWidget):
range[1][0] = min(range[1][0], bounds.top() - px*pxSize) range[1][0] = min(range[1][0], bounds.top() - px*pxSize)
range[1][1] = max(range[1][1], bounds.bottom() + px*pxSize) range[1][1] = max(range[1][1], bounds.bottom() + px*pxSize)
#print "final range", range
prof.finish()
return range return range
def childrenBoundingRect(self, *args, **kwds): def childrenBoundingRect(self, *args, **kwds):

View File

@ -190,7 +190,7 @@ class ImageView(QtGui.QWidget):
image data. image data.
================== ======================================================================= ================== =======================================================================
""" """
prof = debug.Profiler('ImageView.setImage', disabled=True) profiler = debug.Profiler()
if hasattr(img, 'implements') and img.implements('MetaArray'): if hasattr(img, 'implements') and img.implements('MetaArray'):
img = img.asarray() img = img.asarray()
@ -209,7 +209,7 @@ class ImageView(QtGui.QWidget):
else: else:
self.tVals = np.arange(img.shape[0]) self.tVals = np.arange(img.shape[0])
prof.mark('1') profiler()
if axes is None: if axes is None:
if img.ndim == 2: if img.ndim == 2:
@ -234,12 +234,8 @@ class ImageView(QtGui.QWidget):
for x in ['t', 'x', 'y', 'c']: for x in ['t', 'x', 'y', 'c']:
self.axes[x] = self.axes.get(x, None) self.axes[x] = self.axes.get(x, None)
prof.mark('2')
self.imageDisp = None profiler()
prof.mark('3')
self.currentIndex = 0 self.currentIndex = 0
self.updateImage(autoHistogramRange=autoHistogramRange) self.updateImage(autoHistogramRange=autoHistogramRange)
@ -250,8 +246,8 @@ class ImageView(QtGui.QWidget):
if self.ui.roiBtn.isChecked(): if self.ui.roiBtn.isChecked():
self.roiChanged() self.roiChanged()
prof.mark('4')
profiler()
if self.axes['t'] is not None: if self.axes['t'] is not None:
#self.ui.roiPlot.show() #self.ui.roiPlot.show()
@ -271,7 +267,7 @@ class ImageView(QtGui.QWidget):
s.setBounds([start, stop]) s.setBounds([start, stop])
#else: #else:
#self.ui.roiPlot.hide() #self.ui.roiPlot.hide()
prof.mark('5') profiler()
self.imageItem.resetTransform() self.imageItem.resetTransform()
if scale is not None: if scale is not None:
@ -280,14 +276,14 @@ class ImageView(QtGui.QWidget):
self.imageItem.setPos(*pos) self.imageItem.setPos(*pos)
if transform is not None: if transform is not None:
self.imageItem.setTransform(transform) self.imageItem.setTransform(transform)
prof.mark('6')
profiler()
if autoRange: if autoRange:
self.autoRange() self.autoRange()
self.roiClicked() self.roiClicked()
prof.mark('7')
prof.finish()
profiler()
def play(self, rate): def play(self, rate):
"""Begin automatically stepping frames forward at the given rate (in fps). """Begin automatically stepping frames forward at the given rate (in fps).