Commit 5ce8d09a authored by Luke Campagnola's avatar Luke Campagnola
Browse files

10-100x speedup for ScatterPlotItem

parent 50aa289a
......@@ -26,7 +26,7 @@
p = ui.plot
data = np.random.normal(size=(50,500), scale=100)
sizeArray = np.random.random(500) * 20.
sizeArray = (np.random.random(500) * 20.).astype(int)
ptr = 0
lastTime = time()
fps = None
......@@ -25,6 +25,7 @@ SI_PREFIXES_ASCII = 'yzafpnum kMGTPEZY'
from .Qt import QtGui, QtCore
import numpy as np
import decimal, re
import ctypes
import scipy.ndimage
......@@ -223,13 +224,15 @@ def mkColor(*args):
return QtGui.QColor(*args)
def mkBrush(*args):
def mkBrush(*args, **kwds):
| Convenience function for constructing Brush.
| This function always constructs a solid brush and accepts the same arguments as :func:`mkColor() <pyqtgraph.mkColor>`
| Calling mkBrush(None) returns an invisible brush.
if len(args) == 1:
if 'color' in kwds:
color = kwds['color']
elif len(args) == 1:
arg = args[0]
if arg is None:
return QtGui.QBrush(QtCore.Qt.NoBrush)
......@@ -237,7 +240,7 @@ def mkBrush(*args):
return QtGui.QBrush(arg)
color = arg
if len(args) > 1:
elif len(args) > 1:
color = args
return QtGui.QBrush(mkColor(color))
......@@ -779,30 +782,107 @@ def makeARGB(data, lut=None, levels=None, useRGBA=False):
return imgData, alpha
def makeQImage(imgData, alpha):
"""Turn an ARGB array into QImage"""
def makeQImage(imgData, alpha=None, copy=True, transpose=True):
Turn an ARGB array into QImage.
By default, the data is copied; changes to the array will not
be reflected in the image. The image will be given a 'data' attribute
pointing to the array which shares its data to prevent python
freeing that memory while the image is in use.
=========== ===================================================================
imgData Array of data to convert. Must have shape (width, height, 3 or 4)
and dtype=ubyte. The order of values in the 3rd axis must be
(b, g, r, a).
alpha If True, the QImage returned will have format ARGB32. If False,
the format will be RGB32. By default, _alpha_ is True if
array.shape[2] == 4.
copy If True, the data is copied before converting to QImage.
If False, the new QImage points directly to the data in the array.
Note that the array must be contiguous for this to work.
transpose If True (the default), the array x/y axes are transposed before
creating the image. Note that Qt expects the axes to be in
(height, width) order whereas pyqtgraph usually prefers the
=========== ===================================================================
## create QImage from buffer
prof = debug.Profiler('functions.makeQImage', disabled=True)
## If we didn't explicitly specify alpha, check the array shape.
if alpha is None:
alpha = (imgData.shape[2] == 4)
copied = False
if imgData.shape[2] == 3: ## need to make alpha channel (even if alpha==False; QImage requires 32 bpp)
if copy is True:
d2 = np.empty(imgData.shape[:2] + (4,), dtype=imgData.dtype)
d2[:,:,:3] = imgData
d2[:,:,3] = 255
imgData = d2
copied = True
raise Exception('Array has only 3 channels; cannot make QImage without copying.')
if alpha:
imgFormat = QtGui.QImage.Format_ARGB32
imgFormat = QtGui.QImage.Format_RGB32
imgData = imgData.transpose((1, 0, 2)) ## QImage expects the row/column order to be opposite
buf =
except AttributeError: ## happens when image data is non-contiguous
if transpose:
imgData = imgData.transpose((1, 0, 2)) ## QImage expects the row/column order to be opposite
if not imgData.flags['C_CONTIGUOUS']:
if copy is False:
extra = ' (try setting transpose=False)' if transpose else ''
raise Exception('Array is not contiguous; cannot make QImage without copying.'+extra)
imgData = np.ascontiguousarray(imgData)
buf =
copied = True
qimage = QtGui.QImage(buf, imgData.shape[1], imgData.shape[0], imgFormat)
prof.mark('2') = imgData
return qimage
if copy is True and copied is False:
imgData = imgData.copy()
addr = ctypes.addressof(ctypes.c_char.from_buffer(imgData, 0))
img = QtGui.QImage(addr, imgData.shape[1], imgData.shape[0], imgFormat) = imgData
return img
#buf =
#except AttributeError: ## happens when image data is non-contiguous
#buf =
#qimage = QtGui.QImage(buf, imgData.shape[1], imgData.shape[0], imgFormat)
#prof.mark('2') = imgData
#return qimage
def imageToArray(img, copy=False, transpose=True):
Convert a QImage into numpy array. The image must have format RGB32, ARGB32, or ARGB32_Premultiplied.
By default, the image is not copied; changes made to the array will appear in the QImage as well (beware: if
the QImage is collected before the array, there may be trouble).
The array will have shape (width, height, (b,g,r,a)).
ptr = img.bits()
fmt = img.format()
if fmt == img.Format_RGB32:
arr = np.asarray(ptr).reshape(img.height(), img.width(), 3)
elif fmt == img.Format_ARGB32 or fmt == img.Format_ARGB32_Premultiplied:
arr = np.asarray(ptr).reshape(img.height(), img.width(), 4)
if copy:
arr = arr.copy()
if transpose:
return arr.transpose((1,0,2))
return arr
def rescaleData(data, scale, offset):
newData = np.empty((data.size,),
......@@ -130,6 +130,8 @@ class PlotDataItem(GraphicsObject):
'symbolBrush': (50, 50, 150),
'pxMode': True,
'pointMode': None,
'data': None,
self.setData(*args, **kargs)
......@@ -144,22 +146,30 @@ class PlotDataItem(GraphicsObject):
return QtCore.QRectF() ## let child items handle this
def setAlpha(self, alpha, auto):
if self.opts['alphaHint'] == alpha and self.opts['alphaMode'] == auto:
self.opts['alphaHint'] = alpha
self.opts['alphaMode'] = auto
def setFftMode(self, mode):
if self.opts['fftMode'] == mode:
self.opts['fftMode'] = mode
self.xDisp = self.yDisp = None
def setLogMode(self, xMode, yMode):
self.opts['logMode'] = (xMode, yMode)
if self.opts['logMode'] == [xMode, yMode]:
self.opts['logMode'] = [xMode, yMode]
self.xDisp = self.yDisp = None
def setPointMode(self, mode):
if self.opts['pointMode'] == mode:
self.opts['pointMode'] = mode
......@@ -193,6 +203,8 @@ class PlotDataItem(GraphicsObject):
def setFillBrush(self, *args, **kargs):
brush = fn.mkBrush(*args, **kargs)
if self.opts['fillBrush'] == brush:
self.opts['fillBrush'] = brush
......@@ -200,16 +212,22 @@ class PlotDataItem(GraphicsObject):
return self.setFillBrush(*args, **kargs)
def setFillLevel(self, level):
if self.opts['fillLevel'] == level:
self.opts['fillLevel'] = level
def setSymbol(self, symbol):
if self.opts['symbol'] == symbol:
self.opts['symbol'] = symbol
def setSymbolPen(self, *args, **kargs):
pen = fn.mkPen(*args, **kargs)
if self.opts['symbolPen'] == pen:
self.opts['symbolPen'] = pen
......@@ -218,21 +236,26 @@ class PlotDataItem(GraphicsObject):
def setSymbolBrush(self, *args, **kargs):
brush = fn.mkBrush(*args, **kargs)
if self.opts['symbolBrush'] == brush:
self.opts['symbolBrush'] = brush
def setSymbolSize(self, size):
if self.opts['symbolSize'] == size:
self.opts['symbolSize'] = size
def setDownsampling(self, ds):
if self.opts['downsample'] != ds:
self.opts['downsample'] = ds
self.xDisp = self.yDisp = None
if self.opts['downsample'] == ds:
self.opts['downsample'] = ds
self.xDisp = self.yDisp = None
def setData(self, *args, **kargs):
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment