pyqtgraph/examples/ScatterPlotSpeedTest.py

131 lines
4.2 KiB
Python
Raw Normal View History

#!/usr/bin/python
# -*- coding: utf-8 -*-
2013-02-25 04:09:03 +00:00
"""
For testing rapid updates of ScatterPlotItem under various conditions.
(Scatter plots are still rather slow to draw; expect about 20fps)
"""
# Add path to library (just for examples; you do not need this)
import initExample
import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, QtCore, QtWidgets
import pyqtgraph.parametertree as ptree
import pyqtgraph.graphicsItems.ScatterPlotItem
from time import perf_counter
translate = QtCore.QCoreApplication.translate
app = pg.mkQApp()
param = ptree.Parameter.create(name=translate('ScatterPlot', 'Parameters'), type='group', children=[
dict(name='paused', title=translate('ScatterPlot', 'Paused: '), type='bool', value=False),
dict(name='count', title=translate('ScatterPlot', 'Count: '), type='int', limits=[1, None], value=500, step=100),
dict(name='size', title=translate('ScatterPlot', 'Size: '), type='int', limits=[1, None], value=10),
dict(name='randomize', title=translate('ScatterPlot', 'Randomize: '), type='bool', value=False),
dict(name='pxMode', title='pxMode: ', type='bool', value=True),
dict(name='useCache', title='useCache: ', type='bool', value=True),
2021-08-05 19:28:05 +00:00
dict(name='mode', title=translate('ScatterPlot', 'Mode: '), type='list', limits={translate('ScatterPlot', 'New Item'): 'newItem', translate('ScatterPlot', 'Reuse Item'): 'reuseItem', translate('ScatterPlot', 'Simulate Pan/Zoom'): 'panZoom', translate('ScatterPlot', 'Simulate Hover'): 'hover'}, value='reuseItem'),
])
for c in param.children():
c.setDefault(c.value())
pt = ptree.ParameterTree(showHeader=False)
pt.setParameters(param)
p = pg.PlotWidget()
splitter = QtWidgets.QSplitter()
splitter.addWidget(pt)
splitter.addWidget(p)
splitter.show()
data = {}
item = pg.ScatterPlotItem()
hoverBrush = pg.mkBrush('y')
ptr = 0
lastTime = perf_counter()
fps = None
timer = QtCore.QTimer()
Scatter Plot Improvements (#1420) * Added hovering demo to ScatterPlot example * Use Qt's serialization for SymbolAtlas.symbolMap keys Yields significant performance improvements when updating the scatter plot's options. See e.g. the plot hover example. * Further optimized scatter plot picking * Fix ScatterPlot example tool tip * Clean up while I'm here * Compatibility * Some simple optimizations for ScatterPlotItem Speedups for ScatterPlotSpeedTest.py: ~50% without pxMode ~ 0% pxMode with useCache ~30% pxMode without useCache * ~3x speedup in scatter plot speed test with pxMode * More optimization low-hanging fruit for the scatter plot * Removed hover example to lazily pass tests * Avoid segfault * Re-add hover example to ScatterPlot.py * Switch to id-based keying for scatter plot symbol atlas - Use cases exist where serialization-based keying is a significant bottleneck, e.g. updating without atlas invalidation when a large variety pens or brushes are present. - To avoid a performance hit, the onus is on the user to carefully reuse pen and brush objects. * Optimized caching in scatter plot hovering example * Fixed and optimized scatter plot hovering example * Minor scatter plot optimization * Cleanup * Store hovered points in a set for the hovering example * Keep a limited number symbol atlas entries around for future reuse * Added a docstring note to remind the user to reuse QPen and QBrush objects for better performance * Tidied up hovering example * Typo * Avoid unnecessary atlas rebuilds * Refactored SymbolAtlas * Efficient appending to SymbolAtlas * SymbolAtlas rewrite * Cleanup and profiling * Add randomized brushes to speed test * Add loc indexer to ScatterPlotItem * Profile ScatterPlotItem.paint to identify bottlenecks * Reuse targetRect to improve paint performance * Readability improvements (opinionated) * Only need to set x and y of targetRect - w and h can stay set to 0 (not entirely sure why) - this is a bit faster than setting all of x, y, w, h * Minor renaming * Strip off API changes and leave to another PR * Renaming * Compatibility * Use drawPixmap(x, y, pm, sx, sy, sw, sh) signature to avoid needing to update QRectFs * Use different drawing approaches for each Qt binding for performance reasons * Fix a bug introduced two commits ago Incidentally, I think there is a similar bug in the main branch currently. * Minor performance and readability improvements * Strip out source and target QRectF stuff * Bring source and target QRectF stuff back in a less coupled way * Leave deprecating getSpotOpts for another PR * Compatibility fix * Added docstrings and use SymbolAtlas__len__ where possible * Fix export issue * Add missing import * Add deprecation warnings * Avoid using deprecated methods * Fix and cleanup max spot size measurements * Make creation of style opts entries explicit * Add hovering API to ScatterPlotItem * Compatibility * Marshal pen and brush lists in setPen and setBrush * Fixed platform dependent bug
2020-12-16 19:07:39 +00:00
def mkDataAndItem():
global data, fps
scale = 100
data = {
'pos': np.random.normal(size=(50, param['count']), scale=scale),
'pen': [pg.mkPen(x) for x in np.random.randint(0, 256, (param['count'], 3))],
'brush': [pg.mkBrush(x) for x in np.random.randint(0, 256, (param['count'], 3))],
'size': (np.random.random(param['count']) * param['size']).astype(int)
}
data['pen'][0] = pg.mkPen('w')
data['size'][0] = param['size']
data['brush'][0] = pg.mkBrush('b')
bound = 5 * scale
p.setRange(xRange=[-bound, bound], yRange=[-bound, bound])
mkItem()
def mkItem():
global item
item = pg.ScatterPlotItem(pxMode=param['pxMode'], **getData())
item.opts['useCache'] = param['useCache']
p.clear()
p.addItem(item)
def getData():
pos = data['pos']
pen = data['pen']
size = data['size']
brush = data['brush']
if not param['randomize']:
pen = pen[0]
size = size[0]
brush = brush[0]
return dict(x=pos[ptr % 50], y=pos[(ptr + 1) % 50], pen=pen, brush=brush, size=size)
def update():
global ptr, lastTime, fps
mode = param['mode']
if mode == 'newItem':
mkItem()
elif mode == 'reuseItem':
item.setData(**getData())
elif mode == 'panZoom':
item.viewTransformChanged()
item.update()
elif mode == 'hover':
pts = item.points()
old = pts[(ptr - 1) % len(pts)]
new = pts[ptr % len(pts)]
item.pointsAt(new.pos())
old.resetBrush() # reset old's brush before setting new's to better simulate hovering
new.setBrush(hoverBrush)
ptr += 1
now = perf_counter()
dt = now - lastTime
lastTime = now
if fps is None:
fps = 1.0 / dt
else:
s = np.clip(dt * 3., 0, 1)
fps = fps * (1 - s) + (1.0 / dt) * s
p.setTitle('%0.2f fps' % fps)
p.repaint()
# app.processEvents() # force complete redraw for every plot
mkDataAndItem()
for name in ['count', 'size']:
param.child(name).sigValueChanged.connect(mkDataAndItem)
for name in ['useCache', 'pxMode', 'randomize']:
param.child(name).sigValueChanged.connect(mkItem)
param.child('paused').sigValueChanged.connect(lambda _, v: timer.stop() if v else timer.start())
timer.timeout.connect(update)
timer.start(0)
if __name__ == '__main__':
2021-05-13 21:28:22 +00:00
pg.exec()