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
This commit is contained in:
Luke Campagnola 2012-11-23 16:01:25 -05:00
parent a5a40be8bb
commit e5f383fbb5
9 changed files with 273 additions and 184 deletions

View File

@ -61,37 +61,41 @@ ui.alphaCheck.toggled.connect(updateLUT)
def updateScale(): def updateScale():
global ui global ui
spins = [ui.minSpin1, ui.maxSpin1, ui.minSpin2, ui.maxSpin2, ui.minSpin3, ui.maxSpin3] 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:]: for s in spins[2:]:
s.setEnabled(True) s.setEnabled(True)
else: else:
for s in spins[2:]: for s in spins[2:]:
s.setEnabled(False) s.setEnabled(False)
ui.rgbCheck.toggled.connect(updateScale) ui.rgbLevelsCheck.toggled.connect(updateScale)
cache = {} cache = {}
def mkData(): def mkData():
global data, cache, ui global data, cache, ui
dtype = ui.dtypeCombo.currentText() dtype = (ui.dtypeCombo.currentText(), ui.rgbCheck.isChecked())
if dtype not in cache: if dtype not in cache:
if dtype == 'uint8': if dtype[0] == 'uint8':
dt = np.uint8 dt = np.uint8
loc = 128 loc = 128
scale = 64 scale = 64
mx = 255 mx = 255
elif dtype == 'uint16': elif dtype[0] == 'uint16':
dt = np.uint16 dt = np.uint16
loc = 4096 loc = 4096
scale = 1024 scale = 1024
mx = 2**16 mx = 2**16
elif dtype == 'float': elif dtype[0] == 'float':
dt = np.float dt = np.float
loc = 1.0 loc = 1.0
scale = 0.1 scale = 0.1
data = np.random.normal(size=(20,512,512), loc=loc, scale=scale) if ui.rgbCheck.isChecked():
data = ndi.gaussian_filter(data, (0, 3, 3)) data = np.random.normal(size=(20,512,512,3), loc=loc, scale=scale)
if dtype != 'float': 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 = np.clip(data, 0, mx)
data = data.astype(dt) data = data.astype(dt)
cache[dtype] = data cache[dtype] = data
@ -100,7 +104,7 @@ def mkData():
updateLUT() updateLUT()
mkData() mkData()
ui.dtypeCombo.currentIndexChanged.connect(mkData) ui.dtypeCombo.currentIndexChanged.connect(mkData)
ui.rgbCheck.toggled.connect(mkData)
ptr = 0 ptr = 0
lastTime = ptime.time() lastTime = ptime.time()
@ -113,7 +117,7 @@ def update():
useLut = None useLut = None
if ui.scaleCheck.isChecked(): if ui.scaleCheck.isChecked():
if ui.rgbCheck.isChecked(): if ui.rgbLevelsCheck.isChecked():
useScale = [ useScale = [
[ui.minSpin1.value(), ui.maxSpin1.value()], [ui.minSpin1.value(), ui.maxSpin1.value()],
[ui.minSpin2.value(), ui.maxSpin2.value()], [ui.minSpin2.value(), ui.maxSpin2.value()],

View File

@ -25,7 +25,6 @@
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<zorder>fpsLabel</zorder>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
@ -84,7 +83,7 @@
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="3" column="1">
<widget class="QCheckBox" name="rgbCheck"> <widget class="QCheckBox" name="rgbLevelsCheck">
<property name="text"> <property name="text">
<string>RGB</string> <string>RGB</string>
</property> </property>
@ -218,6 +217,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1">
<widget class="QCheckBox" name="rgbCheck">
<property name="text">
<string>RGB</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</widget> </widget>

View File

@ -2,7 +2,7 @@
# Form implementation generated from reading ui file './examples/VideoTemplate.ui' # 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 # by: PyQt4 UI code generator 4.9.1
# #
# WARNING! All changes made in this file will be lost! # 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 = QtGui.QCheckBox(self.centralwidget)
self.scaleCheck.setObjectName(_fromUtf8("scaleCheck")) self.scaleCheck.setObjectName(_fromUtf8("scaleCheck"))
self.gridLayout_2.addWidget(self.scaleCheck, 3, 0, 1, 1) self.gridLayout_2.addWidget(self.scaleCheck, 3, 0, 1, 1)
self.rgbCheck = QtGui.QCheckBox(self.centralwidget) self.rgbLevelsCheck = QtGui.QCheckBox(self.centralwidget)
self.rgbCheck.setObjectName(_fromUtf8("rgbCheck")) self.rgbLevelsCheck.setObjectName(_fromUtf8("rgbLevelsCheck"))
self.gridLayout_2.addWidget(self.rgbCheck, 3, 1, 1, 1) self.gridLayout_2.addWidget(self.rgbLevelsCheck, 3, 1, 1, 1)
self.horizontalLayout = QtGui.QHBoxLayout() self.horizontalLayout = QtGui.QHBoxLayout()
self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout"))
self.minSpin1 = SpinBox(self.centralwidget) self.minSpin1 = SpinBox(self.centralwidget)
@ -124,6 +124,9 @@ class Ui_MainWindow(object):
self.fpsLabel.setAlignment(QtCore.Qt.AlignCenter) self.fpsLabel.setAlignment(QtCore.Qt.AlignCenter)
self.fpsLabel.setObjectName(_fromUtf8("fpsLabel")) self.fpsLabel.setObjectName(_fromUtf8("fpsLabel"))
self.gridLayout_2.addWidget(self.fpsLabel, 0, 0, 1, 4) 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) MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow) 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(1, QtGui.QApplication.translate("MainWindow", "uint16", None, QtGui.QApplication.UnicodeUTF8))
self.dtypeCombo.setItemText(2, QtGui.QApplication.translate("MainWindow", "float", 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.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_2.setText(QtGui.QApplication.translate("MainWindow", "<--->", None, QtGui.QApplication.UnicodeUTF8))
self.label_3.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.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.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.alphaCheck.setText(QtGui.QApplication.translate("MainWindow", "alpha", None, QtGui.QApplication.UnicodeUTF8))
self.fpsLabel.setText(QtGui.QApplication.translate("MainWindow", "FPS", 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 from pyqtgraph import SpinBox, GradientWidget, GraphicsView, RawImageWidget

View File

@ -2,7 +2,7 @@
# Form implementation generated from reading ui file './examples/VideoTemplate.ui' # 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 # by: pyside-uic 0.2.13 running on PySide 1.1.0
# #
# WARNING! All changes made in this file will be lost! # 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 = QtGui.QCheckBox(self.centralwidget)
self.scaleCheck.setObjectName("scaleCheck") self.scaleCheck.setObjectName("scaleCheck")
self.gridLayout_2.addWidget(self.scaleCheck, 3, 0, 1, 1) self.gridLayout_2.addWidget(self.scaleCheck, 3, 0, 1, 1)
self.rgbCheck = QtGui.QCheckBox(self.centralwidget) self.rgbLevelsCheck = QtGui.QCheckBox(self.centralwidget)
self.rgbCheck.setObjectName("rgbCheck") self.rgbLevelsCheck.setObjectName("rgbLevelsCheck")
self.gridLayout_2.addWidget(self.rgbCheck, 3, 1, 1, 1) self.gridLayout_2.addWidget(self.rgbLevelsCheck, 3, 1, 1, 1)
self.horizontalLayout = QtGui.QHBoxLayout() self.horizontalLayout = QtGui.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout") self.horizontalLayout.setObjectName("horizontalLayout")
self.minSpin1 = SpinBox(self.centralwidget) self.minSpin1 = SpinBox(self.centralwidget)
@ -119,6 +119,9 @@ class Ui_MainWindow(object):
self.fpsLabel.setAlignment(QtCore.Qt.AlignCenter) self.fpsLabel.setAlignment(QtCore.Qt.AlignCenter)
self.fpsLabel.setObjectName("fpsLabel") self.fpsLabel.setObjectName("fpsLabel")
self.gridLayout_2.addWidget(self.fpsLabel, 0, 0, 1, 4) 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) MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow) 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(1, QtGui.QApplication.translate("MainWindow", "uint16", None, QtGui.QApplication.UnicodeUTF8))
self.dtypeCombo.setItemText(2, QtGui.QApplication.translate("MainWindow", "float", 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.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_2.setText(QtGui.QApplication.translate("MainWindow", "<--->", None, QtGui.QApplication.UnicodeUTF8))
self.label_3.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.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.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.alphaCheck.setText(QtGui.QApplication.translate("MainWindow", "alpha", None, QtGui.QApplication.UnicodeUTF8))
self.fpsLabel.setText(QtGui.QApplication.translate("MainWindow", "FPS", 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 from pyqtgraph import SpinBox, GradientWidget, GraphicsView, RawImageWidget

View File

@ -434,8 +434,10 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False,
## make sure vectors are arrays ## make sure vectors are arrays
vectors = np.array(vectors) if not isinstance(vectors, np.ndarray):
origin = np.array(origin) vectors = np.array(vectors)
if not isinstance(origin, np.ndarray):
origin = np.array(origin)
origin.shape = (len(axes),) + (1,)*len(shape) origin.shape = (len(axes),) + (1,)*len(shape)
## Build array of sample locations. ## Build array of sample locations.
@ -580,6 +582,114 @@ def solveBilinearTransform(points1, points2):
return matrix 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; i<size; i++ ) {
newData[i] = ((double)flat[i] - off) * sc;
}
"""
scipy.weave.inline(code, ['flat', 'newData', 'size', 'offset', 'scale'], compiler='gcc')
data = newData.reshape(data.shape)
except:
if USE_WEAVE:
debug.printExc("Error; disabling weave.")
USE_WEAVE = False
#p = np.poly1d([scale, -offset*scale])
#data = p(data).astype(dtype)
d2 = data-offset
d2 *= scale
data = d2.astype(dtype)
return data
def applyLookupTable(data, lut):
"""
Uses values in *data* as indexes to select values from *lut*.
The returned data has shape data.shape + lut.shape[1:]
Uses scipy.weave to improve performance if it is available.
Note: color gradient lookup tables can be generated using GradientWidget.
"""
global USE_WEAVE
if data.dtype.kind not in ('i', 'u'):
data = data.astype(int)
## using np.take appears to be faster than even the scipy.weave method and takes care of clipping as well.
return np.take(lut, data, axis=0, mode='clip')
### old methods:
#data = np.clip(data, 0, lut.shape[0]-1)
#try:
#if not USE_WEAVE:
#raise Exception('Weave is disabled; falling back to slower version.')
### number of values to copy for each LUT lookup
#if lut.ndim == 1:
#ncol = 1
#else:
#ncol = sum(lut.shape[1:])
### output array
#newData = np.empty((data.size, ncol), dtype=lut.dtype)
### flattened input arrays
#flatData = data.flatten()
#flatLut = lut.reshape((lut.shape[0], ncol))
#dataSize = data.size
### strides for accessing each item
#newStride = newData.strides[0] / newData.dtype.itemsize
#lutStride = flatLut.strides[0] / flatLut.dtype.itemsize
#dataStride = flatData.strides[0] / flatData.dtype.itemsize
### strides for accessing individual values within a single LUT lookup
#newColStride = newData.strides[1] / newData.dtype.itemsize
#lutColStride = flatLut.strides[1] / flatLut.dtype.itemsize
#code = """
#for( int i=0; i<dataSize; i++ ) {
#for( int j=0; j<ncol; j++ ) {
#newData[i*newStride + j*newColStride] = flatLut[flatData[i*dataStride]*lutStride + j*lutColStride];
#}
#}
#"""
#scipy.weave.inline(code, ['flatData', 'flatLut', 'newData', 'dataSize', 'ncol', 'newStride', 'lutStride', 'dataStride', 'newColStride', 'lutColStride'])
#newData = newData.reshape(data.shape + lut.shape[1:])
##if np.any(newData != lut[data]):
##print "mismatch!"
#data = newData
#except:
#if USE_WEAVE:
#debug.printExc("Error; disabling weave.")
#USE_WEAVE = False
#data = lut[data]
#return data
def makeRGBA(*args, **kwds): def makeRGBA(*args, **kwds):
@ -587,164 +697,132 @@ def makeRGBA(*args, **kwds):
kwds['useRGBA'] = True kwds['useRGBA'] = True
return makeARGB(*args, **kwds) return makeARGB(*args, **kwds)
def makeARGB(data, lut=None, levels=None, useRGBA=False): def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
""" """
Convert a 2D or 3D array into an ARGB array suitable for building QImages Convert an array of values into an ARGB array suitable for building QImages, OpenGL textures, etc.
Will optionally do scaling and/or table lookups to determine final colors.
Returns the ARGB array (values 0-255) and a boolean indicating whether there is alpha channel data. Returns the ARGB array (values 0-255) and a boolean indicating whether there is alpha channel data.
This is a two stage process:
1) Rescale the data based on the values in the *levels* argument (min, max).
2) Determine the final output by passing the rescaled values through a lookup table.
Both stages are optional.
============ ==================================================================================
Arguments: Arguments:
data - 2D or 3D numpy array of int/float types data numpy array of int/float types. If
levels List [min, max]; optionally rescale data before converting through the
lookup table. The data is rescaled such that min->0 and max->*scale*::
For 2D arrays (x, y): rescaled = (clip(data, min, max) - min) * (*scale* / (max - min))
* The color will be determined using a lookup table (see argument 'lut').
* If levels are given, the data is rescaled and converted to int
before using the lookup table.
For 3D arrays (x, y, rgba): It is also possible to use a 2D (N,2) array of values for levels. In this case,
* The third axis must have length 3 or 4 and will be interpreted as RGBA. it is assumed that each pair of min,max values in the levels array should be
* The 'lut' argument is not allowed. 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:].
lut - Lookup table for 2D data. May be 1D or 2D (N,rgba) and must have dtype=ubyte. Note: the output of makeARGB will have the same dtype as the lookup table, so
Values in data will be converted to color by indexing directly from lut. for conversion to QImage, the dtype must be ubyte.
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.
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) prof = debug.Profiler('functions.makeARGB', disabled=True)
## sanity checks if lut is not None and not isinstance(lut, np.ndarray):
if data.ndim == 3: lut = np.array(lut)
if data.shape[2] not in (3,4): if levels is not None and not isinstance(levels, np.ndarray):
raise Exception("data.shape[2] must be 3 or 4") levels = np.array(levels)
#if lut is not None:
#raise Exception("can not use lookup table with 3D data") ## sanity checks
elif data.ndim != 2: #if data.ndim == 3:
raise Exception("data must be 2D or 3D") #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] :
##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] 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 levels is not None: if levels is not None:
levels = np.array(levels) if levels.ndim == 1:
if levels.shape == (2,): if len(levels) != 2:
pass raise Exception('levels argument must have length 2')
elif levels.shape in [(3,2), (4,2)]: elif levels.ndim == 2:
if data.ndim == 3: if lut is not None and lut.ndim > 1:
raise Exception("Can not use 2D levels with 3D data.") raise Exception('Cannot make ARGB data when bot levels and lut have ndim > 2')
if lut is not None: if levels.shape != (data.shape[-1], 2):
raise Exception('Can not use 2D levels and lookup table together.') raise Exception('levels must have shape (data.shape[-1], 2)')
else: 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') prof.mark('1')
if lut is not None: if scale is None:
lutLength = lut.shape[0] if lut is not None:
else: scale = lut.shape[0]
lutLength = 256 else:
scale = 255.
## weave requires contiguous arrays
global USE_WEAVE
if (levels is not None or lut is not None) and USE_WEAVE:
data = np.ascontiguousarray(data)
## Apply levels if given ## Apply levels if given
if levels is not None: if levels is not None:
try: ## use weave to speed up scaling if isinstance(levels, np.ndarray) and levels.ndim == 2:
if not USE_WEAVE: ## we are going to rescale each channel independently
raise Exception('Weave is disabled; falling back to slower version.') if levels.shape[0] != data.shape[-1]:
if levels.ndim == 1: raise Exception("When rescaling multi-channel data, there must be the same number of levels as channels (data.shape[-1] == levels.shape[0])")
scale = float(lutLength) / (levels[1]-levels[0]) newData = np.empty(data.shape, dtype=int)
offset = float(levels[0]) for i in range(data.shape[-1]):
data = rescaleData(data, scale, offset) minVal, maxVal = levels[i]
else: if minVal == maxVal:
if data.ndim == 2: maxVal += 1e-16
newData = np.empty(data.shape+(levels.shape[0],), dtype=np.uint32) newData[...,i] = rescaleData(data[...,i], scale/(maxVal-minVal), minVal, dtype=int)
for i in range(levels.shape[0]): data = newData
scale = float(lutLength / (levels[i,1]-levels[i,0])) else:
offset = float(levels[i,0]) minVal, maxVal = levels
newData[...,i] = rescaleData(data, scale, offset) if minVal == maxVal:
elif data.ndim == 3: maxVal += 1e-16
newData = np.empty(data.shape, dtype=np.uint32) data = rescaleData(data, scale/(maxVal-minVal), minVal, dtype=int)
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])
prof.mark('2') prof.mark('2')
## apply LUT if given ## apply LUT if given
if lut is not None and data.ndim == 2: if lut is not None:
data = applyLookupTable(data, lut)
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<size; i++ ) {
for( int j=0; j<ncol; j++ ) {
newData[i*newStride + j*newColStride] = lut[flat[i*flatStride]*lutStride + j*lutColStride];
}
}
"""
scipy.weave.inline(code, ['flat', 'lut', 'newData', 'size', 'ncol', 'newStride', 'lutStride', 'flatStride', 'newColStride', 'lutColStride'])
data = newData.reshape(data.shape + lut.shape[1:])
except:
if USE_WEAVE:
debug.printExc("Error; disabling weave.")
USE_WEAVE = False
data = lut[data]
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)
@ -895,25 +973,6 @@ def imageToArray(img, copy=False, transpose=True):
return arr return arr
def rescaleData(data, scale, offset):
newData = np.empty((data.size,), dtype=np.int)
flat = data.reshape(data.size)
size = data.size
code = """
double sc = (double)scale;
double off = (double)offset;
for( int i=0; i<size; i++ ) {
newData[i] = (int)(((double)flat[i] - off) * sc);
}
"""
scipy.weave.inline(code, ['flat', 'newData', 'size', 'offset', 'scale'], compiler='gcc')
data = newData.reshape(data.shape)
return data
#def isosurface(data, level): #def isosurface(data, level):
#""" #"""
#Generate isosurface from volumetric data using marching tetrahedra algorithm. #Generate isosurface from volumetric data using marching tetrahedra algorithm.
@ -1090,7 +1149,7 @@ def isosurface(data, level):
*data* 3D numpy array of scalar values *data* 3D numpy array of scalar values
*level* The level at which to generate an isosurface *level* The level at which to generate an isosurface
Returns a list of faces; each face is a list of three vertexes and each vertex is a tuple of three floats. Returns an array of vertex coordinates (N, 3, 3);
This function is SLOW; plenty of room for optimization here. This function is SLOW; plenty of room for optimization here.
""" """
@ -1457,7 +1516,7 @@ def isosurface(data, level):
pts.append(p) pts.append(p)
facets.append(pts) facets.append(pts)
return facets return np.array(facets)

