pyqtgraph/pyqtgraph/widgets/RawImageWidget.py

164 lines
6.0 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
"""
RawImageWidget.py
Copyright 2010-2016 Luke Campagnola
Distributed under MIT/X11 license. See license.txt for more infomation.
"""
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>
2021-01-20 05:26:24 +00:00
from .. import getConfigOption, functions as fn, getCupy
from ..Qt import QtCore, QtGui, QtWidgets
2012-05-11 22:05:41 +00:00
try:
QOpenGLWidget = QtWidgets.QOpenGLWidget
2013-09-10 04:31:43 +00:00
from OpenGL.GL import *
2012-05-11 22:05:41 +00:00
HAVE_OPENGL = True
except (ImportError, AttributeError):
2016-10-26 04:03:09 +00:00
# Would prefer `except ImportError` here, but some versions of pyopengl generate
# AttributeError upon import
2012-05-11 22:05:41 +00:00
HAVE_OPENGL = False
class RawImageWidget(QtGui.QWidget):
"""
Widget optimized for very fast video display.
Generally using an ImageItem inside GraphicsView is fast enough.
On some systems this may provide faster video. See the VideoSpeedTest example for benchmarking.
"""
def __init__(self, parent=None, scaled=False):
"""
Setting scaled=True will cause the entire image to be displayed within the boundaries of the widget.
This also greatly reduces the speed at which it will draw frames.
"""
QtGui.QWidget.__init__(self, parent)
self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding))
self.scaled = scaled
self.opts = None
self.image = None
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>
2021-01-20 05:26:24 +00:00
self._cp = getCupy()
def setImage(self, img, *args, **kargs):
"""
img must be ndarray of shape (x,y), (x,y,3), or (x,y,4).
Extra arguments are sent to functions.makeARGB
"""
if getConfigOption('imageAxisOrder') == 'col-major':
img = img.swapaxes(0, 1)
self.opts = (img, args, kargs)
self.image = None
self.update()
def paintEvent(self, ev):
if self.opts is None:
return
if self.image is None:
argb, alpha = fn.makeARGB(self.opts[0], *self.opts[1], **self.opts[2])
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>
2021-01-20 05:26:24 +00:00
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, copy=False, transpose=False)
self.opts = ()
# if self.pixmap is None:
# self.pixmap = QtGui.QPixmap.fromImage(self.image)
p = QtGui.QPainter(self)
if self.scaled:
rect = self.rect()
ar = rect.width() / float(rect.height())
imar = self.image.width() / float(self.image.height())
if ar > imar:
rect.setWidth(int(rect.width() * imar / ar))
else:
rect.setHeight(int(rect.height() * ar / imar))
p.drawImage(rect, self.image)
else:
p.drawImage(QtCore.QPointF(), self.image)
# p.drawPixmap(self.rect(), self.pixmap)
p.end()
2012-05-11 22:05:41 +00:00
if HAVE_OPENGL:
class RawImageGLWidget(QOpenGLWidget):
2012-05-11 22:05:41 +00:00
"""
Similar to RawImageWidget, but uses a GL widget to do all drawing.
Performance varies between platforms; see examples/VideoSpeedTest for benchmarking.
Checks if setConfigOptions(imageAxisOrder='row-major') was set.
2012-05-11 22:05:41 +00:00
"""
2012-05-11 22:05:41 +00:00
def __init__(self, parent=None, scaled=False):
QOpenGLWidget.__init__(self, parent)
2012-05-11 22:05:41 +00:00
self.scaled = scaled
self.image = None
self.uploaded = False
self.smooth = False
self.opts = None
def setImage(self, img, *args, **kargs):
"""
img must be ndarray of shape (x,y), (x,y,3), or (x,y,4).
Extra arguments are sent to functions.makeARGB
"""
if getConfigOption('imageAxisOrder') == 'col-major':
img = img.swapaxes(0, 1)
self.opts = (img, args, kargs)
self.image = None
self.uploaded = False
2012-05-11 22:05:41 +00:00
self.update()
def initializeGL(self):
self.texture = glGenTextures(1)
def uploadTexture(self):
glEnable(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, self.texture)
if self.smooth:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
else:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER)
# glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER)
## Test texture dimensions first
# shape = self.image.shape
# glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, shape[0], shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, None)
# if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) == 0:
# raise Exception("OpenGL failed to create 2D texture (%dx%d); too large for this hardware." % shape[:2])
h, w = self.image.shape[:2]
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, self.image)
glDisable(GL_TEXTURE_2D)
self.uploaded = True
def paintGL(self):
glClear(GL_COLOR_BUFFER_BIT)
2012-05-11 22:05:41 +00:00
if self.image is None:
if self.opts is None:
return
img, args, kwds = self.opts
kwds['useRGBA'] = True
self.image, alpha = fn.makeARGB(img, *args, **kwds)
if not self.uploaded:
self.uploadTexture()
glEnable(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, self.texture)
glColor4f(1, 1, 1, 1)
glBegin(GL_QUADS)
glTexCoord2f(0, 1)
glVertex3f(-1, -1, 0)
glTexCoord2f(1, 1)
glVertex3f(1, -1, 0)
glTexCoord2f(1, 0)
glVertex3f(1, 1, 0)
glTexCoord2f(0, 0)
glVertex3f(-1, 1, 0)
glEnd()
glDisable(GL_TEXTURE_3D)