From e5f383fbb5e3953d44b9767d3a73b32d2a32e480 Mon Sep 17 00:00:00 2001
From: Luke Campagnola <>
Date: Fri, 23 Nov 2012 16:01:25 -0500
Subject: [PATCH] Bugfixes and updates to functions.py: - generalized
makeARGB API: can now process arrays of arbitrary shape. - affineSlice
automatically converts vector arguments to array - new function
applyLookupTable taken from makeARGB - isosurface function returns array
Updated VideoSpeedTest example to follow new makeARGB API
LayoutWidget: row argument now accepts 'next' as value
ParameterTree bugfix: avoid infinite recursion when accessing non-existent attributes
ViewBox: avoid exit error caused when cleanup callback is invoked while python is shutting down
---
examples/VideoSpeedTest.py | 26 ++-
examples/VideoTemplate.ui | 10 +-
examples/VideoTemplate_pyqt.py | 14 +-
examples/VideoTemplate_pyside.py | 14 +-
functions.py | 367 ++++++++++++++++++-------------
graphicsItems/GraphicsItem.py | 8 +-
graphicsItems/ViewBox/ViewBox.py | 4 +-
parametertree/Parameter.py | 7 +-
widgets/LayoutWidget.py | 7 +-
9 files changed, 273 insertions(+), 184 deletions(-)
diff --git a/examples/VideoSpeedTest.py b/examples/VideoSpeedTest.py
index d51798bd..0a20e9cf 100644
--- a/examples/VideoSpeedTest.py
+++ b/examples/VideoSpeedTest.py
@@ -61,37 +61,41 @@ ui.alphaCheck.toggled.connect(updateLUT)
def updateScale():
global ui
spins = [ui.minSpin1, ui.maxSpin1, ui.minSpin2, ui.maxSpin2, ui.minSpin3, ui.maxSpin3]
- if ui.rgbCheck.isChecked():
+ if ui.rgbLevelsCheck.isChecked():
for s in spins[2:]:
s.setEnabled(True)
else:
for s in spins[2:]:
s.setEnabled(False)
-ui.rgbCheck.toggled.connect(updateScale)
+ui.rgbLevelsCheck.toggled.connect(updateScale)
cache = {}
def mkData():
global data, cache, ui
- dtype = ui.dtypeCombo.currentText()
+ dtype = (ui.dtypeCombo.currentText(), ui.rgbCheck.isChecked())
if dtype not in cache:
- if dtype == 'uint8':
+ if dtype[0] == 'uint8':
dt = np.uint8
loc = 128
scale = 64
mx = 255
- elif dtype == 'uint16':
+ elif dtype[0] == 'uint16':
dt = np.uint16
loc = 4096
scale = 1024
mx = 2**16
- elif dtype == 'float':
+ elif dtype[0] == 'float':
dt = np.float
loc = 1.0
scale = 0.1
- data = np.random.normal(size=(20,512,512), loc=loc, scale=scale)
- data = ndi.gaussian_filter(data, (0, 3, 3))
- if dtype != 'float':
+ if ui.rgbCheck.isChecked():
+ data = np.random.normal(size=(20,512,512,3), loc=loc, scale=scale)
+ data = ndi.gaussian_filter(data, (0, 6, 6, 0))
+ else:
+ data = np.random.normal(size=(20,512,512), loc=loc, scale=scale)
+ data = ndi.gaussian_filter(data, (0, 6, 6))
+ if dtype[0] != 'float':
data = np.clip(data, 0, mx)
data = data.astype(dt)
cache[dtype] = data
@@ -100,7 +104,7 @@ def mkData():
updateLUT()
mkData()
ui.dtypeCombo.currentIndexChanged.connect(mkData)
-
+ui.rgbCheck.toggled.connect(mkData)
ptr = 0
lastTime = ptime.time()
@@ -113,7 +117,7 @@ def update():
useLut = None
if ui.scaleCheck.isChecked():
- if ui.rgbCheck.isChecked():
+ if ui.rgbLevelsCheck.isChecked():
useScale = [
[ui.minSpin1.value(), ui.maxSpin1.value()],
[ui.minSpin2.value(), ui.maxSpin2.value()],
diff --git a/examples/VideoTemplate.ui b/examples/VideoTemplate.ui
index 078e7ccf..3dddb928 100644
--- a/examples/VideoTemplate.ui
+++ b/examples/VideoTemplate.ui
@@ -25,7 +25,6 @@
0
- fpsLabel
-
@@ -84,7 +83,7 @@
-
-
+
RGB
@@ -218,6 +217,13 @@
+ -
+
+
+ RGB
+
+
+
diff --git a/examples/VideoTemplate_pyqt.py b/examples/VideoTemplate_pyqt.py
index 21e66635..c3430e2d 100644
--- a/examples/VideoTemplate_pyqt.py
+++ b/examples/VideoTemplate_pyqt.py
@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file './examples/VideoTemplate.ui'
#
-# Created: Sun Sep 9 14:41:31 2012
+# Created: Sun Nov 4 18:24:20 2012
# by: PyQt4 UI code generator 4.9.1
#
# WARNING! All changes made in this file will be lost!
@@ -55,9 +55,9 @@ class Ui_MainWindow(object):
self.scaleCheck = QtGui.QCheckBox(self.centralwidget)
self.scaleCheck.setObjectName(_fromUtf8("scaleCheck"))
self.gridLayout_2.addWidget(self.scaleCheck, 3, 0, 1, 1)
- self.rgbCheck = QtGui.QCheckBox(self.centralwidget)
- self.rgbCheck.setObjectName(_fromUtf8("rgbCheck"))
- self.gridLayout_2.addWidget(self.rgbCheck, 3, 1, 1, 1)
+ self.rgbLevelsCheck = QtGui.QCheckBox(self.centralwidget)
+ self.rgbLevelsCheck.setObjectName(_fromUtf8("rgbLevelsCheck"))
+ self.gridLayout_2.addWidget(self.rgbLevelsCheck, 3, 1, 1, 1)
self.horizontalLayout = QtGui.QHBoxLayout()
self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout"))
self.minSpin1 = SpinBox(self.centralwidget)
@@ -124,6 +124,9 @@ class Ui_MainWindow(object):
self.fpsLabel.setAlignment(QtCore.Qt.AlignCenter)
self.fpsLabel.setObjectName(_fromUtf8("fpsLabel"))
self.gridLayout_2.addWidget(self.fpsLabel, 0, 0, 1, 4)
+ self.rgbCheck = QtGui.QCheckBox(self.centralwidget)
+ self.rgbCheck.setObjectName(_fromUtf8("rgbCheck"))
+ self.gridLayout_2.addWidget(self.rgbCheck, 2, 1, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
@@ -138,12 +141,13 @@ class Ui_MainWindow(object):
self.dtypeCombo.setItemText(1, QtGui.QApplication.translate("MainWindow", "uint16", None, QtGui.QApplication.UnicodeUTF8))
self.dtypeCombo.setItemText(2, QtGui.QApplication.translate("MainWindow", "float", None, QtGui.QApplication.UnicodeUTF8))
self.scaleCheck.setText(QtGui.QApplication.translate("MainWindow", "Scale Data", None, QtGui.QApplication.UnicodeUTF8))
- self.rgbCheck.setText(QtGui.QApplication.translate("MainWindow", "RGB", None, QtGui.QApplication.UnicodeUTF8))
+ self.rgbLevelsCheck.setText(QtGui.QApplication.translate("MainWindow", "RGB", None, QtGui.QApplication.UnicodeUTF8))
self.label_2.setText(QtGui.QApplication.translate("MainWindow", "<--->", None, QtGui.QApplication.UnicodeUTF8))
self.label_3.setText(QtGui.QApplication.translate("MainWindow", "<--->", None, QtGui.QApplication.UnicodeUTF8))
self.label_4.setText(QtGui.QApplication.translate("MainWindow", "<--->", None, QtGui.QApplication.UnicodeUTF8))
self.lutCheck.setText(QtGui.QApplication.translate("MainWindow", "Use Lookup Table", None, QtGui.QApplication.UnicodeUTF8))
self.alphaCheck.setText(QtGui.QApplication.translate("MainWindow", "alpha", None, QtGui.QApplication.UnicodeUTF8))
self.fpsLabel.setText(QtGui.QApplication.translate("MainWindow", "FPS", None, QtGui.QApplication.UnicodeUTF8))
+ self.rgbCheck.setText(QtGui.QApplication.translate("MainWindow", "RGB", None, QtGui.QApplication.UnicodeUTF8))
from pyqtgraph import SpinBox, GradientWidget, GraphicsView, RawImageWidget
diff --git a/examples/VideoTemplate_pyside.py b/examples/VideoTemplate_pyside.py
index 5cbce05c..d19e0f23 100644
--- a/examples/VideoTemplate_pyside.py
+++ b/examples/VideoTemplate_pyside.py
@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file './examples/VideoTemplate.ui'
#
-# Created: Sun Sep 9 14:41:31 2012
+# Created: Sun Nov 4 18:24:21 2012
# by: pyside-uic 0.2.13 running on PySide 1.1.0
#
# WARNING! All changes made in this file will be lost!
@@ -50,9 +50,9 @@ class Ui_MainWindow(object):
self.scaleCheck = QtGui.QCheckBox(self.centralwidget)
self.scaleCheck.setObjectName("scaleCheck")
self.gridLayout_2.addWidget(self.scaleCheck, 3, 0, 1, 1)
- self.rgbCheck = QtGui.QCheckBox(self.centralwidget)
- self.rgbCheck.setObjectName("rgbCheck")
- self.gridLayout_2.addWidget(self.rgbCheck, 3, 1, 1, 1)
+ self.rgbLevelsCheck = QtGui.QCheckBox(self.centralwidget)
+ self.rgbLevelsCheck.setObjectName("rgbLevelsCheck")
+ self.gridLayout_2.addWidget(self.rgbLevelsCheck, 3, 1, 1, 1)
self.horizontalLayout = QtGui.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.minSpin1 = SpinBox(self.centralwidget)
@@ -119,6 +119,9 @@ class Ui_MainWindow(object):
self.fpsLabel.setAlignment(QtCore.Qt.AlignCenter)
self.fpsLabel.setObjectName("fpsLabel")
self.gridLayout_2.addWidget(self.fpsLabel, 0, 0, 1, 4)
+ self.rgbCheck = QtGui.QCheckBox(self.centralwidget)
+ self.rgbCheck.setObjectName("rgbCheck")
+ self.gridLayout_2.addWidget(self.rgbCheck, 2, 1, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
@@ -133,12 +136,13 @@ class Ui_MainWindow(object):
self.dtypeCombo.setItemText(1, QtGui.QApplication.translate("MainWindow", "uint16", None, QtGui.QApplication.UnicodeUTF8))
self.dtypeCombo.setItemText(2, QtGui.QApplication.translate("MainWindow", "float", None, QtGui.QApplication.UnicodeUTF8))
self.scaleCheck.setText(QtGui.QApplication.translate("MainWindow", "Scale Data", None, QtGui.QApplication.UnicodeUTF8))
- self.rgbCheck.setText(QtGui.QApplication.translate("MainWindow", "RGB", None, QtGui.QApplication.UnicodeUTF8))
+ self.rgbLevelsCheck.setText(QtGui.QApplication.translate("MainWindow", "RGB", None, QtGui.QApplication.UnicodeUTF8))
self.label_2.setText(QtGui.QApplication.translate("MainWindow", "<--->", None, QtGui.QApplication.UnicodeUTF8))
self.label_3.setText(QtGui.QApplication.translate("MainWindow", "<--->", None, QtGui.QApplication.UnicodeUTF8))
self.label_4.setText(QtGui.QApplication.translate("MainWindow", "<--->", None, QtGui.QApplication.UnicodeUTF8))
self.lutCheck.setText(QtGui.QApplication.translate("MainWindow", "Use Lookup Table", None, QtGui.QApplication.UnicodeUTF8))
self.alphaCheck.setText(QtGui.QApplication.translate("MainWindow", "alpha", None, QtGui.QApplication.UnicodeUTF8))
self.fpsLabel.setText(QtGui.QApplication.translate("MainWindow", "FPS", None, QtGui.QApplication.UnicodeUTF8))
+ self.rgbCheck.setText(QtGui.QApplication.translate("MainWindow", "RGB", None, QtGui.QApplication.UnicodeUTF8))
from pyqtgraph import SpinBox, GradientWidget, GraphicsView, RawImageWidget
diff --git a/functions.py b/functions.py
index 1342b527..4eafbbf3 100644
--- a/functions.py
+++ b/functions.py
@@ -434,8 +434,10 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False,
## make sure vectors are arrays
- vectors = np.array(vectors)
- origin = np.array(origin)
+ if not isinstance(vectors, np.ndarray):
+ vectors = np.array(vectors)
+ if not isinstance(origin, np.ndarray):
+ origin = np.array(origin)
origin.shape = (len(axes),) + (1,)*len(shape)
## Build array of sample locations.
@@ -580,171 +582,247 @@ def solveBilinearTransform(points1, points2):
return matrix
+def rescaleData(data, scale, offset, dtype=None):
+ """Return data rescaled and optionally cast to a new dtype::
+ data => (data-offset) * scale
+
+ Uses scipy.weave (if available) to improve performance.
+ """
+ global USE_WEAVE
+ if dtype is None:
+ dtype = data.dtype
+ try:
+ if not USE_WEAVE:
+ raise Exception('Weave is disabled; falling back to slower version.')
+
+ newData = np.empty((data.size,), dtype=dtype)
+ flat = np.ascontiguousarray(data).reshape(data.size)
+ size = data.size
+
+ code = """
+ double sc = (double)scale;
+ double off = (double)offset;
+ for( int i=0; i0 and max->*scale*::
- For 3D arrays (x, y, rgba):
- * The third axis must have length 3 or 4 and will be interpreted as RGBA.
- * The 'lut' argument is not allowed.
+ rescaled = (clip(data, min, max) - min) * (*scale* / (max - min))
- lut - Lookup table for 2D data. May be 1D or 2D (N,rgba) and must have dtype=ubyte.
- Values in data will be converted to color by indexing directly from lut.
- Lookup tables can be built using GradientWidget.
- levels - List [min, max]; optionally rescale data before converting through the
- lookup table. rescaled = (data-min) * len(lut) / (max-min)
- useRGBA - If True, the data is returned in RGBA order (useful for building OpenGL textures). The default is
- False, which returns in BGRA order for use with QImage.
-
+ It is also possible to use a 2D (N,2) array of values for levels. In this case,
+ it is assumed that each pair of min,max values in the levels array should be
+ applied to a different subset of the input data (for example, the input data may
+ already have RGB values and the levels are used to independently scale each
+ channel). The use of this feature requires that levels.shape[0] == data.shape[-1].
+ scale The maximum value to which data will be rescaled before being passed through the
+ lookup table (or returned if there is no lookup table). By default this will
+ be set to the length of the lookup table, or 256 is no lookup table is provided.
+ For OpenGL color specifications (as in GLColor4f) use scale=1.0
+ lut Optional lookup table (array with dtype=ubyte).
+ Values in data will be converted to color by indexing directly from lut.
+ The output data shape will be input.shape + lut.shape[1:].
+
+ Note: the output of makeARGB will have the same dtype as the lookup table, so
+ for conversion to QImage, the dtype must be ubyte.
+
+ Lookup tables can be built using GradientWidget.
+ useRGBA If True, the data is returned in RGBA order (useful for building OpenGL textures).
+ The default is False, which returns in ARGB order for use with QImage
+ (Note that 'ARGB' is a term used by the Qt documentation; the _actual_ order
+ is BGRA).
+ ============ ==================================================================================
"""
prof = debug.Profiler('functions.makeARGB', disabled=True)
+ if lut is not None and not isinstance(lut, np.ndarray):
+ lut = np.array(lut)
+ if levels is not None and not isinstance(levels, np.ndarray):
+ levels = np.array(levels)
+
## sanity checks
- if data.ndim == 3:
- if data.shape[2] not in (3,4):
- raise Exception("data.shape[2] must be 3 or 4")
- #if lut is not None:
- #raise Exception("can not use lookup table with 3D data")
- elif data.ndim != 2:
- raise Exception("data must be 2D or 3D")
+ #if data.ndim == 3:
+ #if data.shape[2] not in (3,4):
+ #raise Exception("data.shape[2] must be 3 or 4")
+ ##if lut is not None:
+ ##raise Exception("can not use lookup table with 3D data")
+ #elif data.ndim != 2:
+ #raise Exception("data must be 2D or 3D")
- if lut is not None:
- if lut.ndim == 2:
- if lut.shape[1] not in (3,4):
- raise Exception("lut.shape[1] must be 3 or 4")
- elif lut.ndim != 1:
- raise Exception("lut must be 1D or 2D")
- if lut.dtype != np.ubyte:
- raise Exception('lookup table must have dtype=ubyte (got %s instead)' % str(lut.dtype))
+ #if lut is not None:
+ ##if lut.ndim == 2:
+ ##if lut.shape[1] :
+ ##raise Exception("lut.shape[1] must be 3 or 4")
+ ##elif lut.ndim != 1:
+ ##raise Exception("lut must be 1D or 2D")
+ #if lut.dtype != np.ubyte:
+ #raise Exception('lookup table must have dtype=ubyte (got %s instead)' % str(lut.dtype))
+
if levels is not None:
- levels = np.array(levels)
- if levels.shape == (2,):
- pass
- elif levels.shape in [(3,2), (4,2)]:
- if data.ndim == 3:
- raise Exception("Can not use 2D levels with 3D data.")
- if lut is not None:
- raise Exception('Can not use 2D levels and lookup table together.')
+ if levels.ndim == 1:
+ if len(levels) != 2:
+ raise Exception('levels argument must have length 2')
+ elif levels.ndim == 2:
+ if lut is not None and lut.ndim > 1:
+ raise Exception('Cannot make ARGB data when bot levels and lut have ndim > 2')
+ if levels.shape != (data.shape[-1], 2):
+ raise Exception('levels must have shape (data.shape[-1], 2)')
else:
- raise Exception("Levels must have shape (2,) or (3,2) or (4,2)")
+ print levels
+ raise Exception("levels argument must be 1D or 2D.")
+ #levels = np.array(levels)
+ #if levels.shape == (2,):
+ #pass
+ #elif levels.shape in [(3,2), (4,2)]:
+ #if data.ndim == 3:
+ #raise Exception("Can not use 2D levels with 3D data.")
+ #if lut is not None:
+ #raise Exception('Can not use 2D levels and lookup table together.')
+ #else:
+ #raise Exception("Levels must have shape (2,) or (3,2) or (4,2)")
prof.mark('1')
- if lut is not None:
- lutLength = lut.shape[0]
- else:
- lutLength = 256
-
- ## weave requires contiguous arrays
- global USE_WEAVE
- if (levels is not None or lut is not None) and USE_WEAVE:
- data = np.ascontiguousarray(data)
+ if scale is None:
+ if lut is not None:
+ scale = lut.shape[0]
+ else:
+ scale = 255.
## Apply levels if given
if levels is not None:
- try: ## use weave to speed up scaling
- if not USE_WEAVE:
- raise Exception('Weave is disabled; falling back to slower version.')
- if levels.ndim == 1:
- scale = float(lutLength) / (levels[1]-levels[0])
- offset = float(levels[0])
- data = rescaleData(data, scale, offset)
- else:
- if data.ndim == 2:
- newData = np.empty(data.shape+(levels.shape[0],), dtype=np.uint32)
- for i in range(levels.shape[0]):
- scale = float(lutLength / (levels[i,1]-levels[i,0]))
- offset = float(levels[i,0])
- newData[...,i] = rescaleData(data, scale, offset)
- elif data.ndim == 3:
- newData = np.empty(data.shape, dtype=np.uint32)
- for i in range(data.shape[2]):
- scale = float(lutLength / (levels[i,1]-levels[i,0]))
- offset = float(levels[i,0])
- #print scale, offset, data.shape, newData.shape, levels.shape
- newData[...,i] = rescaleData(data[...,i], scale, offset)
- data = newData
- except:
- if USE_WEAVE:
- debug.printExc("Error; disabling weave.")
- USE_WEAVE = False
-
- if levels.ndim == 1:
- if data.ndim == 2:
- levels = levels[np.newaxis, np.newaxis, :]
- else:
- levels = levels[np.newaxis, np.newaxis, np.newaxis, :]
- else:
- levels = levels[np.newaxis, np.newaxis, ...]
- if data.ndim == 2:
- data = data[..., np.newaxis]
- data = ((data-levels[...,0]) * lutLength) / (levels[...,1]-levels[...,0])
+ if isinstance(levels, np.ndarray) and levels.ndim == 2:
+ ## we are going to rescale each channel independently
+ if levels.shape[0] != data.shape[-1]:
+ raise Exception("When rescaling multi-channel data, there must be the same number of levels as channels (data.shape[-1] == levels.shape[0])")
+ newData = np.empty(data.shape, dtype=int)
+ for i in range(data.shape[-1]):
+ minVal, maxVal = levels[i]
+ if minVal == maxVal:
+ maxVal += 1e-16
+ newData[...,i] = rescaleData(data[...,i], scale/(maxVal-minVal), minVal, dtype=int)
+ data = newData
+ else:
+ minVal, maxVal = levels
+ if minVal == maxVal:
+ maxVal += 1e-16
+ data = rescaleData(data, scale/(maxVal-minVal), minVal, dtype=int)
prof.mark('2')
## apply LUT if given
- if lut is not None and data.ndim == 2:
-
- if data.dtype.kind not in ('i', 'u'):
- data = data.astype(int)
-
- data = np.clip(data, 0, lutLength-1)
- try:
- if not USE_WEAVE:
- raise Exception('Weave is disabled; falling back to slower version.')
-
- newData = np.empty((data.size,) + lut.shape[1:], dtype=np.uint8)
- flat = data.reshape(data.size)
- size = data.size
- ncol = lut.shape[1]
- newStride = newData.strides[0]
- newColStride = newData.strides[1]
- lutStride = lut.strides[0]
- lutColStride = lut.strides[1]
- flatStride = flat.strides[0] / flat.dtype.itemsize
-
- #print "newData:", newData.shape, newData.dtype
- #print "flat:", flat.shape, flat.dtype, flat.min(), flat.max()
- #print "lut:", lut.shape, lut.dtype
- #print "size:", size, "ncols:", ncol
- #print "strides:", newStride, newColStride, lutStride, lutColStride, flatStride
-
- code = """
-
- for( int i=0; i