View File

@ -166,13 +166,15 @@ class GraphicsItem(object):
## attempt to re-scale direction vector to fit within the precision of the coordinate system ## attempt to re-scale direction vector to fit within the precision of the coordinate system
if direction.x() == 0: if direction.x() == 0:
r = abs(dt.m32())/(abs(dt.m12()) + abs(dt.m22())) r = abs(dt.m32())/(abs(dt.m12()) + abs(dt.m22()))
#r = 1.0/(abs(dt.m12()) + abs(dt.m22()))
elif direction.y() == 0: elif direction.y() == 0:
r = abs(dt.m31())/(abs(dt.m11()) + abs(dt.m21())) r = abs(dt.m31())/(abs(dt.m11()) + abs(dt.m21()))
#r = 1.0/(abs(dt.m11()) + abs(dt.m21()))
else: else:
r = ((abs(dt.m32())/(abs(dt.m12()) + abs(dt.m22()))) * (abs(dt.m31())/(abs(dt.m11()) + abs(dt.m21()))))**0.5 r = ((abs(dt.m32())/(abs(dt.m12()) + abs(dt.m22()))) * (abs(dt.m31())/(abs(dt.m11()) + abs(dt.m21()))))**0.5
direction = direction * r directionr = direction * r
viewDir = Point(dt.map(direction) - dt.map(Point(0,0))) viewDir = Point(dt.map(directionr) - dt.map(Point(0,0)))
if viewDir.manhattanLength() == 0: if viewDir.manhattanLength() == 0:
return None, None ## pixel size cannot be represented on this scale return None, None ## pixel size cannot be represented on this scale
@ -182,7 +184,7 @@ class GraphicsItem(object):
normView = viewDir.norm() ## direction of one pixel orthogonal to line normView = viewDir.norm() ## direction of one pixel orthogonal to line
normOrtho = orthoDir.norm() normOrtho = orthoDir.norm()
except: except:
raise Exception("Invalid direction %s" %direction) raise Exception("Invalid direction %s" %directionr)
dti = fn.invertQTransform(dt) dti = fn.invertQTransform(dt)

