Performance enhancement: use CUDA in ImageItem (#1466)
* Add CLI args to video speed test for easier / automated benchmarking * use a buffer-qimage so we can avoid allocing so much this should improve performance under windows * playing with numba * oh, mins/maxes in the other order * maybe put the cupy in here and see what happens * pre-alloc for gpu and cpu * handle possibility of not having cupy * no numba in this branch * organize imports * name them after their use, not their expected device * cupy.take does not support clip mode, so do it explicitly * add CUDA option to the VideoSpeedTest * rename private attr xp to _xp * handle resizes at the last moment * cupy is less accepting of lists as args * or somehow range isn't allowed? what histogram is this? * construct the array with python objects * get the python value right away * put LUT into cupy if needed * docstring about cuda toolkit version * better handling and display of missing cuda lib * lint * import need * handle switching between cupy and numpy in a single ImageItem * only use xp when necessary we can now depend on numpy >= 1.17, which means __array_function__-implementing cupy can seamlessly pass into numpy functions. the remaining uses of xp are for our functions which need to allocate new data structures, an operation that has to be substrate-specific. remove empty_cupy; just check if the import succeeded, instead. * use an option to control use of cupy * convert cupy.ceil array to int for easier mathing * RawImageWidget gets to use the getCupy function now, too * raise error to calm linters; rename for clarity * Add Generated Template Files * document things better * cruft removal * warnings to communicate when cupy is expected but somehow broken * playing with settings to suss out timeout * playing with more stuff to suss out timeout * replace with empty list * skip test_ExampleApp on linux+pyside2 only Co-authored-by: Luke Campagnola <luke.campagnola@gmail.com> Co-authored-by: Ogi Moore <ognyan.moore@gmail.com>
This commit is contained in:
parent
78bc0fd3ca
commit
f2b4a15b2d
@ -44,6 +44,8 @@ Currently this means:
|
|||||||
* `pyopengl` on macOS Big Sur only works with python 3.9.1+
|
* `pyopengl` on macOS Big Sur only works with python 3.9.1+
|
||||||
* `hdf5` for large hdf5 binary format support
|
* `hdf5` for large hdf5 binary format support
|
||||||
* `colorcet` for supplemental colormaps
|
* `colorcet` for supplemental colormaps
|
||||||
|
* [`cupy`](https://docs.cupy.dev/en/stable/install.html) for CUDA-enhanced image processing
|
||||||
|
* On Windows, CUDA toolkit must be >= 11.1
|
||||||
|
|
||||||
Qt Bindings Test Matrix
|
Qt Bindings Test Matrix
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@ -30,6 +30,8 @@ useWeave bool False Use weave to speed up
|
|||||||
weaveDebug bool False Print full error message if weave compile fails.
|
weaveDebug bool False Print full error message if weave compile fails.
|
||||||
useOpenGL bool False Enable OpenGL in GraphicsView. This can have unpredictable effects on stability
|
useOpenGL bool False Enable OpenGL in GraphicsView. This can have unpredictable effects on stability
|
||||||
and performance.
|
and performance.
|
||||||
|
useCupy bool False Use cupy to perform calculations on the GPU. Only currently applies to
|
||||||
|
ImageItem and its associated functions.
|
||||||
enableExperimental bool False Enable experimental features (the curious can search for this key in the code).
|
enableExperimental bool False Enable experimental features (the curious can search for this key in the code).
|
||||||
crashWarning bool False If True, print warnings about situations that may result in a crash.
|
crashWarning bool False If True, print warnings about situations that may result in a crash.
|
||||||
================== =================== ================== ================================================================================
|
================== =================== ================== ================================================================================
|
||||||
|
@ -6,18 +6,36 @@ it is being scaled and/or converted by lookup table, and whether OpenGL
|
|||||||
is used by the view widget
|
is used by the view widget
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
|
||||||
import initExample ## Add path to library (just for examples; you do not need this)
|
|
||||||
|
|
||||||
|
|
||||||
from pyqtgraph.Qt import QtGui, QtCore, QT_LIB
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
import pyqtgraph.ptime as ptime
|
import pyqtgraph.ptime as ptime
|
||||||
|
from pyqtgraph.Qt import QtGui, QtCore, QT_LIB
|
||||||
|
|
||||||
import importlib
|
import importlib
|
||||||
ui_template = importlib.import_module(f'VideoTemplate_{QT_LIB.lower()}')
|
ui_template = importlib.import_module(f'VideoTemplate_{QT_LIB.lower()}')
|
||||||
|
|
||||||
|
try:
|
||||||
|
import cupy as cp
|
||||||
|
pg.setConfigOption("useCupy", True)
|
||||||
|
_has_cupy = True
|
||||||
|
except ImportError:
|
||||||
|
cp = None
|
||||||
|
_has_cupy = False
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="Benchmark for testing video performance")
|
||||||
|
parser.add_argument('--cuda', default=False, action='store_true', help="Use CUDA to process on the GPU", dest="cuda")
|
||||||
|
parser.add_argument('--dtype', default='uint8', choices=['uint8', 'uint16', 'float'], help="Image dtype (uint8, uint16, or float)")
|
||||||
|
parser.add_argument('--frames', default=3, type=int, help="Number of image frames to generate (default=3)")
|
||||||
|
parser.add_argument('--image-mode', default='mono', choices=['mono', 'rgb'], help="Image data mode (mono or rgb)", dest='image_mode')
|
||||||
|
parser.add_argument('--levels', default=None, type=lambda s: tuple([float(x) for x in s.split(',')]), help="min,max levels to scale monochromatic image dynamic range, or rmin,rmax,gmin,gmax,bmin,bmax to scale rgb")
|
||||||
|
parser.add_argument('--lut', default=False, action='store_true', help="Use color lookup table")
|
||||||
|
parser.add_argument('--lut-alpha', default=False, action='store_true', help="Use alpha color lookup table", dest='lut_alpha')
|
||||||
|
parser.add_argument('--size', default='512x512', type=lambda s: tuple([int(x) for x in s.split('x')]), help="WxH image dimensions default='512x512'")
|
||||||
|
args = parser.parse_args(sys.argv[1:])
|
||||||
|
|
||||||
#QtGui.QApplication.setGraphicsSystem('raster')
|
#QtGui.QApplication.setGraphicsSystem('raster')
|
||||||
app = QtGui.QApplication([])
|
app = QtGui.QApplication([])
|
||||||
@ -31,14 +49,46 @@ win.show()
|
|||||||
try:
|
try:
|
||||||
from pyqtgraph.widgets.RawImageWidget import RawImageGLWidget
|
from pyqtgraph.widgets.RawImageWidget import RawImageGLWidget
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
RawImageGLWidget = None
|
||||||
ui.rawGLRadio.setEnabled(False)
|
ui.rawGLRadio.setEnabled(False)
|
||||||
ui.rawGLRadio.setText(ui.rawGLRadio.text() + " (OpenGL not available)")
|
ui.rawGLRadio.setText(ui.rawGLRadio.text() + " (OpenGL not available)")
|
||||||
else:
|
else:
|
||||||
ui.rawGLImg = RawImageGLWidget()
|
ui.rawGLImg = RawImageGLWidget()
|
||||||
ui.stack.addWidget(ui.rawGLImg)
|
ui.stack.addWidget(ui.rawGLImg)
|
||||||
|
|
||||||
|
# read in CLI args
|
||||||
|
ui.cudaCheck.setChecked(args.cuda and _has_cupy)
|
||||||
|
ui.cudaCheck.setEnabled(_has_cupy)
|
||||||
|
ui.framesSpin.setValue(args.frames)
|
||||||
|
ui.widthSpin.setValue(args.size[0])
|
||||||
|
ui.heightSpin.setValue(args.size[1])
|
||||||
|
ui.dtypeCombo.setCurrentText(args.dtype)
|
||||||
|
ui.rgbCheck.setChecked(args.image_mode=='rgb')
|
||||||
ui.maxSpin1.setOpts(value=255, step=1)
|
ui.maxSpin1.setOpts(value=255, step=1)
|
||||||
ui.minSpin1.setOpts(value=0, step=1)
|
ui.minSpin1.setOpts(value=0, step=1)
|
||||||
|
levelSpins = [ui.minSpin1, ui.maxSpin1, ui.minSpin2, ui.maxSpin2, ui.minSpin3, ui.maxSpin3]
|
||||||
|
if args.cuda and _has_cupy:
|
||||||
|
xp = cp
|
||||||
|
else:
|
||||||
|
xp = np
|
||||||
|
if args.levels is None:
|
||||||
|
ui.scaleCheck.setChecked(False)
|
||||||
|
ui.rgbLevelsCheck.setChecked(False)
|
||||||
|
else:
|
||||||
|
ui.scaleCheck.setChecked(True)
|
||||||
|
if len(args.levels) == 2:
|
||||||
|
ui.rgbLevelsCheck.setChecked(False)
|
||||||
|
ui.minSpin1.setValue(args.levels[0])
|
||||||
|
ui.maxSpin1.setValue(args.levels[1])
|
||||||
|
elif len(args.levels) == 6:
|
||||||
|
ui.rgbLevelsCheck.setChecked(True)
|
||||||
|
for spin,val in zip(levelSpins, args.levels):
|
||||||
|
spin.setValue(val)
|
||||||
|
else:
|
||||||
|
raise ValueError("levels argument must be 2 or 6 comma-separated values (got %r)" % (args.levels,))
|
||||||
|
ui.lutCheck.setChecked(args.lut)
|
||||||
|
ui.alphaCheck.setChecked(args.lut_alpha)
|
||||||
|
|
||||||
|
|
||||||
#ui.graphicsView.useOpenGL() ## buggy, but you can try it if you need extra speed.
|
#ui.graphicsView.useOpenGL() ## buggy, but you can try it if you need extra speed.
|
||||||
|
|
||||||
@ -47,7 +97,8 @@ ui.graphicsView.setCentralItem(vb)
|
|||||||
vb.setAspectLocked()
|
vb.setAspectLocked()
|
||||||
img = pg.ImageItem()
|
img = pg.ImageItem()
|
||||||
vb.addItem(img)
|
vb.addItem(img)
|
||||||
vb.setRange(QtCore.QRectF(0, 0, 512, 512))
|
|
||||||
|
|
||||||
|
|
||||||
LUT = None
|
LUT = None
|
||||||
def updateLUT():
|
def updateLUT():
|
||||||
@ -58,74 +109,94 @@ def updateLUT():
|
|||||||
else:
|
else:
|
||||||
n = 4096
|
n = 4096
|
||||||
LUT = ui.gradient.getLookupTable(n, alpha=ui.alphaCheck.isChecked())
|
LUT = ui.gradient.getLookupTable(n, alpha=ui.alphaCheck.isChecked())
|
||||||
|
if _has_cupy and xp == cp:
|
||||||
|
LUT = cp.asarray(LUT)
|
||||||
ui.gradient.sigGradientChanged.connect(updateLUT)
|
ui.gradient.sigGradientChanged.connect(updateLUT)
|
||||||
updateLUT()
|
updateLUT()
|
||||||
|
|
||||||
ui.alphaCheck.toggled.connect(updateLUT)
|
ui.alphaCheck.toggled.connect(updateLUT)
|
||||||
|
|
||||||
def updateScale():
|
def updateScale():
|
||||||
global ui
|
global ui, levelSpins
|
||||||
spins = [ui.minSpin1, ui.maxSpin1, ui.minSpin2, ui.maxSpin2, ui.minSpin3, ui.maxSpin3]
|
|
||||||
if ui.rgbLevelsCheck.isChecked():
|
if ui.rgbLevelsCheck.isChecked():
|
||||||
for s in spins[2:]:
|
for s in levelSpins[2:]:
|
||||||
s.setEnabled(True)
|
s.setEnabled(True)
|
||||||
else:
|
else:
|
||||||
for s in spins[2:]:
|
for s in levelSpins[2:]:
|
||||||
s.setEnabled(False)
|
s.setEnabled(False)
|
||||||
|
|
||||||
|
updateScale()
|
||||||
|
|
||||||
ui.rgbLevelsCheck.toggled.connect(updateScale)
|
ui.rgbLevelsCheck.toggled.connect(updateScale)
|
||||||
|
|
||||||
cache = {}
|
cache = {}
|
||||||
def mkData():
|
def mkData():
|
||||||
with pg.BusyCursor():
|
with pg.BusyCursor():
|
||||||
global data, cache, ui
|
global data, cache, ui, xp
|
||||||
frames = ui.framesSpin.value()
|
frames = ui.framesSpin.value()
|
||||||
width = ui.widthSpin.value()
|
width = ui.widthSpin.value()
|
||||||
height = ui.heightSpin.value()
|
height = ui.heightSpin.value()
|
||||||
dtype = (ui.dtypeCombo.currentText(), ui.rgbCheck.isChecked(), frames, width, height)
|
cacheKey = (ui.dtypeCombo.currentText(), ui.rgbCheck.isChecked(), frames, width, height)
|
||||||
if dtype not in cache:
|
if cacheKey not in cache:
|
||||||
if dtype[0] == 'uint8':
|
if cacheKey[0] == 'uint8':
|
||||||
dt = np.uint8
|
dt = xp.uint8
|
||||||
loc = 128
|
loc = 128
|
||||||
scale = 64
|
scale = 64
|
||||||
mx = 255
|
mx = 255
|
||||||
elif dtype[0] == 'uint16':
|
elif cacheKey[0] == 'uint16':
|
||||||
dt = np.uint16
|
dt = xp.uint16
|
||||||
loc = 4096
|
loc = 4096
|
||||||
scale = 1024
|
scale = 1024
|
||||||
mx = 2**16
|
mx = 2**16
|
||||||
elif dtype[0] == 'float':
|
elif cacheKey[0] == 'float':
|
||||||
dt = np.float
|
dt = xp.float
|
||||||
loc = 1.0
|
loc = 1.0
|
||||||
scale = 0.1
|
scale = 0.1
|
||||||
mx = 1.0
|
mx = 1.0
|
||||||
|
else:
|
||||||
|
raise ValueError(f"unable to handle dtype: {cacheKey[0]}")
|
||||||
|
|
||||||
if ui.rgbCheck.isChecked():
|
if ui.rgbCheck.isChecked():
|
||||||
data = np.random.normal(size=(frames,width,height,3), loc=loc, scale=scale)
|
data = xp.random.normal(size=(frames,width,height,3), loc=loc, scale=scale)
|
||||||
data = pg.gaussianFilter(data, (0, 6, 6, 0))
|
data = pg.gaussianFilter(data, (0, 6, 6, 0))
|
||||||
else:
|
else:
|
||||||
data = np.random.normal(size=(frames,width,height), loc=loc, scale=scale)
|
data = xp.random.normal(size=(frames,width,height), loc=loc, scale=scale)
|
||||||
data = pg.gaussianFilter(data, (0, 6, 6))
|
data = pg.gaussianFilter(data, (0, 6, 6))
|
||||||
if dtype[0] != 'float':
|
if cacheKey[0] != 'float':
|
||||||
data = np.clip(data, 0, mx)
|
data = xp.clip(data, 0, mx)
|
||||||
data = data.astype(dt)
|
data = data.astype(dt)
|
||||||
data[:, 10, 10:50] = mx
|
data[:, 10, 10:50] = mx
|
||||||
data[:, 9:12, 48] = mx
|
data[:, 9:12, 48] = mx
|
||||||
data[:, 8:13, 47] = mx
|
data[:, 8:13, 47] = mx
|
||||||
cache = {dtype: data} # clear to save memory (but keep one to prevent unnecessary regeneration)
|
cache = {cacheKey: data} # clear to save memory (but keep one to prevent unnecessary regeneration)
|
||||||
|
|
||||||
data = cache[dtype]
|
data = cache[cacheKey]
|
||||||
updateLUT()
|
updateLUT()
|
||||||
updateSize()
|
updateSize()
|
||||||
|
|
||||||
def updateSize():
|
def updateSize():
|
||||||
global ui
|
global ui, vb
|
||||||
frames = ui.framesSpin.value()
|
frames = ui.framesSpin.value()
|
||||||
width = ui.widthSpin.value()
|
width = ui.widthSpin.value()
|
||||||
height = ui.heightSpin.value()
|
height = ui.heightSpin.value()
|
||||||
dtype = np.dtype(str(ui.dtypeCombo.currentText()))
|
dtype = xp.dtype(str(ui.dtypeCombo.currentText()))
|
||||||
rgb = 3 if ui.rgbCheck.isChecked() else 1
|
rgb = 3 if ui.rgbCheck.isChecked() else 1
|
||||||
ui.sizeLabel.setText('%d MB' % (frames * width * height * rgb * dtype.itemsize / 1e6))
|
ui.sizeLabel.setText('%d MB' % (frames * width * height * rgb * dtype.itemsize / 1e6))
|
||||||
|
vb.setRange(QtCore.QRectF(0, 0, width, height))
|
||||||
|
|
||||||
|
|
||||||
|
def noticeCudaCheck():
|
||||||
|
global xp, cache
|
||||||
|
cache = {}
|
||||||
|
if ui.cudaCheck.isChecked():
|
||||||
|
if _has_cupy:
|
||||||
|
xp = cp
|
||||||
|
else:
|
||||||
|
xp = np
|
||||||
|
ui.cudaCheck.setChecked(False)
|
||||||
|
else:
|
||||||
|
xp = np
|
||||||
|
mkData()
|
||||||
|
|
||||||
mkData()
|
mkData()
|
||||||
|
|
||||||
@ -139,7 +210,7 @@ ui.framesSpin.editingFinished.connect(mkData)
|
|||||||
ui.widthSpin.valueChanged.connect(updateSize)
|
ui.widthSpin.valueChanged.connect(updateSize)
|
||||||
ui.heightSpin.valueChanged.connect(updateSize)
|
ui.heightSpin.valueChanged.connect(updateSize)
|
||||||
ui.framesSpin.valueChanged.connect(updateSize)
|
ui.framesSpin.valueChanged.connect(updateSize)
|
||||||
|
ui.cudaCheck.toggled.connect(noticeCudaCheck)
|
||||||
|
|
||||||
|
|
||||||
ptr = 0
|
ptr = 0
|
||||||
@ -151,14 +222,14 @@ def update():
|
|||||||
useLut = LUT
|
useLut = LUT
|
||||||
else:
|
else:
|
||||||
useLut = None
|
useLut = None
|
||||||
|
|
||||||
downsample = ui.downsampleCheck.isChecked()
|
downsample = ui.downsampleCheck.isChecked()
|
||||||
|
|
||||||
if ui.scaleCheck.isChecked():
|
if ui.scaleCheck.isChecked():
|
||||||
if ui.rgbLevelsCheck.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()],
|
||||||
[ui.minSpin3.value(), ui.maxSpin3.value()]]
|
[ui.minSpin3.value(), ui.maxSpin3.value()]]
|
||||||
else:
|
else:
|
||||||
useScale = [ui.minSpin1.value(), ui.maxSpin1.value()]
|
useScale = [ui.minSpin1.value(), ui.maxSpin1.value()]
|
||||||
@ -175,7 +246,7 @@ def update():
|
|||||||
img.setImage(data[ptr%data.shape[0]], autoLevels=False, levels=useScale, lut=useLut, autoDownsample=downsample)
|
img.setImage(data[ptr%data.shape[0]], autoLevels=False, levels=useScale, lut=useLut, autoDownsample=downsample)
|
||||||
ui.stack.setCurrentIndex(0)
|
ui.stack.setCurrentIndex(0)
|
||||||
#img.setImage(data[ptr%data.shape[0]], autoRange=False)
|
#img.setImage(data[ptr%data.shape[0]], autoRange=False)
|
||||||
|
|
||||||
ptr += 1
|
ptr += 1
|
||||||
now = ptime.time()
|
now = ptime.time()
|
||||||
dt = now - lastTime
|
dt = now - lastTime
|
||||||
@ -190,7 +261,7 @@ def update():
|
|||||||
timer = QtCore.QTimer()
|
timer = QtCore.QTimer()
|
||||||
timer.timeout.connect(update)
|
timer.timeout.connect(update)
|
||||||
timer.start(0)
|
timer.start(0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Start Qt event loop unless running in interactive mode or using pyside.
|
## Start Qt event loop unless running in interactive mode or using pyside.
|
||||||
|
@ -15,6 +15,13 @@
|
|||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="centralwidget">
|
<widget class="QWidget" name="centralwidget">
|
||||||
<layout class="QGridLayout" name="gridLayout_2">
|
<layout class="QGridLayout" name="gridLayout_2">
|
||||||
|
<item row="9" column="0" colspan="2">
|
||||||
|
<widget class="QCheckBox" name="cudaCheck">
|
||||||
|
<property name="text">
|
||||||
|
<string>Use CUDA (GPU) if available</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item row="8" column="0" colspan="2">
|
<item row="8" column="0" colspan="2">
|
||||||
<widget class="QCheckBox" name="downsampleCheck">
|
<widget class="QCheckBox" name="downsampleCheck">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -30,6 +30,9 @@ class Ui_MainWindow(object):
|
|||||||
self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
|
self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
|
||||||
self.gridLayout_2 = QtGui.QGridLayout(self.centralwidget)
|
self.gridLayout_2 = QtGui.QGridLayout(self.centralwidget)
|
||||||
self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
|
self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
|
||||||
|
self.cudaCheck = QtGui.QCheckBox(self.centralwidget)
|
||||||
|
self.cudaCheck.setObjectName(_fromUtf8("cudaCheck"))
|
||||||
|
self.gridLayout_2.addWidget(self.cudaCheck, 9, 0, 1, 2)
|
||||||
self.downsampleCheck = QtGui.QCheckBox(self.centralwidget)
|
self.downsampleCheck = QtGui.QCheckBox(self.centralwidget)
|
||||||
self.downsampleCheck.setObjectName(_fromUtf8("downsampleCheck"))
|
self.downsampleCheck.setObjectName(_fromUtf8("downsampleCheck"))
|
||||||
self.gridLayout_2.addWidget(self.downsampleCheck, 8, 0, 1, 2)
|
self.gridLayout_2.addWidget(self.downsampleCheck, 8, 0, 1, 2)
|
||||||
@ -189,6 +192,7 @@ class Ui_MainWindow(object):
|
|||||||
|
|
||||||
def retranslateUi(self, MainWindow):
|
def retranslateUi(self, MainWindow):
|
||||||
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
|
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
|
||||||
|
self.cudaCheck.setText(_translate("MainWindow", "Use CUDA (GPU) if available", None))
|
||||||
self.downsampleCheck.setText(_translate("MainWindow", "Auto downsample", None))
|
self.downsampleCheck.setText(_translate("MainWindow", "Auto downsample", None))
|
||||||
self.scaleCheck.setText(_translate("MainWindow", "Scale Data", None))
|
self.scaleCheck.setText(_translate("MainWindow", "Scale Data", None))
|
||||||
self.rawRadio.setText(_translate("MainWindow", "RawImageWidget", None))
|
self.rawRadio.setText(_translate("MainWindow", "RawImageWidget", None))
|
||||||
|
@ -2,12 +2,15 @@
|
|||||||
|
|
||||||
# Form implementation generated from reading ui file 'examples/VideoTemplate.ui'
|
# Form implementation generated from reading ui file 'examples/VideoTemplate.ui'
|
||||||
#
|
#
|
||||||
# Created by: PyQt5 UI code generator 5.5.1
|
# Created by: PyQt5 UI code generator 5.15.1
|
||||||
#
|
#
|
||||||
# WARNING! All changes made in this file will be lost!
|
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||||
|
# run again. Do not edit this file unless you know what you are doing.
|
||||||
|
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
|
|
||||||
class Ui_MainWindow(object):
|
class Ui_MainWindow(object):
|
||||||
def setupUi(self, MainWindow):
|
def setupUi(self, MainWindow):
|
||||||
MainWindow.setObjectName("MainWindow")
|
MainWindow.setObjectName("MainWindow")
|
||||||
@ -16,6 +19,9 @@ class Ui_MainWindow(object):
|
|||||||
self.centralwidget.setObjectName("centralwidget")
|
self.centralwidget.setObjectName("centralwidget")
|
||||||
self.gridLayout_2 = QtWidgets.QGridLayout(self.centralwidget)
|
self.gridLayout_2 = QtWidgets.QGridLayout(self.centralwidget)
|
||||||
self.gridLayout_2.setObjectName("gridLayout_2")
|
self.gridLayout_2.setObjectName("gridLayout_2")
|
||||||
|
self.cudaCheck = QtWidgets.QCheckBox(self.centralwidget)
|
||||||
|
self.cudaCheck.setObjectName("cudaCheck")
|
||||||
|
self.gridLayout_2.addWidget(self.cudaCheck, 9, 0, 1, 2)
|
||||||
self.downsampleCheck = QtWidgets.QCheckBox(self.centralwidget)
|
self.downsampleCheck = QtWidgets.QCheckBox(self.centralwidget)
|
||||||
self.downsampleCheck.setObjectName("downsampleCheck")
|
self.downsampleCheck.setObjectName("downsampleCheck")
|
||||||
self.gridLayout_2.addWidget(self.downsampleCheck, 8, 0, 1, 2)
|
self.gridLayout_2.addWidget(self.downsampleCheck, 8, 0, 1, 2)
|
||||||
@ -176,6 +182,7 @@ class Ui_MainWindow(object):
|
|||||||
def retranslateUi(self, MainWindow):
|
def retranslateUi(self, MainWindow):
|
||||||
_translate = QtCore.QCoreApplication.translate
|
_translate = QtCore.QCoreApplication.translate
|
||||||
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
|
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
|
||||||
|
self.cudaCheck.setText(_translate("MainWindow", "Use CUDA (GPU) if available"))
|
||||||
self.downsampleCheck.setText(_translate("MainWindow", "Auto downsample"))
|
self.downsampleCheck.setText(_translate("MainWindow", "Auto downsample"))
|
||||||
self.scaleCheck.setText(_translate("MainWindow", "Scale Data"))
|
self.scaleCheck.setText(_translate("MainWindow", "Scale Data"))
|
||||||
self.rawRadio.setText(_translate("MainWindow", "RawImageWidget"))
|
self.rawRadio.setText(_translate("MainWindow", "RawImageWidget"))
|
||||||
@ -194,6 +201,5 @@ class Ui_MainWindow(object):
|
|||||||
self.fpsLabel.setText(_translate("MainWindow", "FPS"))
|
self.fpsLabel.setText(_translate("MainWindow", "FPS"))
|
||||||
self.rgbCheck.setText(_translate("MainWindow", "RGB"))
|
self.rgbCheck.setText(_translate("MainWindow", "RGB"))
|
||||||
self.label_5.setText(_translate("MainWindow", "Image size"))
|
self.label_5.setText(_translate("MainWindow", "Image size"))
|
||||||
|
|
||||||
from pyqtgraph import GradientWidget, GraphicsView, SpinBox
|
from pyqtgraph import GradientWidget, GraphicsView, SpinBox
|
||||||
from pyqtgraph.widgets.RawImageWidget import RawImageWidget
|
from pyqtgraph.widgets.RawImageWidget import RawImageWidget
|
||||||
|
@ -17,6 +17,9 @@ class Ui_MainWindow(object):
|
|||||||
self.centralwidget.setObjectName("centralwidget")
|
self.centralwidget.setObjectName("centralwidget")
|
||||||
self.gridLayout_2 = QtGui.QGridLayout(self.centralwidget)
|
self.gridLayout_2 = QtGui.QGridLayout(self.centralwidget)
|
||||||
self.gridLayout_2.setObjectName("gridLayout_2")
|
self.gridLayout_2.setObjectName("gridLayout_2")
|
||||||
|
self.cudaCheck = QtGui.QCheckBox(self.centralwidget)
|
||||||
|
self.cudaCheck.setObjectName("cudaCheck")
|
||||||
|
self.gridLayout_2.addWidget(self.cudaCheck, 9, 0, 1, 2)
|
||||||
self.downsampleCheck = QtGui.QCheckBox(self.centralwidget)
|
self.downsampleCheck = QtGui.QCheckBox(self.centralwidget)
|
||||||
self.downsampleCheck.setObjectName("downsampleCheck")
|
self.downsampleCheck.setObjectName("downsampleCheck")
|
||||||
self.gridLayout_2.addWidget(self.downsampleCheck, 8, 0, 1, 2)
|
self.gridLayout_2.addWidget(self.downsampleCheck, 8, 0, 1, 2)
|
||||||
@ -176,6 +179,7 @@ class Ui_MainWindow(object):
|
|||||||
|
|
||||||
def retranslateUi(self, MainWindow):
|
def retranslateUi(self, MainWindow):
|
||||||
MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8))
|
MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
self.cudaCheck.setText(QtGui.QApplication.translate("MainWindow", "Use CUDA (GPU) if available", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.downsampleCheck.setText(QtGui.QApplication.translate("MainWindow", "Auto downsample", None, QtGui.QApplication.UnicodeUTF8))
|
self.downsampleCheck.setText(QtGui.QApplication.translate("MainWindow", "Auto downsample", 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.rawRadio.setText(QtGui.QApplication.translate("MainWindow", "RawImageWidget", None, QtGui.QApplication.UnicodeUTF8))
|
self.rawRadio.setText(QtGui.QApplication.translate("MainWindow", "RawImageWidget", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
@ -1,207 +1,288 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Form implementation generated from reading ui file 'VideoTemplate.ui'
|
################################################################################
|
||||||
#
|
## Form generated from reading UI file 'VideoTemplate.ui'
|
||||||
# Created: Sun Sep 18 19:22:41 2016
|
##
|
||||||
# by: pyside2-uic running on PySide2 2.0.0~alpha0
|
## Created by: Qt User Interface Compiler version 5.15.2
|
||||||
#
|
##
|
||||||
# WARNING! All changes made in this file will be lost!
|
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
from PySide2.QtCore import *
|
||||||
|
from PySide2.QtGui import *
|
||||||
|
from PySide2.QtWidgets import *
|
||||||
|
|
||||||
|
from pyqtgraph import GraphicsView
|
||||||
|
from pyqtgraph.widgets.RawImageWidget import RawImageWidget
|
||||||
|
from pyqtgraph import GradientWidget
|
||||||
|
from pyqtgraph import SpinBox
|
||||||
|
|
||||||
from PySide2 import QtCore, QtGui, QtWidgets
|
|
||||||
|
|
||||||
class Ui_MainWindow(object):
|
class Ui_MainWindow(object):
|
||||||
def setupUi(self, MainWindow):
|
def setupUi(self, MainWindow):
|
||||||
MainWindow.setObjectName("MainWindow")
|
if not MainWindow.objectName():
|
||||||
|
MainWindow.setObjectName(u"MainWindow")
|
||||||
MainWindow.resize(695, 798)
|
MainWindow.resize(695, 798)
|
||||||
self.centralwidget = QtWidgets.QWidget(MainWindow)
|
self.centralwidget = QWidget(MainWindow)
|
||||||
self.centralwidget.setObjectName("centralwidget")
|
self.centralwidget.setObjectName(u"centralwidget")
|
||||||
self.gridLayout_2 = QtWidgets.QGridLayout(self.centralwidget)
|
self.gridLayout_2 = QGridLayout(self.centralwidget)
|
||||||
self.gridLayout_2.setObjectName("gridLayout_2")
|
self.gridLayout_2.setObjectName(u"gridLayout_2")
|
||||||
self.downsampleCheck = QtWidgets.QCheckBox(self.centralwidget)
|
self.cudaCheck = QCheckBox(self.centralwidget)
|
||||||
self.downsampleCheck.setObjectName("downsampleCheck")
|
self.cudaCheck.setObjectName(u"cudaCheck")
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.cudaCheck, 9, 0, 1, 2)
|
||||||
|
|
||||||
|
self.downsampleCheck = QCheckBox(self.centralwidget)
|
||||||
|
self.downsampleCheck.setObjectName(u"downsampleCheck")
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.downsampleCheck, 8, 0, 1, 2)
|
self.gridLayout_2.addWidget(self.downsampleCheck, 8, 0, 1, 2)
|
||||||
self.scaleCheck = QtWidgets.QCheckBox(self.centralwidget)
|
|
||||||
self.scaleCheck.setObjectName("scaleCheck")
|
self.scaleCheck = QCheckBox(self.centralwidget)
|
||||||
|
self.scaleCheck.setObjectName(u"scaleCheck")
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.scaleCheck, 4, 0, 1, 1)
|
self.gridLayout_2.addWidget(self.scaleCheck, 4, 0, 1, 1)
|
||||||
self.gridLayout = QtWidgets.QGridLayout()
|
|
||||||
self.gridLayout.setObjectName("gridLayout")
|
self.gridLayout = QGridLayout()
|
||||||
self.rawRadio = QtWidgets.QRadioButton(self.centralwidget)
|
self.gridLayout.setObjectName(u"gridLayout")
|
||||||
self.rawRadio.setObjectName("rawRadio")
|
self.rawRadio = QRadioButton(self.centralwidget)
|
||||||
|
self.rawRadio.setObjectName(u"rawRadio")
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.rawRadio, 3, 0, 1, 1)
|
self.gridLayout.addWidget(self.rawRadio, 3, 0, 1, 1)
|
||||||
self.gfxRadio = QtWidgets.QRadioButton(self.centralwidget)
|
|
||||||
|
self.gfxRadio = QRadioButton(self.centralwidget)
|
||||||
|
self.gfxRadio.setObjectName(u"gfxRadio")
|
||||||
self.gfxRadio.setChecked(True)
|
self.gfxRadio.setChecked(True)
|
||||||
self.gfxRadio.setObjectName("gfxRadio")
|
|
||||||
self.gridLayout.addWidget(self.gfxRadio, 2, 0, 1, 1)
|
self.gridLayout.addWidget(self.gfxRadio, 2, 0, 1, 1)
|
||||||
self.stack = QtWidgets.QStackedWidget(self.centralwidget)
|
|
||||||
self.stack.setObjectName("stack")
|
self.stack = QStackedWidget(self.centralwidget)
|
||||||
self.page = QtWidgets.QWidget()
|
self.stack.setObjectName(u"stack")
|
||||||
self.page.setObjectName("page")
|
self.page = QWidget()
|
||||||
self.gridLayout_3 = QtWidgets.QGridLayout(self.page)
|
self.page.setObjectName(u"page")
|
||||||
self.gridLayout_3.setObjectName("gridLayout_3")
|
self.gridLayout_3 = QGridLayout(self.page)
|
||||||
|
self.gridLayout_3.setObjectName(u"gridLayout_3")
|
||||||
self.graphicsView = GraphicsView(self.page)
|
self.graphicsView = GraphicsView(self.page)
|
||||||
self.graphicsView.setObjectName("graphicsView")
|
self.graphicsView.setObjectName(u"graphicsView")
|
||||||
|
|
||||||
self.gridLayout_3.addWidget(self.graphicsView, 0, 0, 1, 1)
|
self.gridLayout_3.addWidget(self.graphicsView, 0, 0, 1, 1)
|
||||||
|
|
||||||
self.stack.addWidget(self.page)
|
self.stack.addWidget(self.page)
|
||||||
self.page_2 = QtWidgets.QWidget()
|
self.page_2 = QWidget()
|
||||||
self.page_2.setObjectName("page_2")
|
self.page_2.setObjectName(u"page_2")
|
||||||
self.gridLayout_4 = QtWidgets.QGridLayout(self.page_2)
|
self.gridLayout_4 = QGridLayout(self.page_2)
|
||||||
self.gridLayout_4.setObjectName("gridLayout_4")
|
self.gridLayout_4.setObjectName(u"gridLayout_4")
|
||||||
self.rawImg = RawImageWidget(self.page_2)
|
self.rawImg = RawImageWidget(self.page_2)
|
||||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
|
self.rawImg.setObjectName(u"rawImg")
|
||||||
|
sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
|
||||||
sizePolicy.setHorizontalStretch(0)
|
sizePolicy.setHorizontalStretch(0)
|
||||||
sizePolicy.setVerticalStretch(0)
|
sizePolicy.setVerticalStretch(0)
|
||||||
sizePolicy.setHeightForWidth(self.rawImg.sizePolicy().hasHeightForWidth())
|
sizePolicy.setHeightForWidth(self.rawImg.sizePolicy().hasHeightForWidth())
|
||||||
self.rawImg.setSizePolicy(sizePolicy)
|
self.rawImg.setSizePolicy(sizePolicy)
|
||||||
self.rawImg.setObjectName("rawImg")
|
|
||||||
self.gridLayout_4.addWidget(self.rawImg, 0, 0, 1, 1)
|
self.gridLayout_4.addWidget(self.rawImg, 0, 0, 1, 1)
|
||||||
|
|
||||||
self.stack.addWidget(self.page_2)
|
self.stack.addWidget(self.page_2)
|
||||||
self.page_3 = QtWidgets.QWidget()
|
|
||||||
self.page_3.setObjectName("page_3")
|
|
||||||
self.gridLayout_5 = QtWidgets.QGridLayout(self.page_3)
|
|
||||||
self.gridLayout_5.setObjectName("gridLayout_5")
|
|
||||||
self.rawGLImg = RawImageGLWidget(self.page_3)
|
|
||||||
self.rawGLImg.setObjectName("rawGLImg")
|
|
||||||
self.gridLayout_5.addWidget(self.rawGLImg, 0, 0, 1, 1)
|
|
||||||
self.stack.addWidget(self.page_3)
|
|
||||||
self.gridLayout.addWidget(self.stack, 0, 0, 1, 1)
|
self.gridLayout.addWidget(self.stack, 0, 0, 1, 1)
|
||||||
self.rawGLRadio = QtWidgets.QRadioButton(self.centralwidget)
|
|
||||||
self.rawGLRadio.setObjectName("rawGLRadio")
|
self.rawGLRadio = QRadioButton(self.centralwidget)
|
||||||
|
self.rawGLRadio.setObjectName(u"rawGLRadio")
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.rawGLRadio, 4, 0, 1, 1)
|
self.gridLayout.addWidget(self.rawGLRadio, 4, 0, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
self.gridLayout_2.addLayout(self.gridLayout, 1, 0, 1, 4)
|
self.gridLayout_2.addLayout(self.gridLayout, 1, 0, 1, 4)
|
||||||
self.dtypeCombo = QtWidgets.QComboBox(self.centralwidget)
|
|
||||||
self.dtypeCombo.setObjectName("dtypeCombo")
|
self.dtypeCombo = QComboBox(self.centralwidget)
|
||||||
self.dtypeCombo.addItem("")
|
self.dtypeCombo.addItem("")
|
||||||
self.dtypeCombo.addItem("")
|
self.dtypeCombo.addItem("")
|
||||||
self.dtypeCombo.addItem("")
|
self.dtypeCombo.addItem("")
|
||||||
|
self.dtypeCombo.setObjectName(u"dtypeCombo")
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.dtypeCombo, 3, 2, 1, 1)
|
self.gridLayout_2.addWidget(self.dtypeCombo, 3, 2, 1, 1)
|
||||||
self.label = QtWidgets.QLabel(self.centralwidget)
|
|
||||||
self.label.setObjectName("label")
|
self.label = QLabel(self.centralwidget)
|
||||||
|
self.label.setObjectName(u"label")
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.label, 3, 0, 1, 1)
|
self.gridLayout_2.addWidget(self.label, 3, 0, 1, 1)
|
||||||
self.rgbLevelsCheck = QtWidgets.QCheckBox(self.centralwidget)
|
|
||||||
self.rgbLevelsCheck.setObjectName("rgbLevelsCheck")
|
self.rgbLevelsCheck = QCheckBox(self.centralwidget)
|
||||||
|
self.rgbLevelsCheck.setObjectName(u"rgbLevelsCheck")
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.rgbLevelsCheck, 4, 1, 1, 1)
|
self.gridLayout_2.addWidget(self.rgbLevelsCheck, 4, 1, 1, 1)
|
||||||
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
|
|
||||||
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
|
self.horizontalLayout_2 = QHBoxLayout()
|
||||||
|
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
|
||||||
self.minSpin2 = SpinBox(self.centralwidget)
|
self.minSpin2 = SpinBox(self.centralwidget)
|
||||||
|
self.minSpin2.setObjectName(u"minSpin2")
|
||||||
self.minSpin2.setEnabled(False)
|
self.minSpin2.setEnabled(False)
|
||||||
self.minSpin2.setObjectName("minSpin2")
|
|
||||||
self.horizontalLayout_2.addWidget(self.minSpin2)
|
self.horizontalLayout_2.addWidget(self.minSpin2)
|
||||||
self.label_3 = QtWidgets.QLabel(self.centralwidget)
|
|
||||||
self.label_3.setAlignment(QtCore.Qt.AlignCenter)
|
self.label_3 = QLabel(self.centralwidget)
|
||||||
self.label_3.setObjectName("label_3")
|
self.label_3.setObjectName(u"label_3")
|
||||||
|
self.label_3.setAlignment(Qt.AlignCenter)
|
||||||
|
|
||||||
self.horizontalLayout_2.addWidget(self.label_3)
|
self.horizontalLayout_2.addWidget(self.label_3)
|
||||||
|
|
||||||
self.maxSpin2 = SpinBox(self.centralwidget)
|
self.maxSpin2 = SpinBox(self.centralwidget)
|
||||||
|
self.maxSpin2.setObjectName(u"maxSpin2")
|
||||||
self.maxSpin2.setEnabled(False)
|
self.maxSpin2.setEnabled(False)
|
||||||
self.maxSpin2.setObjectName("maxSpin2")
|
|
||||||
self.horizontalLayout_2.addWidget(self.maxSpin2)
|
self.horizontalLayout_2.addWidget(self.maxSpin2)
|
||||||
|
|
||||||
|
|
||||||
self.gridLayout_2.addLayout(self.horizontalLayout_2, 5, 2, 1, 1)
|
self.gridLayout_2.addLayout(self.horizontalLayout_2, 5, 2, 1, 1)
|
||||||
self.horizontalLayout = QtWidgets.QHBoxLayout()
|
|
||||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
self.horizontalLayout = QHBoxLayout()
|
||||||
|
self.horizontalLayout.setObjectName(u"horizontalLayout")
|
||||||
self.minSpin1 = SpinBox(self.centralwidget)
|
self.minSpin1 = SpinBox(self.centralwidget)
|
||||||
self.minSpin1.setObjectName("minSpin1")
|
self.minSpin1.setObjectName(u"minSpin1")
|
||||||
|
|
||||||
self.horizontalLayout.addWidget(self.minSpin1)
|
self.horizontalLayout.addWidget(self.minSpin1)
|
||||||
self.label_2 = QtWidgets.QLabel(self.centralwidget)
|
|
||||||
self.label_2.setAlignment(QtCore.Qt.AlignCenter)
|
self.label_2 = QLabel(self.centralwidget)
|
||||||
self.label_2.setObjectName("label_2")
|
self.label_2.setObjectName(u"label_2")
|
||||||
|
self.label_2.setAlignment(Qt.AlignCenter)
|
||||||
|
|
||||||
self.horizontalLayout.addWidget(self.label_2)
|
self.horizontalLayout.addWidget(self.label_2)
|
||||||
|
|
||||||
self.maxSpin1 = SpinBox(self.centralwidget)
|
self.maxSpin1 = SpinBox(self.centralwidget)
|
||||||
self.maxSpin1.setObjectName("maxSpin1")
|
self.maxSpin1.setObjectName(u"maxSpin1")
|
||||||
|
|
||||||
self.horizontalLayout.addWidget(self.maxSpin1)
|
self.horizontalLayout.addWidget(self.maxSpin1)
|
||||||
|
|
||||||
|
|
||||||
self.gridLayout_2.addLayout(self.horizontalLayout, 4, 2, 1, 1)
|
self.gridLayout_2.addLayout(self.horizontalLayout, 4, 2, 1, 1)
|
||||||
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
|
|
||||||
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
|
self.horizontalLayout_3 = QHBoxLayout()
|
||||||
|
self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
|
||||||
self.minSpin3 = SpinBox(self.centralwidget)
|
self.minSpin3 = SpinBox(self.centralwidget)
|
||||||
|
self.minSpin3.setObjectName(u"minSpin3")
|
||||||
self.minSpin3.setEnabled(False)
|
self.minSpin3.setEnabled(False)
|
||||||
self.minSpin3.setObjectName("minSpin3")
|
|
||||||
self.horizontalLayout_3.addWidget(self.minSpin3)
|
self.horizontalLayout_3.addWidget(self.minSpin3)
|
||||||
self.label_4 = QtWidgets.QLabel(self.centralwidget)
|
|
||||||
self.label_4.setAlignment(QtCore.Qt.AlignCenter)
|
self.label_4 = QLabel(self.centralwidget)
|
||||||
self.label_4.setObjectName("label_4")
|
self.label_4.setObjectName(u"label_4")
|
||||||
|
self.label_4.setAlignment(Qt.AlignCenter)
|
||||||
|
|
||||||
self.horizontalLayout_3.addWidget(self.label_4)
|
self.horizontalLayout_3.addWidget(self.label_4)
|
||||||
|
|
||||||
self.maxSpin3 = SpinBox(self.centralwidget)
|
self.maxSpin3 = SpinBox(self.centralwidget)
|
||||||
|
self.maxSpin3.setObjectName(u"maxSpin3")
|
||||||
self.maxSpin3.setEnabled(False)
|
self.maxSpin3.setEnabled(False)
|
||||||
self.maxSpin3.setObjectName("maxSpin3")
|
|
||||||
self.horizontalLayout_3.addWidget(self.maxSpin3)
|
self.horizontalLayout_3.addWidget(self.maxSpin3)
|
||||||
|
|
||||||
|
|
||||||
self.gridLayout_2.addLayout(self.horizontalLayout_3, 6, 2, 1, 1)
|
self.gridLayout_2.addLayout(self.horizontalLayout_3, 6, 2, 1, 1)
|
||||||
self.lutCheck = QtWidgets.QCheckBox(self.centralwidget)
|
|
||||||
self.lutCheck.setObjectName("lutCheck")
|
self.lutCheck = QCheckBox(self.centralwidget)
|
||||||
|
self.lutCheck.setObjectName(u"lutCheck")
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.lutCheck, 7, 0, 1, 1)
|
self.gridLayout_2.addWidget(self.lutCheck, 7, 0, 1, 1)
|
||||||
self.alphaCheck = QtWidgets.QCheckBox(self.centralwidget)
|
|
||||||
self.alphaCheck.setObjectName("alphaCheck")
|
self.alphaCheck = QCheckBox(self.centralwidget)
|
||||||
|
self.alphaCheck.setObjectName(u"alphaCheck")
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.alphaCheck, 7, 1, 1, 1)
|
self.gridLayout_2.addWidget(self.alphaCheck, 7, 1, 1, 1)
|
||||||
|
|
||||||
self.gradient = GradientWidget(self.centralwidget)
|
self.gradient = GradientWidget(self.centralwidget)
|
||||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
|
self.gradient.setObjectName(u"gradient")
|
||||||
sizePolicy.setHorizontalStretch(0)
|
|
||||||
sizePolicy.setVerticalStretch(0)
|
|
||||||
sizePolicy.setHeightForWidth(self.gradient.sizePolicy().hasHeightForWidth())
|
sizePolicy.setHeightForWidth(self.gradient.sizePolicy().hasHeightForWidth())
|
||||||
self.gradient.setSizePolicy(sizePolicy)
|
self.gradient.setSizePolicy(sizePolicy)
|
||||||
self.gradient.setObjectName("gradient")
|
|
||||||
self.gridLayout_2.addWidget(self.gradient, 7, 2, 1, 2)
|
self.gridLayout_2.addWidget(self.gradient, 7, 2, 1, 2)
|
||||||
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
|
||||||
self.gridLayout_2.addItem(spacerItem, 3, 3, 1, 1)
|
self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
|
||||||
self.fpsLabel = QtWidgets.QLabel(self.centralwidget)
|
|
||||||
font = QtGui.QFont()
|
self.gridLayout_2.addItem(self.horizontalSpacer, 3, 3, 1, 1)
|
||||||
|
|
||||||
|
self.fpsLabel = QLabel(self.centralwidget)
|
||||||
|
self.fpsLabel.setObjectName(u"fpsLabel")
|
||||||
|
font = QFont()
|
||||||
font.setPointSize(12)
|
font.setPointSize(12)
|
||||||
self.fpsLabel.setFont(font)
|
self.fpsLabel.setFont(font)
|
||||||
self.fpsLabel.setAlignment(QtCore.Qt.AlignCenter)
|
self.fpsLabel.setAlignment(Qt.AlignCenter)
|
||||||
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 = QtWidgets.QCheckBox(self.centralwidget)
|
|
||||||
self.rgbCheck.setObjectName("rgbCheck")
|
self.rgbCheck = QCheckBox(self.centralwidget)
|
||||||
|
self.rgbCheck.setObjectName(u"rgbCheck")
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.rgbCheck, 3, 1, 1, 1)
|
self.gridLayout_2.addWidget(self.rgbCheck, 3, 1, 1, 1)
|
||||||
self.label_5 = QtWidgets.QLabel(self.centralwidget)
|
|
||||||
self.label_5.setObjectName("label_5")
|
self.label_5 = QLabel(self.centralwidget)
|
||||||
|
self.label_5.setObjectName(u"label_5")
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.label_5, 2, 0, 1, 1)
|
self.gridLayout_2.addWidget(self.label_5, 2, 0, 1, 1)
|
||||||
self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
|
|
||||||
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
|
self.horizontalLayout_4 = QHBoxLayout()
|
||||||
self.framesSpin = QtWidgets.QSpinBox(self.centralwidget)
|
self.horizontalLayout_4.setObjectName(u"horizontalLayout_4")
|
||||||
self.framesSpin.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
|
self.framesSpin = QSpinBox(self.centralwidget)
|
||||||
self.framesSpin.setProperty("value", 10)
|
self.framesSpin.setObjectName(u"framesSpin")
|
||||||
self.framesSpin.setObjectName("framesSpin")
|
self.framesSpin.setButtonSymbols(QAbstractSpinBox.NoButtons)
|
||||||
|
self.framesSpin.setValue(10)
|
||||||
|
|
||||||
self.horizontalLayout_4.addWidget(self.framesSpin)
|
self.horizontalLayout_4.addWidget(self.framesSpin)
|
||||||
self.widthSpin = QtWidgets.QSpinBox(self.centralwidget)
|
|
||||||
self.widthSpin.setButtonSymbols(QtWidgets.QAbstractSpinBox.PlusMinus)
|
self.widthSpin = QSpinBox(self.centralwidget)
|
||||||
|
self.widthSpin.setObjectName(u"widthSpin")
|
||||||
|
self.widthSpin.setButtonSymbols(QAbstractSpinBox.PlusMinus)
|
||||||
self.widthSpin.setMaximum(10000)
|
self.widthSpin.setMaximum(10000)
|
||||||
self.widthSpin.setProperty("value", 512)
|
self.widthSpin.setValue(512)
|
||||||
self.widthSpin.setObjectName("widthSpin")
|
|
||||||
self.horizontalLayout_4.addWidget(self.widthSpin)
|
self.horizontalLayout_4.addWidget(self.widthSpin)
|
||||||
self.heightSpin = QtWidgets.QSpinBox(self.centralwidget)
|
|
||||||
self.heightSpin.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
|
self.heightSpin = QSpinBox(self.centralwidget)
|
||||||
|
self.heightSpin.setObjectName(u"heightSpin")
|
||||||
|
self.heightSpin.setButtonSymbols(QAbstractSpinBox.NoButtons)
|
||||||
self.heightSpin.setMaximum(10000)
|
self.heightSpin.setMaximum(10000)
|
||||||
self.heightSpin.setProperty("value", 512)
|
self.heightSpin.setValue(512)
|
||||||
self.heightSpin.setObjectName("heightSpin")
|
|
||||||
self.horizontalLayout_4.addWidget(self.heightSpin)
|
self.horizontalLayout_4.addWidget(self.heightSpin)
|
||||||
|
|
||||||
|
|
||||||
self.gridLayout_2.addLayout(self.horizontalLayout_4, 2, 1, 1, 2)
|
self.gridLayout_2.addLayout(self.horizontalLayout_4, 2, 1, 1, 2)
|
||||||
self.sizeLabel = QtWidgets.QLabel(self.centralwidget)
|
|
||||||
self.sizeLabel.setText("")
|
self.sizeLabel = QLabel(self.centralwidget)
|
||||||
self.sizeLabel.setObjectName("sizeLabel")
|
self.sizeLabel.setObjectName(u"sizeLabel")
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.sizeLabel, 2, 3, 1, 1)
|
self.gridLayout_2.addWidget(self.sizeLabel, 2, 3, 1, 1)
|
||||||
|
|
||||||
MainWindow.setCentralWidget(self.centralwidget)
|
MainWindow.setCentralWidget(self.centralwidget)
|
||||||
|
|
||||||
self.retranslateUi(MainWindow)
|
self.retranslateUi(MainWindow)
|
||||||
self.stack.setCurrentIndex(2)
|
|
||||||
QtCore.QMetaObject.connectSlotsByName(MainWindow)
|
self.stack.setCurrentIndex(1)
|
||||||
|
|
||||||
|
|
||||||
|
QMetaObject.connectSlotsByName(MainWindow)
|
||||||
|
# setupUi
|
||||||
|
|
||||||
def retranslateUi(self, MainWindow):
|
def retranslateUi(self, MainWindow):
|
||||||
MainWindow.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", "MainWindow", None, -1))
|
MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None))
|
||||||
self.downsampleCheck.setText(QtWidgets.QApplication.translate("MainWindow", "Auto downsample", None, -1))
|
self.cudaCheck.setText(QCoreApplication.translate("MainWindow", u"Use CUDA (GPU) if available", None))
|
||||||
self.scaleCheck.setText(QtWidgets.QApplication.translate("MainWindow", "Scale Data", None, -1))
|
self.downsampleCheck.setText(QCoreApplication.translate("MainWindow", u"Auto downsample", None))
|
||||||
self.rawRadio.setText(QtWidgets.QApplication.translate("MainWindow", "RawImageWidget", None, -1))
|
self.scaleCheck.setText(QCoreApplication.translate("MainWindow", u"Scale Data", None))
|
||||||
self.gfxRadio.setText(QtWidgets.QApplication.translate("MainWindow", "GraphicsView + ImageItem", None, -1))
|
self.rawRadio.setText(QCoreApplication.translate("MainWindow", u"RawImageWidget", None))
|
||||||
self.rawGLRadio.setText(QtWidgets.QApplication.translate("MainWindow", "RawGLImageWidget", None, -1))
|
self.gfxRadio.setText(QCoreApplication.translate("MainWindow", u"GraphicsView + ImageItem", None))
|
||||||
self.dtypeCombo.setItemText(0, QtWidgets.QApplication.translate("MainWindow", "uint8", None, -1))
|
self.rawGLRadio.setText(QCoreApplication.translate("MainWindow", u"RawGLImageWidget", None))
|
||||||
self.dtypeCombo.setItemText(1, QtWidgets.QApplication.translate("MainWindow", "uint16", None, -1))
|
self.dtypeCombo.setItemText(0, QCoreApplication.translate("MainWindow", u"uint8", None))
|
||||||
self.dtypeCombo.setItemText(2, QtWidgets.QApplication.translate("MainWindow", "float", None, -1))
|
self.dtypeCombo.setItemText(1, QCoreApplication.translate("MainWindow", u"uint16", None))
|
||||||
self.label.setText(QtWidgets.QApplication.translate("MainWindow", "Data type", None, -1))
|
self.dtypeCombo.setItemText(2, QCoreApplication.translate("MainWindow", u"float", None))
|
||||||
self.rgbLevelsCheck.setText(QtWidgets.QApplication.translate("MainWindow", "RGB", None, -1))
|
|
||||||
self.label_3.setText(QtWidgets.QApplication.translate("MainWindow", "<--->", None, -1))
|
self.label.setText(QCoreApplication.translate("MainWindow", u"Data type", None))
|
||||||
self.label_2.setText(QtWidgets.QApplication.translate("MainWindow", "<--->", None, -1))
|
self.rgbLevelsCheck.setText(QCoreApplication.translate("MainWindow", u"RGB", None))
|
||||||
self.label_4.setText(QtWidgets.QApplication.translate("MainWindow", "<--->", None, -1))
|
self.label_3.setText(QCoreApplication.translate("MainWindow", u"<--->", None))
|
||||||
self.lutCheck.setText(QtWidgets.QApplication.translate("MainWindow", "Use Lookup Table", None, -1))
|
self.label_2.setText(QCoreApplication.translate("MainWindow", u"<--->", None))
|
||||||
self.alphaCheck.setText(QtWidgets.QApplication.translate("MainWindow", "alpha", None, -1))
|
self.label_4.setText(QCoreApplication.translate("MainWindow", u"<--->", None))
|
||||||
self.fpsLabel.setText(QtWidgets.QApplication.translate("MainWindow", "FPS", None, -1))
|
self.lutCheck.setText(QCoreApplication.translate("MainWindow", u"Use Lookup Table", None))
|
||||||
self.rgbCheck.setText(QtWidgets.QApplication.translate("MainWindow", "RGB", None, -1))
|
self.alphaCheck.setText(QCoreApplication.translate("MainWindow", u"alpha", None))
|
||||||
self.label_5.setText(QtWidgets.QApplication.translate("MainWindow", "Image size", None, -1))
|
self.fpsLabel.setText(QCoreApplication.translate("MainWindow", u"FPS", None))
|
||||||
|
self.rgbCheck.setText(QCoreApplication.translate("MainWindow", u"RGB", None))
|
||||||
|
self.label_5.setText(QCoreApplication.translate("MainWindow", u"Image size", None))
|
||||||
|
self.sizeLabel.setText("")
|
||||||
|
# retranslateUi
|
||||||
|
|
||||||
from pyqtgraph.widgets.RawImageWidget import RawImageGLWidget, RawImageWidget
|
|
||||||
from pyqtgraph import GradientWidget, SpinBox, GraphicsView
|
|
||||||
|
@ -54,6 +54,10 @@ installedFrontends = sorted([
|
|||||||
|
|
||||||
exceptionCondition = namedtuple("exceptionCondition", ["condition", "reason"])
|
exceptionCondition = namedtuple("exceptionCondition", ["condition", "reason"])
|
||||||
conditionalExamples = {
|
conditionalExamples = {
|
||||||
|
"test_ExampleApp.py": exceptionCondition(
|
||||||
|
not(platform.system() == "Linux" and frontends[Qt.PYSIDE2]),
|
||||||
|
reason="Unexplained, intermittent segfault and subsequent timeout on CI"
|
||||||
|
),
|
||||||
"hdf5.py": exceptionCondition(
|
"hdf5.py": exceptionCondition(
|
||||||
False,
|
False,
|
||||||
reason="Example requires user interaction"
|
reason="Example requires user interaction"
|
||||||
|
@ -61,6 +61,7 @@ CONFIG_OPTIONS = {
|
|||||||
# For 'col-major', image data is expected in reversed (col, row) order.
|
# For 'col-major', image data is expected in reversed (col, row) order.
|
||||||
# The default is 'col-major' for backward compatibility, but this may
|
# The default is 'col-major' for backward compatibility, but this may
|
||||||
# change in the future.
|
# change in the future.
|
||||||
|
'useCupy': False, # When True, attempt to use cupy ( currently only with ImageItem and related functions )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,18 +6,22 @@ Distributed under MIT/X11 license. See license.txt for more information.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import division
|
from __future__ import division
|
||||||
import warnings
|
|
||||||
import numpy as np
|
|
||||||
import decimal, re
|
|
||||||
import ctypes
|
import ctypes
|
||||||
import sys, struct
|
import decimal
|
||||||
|
import re
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from pyqtgraph.util.cupy_helper import getCupy
|
||||||
|
|
||||||
|
from . import debug, reload
|
||||||
|
from .Qt import QtGui, QtCore, QT_LIB, QtVersion
|
||||||
|
from .metaarray import MetaArray
|
||||||
from .pgcollections import OrderedDict
|
from .pgcollections import OrderedDict
|
||||||
from .python2_3 import asUnicode, basestring
|
from .python2_3 import asUnicode, basestring
|
||||||
from .Qt import QtGui, QtCore, QT_LIB, QtVersion
|
|
||||||
from . import getConfigOption, setConfigOptions
|
|
||||||
from . import debug, reload
|
|
||||||
from .metaarray import MetaArray
|
|
||||||
|
|
||||||
|
|
||||||
Colors = {
|
Colors = {
|
||||||
'b': QtGui.QColor(0,0,255,255),
|
'b': QtGui.QColor(0,0,255,255),
|
||||||
@ -940,80 +944,53 @@ def rescaleData(data, scale, offset, dtype=None, clip=None):
|
|||||||
The scaling operation is::
|
The scaling operation is::
|
||||||
|
|
||||||
data => (data-offset) * scale
|
data => (data-offset) * scale
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if dtype is None:
|
if dtype is None:
|
||||||
dtype = data.dtype
|
dtype = data.dtype
|
||||||
else:
|
else:
|
||||||
dtype = np.dtype(dtype)
|
dtype = np.dtype(dtype)
|
||||||
|
|
||||||
try:
|
d2 = data.astype(np.float) - float(offset)
|
||||||
if not getConfigOption('useWeave'):
|
d2 *= scale
|
||||||
raise Exception('Weave is disabled; falling back to slower version.')
|
|
||||||
try:
|
# Clip before converting dtype to avoid overflow
|
||||||
import scipy.weave
|
if dtype.kind in 'ui':
|
||||||
except ImportError:
|
lim = np.iinfo(dtype)
|
||||||
raise Exception('scipy.weave is not importable; falling back to slower version.')
|
if clip is None:
|
||||||
|
# don't let rescale cause integer overflow
|
||||||
## require native dtype when using weave
|
d2 = np.clip(d2, lim.min, lim.max)
|
||||||
if not data.dtype.isnative:
|
|
||||||
data = data.astype(data.dtype.newbyteorder('='))
|
|
||||||
if not dtype.isnative:
|
|
||||||
weaveDtype = dtype.newbyteorder('=')
|
|
||||||
else:
|
else:
|
||||||
weaveDtype = dtype
|
d2 = np.clip(d2, max(clip[0], lim.min), min(clip[1], lim.max))
|
||||||
|
else:
|
||||||
newData = np.empty((data.size,), dtype=weaveDtype)
|
if clip is not None:
|
||||||
flat = np.ascontiguousarray(data).reshape(data.size)
|
d2 = np.clip(d2, *clip)
|
||||||
size = data.size
|
data = d2.astype(dtype)
|
||||||
|
|
||||||
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')
|
|
||||||
if dtype != weaveDtype:
|
|
||||||
newData = newData.astype(dtype)
|
|
||||||
data = newData.reshape(data.shape)
|
|
||||||
except:
|
|
||||||
if getConfigOption('useWeave'):
|
|
||||||
if getConfigOption('weaveDebug'):
|
|
||||||
debug.printExc("Error; disabling weave.")
|
|
||||||
setConfigOptions(useWeave=False)
|
|
||||||
|
|
||||||
#p = np.poly1d([scale, -offset*scale])
|
|
||||||
#d2 = p(data)
|
|
||||||
d2 = data - float(offset)
|
|
||||||
d2 *= scale
|
|
||||||
|
|
||||||
# Clip before converting dtype to avoid overflow
|
|
||||||
if dtype.kind in 'ui':
|
|
||||||
lim = np.iinfo(dtype)
|
|
||||||
if clip is None:
|
|
||||||
# don't let rescale cause integer overflow
|
|
||||||
d2 = np.clip(d2, lim.min, lim.max)
|
|
||||||
else:
|
|
||||||
d2 = np.clip(d2, max(clip[0], lim.min), min(clip[1], lim.max))
|
|
||||||
else:
|
|
||||||
if clip is not None:
|
|
||||||
d2 = np.clip(d2, *clip)
|
|
||||||
data = d2.astype(dtype)
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def applyLookupTable(data, lut):
|
def applyLookupTable(data, lut):
|
||||||
"""
|
"""
|
||||||
Uses values in *data* as indexes to select values from *lut*.
|
Uses values in *data* as indexes to select values from *lut*.
|
||||||
The returned data has shape data.shape + lut.shape[1:]
|
The returned data has shape data.shape + lut.shape[1:]
|
||||||
|
|
||||||
Note: color gradient lookup tables can be generated using GradientWidget.
|
Note: color gradient lookup tables can be generated using GradientWidget.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
data : ndarray
|
||||||
|
lut : ndarray
|
||||||
|
Either cupy or numpy arrays are accepted, though this function has only
|
||||||
|
consistently behaved correctly on windows with cuda toolkit version >= 11.1.
|
||||||
"""
|
"""
|
||||||
if data.dtype.kind not in ('i', 'u'):
|
if data.dtype.kind not in ('i', 'u'):
|
||||||
data = data.astype(int)
|
data = data.astype(int)
|
||||||
|
|
||||||
return np.take(lut, data, axis=0, mode='clip')
|
cp = getCupy()
|
||||||
|
if cp and cp.get_array_module(data) == cp:
|
||||||
|
# cupy.take only supports "wrap" mode
|
||||||
|
return cp.take(lut, cp.clip(data, 0, lut.shape[0] - 1), axis=0)
|
||||||
|
else:
|
||||||
|
return np.take(lut, data, axis=0, mode='clip')
|
||||||
|
|
||||||
|
|
||||||
def makeRGBA(*args, **kwds):
|
def makeRGBA(*args, **kwds):
|
||||||
@ -1022,7 +999,7 @@ def makeRGBA(*args, **kwds):
|
|||||||
return makeARGB(*args, **kwds)
|
return makeARGB(*args, **kwds)
|
||||||
|
|
||||||
|
|
||||||
def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
|
def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False, output=None):
|
||||||
"""
|
"""
|
||||||
Convert an array of values into an ARGB array suitable for building QImages,
|
Convert an array of values into an ARGB array suitable for building QImages,
|
||||||
OpenGL textures, etc.
|
OpenGL textures, etc.
|
||||||
@ -1062,29 +1039,31 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
|
|||||||
is BGRA).
|
is BGRA).
|
||||||
============== ==================================================================================
|
============== ==================================================================================
|
||||||
"""
|
"""
|
||||||
|
cp = getCupy()
|
||||||
|
xp = cp.get_array_module(data) if cp else np
|
||||||
profile = debug.Profiler()
|
profile = debug.Profiler()
|
||||||
if data.ndim not in (2, 3):
|
if data.ndim not in (2, 3):
|
||||||
raise TypeError("data must be 2D or 3D")
|
raise TypeError("data must be 2D or 3D")
|
||||||
if data.ndim == 3 and data.shape[2] > 4:
|
if data.ndim == 3 and data.shape[2] > 4:
|
||||||
raise TypeError("data.shape[2] must be <= 4")
|
raise TypeError("data.shape[2] must be <= 4")
|
||||||
|
|
||||||
if lut is not None and not isinstance(lut, np.ndarray):
|
if lut is not None and not isinstance(lut, xp.ndarray):
|
||||||
lut = np.array(lut)
|
lut = xp.array(lut)
|
||||||
|
|
||||||
if levels is None:
|
if levels is None:
|
||||||
# automatically decide levels based on data dtype
|
# automatically decide levels based on data dtype
|
||||||
if data.dtype.kind == 'u':
|
if data.dtype.kind == 'u':
|
||||||
levels = np.array([0, 2**(data.itemsize*8)-1])
|
levels = xp.array([0, 2**(data.itemsize*8)-1])
|
||||||
elif data.dtype.kind == 'i':
|
elif data.dtype.kind == 'i':
|
||||||
s = 2**(data.itemsize*8 - 1)
|
s = 2**(data.itemsize*8 - 1)
|
||||||
levels = np.array([-s, s-1])
|
levels = xp.array([-s, s-1])
|
||||||
elif data.dtype.kind == 'b':
|
elif data.dtype.kind == 'b':
|
||||||
levels = np.array([0,1])
|
levels = xp.array([0,1])
|
||||||
else:
|
else:
|
||||||
raise Exception('levels argument is required for float input types')
|
raise Exception('levels argument is required for float input types')
|
||||||
if not isinstance(levels, np.ndarray):
|
if not isinstance(levels, xp.ndarray):
|
||||||
levels = np.array(levels)
|
levels = xp.array(levels)
|
||||||
levels = levels.astype(np.float)
|
levels = levels.astype(xp.float)
|
||||||
if levels.ndim == 1:
|
if levels.ndim == 1:
|
||||||
if levels.shape[0] != 2:
|
if levels.shape[0] != 2:
|
||||||
raise Exception('levels argument must have length 2')
|
raise Exception('levels argument must have length 2')
|
||||||
@ -1096,7 +1075,7 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
|
|||||||
else:
|
else:
|
||||||
raise Exception("levels argument must be 1D or 2D (got shape=%s)." % repr(levels.shape))
|
raise Exception("levels argument must be 1D or 2D (got shape=%s)." % repr(levels.shape))
|
||||||
|
|
||||||
profile()
|
profile('check inputs')
|
||||||
|
|
||||||
# Decide on maximum scaled value
|
# Decide on maximum scaled value
|
||||||
if scale is None:
|
if scale is None:
|
||||||
@ -1107,28 +1086,28 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
|
|||||||
|
|
||||||
# Decide on the dtype we want after scaling
|
# Decide on the dtype we want after scaling
|
||||||
if lut is None:
|
if lut is None:
|
||||||
dtype = np.ubyte
|
dtype = xp.ubyte
|
||||||
else:
|
else:
|
||||||
dtype = np.min_scalar_type(lut.shape[0]-1)
|
dtype = xp.min_scalar_type(lut.shape[0]-1)
|
||||||
|
|
||||||
# awkward, but fastest numpy native nan evaluation
|
# awkward, but fastest numpy native nan evaluation
|
||||||
#
|
#
|
||||||
nanMask = None
|
nanMask = None
|
||||||
if data.dtype.kind == 'f' and np.isnan(data.min()):
|
if data.dtype.kind == 'f' and xp.isnan(data.min()):
|
||||||
nanMask = np.isnan(data)
|
nanMask = xp.isnan(data)
|
||||||
if data.ndim > 2:
|
if data.ndim > 2:
|
||||||
nanMask = np.any(nanMask, axis=-1)
|
nanMask = xp.any(nanMask, axis=-1)
|
||||||
# Apply levels if given
|
# Apply levels if given
|
||||||
if levels is not None:
|
if levels is not None:
|
||||||
if isinstance(levels, np.ndarray) and levels.ndim == 2:
|
if isinstance(levels, xp.ndarray) and levels.ndim == 2:
|
||||||
# we are going to rescale each channel independently
|
# we are going to rescale each channel independently
|
||||||
if levels.shape[0] != data.shape[-1]:
|
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])")
|
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)
|
newData = xp.empty(data.shape, dtype=int)
|
||||||
for i in range(data.shape[-1]):
|
for i in range(data.shape[-1]):
|
||||||
minVal, maxVal = levels[i]
|
minVal, maxVal = levels[i]
|
||||||
if minVal == maxVal:
|
if minVal == maxVal:
|
||||||
maxVal = np.nextafter(maxVal, 2*maxVal)
|
maxVal = xp.nextafter(maxVal, 2*maxVal)
|
||||||
rng = maxVal-minVal
|
rng = maxVal-minVal
|
||||||
rng = 1 if rng == 0 else rng
|
rng = 1 if rng == 0 else rng
|
||||||
newData[...,i] = rescaleData(data[...,i], scale / rng, minVal, dtype=dtype)
|
newData[...,i] = rescaleData(data[...,i], scale / rng, minVal, dtype=dtype)
|
||||||
@ -1138,25 +1117,29 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
|
|||||||
minVal, maxVal = levels
|
minVal, maxVal = levels
|
||||||
if minVal != 0 or maxVal != scale:
|
if minVal != 0 or maxVal != scale:
|
||||||
if minVal == maxVal:
|
if minVal == maxVal:
|
||||||
maxVal = np.nextafter(maxVal, 2*maxVal)
|
maxVal = xp.nextafter(maxVal, 2*maxVal)
|
||||||
rng = maxVal-minVal
|
rng = maxVal-minVal
|
||||||
rng = 1 if rng == 0 else rng
|
rng = 1 if rng == 0 else rng
|
||||||
data = rescaleData(data, scale/rng, minVal, dtype=dtype)
|
data = rescaleData(data, scale/rng, minVal, dtype=dtype)
|
||||||
|
|
||||||
profile()
|
profile('apply levels')
|
||||||
|
|
||||||
# apply LUT if given
|
# apply LUT if given
|
||||||
if lut is not None:
|
if lut is not None:
|
||||||
data = applyLookupTable(data, lut)
|
data = applyLookupTable(data, lut)
|
||||||
else:
|
else:
|
||||||
if data.dtype is not np.ubyte:
|
if data.dtype is not xp.ubyte:
|
||||||
data = np.clip(data, 0, 255).astype(np.ubyte)
|
data = xp.clip(data, 0, 255).astype(xp.ubyte)
|
||||||
|
|
||||||
profile()
|
profile('apply lut')
|
||||||
|
|
||||||
# this will be the final image array
|
# this will be the final image array
|
||||||
imgData = np.empty(data.shape[:2]+(4,), dtype=np.ubyte)
|
if output is None:
|
||||||
|
imgData = xp.empty(data.shape[:2]+(4,), dtype=xp.ubyte)
|
||||||
|
else:
|
||||||
|
imgData = output
|
||||||
|
|
||||||
profile()
|
profile('allocate')
|
||||||
|
|
||||||
# decide channel order
|
# decide channel order
|
||||||
if useRGBA:
|
if useRGBA:
|
||||||
@ -1167,7 +1150,7 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
|
|||||||
# copy data into image array
|
# copy data into image array
|
||||||
if data.ndim == 2:
|
if data.ndim == 2:
|
||||||
# This is tempting:
|
# This is tempting:
|
||||||
# imgData[..., :3] = data[..., np.newaxis]
|
# imgData[..., :3] = data[..., xp.newaxis]
|
||||||
# ..but it turns out this is faster:
|
# ..but it turns out this is faster:
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
imgData[..., i] = data
|
imgData[..., i] = data
|
||||||
@ -1178,7 +1161,7 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
|
|||||||
for i in range(0, data.shape[2]):
|
for i in range(0, data.shape[2]):
|
||||||
imgData[..., i] = data[..., order[i]]
|
imgData[..., i] = data[..., order[i]]
|
||||||
|
|
||||||
profile()
|
profile('reorder channels')
|
||||||
|
|
||||||
# add opaque alpha channel if needed
|
# add opaque alpha channel if needed
|
||||||
if data.ndim == 2 or data.shape[2] == 3:
|
if data.ndim == 2 or data.shape[2] == 3:
|
||||||
@ -1192,7 +1175,7 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
|
|||||||
alpha = True
|
alpha = True
|
||||||
imgData[nanMask, 3] = 0
|
imgData[nanMask, 3] = 0
|
||||||
|
|
||||||
profile()
|
profile('alpha channel')
|
||||||
return imgData, alpha
|
return imgData, alpha
|
||||||
|
|
||||||
|
|
||||||
@ -1364,7 +1347,9 @@ def gaussianFilter(data, sigma):
|
|||||||
(note: results are only approximately equal to the output of
|
(note: results are only approximately equal to the output of
|
||||||
gaussian_filter)
|
gaussian_filter)
|
||||||
"""
|
"""
|
||||||
if np.isscalar(sigma):
|
cp = getCupy()
|
||||||
|
xp = cp.get_array_module(data) if cp else np
|
||||||
|
if xp.isscalar(sigma):
|
||||||
sigma = (sigma,) * data.ndim
|
sigma = (sigma,) * data.ndim
|
||||||
|
|
||||||
baseline = data.mean()
|
baseline = data.mean()
|
||||||
@ -1376,17 +1361,17 @@ def gaussianFilter(data, sigma):
|
|||||||
|
|
||||||
# generate 1D gaussian kernel
|
# generate 1D gaussian kernel
|
||||||
ksize = int(s * 6)
|
ksize = int(s * 6)
|
||||||
x = np.arange(-ksize, ksize)
|
x = xp.arange(-ksize, ksize)
|
||||||
kernel = np.exp(-x**2 / (2*s**2))
|
kernel = xp.exp(-x**2 / (2*s**2))
|
||||||
kshape = [1,] * data.ndim
|
kshape = [1,] * data.ndim
|
||||||
kshape[ax] = len(kernel)
|
kshape[ax] = len(kernel)
|
||||||
kernel = kernel.reshape(kshape)
|
kernel = kernel.reshape(kshape)
|
||||||
|
|
||||||
# convolve as product of FFTs
|
# convolve as product of FFTs
|
||||||
shape = data.shape[ax] + ksize
|
shape = data.shape[ax] + ksize
|
||||||
scale = 1.0 / (abs(s) * (2*np.pi)**0.5)
|
scale = 1.0 / (abs(s) * (2*xp.pi)**0.5)
|
||||||
filtered = scale * np.fft.irfft(np.fft.rfft(filtered, shape, axis=ax) *
|
filtered = scale * xp.fft.irfft(xp.fft.rfft(filtered, shape, axis=ax) *
|
||||||
np.fft.rfft(kernel, shape, axis=ax),
|
xp.fft.rfft(kernel, shape, axis=ax),
|
||||||
axis=ax)
|
axis=ax)
|
||||||
|
|
||||||
# clip off extra data
|
# clip off extra data
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import division
|
from __future__ import division
|
||||||
|
|
||||||
from ..Qt import QtGui, QtCore
|
import numpy
|
||||||
import numpy as np
|
|
||||||
from .. import functions as fn
|
|
||||||
from .. import debug as debug
|
|
||||||
from .GraphicsObject import GraphicsObject
|
from .GraphicsObject import GraphicsObject
|
||||||
from ..Point import Point
|
from .. import debug as debug
|
||||||
|
from .. import functions as fn
|
||||||
from .. import getConfigOption
|
from .. import getConfigOption
|
||||||
|
from ..Point import Point
|
||||||
|
from ..Qt import QtGui, QtCore
|
||||||
|
from ..util.cupy_helper import getCupy
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
@ -53,6 +55,13 @@ class ImageItem(GraphicsObject):
|
|||||||
self.lut = None
|
self.lut = None
|
||||||
self.autoDownsample = False
|
self.autoDownsample = False
|
||||||
self._lastDownsample = (1, 1)
|
self._lastDownsample = (1, 1)
|
||||||
|
self._processingBuffer = None
|
||||||
|
self._displayBuffer = None
|
||||||
|
self._renderRequired = True
|
||||||
|
self._unrenderable = False
|
||||||
|
self._cupy = getCupy()
|
||||||
|
self._xp = None # either numpy or cupy, to match the image data
|
||||||
|
self._defferedLevels = None
|
||||||
|
|
||||||
self.axisOrder = getConfigOption('imageAxisOrder')
|
self.axisOrder = getConfigOption('imageAxisOrder')
|
||||||
|
|
||||||
@ -124,13 +133,16 @@ class ImageItem(GraphicsObject):
|
|||||||
Only the first format is compatible with lookup tables. See :func:`makeARGB <pyqtgraph.makeARGB>`
|
Only the first format is compatible with lookup tables. See :func:`makeARGB <pyqtgraph.makeARGB>`
|
||||||
for more details on how levels are applied.
|
for more details on how levels are applied.
|
||||||
"""
|
"""
|
||||||
if levels is not None:
|
if self._xp is None:
|
||||||
levels = np.asarray(levels)
|
|
||||||
if not fn.eq(levels, self.levels):
|
|
||||||
self.levels = levels
|
self.levels = levels
|
||||||
self._effectiveLut = None
|
self._defferedLevels = levels
|
||||||
if update:
|
return
|
||||||
self.updateImage()
|
if levels is not None:
|
||||||
|
levels = self._xp.asarray(levels)
|
||||||
|
self.levels = levels
|
||||||
|
self._effectiveLut = None
|
||||||
|
if update:
|
||||||
|
self.updateImage()
|
||||||
|
|
||||||
def getLevels(self):
|
def getLevels(self):
|
||||||
return self.levels
|
return self.levels
|
||||||
@ -159,7 +171,7 @@ class ImageItem(GraphicsObject):
|
|||||||
Added in version 0.9.9
|
Added in version 0.9.9
|
||||||
"""
|
"""
|
||||||
self.autoDownsample = ads
|
self.autoDownsample = ads
|
||||||
self.qimage = None
|
self._renderRequired = True
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def setOpts(self, update=True, **kargs):
|
def setOpts(self, update=True, **kargs):
|
||||||
@ -200,6 +212,14 @@ class ImageItem(GraphicsObject):
|
|||||||
self.informViewBoundsChanged()
|
self.informViewBoundsChanged()
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
|
def _buildQImageBuffer(self, shape):
|
||||||
|
self._displayBuffer = numpy.empty(shape[:2] + (4,), dtype=numpy.ubyte)
|
||||||
|
if self._xp == self._cupy:
|
||||||
|
self._processingBuffer = self._xp.empty(shape[:2] + (4,), dtype=self._xp.ubyte)
|
||||||
|
else:
|
||||||
|
self._processingBuffer = self._displayBuffer
|
||||||
|
self.qimage = fn.makeQImage(self._displayBuffer, transpose=False, copy=False)
|
||||||
|
|
||||||
def setImage(self, image=None, autoLevels=None, **kargs):
|
def setImage(self, image=None, autoLevels=None, **kargs):
|
||||||
"""
|
"""
|
||||||
Update the image displayed by this item. For more information on how the image
|
Update the image displayed by this item. For more information on how the image
|
||||||
@ -250,9 +270,14 @@ class ImageItem(GraphicsObject):
|
|||||||
if self.image is None:
|
if self.image is None:
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
|
old_xp = self._xp
|
||||||
|
self._xp = self._cupy.get_array_module(image) if self._cupy else numpy
|
||||||
gotNewData = True
|
gotNewData = True
|
||||||
shapeChanged = (self.image is None or image.shape != self.image.shape)
|
processingSubstrateChanged = old_xp != self._xp
|
||||||
image = image.view(np.ndarray)
|
if processingSubstrateChanged:
|
||||||
|
self._processingBuffer = None
|
||||||
|
shapeChanged = (processingSubstrateChanged or self.image is None or image.shape != self.image.shape)
|
||||||
|
image = image.view()
|
||||||
if self.image is None or image.dtype != self.image.dtype:
|
if self.image is None or image.dtype != self.image.dtype:
|
||||||
self._effectiveLut = None
|
self._effectiveLut = None
|
||||||
self.image = image
|
self.image = image
|
||||||
@ -274,9 +299,9 @@ class ImageItem(GraphicsObject):
|
|||||||
img = self.image
|
img = self.image
|
||||||
while img.size > 2**16:
|
while img.size > 2**16:
|
||||||
img = img[::2, ::2]
|
img = img[::2, ::2]
|
||||||
mn, mx = np.nanmin(img), np.nanmax(img)
|
mn, mx = self._xp.nanmin(img), self._xp.nanmax(img)
|
||||||
# mn and mx can still be NaN if the data is all-NaN
|
# mn and mx can still be NaN if the data is all-NaN
|
||||||
if mn == mx or np.isnan(mn) or np.isnan(mx):
|
if mn == mx or self._xp.isnan(mn) or self._xp.isnan(mx):
|
||||||
mn = 0
|
mn = 0
|
||||||
mx = 255
|
mx = 255
|
||||||
kargs['levels'] = [mn,mx]
|
kargs['levels'] = [mn,mx]
|
||||||
@ -287,13 +312,17 @@ class ImageItem(GraphicsObject):
|
|||||||
|
|
||||||
profile()
|
profile()
|
||||||
|
|
||||||
self.qimage = None
|
self._renderRequired = True
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
profile()
|
profile()
|
||||||
|
|
||||||
if gotNewData:
|
if gotNewData:
|
||||||
self.sigImageChanged.emit()
|
self.sigImageChanged.emit()
|
||||||
|
if self._defferedLevels is not None:
|
||||||
|
levels = self._defferedLevels
|
||||||
|
self._defferedLevels = None
|
||||||
|
self.setLevels((levels))
|
||||||
|
|
||||||
def dataTransform(self):
|
def dataTransform(self):
|
||||||
"""Return the transform that maps from this image's input array to its
|
"""Return the transform that maps from this image's input array to its
|
||||||
@ -337,11 +366,11 @@ class ImageItem(GraphicsObject):
|
|||||||
"""
|
"""
|
||||||
data = self.image
|
data = self.image
|
||||||
while data.size > targetSize:
|
while data.size > targetSize:
|
||||||
ax = np.argmax(data.shape)
|
ax = self._xp.argmax(data.shape)
|
||||||
sl = [slice(None)] * data.ndim
|
sl = [slice(None)] * data.ndim
|
||||||
sl[ax] = slice(None, None, 2)
|
sl[ax] = slice(None, None, 2)
|
||||||
data = data[sl]
|
data = data[sl]
|
||||||
return np.nanmin(data), np.nanmax(data)
|
return self._xp.nanmin(data), self._xp.nanmax(data)
|
||||||
|
|
||||||
def updateImage(self, *args, **kargs):
|
def updateImage(self, *args, **kargs):
|
||||||
## used for re-rendering qimage from self.image.
|
## used for re-rendering qimage from self.image.
|
||||||
@ -356,8 +385,7 @@ class ImageItem(GraphicsObject):
|
|||||||
|
|
||||||
def render(self):
|
def render(self):
|
||||||
# Convert data to QImage for display.
|
# Convert data to QImage for display.
|
||||||
|
self._unrenderable = True
|
||||||
profile = debug.Profiler()
|
|
||||||
if self.image is None or self.image.size == 0:
|
if self.image is None or self.image.size == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -373,7 +401,6 @@ class ImageItem(GraphicsObject):
|
|||||||
if self.autoDownsample:
|
if self.autoDownsample:
|
||||||
xds, yds = self._computeDownsampleFactors()
|
xds, yds = self._computeDownsampleFactors()
|
||||||
if xds is None:
|
if xds is None:
|
||||||
self.qimage = None
|
|
||||||
return
|
return
|
||||||
|
|
||||||
axes = [1, 0] if self.axisOrder == 'row-major' else [0, 1]
|
axes = [1, 0] if self.axisOrder == 'row-major' else [0, 1]
|
||||||
@ -390,18 +417,18 @@ class ImageItem(GraphicsObject):
|
|||||||
# if the image data is a small int, then we can combine levels + lut
|
# if the image data is a small int, then we can combine levels + lut
|
||||||
# into a single lut for better performance
|
# into a single lut for better performance
|
||||||
levels = self.levels
|
levels = self.levels
|
||||||
if levels is not None and levels.ndim == 1 and image.dtype in (np.ubyte, np.uint16):
|
if levels is not None and levels.ndim == 1 and image.dtype in (self._xp.ubyte, self._xp.uint16):
|
||||||
if self._effectiveLut is None:
|
if self._effectiveLut is None:
|
||||||
eflsize = 2**(image.itemsize*8)
|
eflsize = 2**(image.itemsize*8)
|
||||||
ind = np.arange(eflsize)
|
ind = self._xp.arange(eflsize)
|
||||||
minlev, maxlev = levels
|
minlev, maxlev = levels
|
||||||
levdiff = maxlev - minlev
|
levdiff = maxlev - minlev
|
||||||
levdiff = 1 if levdiff == 0 else levdiff # don't allow division by 0
|
levdiff = 1 if levdiff == 0 else levdiff # don't allow division by 0
|
||||||
if lut is None:
|
if lut is None:
|
||||||
efflut = fn.rescaleData(ind, scale=255./levdiff,
|
efflut = fn.rescaleData(ind, scale=255./levdiff,
|
||||||
offset=minlev, dtype=np.ubyte)
|
offset=minlev, dtype=self._xp.ubyte)
|
||||||
else:
|
else:
|
||||||
lutdtype = np.min_scalar_type(lut.shape[0]-1)
|
lutdtype = self._xp.min_scalar_type(lut.shape[0] - 1)
|
||||||
efflut = fn.rescaleData(ind, scale=(lut.shape[0]-1)/levdiff,
|
efflut = fn.rescaleData(ind, scale=(lut.shape[0]-1)/levdiff,
|
||||||
offset=minlev, dtype=lutdtype, clip=(0, lut.shape[0]-1))
|
offset=minlev, dtype=lutdtype, clip=(0, lut.shape[0]-1))
|
||||||
efflut = lut[efflut]
|
efflut = lut[efflut]
|
||||||
@ -419,16 +446,22 @@ class ImageItem(GraphicsObject):
|
|||||||
if self.axisOrder == 'col-major':
|
if self.axisOrder == 'col-major':
|
||||||
image = image.transpose((1, 0, 2)[:image.ndim])
|
image = image.transpose((1, 0, 2)[:image.ndim])
|
||||||
|
|
||||||
argb, alpha = fn.makeARGB(image, lut=lut, levels=levels)
|
if self._processingBuffer is None or self._processingBuffer.shape[:2] != image.shape[:2]:
|
||||||
self.qimage = fn.makeQImage(argb, alpha, transpose=False)
|
self._buildQImageBuffer(image.shape)
|
||||||
|
|
||||||
|
fn.makeARGB(image, lut=lut, levels=levels, output=self._processingBuffer)
|
||||||
|
if self._xp == self._cupy:
|
||||||
|
self._processingBuffer.get(out=self._displayBuffer)
|
||||||
|
self._renderRequired = False
|
||||||
|
self._unrenderable = False
|
||||||
|
|
||||||
def paint(self, p, *args):
|
def paint(self, p, *args):
|
||||||
profile = debug.Profiler()
|
profile = debug.Profiler()
|
||||||
if self.image is None:
|
if self.image is None:
|
||||||
return
|
return
|
||||||
if self.qimage is None:
|
if self._renderRequired:
|
||||||
self.render()
|
self.render()
|
||||||
if self.qimage is None:
|
if self._unrenderable:
|
||||||
return
|
return
|
||||||
profile('render QImage')
|
profile('render QImage')
|
||||||
if self.paintMode is not None:
|
if self.paintMode is not None:
|
||||||
@ -444,7 +477,7 @@ class ImageItem(GraphicsObject):
|
|||||||
|
|
||||||
def save(self, fileName, *args):
|
def save(self, fileName, *args):
|
||||||
"""Save this image to file. Note that this saves the visible image (after scale/color changes), not the original data."""
|
"""Save this image to file. Note that this saves the visible image (after scale/color changes), not the original data."""
|
||||||
if self.qimage is None:
|
if self._renderRequired:
|
||||||
self.render()
|
self.render()
|
||||||
self.qimage.save(fileName, *args)
|
self.qimage.save(fileName, *args)
|
||||||
|
|
||||||
@ -458,7 +491,7 @@ class ImageItem(GraphicsObject):
|
|||||||
dimensions roughly *targetImageSize* for each axis.
|
dimensions roughly *targetImageSize* for each axis.
|
||||||
|
|
||||||
The *bins* argument and any extra keyword arguments are passed to
|
The *bins* argument and any extra keyword arguments are passed to
|
||||||
np.histogram(). If *bins* is 'auto', then a bin number is automatically
|
self.xp.histogram(). If *bins* is 'auto', then a bin number is automatically
|
||||||
chosen based on the image characteristics:
|
chosen based on the image characteristics:
|
||||||
|
|
||||||
* Integer images will have approximately *targetHistogramSize* bins,
|
* Integer images will have approximately *targetHistogramSize* bins,
|
||||||
@ -473,33 +506,33 @@ class ImageItem(GraphicsObject):
|
|||||||
if self.image is None or self.image.size == 0:
|
if self.image is None or self.image.size == 0:
|
||||||
return None, None
|
return None, None
|
||||||
if step == 'auto':
|
if step == 'auto':
|
||||||
step = (max(1, int(np.ceil(self.image.shape[0] / targetImageSize))),
|
step = (max(1, int(self._xp.ceil(self.image.shape[0] / targetImageSize))),
|
||||||
max(1, int(np.ceil(self.image.shape[1] / targetImageSize))))
|
max(1, int(self._xp.ceil(self.image.shape[1] / targetImageSize))))
|
||||||
if np.isscalar(step):
|
if self._xp.isscalar(step):
|
||||||
step = (step, step)
|
step = (step, step)
|
||||||
stepData = self.image[::step[0], ::step[1]]
|
stepData = self.image[::step[0], ::step[1]]
|
||||||
|
|
||||||
if isinstance(bins, str) and bins == 'auto':
|
if isinstance(bins, str) and bins == 'auto':
|
||||||
mn = np.nanmin(stepData)
|
mn = self._xp.nanmin(stepData).item()
|
||||||
mx = np.nanmax(stepData)
|
mx = self._xp.nanmax(stepData).item()
|
||||||
if mx == mn:
|
if mx == mn:
|
||||||
# degenerate image, arange will fail
|
# degenerate image, arange will fail
|
||||||
mx += 1
|
mx += 1
|
||||||
if np.isnan(mn) or np.isnan(mx):
|
if self._xp.isnan(mn) or self._xp.isnan(mx):
|
||||||
# the data are all-nan
|
# the data are all-nan
|
||||||
return None, None
|
return None, None
|
||||||
if stepData.dtype.kind in "ui":
|
if stepData.dtype.kind in "ui":
|
||||||
# For integer data, we select the bins carefully to avoid aliasing
|
# For integer data, we select the bins carefully to avoid aliasing
|
||||||
step = np.ceil((mx-mn) / 500.)
|
step = int(self._xp.ceil((mx - mn) / 500.))
|
||||||
bins = []
|
bins = []
|
||||||
if step > 0.0:
|
if step > 0.0:
|
||||||
bins = np.arange(mn, mx+1.01*step, step, dtype=np.int)
|
bins = self._xp.arange(mn, mx + 1.01 * step, step, dtype=self._xp.int)
|
||||||
else:
|
else:
|
||||||
# for float data, let numpy select the bins.
|
# for float data, let numpy select the bins.
|
||||||
bins = np.linspace(mn, mx, 500)
|
bins = self._xp.linspace(mn, mx, 500)
|
||||||
|
|
||||||
if len(bins) == 0:
|
if len(bins) == 0:
|
||||||
bins = [mn, mx]
|
bins = self._xp.asarray((mn, mx))
|
||||||
|
|
||||||
kwds['bins'] = bins
|
kwds['bins'] = bins
|
||||||
|
|
||||||
@ -507,14 +540,20 @@ class ImageItem(GraphicsObject):
|
|||||||
hist = []
|
hist = []
|
||||||
for i in range(stepData.shape[-1]):
|
for i in range(stepData.shape[-1]):
|
||||||
stepChan = stepData[..., i]
|
stepChan = stepData[..., i]
|
||||||
stepChan = stepChan[np.isfinite(stepChan)]
|
stepChan = stepChan[self._xp.isfinite(stepChan)]
|
||||||
h = np.histogram(stepChan, **kwds)
|
h = self._xp.histogram(stepChan, **kwds)
|
||||||
hist.append((h[1][:-1], h[0]))
|
if self._cupy:
|
||||||
|
hist.append((self._cupy.asnumpy(h[1][:-1]), self._cupy.asnumpy(h[0])))
|
||||||
|
else:
|
||||||
|
hist.append((h[1][:-1], h[0]))
|
||||||
return hist
|
return hist
|
||||||
else:
|
else:
|
||||||
stepData = stepData[np.isfinite(stepData)]
|
stepData = stepData[self._xp.isfinite(stepData)]
|
||||||
hist = np.histogram(stepData, **kwds)
|
hist = self._xp.histogram(stepData, **kwds)
|
||||||
return hist[1][:-1], hist[0]
|
if self._cupy:
|
||||||
|
return self._cupy.asnumpy(hist[1][:-1]), self._cupy.asnumpy(hist[0])
|
||||||
|
else:
|
||||||
|
return hist[1][:-1], hist[0]
|
||||||
|
|
||||||
def setPxMode(self, b):
|
def setPxMode(self, b):
|
||||||
"""
|
"""
|
||||||
@ -529,9 +568,9 @@ class ImageItem(GraphicsObject):
|
|||||||
self.setPxMode(False)
|
self.setPxMode(False)
|
||||||
|
|
||||||
def getPixmap(self):
|
def getPixmap(self):
|
||||||
if self.qimage is None:
|
if self._renderRequired:
|
||||||
self.render()
|
self.render()
|
||||||
if self.qimage is None:
|
if self._unrenderable:
|
||||||
return None
|
return None
|
||||||
return QtGui.QPixmap.fromImage(self.qimage)
|
return QtGui.QPixmap.fromImage(self.qimage)
|
||||||
|
|
||||||
@ -546,10 +585,11 @@ class ImageItem(GraphicsObject):
|
|||||||
if self.autoDownsample:
|
if self.autoDownsample:
|
||||||
xds, yds = self._computeDownsampleFactors()
|
xds, yds = self._computeDownsampleFactors()
|
||||||
if xds is None:
|
if xds is None:
|
||||||
self.qimage = None
|
self._renderRequired = True
|
||||||
|
self._unrenderable = True
|
||||||
return
|
return
|
||||||
if (xds, yds) != self._lastDownsample:
|
if (xds, yds) != self._lastDownsample:
|
||||||
self.qimage = None
|
self._renderRequired = True
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def _computeDownsampleFactors(self):
|
def _computeDownsampleFactors(self):
|
||||||
|
18
pyqtgraph/util/cupy_helper.py
Normal file
18
pyqtgraph/util/cupy_helper.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import os
|
||||||
|
from warnings import warn
|
||||||
|
|
||||||
|
from pyqtgraph import getConfigOption
|
||||||
|
|
||||||
|
|
||||||
|
def getCupy():
|
||||||
|
if getConfigOption("useCupy"):
|
||||||
|
try:
|
||||||
|
import cupy
|
||||||
|
except ImportError:
|
||||||
|
warn("cupy library could not be loaded, but 'useCupy' is set.")
|
||||||
|
return None
|
||||||
|
if os.name == "nt" and cupy.cuda.runtime.runtimeGetVersion() < 11000:
|
||||||
|
warn("In Windows, CUDA toolkit should be version 11 or higher, or some functions may misbehave.")
|
||||||
|
return cupy
|
||||||
|
else:
|
||||||
|
return None
|
@ -5,6 +5,7 @@ Copyright 2010-2016 Luke Campagnola
|
|||||||
Distributed under MIT/X11 license. See license.txt for more infomation.
|
Distributed under MIT/X11 license. See license.txt for more infomation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from .. import getConfigOption, functions as fn, getCupy
|
||||||
from ..Qt import QtCore, QtGui
|
from ..Qt import QtCore, QtGui
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -17,8 +18,6 @@ except (ImportError, AttributeError):
|
|||||||
# AttributeError upon import
|
# AttributeError upon import
|
||||||
HAVE_OPENGL = False
|
HAVE_OPENGL = False
|
||||||
|
|
||||||
from .. import getConfigOption, functions as fn
|
|
||||||
|
|
||||||
|
|
||||||
class RawImageWidget(QtGui.QWidget):
|
class RawImageWidget(QtGui.QWidget):
|
||||||
"""
|
"""
|
||||||
@ -37,6 +36,7 @@ class RawImageWidget(QtGui.QWidget):
|
|||||||
self.scaled = scaled
|
self.scaled = scaled
|
||||||
self.opts = None
|
self.opts = None
|
||||||
self.image = None
|
self.image = None
|
||||||
|
self._cp = getCupy()
|
||||||
|
|
||||||
def setImage(self, img, *args, **kargs):
|
def setImage(self, img, *args, **kargs):
|
||||||
"""
|
"""
|
||||||
@ -52,6 +52,8 @@ class RawImageWidget(QtGui.QWidget):
|
|||||||
return
|
return
|
||||||
if self.image is None:
|
if self.image is None:
|
||||||
argb, alpha = fn.makeARGB(self.opts[0], *self.opts[1], **self.opts[2])
|
argb, alpha = fn.makeARGB(self.opts[0], *self.opts[1], **self.opts[2])
|
||||||
|
if self._cp and self._cp.get_array_module(argb) == self._cp:
|
||||||
|
argb = argb.get() # transfer GPU data back to the CPU
|
||||||
self.image = fn.makeQImage(argb, alpha)
|
self.image = fn.makeQImage(argb, alpha)
|
||||||
self.opts = ()
|
self.opts = ()
|
||||||
# if self.pixmap is None:
|
# if self.pixmap is None:
|
||||||
|
Loading…
Reference in New Issue
Block a user