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,84 +367,99 @@ 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(...):
... do stuff ... profiler = Profiler()
prof.mark('did stuff') ... do stuff ...
... do other stuff ... profiler('did stuff')
prof.mark('did other stuff') ... do other stuff ...
prof.finish() profiler('did other stuff')
# 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 = [] _profilers = os.environ.get("PYQTGRAPHPROFILE", "")
_depth = 0
def __init__(self, msg="Profiler", disabled=False, delayed=True, globalDelay=True): _msgs = []
self.disabled = disabled
if disabled: if _profilers:
return _profilers = _profilers.split(",")
def __new__(cls, delayed=True):
self.markCount = 0 """Optionally create a new profiler based on caller's qualname.
self.finished = False """
self.depth = Profiler.depth # determine the qualified name of the caller function
Profiler.depth += 1 caller_frame = sys._getframe(1)
if not globalDelay: try:
self.msgs = [] caller_object_type = type(caller_frame.f_locals["self"])
self.delayed = delayed except KeyError: # we are in a regular function
self.msg = " "*self.depth + msg qualifier = caller_frame.f_globals["__name__"].split(".", 1)[1]
msg2 = self.msg + " >>> Started" else: # we are in a method
if self.delayed: qualifier = caller_object_type.__name__
self.msgs.append(msg2) func_qualname = qualifier + "." + caller_frame.f_code.co_name
else: if func_qualname not in cls._profilers: # don't do anything
print(msg2) return lambda msg=None: None
self.t0 = ptime.time() # create an actual profiling object
self.t1 = self.t0 cls._depth += 1
obj = super(Profiler, cls).__new__(cls)
def mark(self, msg=None): obj._name = func_qualname
if self.disabled: obj._delayed = delayed
return obj._markCount = 0
obj._firstTime = obj._lastTime = ptime.time()
obj._newMsg("> Entering " + func_qualname)
return obj
else:
def __new__(cls, delayed=True):
return lambda msg=None: None
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()
t1 = ptime.time() self._newMsg(
msg2 = " "+self.msg+" "+msg+" "+"%gms" % ((t1-self.t1)*1000) msg + ": " + str((newTime - self._lastTime) * 1000) + "ms")
if self.delayed: self._lastTime = newTime
self.msgs.append(msg2)
else: def _newMsg(self, msg):
print(msg2) msg = " " * (self._depth - 1) + msg
self.t1 = ptime.time() ## don't measure time it took to print if self._delayed:
self._msgs.append(msg)
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):
"""Common-use for cProfile""" """Common-use for cProfile"""
@ -943,4 +960,4 @@ class PrintDetector(object):
traceback.print_stack() traceback.print_stack()
def flush(self): def flush(self):
self.stdout.flush() self.stdout.flush()

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)
@ -794,8 +794,8 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
else: else:
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,11 +865,9 @@ 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
def makeQImage(imgData, alpha=None, copy=True, transpose=True): def makeQImage(imgData, alpha=None, copy=True, transpose=True):
""" """
@ -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:
@ -922,7 +920,9 @@ 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,8 +623,8 @@ 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,8 +862,8 @@ 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,8 +877,8 @@ 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:
p.setFont(self.tickFont) p.setFont(self.tickFont)
@ -889,10 +886,8 @@ 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):
if self.orientation in ['left', 'right']: if self.orientation in ['left', 'right']:

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,8 +188,8 @@ 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:
if self.image is None: if self.image is None:
@ -201,9 +201,9 @@ class ImageItem(GraphicsObject):
if shapeChanged: if shapeChanged:
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:
autoLevels = False autoLevels = False
@ -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,11 +240,10 @@ 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)
def setData(self, *args, **kargs): def setData(self, *args, **kargs):

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
@ -1306,10 +1306,7 @@ class ViewBox(GraphicsWidget):
continue continue
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,13 +234,9 @@ 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')
profiler()
self.imageDisp = None
prof.mark('3')
self.currentIndex = 0 self.currentIndex = 0
self.updateImage(autoHistogramRange=autoHistogramRange) self.updateImage(autoHistogramRange=autoHistogramRange)
if levels is None and autoLevels: if levels is None and autoLevels:
@ -250,9 +246,9 @@ 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()
self.ui.roiPlot.setXRange(self.tVals.min(), self.tVals.max()) self.ui.roiPlot.setXRange(self.tVals.min(), self.tVals.max())
@ -271,8 +267,8 @@ 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:
self.imageItem.scale(*scale) self.imageItem.scale(*scale)
@ -280,15 +276,15 @@ 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).
This can also be accessed by pressing the spacebar.""" This can also be accessed by pressing the spacebar."""