View File

@ -166,7 +166,7 @@ class ViewBox(GraphicsWidget):
ViewBox.NamedViews[name] = self ViewBox.NamedViews[name] = self
ViewBox.updateAllViewLists() ViewBox.updateAllViewLists()
sid = id(self) sid = id(self)
self.destroyed.connect(lambda: ViewBox.forgetView(sid, name) if ViewBox is not None else None) self.destroyed.connect(lambda: ViewBox.forgetView(sid, name) if (ViewBox is not None and 'sid' in locals() and 'name' in locals()) else None)
#self.destroyed.connect(self.unregister) #self.destroyed.connect(self.unregister)
def unregister(self): def unregister(self):
@ -1154,7 +1154,7 @@ class ViewBox(GraphicsWidget):
if any(changed): if any(changed):
self.sigRangeChanged.emit(self, self.state['viewRange']) self.sigRangeChanged.emit(self, self.state['viewRange'])
self.sigTransformChanged.emit(self) self.sigTransformChanged.emit(self) ## segfaults here: 1
def paint(self, p, opt, widget): def paint(self, p, opt, widget):
if self.border is not None: if self.border is not None:

View File

@ -460,7 +460,10 @@ class Parameter(QtCore.QObject):
self.childs.pop(self.childs.index(child)) self.childs.pop(self.childs.index(child))
child.parentChanged(None) child.parentChanged(None)
self.sigChildRemoved.emit(self, child) self.sigChildRemoved.emit(self, child)
child.sigTreeStateChanged.disconnect(self.treeStateChanged) try:
child.sigTreeStateChanged.disconnect(self.treeStateChanged)
except TypeError: ## already disconnected
pass
def clearChildren(self): def clearChildren(self):
"""Remove all child parameters.""" """Remove all child parameters."""
@ -550,6 +553,8 @@ class Parameter(QtCore.QObject):
def __getattr__(self, attr): def __getattr__(self, attr):
## Leaving this undocumented because I might like to remove it in the future.. ## Leaving this undocumented because I might like to remove it in the future..
#print type(self), attr #print type(self), attr
if 'names' not in self.__dict__:
raise AttributeError(attr)
if attr in self.names: if attr in self.names:
return self.param(attr) return self.param(attr)
else: else:

View File

@ -56,8 +56,13 @@ class LayoutWidget(QtGui.QWidget):
""" """
Add a widget to the layout and place it in the next available cell (or in the cell specified). Add a widget to the layout and place it in the next available cell (or in the cell specified).
""" """
if row is None: if row == 'next':
self.nextRow()
row = self.currentRow row = self.currentRow
elif row is None:
row = self.currentRow
if col is None: if col is None:
col = self.nextCol(colspan) col = self.nextCol(colspan)