optimize ScatterPlotItem with pxMode=True

This commit is contained in:
Guillaume Poulin 2013-09-20 15:46:10 +08:00
parent 029282bb9d
commit 63bf2b3270
4 changed files with 67 additions and 46 deletions

View File

@ -48,8 +48,8 @@ else:
CONFIG_OPTIONS = { CONFIG_OPTIONS = {
'useOpenGL': useOpenGL, ## by default, this is platform-dependent (see widgets/GraphicsView). Set to True or False to explicitly enable/disable opengl. 'useOpenGL': useOpenGL, ## by default, this is platform-dependent (see widgets/GraphicsView). Set to True or False to explicitly enable/disable opengl.
'leftButtonPan': True, ## if false, left button drags a rubber band for zooming in viewbox 'leftButtonPan': True, ## if false, left button drags a rubber band for zooming in viewbox
'foreground': (150, 150, 150), ## default foreground color for axes, labels, etc. 'foreground': 'd', ## default foreground color for axes, labels, etc.
'background': (0, 0, 0), ## default background for GraphicsWidget 'background': 'k', ## default background for GraphicsWidget
'antialias': False, 'antialias': False,
'editorCommand': None, ## command used to invoke code editor from ConsoleWidgets 'editorCommand': None, ## command used to invoke code editor from ConsoleWidgets
'useWeave': True, ## Use weave to speed up some operations, if it is available 'useWeave': True, ## Use weave to speed up some operations, if it is available

View File

@ -7,15 +7,19 @@ Distributed under MIT/X11 license. See license.txt for more infomation.
from __future__ import division from __future__ import division
from .python2_3 import asUnicode from .python2_3 import asUnicode
from .Qt import QtGui, QtCore, USE_PYSIDE
Colors = { Colors = {
'b': (0,0,255,255), 'b': QtGui.QColor(0,0,255,255),
'g': (0,255,0,255), 'g': QtGui.QColor(0,255,0,255),
'r': (255,0,0,255), 'r': QtGui.QColor(255,0,0,255),
'c': (0,255,255,255), 'c': QtGui.QColor(0,255,255,255),
'm': (255,0,255,255), 'm': QtGui.QColor(255,0,255,255),
'y': (255,255,0,255), 'y': QtGui.QColor(255,255,0,255),
'k': (0,0,0,255), 'k': QtGui.QColor(0,0,0,255),
'w': (255,255,255,255), 'w': QtGui.QColor(255,255,255,255),
'd': QtGui.QColor(150,150,150,255),
'l': QtGui.QColor(200,200,200,255),
's': QtGui.QColor(100,100,150,255),
} }
SI_PREFIXES = asUnicode('yzafpnµm kMGTPEZY') SI_PREFIXES = asUnicode('yzafpnµm kMGTPEZY')
@ -23,7 +27,6 @@ SI_PREFIXES_ASCII = 'yzafpnum kMGTPEZY'
from .Qt import QtGui, QtCore, USE_PYSIDE
import pyqtgraph as pg import pyqtgraph as pg
import numpy as np import numpy as np
import decimal, re import decimal, re
@ -169,17 +172,15 @@ def mkColor(*args):
""" """
err = 'Not sure how to make a color from "%s"' % str(args) err = 'Not sure how to make a color from "%s"' % str(args)
if len(args) == 1: if len(args) == 1:
if isinstance(args[0], QtGui.QColor): if isinstance(args[0], basestring):
return QtGui.QColor(args[0])
elif isinstance(args[0], float):
r = g = b = int(args[0] * 255)
a = 255
elif isinstance(args[0], basestring):
c = args[0] c = args[0]
if c[0] == '#': if c[0] == '#':
c = c[1:] c = c[1:]
if len(c) == 1: if len(c) == 1:
(r, g, b, a) = Colors[c] try:
return Colors[c]
except KeyError:
raise Exception(err)
if len(c) == 3: if len(c) == 3:
r = int(c[0]*2, 16) r = int(c[0]*2, 16)
g = int(c[1]*2, 16) g = int(c[1]*2, 16)
@ -200,6 +201,11 @@ def mkColor(*args):
g = int(c[2:4], 16) g = int(c[2:4], 16)
b = int(c[4:6], 16) b = int(c[4:6], 16)
a = int(c[6:8], 16) a = int(c[6:8], 16)
elif isinstance(args[0], QtGui.QColor):
return QtGui.QColor(args[0])
elif isinstance(args[0], float):
r = g = b = int(args[0] * 255)
a = 255
elif hasattr(args[0], '__len__'): elif hasattr(args[0], '__len__'):
if len(args[0]) == 3: if len(args[0]) == 3:
(r, g, b) = args[0] (r, g, b) = args[0]
@ -283,7 +289,7 @@ def mkPen(*args, **kargs):
color = args color = args
if color is None: if color is None:
color = mkColor(200, 200, 200) color = mkColor('l')
if hsv is not None: if hsv is not None:
color = hsvColor(*hsv) color = hsvColor(*hsv)
else: else:

View File

@ -266,8 +266,7 @@ class AxisItem(GraphicsWidget):
self.picture = None self.picture = None
def pen(self): def pen(self):
if self._pen is None: #return self._pen
return fn.mkPen(pg.getConfigOption('foreground'))
return pg.mkPen(self._pen) return pg.mkPen(self._pen)
def setPen(self, pen): def setPen(self, pen):
@ -276,11 +275,11 @@ class AxisItem(GraphicsWidget):
if pen == None, the default will be used (see :func:`setConfigOption if pen == None, the default will be used (see :func:`setConfigOption
<pyqtgraph.setConfigOption>`) <pyqtgraph.setConfigOption>`)
""" """
self._pen = pen
self.picture = None self.picture = None
if pen is None: if pen is None:
pen = pg.getConfigOption('foreground') pen = pg.getConfigOption('foreground')
self.labelStyle['color'] = '#' + pg.colorStr(pg.mkPen(pen).color())[:6] self._pen = pg.mkPen(pen)
self.labelStyle['color'] = '#' + pg.colorStr(self._pen.color())[:6]
self.setLabel() self.setLabel()
self.update() self.update()

View File

@ -98,31 +98,41 @@ class SymbolAtlas(object):
# weak value; if all external refs to this list disappear, # weak value; if all external refs to this list disappear,
# the symbol will be forgotten. # the symbol will be forgotten.
self.symbolMap = weakref.WeakValueDictionary() self.symbolMap = weakref.WeakValueDictionary()
self.symbolPen = weakref.WeakValueDictionary()
self.symbolBrush = weakref.WeakValueDictionary()
self.atlasData = None # numpy array of atlas image self.atlasData = None # numpy array of atlas image
self.atlas = None # atlas as QPixmap self.atlas = None # atlas as QPixmap
self.atlasValid = False self.atlasValid = False
self.max_width=0
def getSymbolCoords(self, opts): def getSymbolCoords(self, opts):
""" """
Given a list of spot records, return an object representing the coordinates of that symbol within the atlas Given a list of spot records, return an object representing the coordinates of that symbol within the atlas
""" """
coords = np.empty(len(opts), dtype=object) coords = np.empty(len(opts), dtype=object)
keyi = None
coordi = None
for i, rec in enumerate(opts): for i, rec in enumerate(opts):
symbol, size, pen, brush = rec['symbol'], rec['size'], rec['pen'], rec['brush'] key = (rec[3], rec[2], id(rec[4]), id(rec[5]))
pen = fn.mkPen(pen) if not isinstance(pen, QtGui.QPen) else pen if key == keyi:
brush = fn.mkBrush(brush) if not isinstance(pen, QtGui.QBrush) else brush coords[i]=coordi
key = (symbol, size, fn.colorTuple(pen.color()), pen.widthF(), pen.style(), fn.colorTuple(brush.color())) else:
if key not in self.symbolMap: try:
newCoords = SymbolAtlas.SymbolCoords() coords[i] = self.symbolMap[key]
self.symbolMap[key] = newCoords except KeyError:
self.atlasValid = False newCoords = SymbolAtlas.SymbolCoords()
#try: self.symbolMap[key] = newCoords
#self.addToAtlas(key) ## squeeze this into the atlas if there is room self.symbolPen[key] = rec['pen']
#except: self.symbolBrush[key] = rec['brush']
#self.buildAtlas() ## otherwise, we need to rebuild self.atlasValid = False
#try:
coords[i] = self.symbolMap[key] #self.addToAtlas(key) ## squeeze this into the atlas if there is room
#except:
#self.buildAtlas() ## otherwise, we need to rebuild
coords[i] = newCoords
keyi = key
coordi = newCoords
return coords return coords
def buildAtlas(self): def buildAtlas(self):
@ -133,8 +143,8 @@ class SymbolAtlas(object):
images = [] images = []
for key, coords in self.symbolMap.items(): for key, coords in self.symbolMap.items():
if len(coords) == 0: if len(coords) == 0:
pen = fn.mkPen(color=key[2], width=key[3], style=key[4]) pen = self.symbolPen[key]
brush = fn.mkBrush(color=key[5]) brush = self.symbolBrush[key]
img = renderSymbol(key[0], key[1], pen, brush) img = renderSymbol(key[0], key[1], pen, brush)
images.append(img) ## we only need this to prevent the images being garbage collected immediately images.append(img) ## we only need this to prevent the images being garbage collected immediately
arr = fn.imageToArray(img, copy=False, transpose=False) arr = fn.imageToArray(img, copy=False, transpose=False)
@ -181,6 +191,7 @@ class SymbolAtlas(object):
self.atlasData[x:x+w, y:y+h] = rendered[key] self.atlasData[x:x+w, y:y+h] = rendered[key]
self.atlas = None self.atlas = None
self.atlasValid = True self.atlasValid = True
self.max_width=maxWidth
def getAtlas(self): def getAtlas(self):
if not self.atlasValid: if not self.atlasValid:
@ -237,8 +248,8 @@ class ScatterPlotItem(GraphicsObject):
'antialias': pg.getConfigOption('antialias'), 'antialias': pg.getConfigOption('antialias'),
} }
self.setPen(200,200,200, update=False) self.setPen('l', update=False)
self.setBrush(100,100,150, update=False) self.setBrush('s', 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') prof.mark('1')
@ -533,10 +544,8 @@ class ScatterPlotItem(GraphicsObject):
def updateSpots(self, dataSet=None): def updateSpots(self, dataSet=None):
if dataSet is None: if dataSet is None:
dataSet = self.data dataSet = self.data
self._maxSpotWidth = 0
self._maxSpotPxWidth = 0
invalidate = False invalidate = False
self.measureSpotSizes(dataSet)
if self.opts['pxMode']: if self.opts['pxMode']:
mask = np.equal(dataSet['fragCoords'], None) mask = np.equal(dataSet['fragCoords'], None)
if np.any(mask): if np.any(mask):
@ -549,6 +558,13 @@ class ScatterPlotItem(GraphicsObject):
#if rec['fragCoords'] is None: #if rec['fragCoords'] is None:
#invalidate = True #invalidate = True
#rec['fragCoords'] = self.fragmentAtlas.getSymbolCoords(*self.getSpotOpts(rec)) #rec['fragCoords'] = self.fragmentAtlas.getSymbolCoords(*self.getSpotOpts(rec))
self.fragmentAtlas.getAtlas()
self._maxSpotPxWidth=self.fragmentAtlas.max_width
else:
self._maxSpotWidth = 0
self._maxSpotPxWidth = 0
self.measureSpotSizes(dataSet)
if invalidate: if invalidate:
self.invalidate() self.invalidate()
@ -711,7 +727,7 @@ class ScatterPlotItem(GraphicsObject):
#self.lastAtlas = arr #self.lastAtlas = arr
if self.fragments is None: if self.fragments is None:
self.updateSpots() #self.updateSpots()
self.generateFragments() self.generateFragments()
p.resetTransform() p.resetTransform()