Add NonUniformImage, example and tests. (#509)
* Add NonUniformImage, example and tests. * Use updated test-data tag * Fix line ending, remove lut.setLevels keyword arguments call Co-authored-by: Ogi <ognyan.moore@gmail.com>
This commit is contained in:
parent
f2b4a15b2d
commit
510626c15f
|
@ -0,0 +1,86 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Display a non-uniform image.
|
||||
This example displays 2-d data as an image with non-uniformly
|
||||
distributed sample points.
|
||||
"""
|
||||
|
||||
import initExample ## Add path to library (just for examples; you do not need this)
|
||||
|
||||
from pyqtgraph.Qt import QtGui, QtCore
|
||||
import pyqtgraph as pg
|
||||
import numpy as np
|
||||
from pyqtgraph.graphicsItems.GradientEditorItem import Gradients
|
||||
from pyqtgraph.graphicsItems.NonUniformImage import NonUniformImage
|
||||
|
||||
|
||||
RPM2RADS = 2 * np.pi / 60
|
||||
RADS2RPM = 1 / RPM2RADS
|
||||
|
||||
kfric = 1 # [Ws/rad] angular damping coefficient [0;100]
|
||||
kfric3 = 1.5e-6 # [Ws3/rad3] angular damping coefficient (3rd order) [0;10-3]
|
||||
psi = 0.2 # [Vs] flux linkage [0.001;10]
|
||||
res = 5e-3 # [Ohm] resistance [0;100]
|
||||
v_ref = 200 # [V] reference DC voltage [0;1000]
|
||||
k_v = 5 # linear voltage coefficient [-100;100]
|
||||
|
||||
# create the (non-uniform) scales
|
||||
tau = np.array([0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 120, 140, 160, 180, 200, 220], dtype=np.float32)
|
||||
w = np.array([0, 250, 500, 750, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000], dtype=np.float32) * RPM2RADS
|
||||
v = 380
|
||||
|
||||
# calculate the power losses
|
||||
TAU, W = np.meshgrid(tau, w, indexing='ij')
|
||||
V = np.ones_like(TAU) * v
|
||||
|
||||
P_loss = kfric * W + kfric3 * W ** 3 + (res * (TAU / psi) ** 2) + k_v * (V - v_ref)
|
||||
|
||||
P_mech = TAU * W
|
||||
P_loss[P_mech > 1.5e5] = np.NaN
|
||||
|
||||
# green - orange - red
|
||||
Gradients['gor'] = {'ticks': [(0.0, (74, 158, 71)), (0.5, (255, 230, 0)), (1, (191, 79, 76))], 'mode': 'rgb'}
|
||||
|
||||
app = QtGui.QApplication([])
|
||||
|
||||
win = QtGui.QMainWindow()
|
||||
cw = pg.GraphicsLayoutWidget()
|
||||
win.show()
|
||||
win.resize(600, 400)
|
||||
win.setCentralWidget(cw)
|
||||
win.setWindowTitle('pyqtgraph example: Non-uniform Image')
|
||||
|
||||
p = cw.addPlot(title="Power Losses [W]", row=0, col=0)
|
||||
|
||||
lut = pg.HistogramLUTItem()
|
||||
|
||||
p.setMouseEnabled(x=False, y=False)
|
||||
|
||||
cw.addItem(lut)
|
||||
|
||||
# load the gradient
|
||||
lut.gradient.loadPreset('gor')
|
||||
|
||||
image = NonUniformImage(w * RADS2RPM, tau, P_loss.T)
|
||||
image.setLookupTable(lut, autoLevel=True)
|
||||
image.setZValue(-1)
|
||||
p.addItem(image)
|
||||
|
||||
h = image.getHistogram()
|
||||
lut.plot.setData(*h)
|
||||
|
||||
p.showGrid(x=True, y=True)
|
||||
|
||||
p.setLabel(axis='bottom', text='Speed [rpm]')
|
||||
p.setLabel(axis='left', text='Torque [Nm]')
|
||||
|
||||
# elevate the grid lines
|
||||
p.axes['bottom']['item'].setZValue(1000)
|
||||
p.axes['left']['item'].setZValue(1000)
|
||||
|
||||
|
||||
## Start Qt event loop unless running in interactive mode or using pyside.
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
|
||||
QtGui.QApplication.instance().exec_()
|
|
@ -41,6 +41,7 @@ examples = OrderedDict([
|
|||
('FillBetweenItem', 'FillBetweenItem.py'),
|
||||
('ImageItem - video', 'ImageItem.py'),
|
||||
('ImageItem - draw', 'Draw.py'),
|
||||
('Non-uniform Image', 'NonUniformImage.py'),
|
||||
('Region-of-Interest', 'ROIExamples.py'),
|
||||
('Bar Graph', 'BarGraphItem.py'),
|
||||
('GraphicsLayout', 'GraphicsLayout.py'),
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
from ..Qt import QtGui, QtCore
|
||||
import numpy as np
|
||||
from ..colormap import ColorMap
|
||||
from .GraphicsObject import GraphicsObject
|
||||
from .. import mkBrush, mkPen
|
||||
from .. import functions as fn
|
||||
|
||||
|
||||
class NonUniformImage(GraphicsObject):
|
||||
"""
|
||||
**Bases:** :class:`GraphicsObject <pyqtgraph.GraphicsObject>`
|
||||
|
||||
GraphicsObject displaying an image with non-uniform sample points. It's
|
||||
commonly used to display 2-d or slices of higher dimensional data that
|
||||
have a regular but non-uniform grid e.g. measurements or simulation results.
|
||||
"""
|
||||
|
||||
def __init__(self, x, y, z, border=None):
|
||||
|
||||
GraphicsObject.__init__(self)
|
||||
|
||||
# convert to numpy arrays
|
||||
x = np.asarray(x, dtype=np.float64)
|
||||
y = np.asarray(y, dtype=np.float64)
|
||||
z = np.asarray(z, dtype=np.float64)
|
||||
|
||||
if x.ndim != 1 or y.ndim != 1:
|
||||
raise Exception("x and y must be 1-d arrays.")
|
||||
|
||||
if np.any(np.diff(x) < 0) or np.any(np.diff(y) < 0):
|
||||
raise Exception("The values in x and y must be monotonically increasing.")
|
||||
|
||||
if len(z.shape) != 2 or z.shape != (x.size, y.size):
|
||||
raise Exception("The length of x and y must match the shape of z.")
|
||||
|
||||
# default colormap (black - white)
|
||||
self.cmap = ColorMap(pos=[0.0, 1.0], color=[(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)])
|
||||
|
||||
self.data = (x, y, z)
|
||||
self.lut = None
|
||||
self.border = border
|
||||
self.generatePicture()
|
||||
|
||||
def setLookupTable(self, lut, autoLevel=False):
|
||||
lut.sigLevelsChanged.connect(self.generatePicture)
|
||||
lut.gradient.sigGradientChanged.connect(self.generatePicture)
|
||||
self.lut = lut
|
||||
|
||||
if autoLevel:
|
||||
_, _, z = self.data
|
||||
f = z[np.isfinite(z)]
|
||||
lut.setLevels(f.min(), f.max())
|
||||
|
||||
self.generatePicture()
|
||||
|
||||
def setColorMap(self, cmap):
|
||||
self.cmap = cmap
|
||||
self.generatePicture()
|
||||
|
||||
def getHistogram(self, **kwds):
|
||||
"""Returns x and y arrays containing the histogram values for the current image.
|
||||
For an explanation of the return format, see numpy.histogram().
|
||||
"""
|
||||
|
||||
z = self.data[2]
|
||||
z = z[np.isfinite(z)]
|
||||
hist = np.histogram(z, **kwds)
|
||||
|
||||
return hist[1][:-1], hist[0]
|
||||
|
||||
def generatePicture(self):
|
||||
|
||||
x, y, z = self.data
|
||||
|
||||
self.picture = QtGui.QPicture()
|
||||
p = QtGui.QPainter(self.picture)
|
||||
p.setPen(mkPen(None))
|
||||
|
||||
# normalize
|
||||
if self.lut is not None:
|
||||
mn, mx = self.lut.getLevels()
|
||||
else:
|
||||
f = z[np.isfinite(z)]
|
||||
mn = f.min()
|
||||
mx = f.max()
|
||||
|
||||
# draw the tiles
|
||||
for i in range(x.size):
|
||||
for j in range(y.size):
|
||||
|
||||
value = z[i, j]
|
||||
|
||||
if np.isneginf(value):
|
||||
value = 0.0
|
||||
elif np.isposinf(value):
|
||||
value = 1.0
|
||||
elif np.isnan(value):
|
||||
continue # ignore NaN
|
||||
else:
|
||||
value = (value - mn) / (mx - mn) # normalize
|
||||
|
||||
if self.lut:
|
||||
color = self.lut.gradient.getColor(value)
|
||||
else:
|
||||
color = self.cmap.mapToQColor(value)
|
||||
|
||||
p.setBrush(mkBrush(color))
|
||||
|
||||
# left, right, bottom, top
|
||||
l = x[0] if i == 0 else (x[i - 1] + x[i]) / 2
|
||||
r = (x[i] + x[i + 1]) / 2 if i < x.size - 1 else x[-1]
|
||||
b = y[0] if j == 0 else (y[j - 1] + y[j]) / 2
|
||||
t = (y[j] + y[j + 1]) / 2 if j < y.size - 1 else y[-1]
|
||||
|
||||
p.drawRect(QtCore.QRectF(l, t, r - l, b - t))
|
||||
|
||||
if self.border is not None:
|
||||
p.setPen(self.border)
|
||||
p.setBrush(fn.mkBrush(None))
|
||||
p.drawRect(self.boundingRect())
|
||||
|
||||
p.end()
|
||||
|
||||
self.update()
|
||||
|
||||
def paint(self, p, *args):
|
||||
p.drawPicture(0, 0, self.picture)
|
||||
|
||||
def boundingRect(self):
|
||||
return QtCore.QRectF(self.picture.boundingRect())
|
|
@ -0,0 +1,104 @@
|
|||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph.Qt import QtTest
|
||||
from pyqtgraph.graphicsItems.NonUniformImage import NonUniformImage
|
||||
from pyqtgraph.tests import assertImageApproved
|
||||
from pyqtgraph.colormap import ColorMap
|
||||
import pyqtgraph.functions as fn
|
||||
import pytest
|
||||
|
||||
app = pg.mkQApp()
|
||||
|
||||
|
||||
def test_NonUniformImage_scale_dimensions():
|
||||
|
||||
x = [1.0, 3.0, 10.0]
|
||||
y = [1.0, 2.0, 4.0]
|
||||
X, Y = np.meshgrid(x, y, indexing='ij')
|
||||
Z = X * Y
|
||||
|
||||
for args in [(Z, y, Z), (x, Z, Z)]:
|
||||
with pytest.raises(Exception) as ex:
|
||||
NonUniformImage(*args)
|
||||
assert "x and y must be 1-d arrays." in str(ex)
|
||||
|
||||
|
||||
def test_NonUniformImage_scale_monotonicity():
|
||||
|
||||
x = [1.0, 0.0, 10.0]
|
||||
y = [1.0, 2.0, 4.0]
|
||||
X, Y = np.meshgrid(x, y, indexing='ij')
|
||||
Z = X * Y
|
||||
|
||||
for args in [(x, y, Z), (y, x, Z)]:
|
||||
with pytest.raises(Exception) as ex:
|
||||
NonUniformImage(*args)
|
||||
assert "The values in x and y must be monotonically increasing." in str(ex)
|
||||
|
||||
|
||||
def test_NonUniformImage_data_dimensions():
|
||||
|
||||
x = [1.0, 3.0, 10.0]
|
||||
y = [1.0, 2.0, 4.0]
|
||||
|
||||
with pytest.raises(Exception) as ex:
|
||||
NonUniformImage(x, y, x)
|
||||
assert "The length of x and y must match the shape of z." in str(ex)
|
||||
|
||||
|
||||
def test_NonUniformImage_lut():
|
||||
|
||||
window = pg.GraphicsWindow()
|
||||
viewbox = pg.ViewBox()
|
||||
window.setCentralWidget(viewbox)
|
||||
window.resize(200, 200)
|
||||
window.show()
|
||||
|
||||
x = [1.0, 3.0, 10.0]
|
||||
y = [1.0, 2.0, 4.0]
|
||||
X, Y = np.meshgrid(x, y, indexing='ij')
|
||||
Z = X * Y
|
||||
|
||||
image = NonUniformImage(x, y, Z, border=fn.mkPen('g'))
|
||||
viewbox.addItem(image)
|
||||
|
||||
lut = pg.HistogramLUTItem()
|
||||
window.addItem(lut)
|
||||
|
||||
image.setLookupTable(lut, autoLevel=True)
|
||||
|
||||
h = image.getHistogram()
|
||||
lut.plot.setData(*h)
|
||||
|
||||
QtTest.QTest.qWaitForWindowShown(window)
|
||||
QtTest.QTest.qWait(100)
|
||||
|
||||
assertImageApproved(window, 'nonuniform_image/lut-3x3')
|
||||
|
||||
|
||||
def test_NonUniformImage_colormap():
|
||||
|
||||
window = pg.GraphicsWindow()
|
||||
viewbox = pg.ViewBox()
|
||||
window.setCentralWidget(viewbox)
|
||||
window.resize(200, 200)
|
||||
window.show()
|
||||
|
||||
x = [1.0, 3.0, 10.0]
|
||||
y = [1.0, 2.0, 4.0]
|
||||
X, Y = np.meshgrid(x, y, indexing='ij')
|
||||
Z = X * Y
|
||||
|
||||
Z[:, 0] = [np.NINF, np.NAN, np.PINF]
|
||||
|
||||
image = NonUniformImage(x, y, Z, border=fn.mkPen('g'))
|
||||
|
||||
cmap = ColorMap(pos=[0.0, 1.0], color=[(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)], mode='rgb')
|
||||
image.setColorMap(cmap)
|
||||
|
||||
viewbox.addItem(image)
|
||||
|
||||
QtTest.QTest.qWaitForWindowShown(window)
|
||||
QtTest.QTest.qWait(100)
|
||||
|
||||
assertImageApproved(window, 'nonuniform_image/colormap-3x3')
|
|
@ -44,7 +44,7 @@ Procedure for unit-testing with images:
|
|||
# pyqtgraph should be tested against. When adding or changing test images,
|
||||
# create and push a new tag and update this variable. To test locally, begin
|
||||
# by creating the tag in your ~/.pyqtgraph/test-data repository.
|
||||
testDataTag = 'test-data-7'
|
||||
testDataTag = 'test-data-8'
|
||||
|
||||
|
||||
import time
|
||||
|
|
Loading…
Reference in New Issue