PColorMeshItem know require only one 2d array

Allow PColorMeshItem to work with only one 2d array, a regular grid is then assume for the polygons vertices.
Improvement of comments.
This commit is contained in:
Etienne Dumur 2020-06-28 20:32:22 +02:00
parent 98c6c56358
commit 667c41d22b

View File

@ -7,7 +7,7 @@ from .. import debug as debug
from .GraphicsObject import GraphicsObject from .GraphicsObject import GraphicsObject
from ..Point import Point from ..Point import Point
from .. import getConfigOption from .. import getConfigOption
from .GradientEditorItem import Gradients from .GradientEditorItem import Gradients # List of colormaps
from ..colormap import ColorMap from ..colormap import ColorMap
try: try:
@ -22,26 +22,40 @@ __all__ = ['PColorMeshItem']
class PColorMeshItem(GraphicsObject): class PColorMeshItem(GraphicsObject):
""" """
**Bases:** :class:`GraphicsObject <pyqtgraph.GraphicsObject>` **Bases:** :class:`GraphicsObject <pyqtgraph.GraphicsObject>`
Create a pseudocolor plot with convex polygons.
""" """
sigImageChanged = QtCore.Signal() sigImageChanged = QtCore.Signal()
sigRemoveRequested = QtCore.Signal(object) # self; emitted when 'remove' is selected from context menu sigRemoveRequested = QtCore.Signal(object) # self; emitted when 'remove' is selected from context menu
def __init__(self, x=None, y=None, z=None, def __init__(self, *args,
cmap='viridis', edgecolors=None): cmap='viridis', edgecolors=None, ):
""" """
Create a pseudocolor plot with convex polygons.
Call signature:
pcolor([x, y,] c, **kwargs)
x and Y can be used to specify the corners of the quadrilaterals.
Parameters Parameters
---------- ----------
x, y : np.ndarray x, y : np.ndarray, optional, default None
2D array containing the coordinates of the polygons 2D array containing the coordinates of the polygons
z : np.ndarray z : np.ndarray
2D array containing the value which will be maped into the polygons 2D array containing the value which will be maped into the polygons
colors. colors.
If x and y is None, the polygons will be displaced on a grid
otherwise x and y will be used as polygons vertices coordinates as:
(x[i+1, j], y[i+1, j]) (x[i+1, j+1], y[i+1, j+1])
+---------+
| z[i, j] |
+---------+
(x[i, j], y[i, j]) (x[i, j+1], y[i, j+1])
"ASCII from: https://matplotlib.org/3.2.1/api/_as_gen/
matplotlib.pyplot.pcolormesh.html".
cmap : str, default 'viridis cmap : str, default 'viridis
Colormap used to map the z value to colors. Colormap used to map the z value to colors.
edgecolors : dict , default None edgecolors : dict , default None
@ -53,11 +67,7 @@ class PColorMeshItem(GraphicsObject):
""" """
GraphicsObject.__init__(self) GraphicsObject.__init__(self)
self.x = x self.qpicture = None ## rendered picture for display
self.y = y
self.z = z
self.qpicture = None ## rendered image for display
self.axisOrder = getConfigOption('imageAxisOrder') self.axisOrder = getConfigOption('imageAxisOrder')
@ -65,24 +75,89 @@ class PColorMeshItem(GraphicsObject):
if cmap in Gradients.keys(): if cmap in Gradients.keys():
self.cmap = cmap self.cmap = cmap
else: else:
raise NameError('Undefined colormap') raise NameError('Undefined colormap, should be one of the following: '+', '.join(['"'+i+'"' for i in Gradients.keys()])+'.')
# If some data have been sent we directly display it
if x is not None and y is not None and z is not None:
self.setData(x, y, z)
def setData(self, x, y, z):
# If some data have been sent we directly display it
if len(args)>0:
self.setData(*args)
# We test of the view has changed
if np.any(self.x != x) or np.any(self.y != y) or np.any(self.z != z):
self.informViewBoundsChanged()
# Replace data def _prepareData(self, args):
self.x = x """
self.y = y Check the shape of the data.
self.z = z Return a set of 2d array x, y, z ready to be used to draw the picture.
"""
# User didn't specified data
if len(args)==0:
self.x = None
self.y = None
self.z = None
# User only specified z
elif len(args)==1:
# If x and y is None, the polygons will be displaced on a grid
x = np.arange(0, args[0].shape[0]+1, 1)
y = np.arange(0, args[0].shape[1]+1, 1)
self.x, self.y = np.meshgrid(x, y, indexing='ij')
self.z = args[0]
# User specified x, y, z
elif len(args)==3:
# Shape checking
if args[0].shape[0] != args[2].shape[0]+1 or args[0].shape[1] != args[2].shape[1]+1:
raise ValueError('The dimension of x should be one greater than the one of z')
if args[1].shape[0] != args[2].shape[0]+1 or args[1].shape[1] != args[2].shape[1]+1:
raise ValueError('The dimension of y should be one greater than the one of z')
self.x = args[0]
self.y = args[1]
self.z = args[2]
else:
ValueError('Data must been sent as (z) or (x, y, z)')
def setData(self, *args):
"""
Set the data to be drawn.
Parameters
----------
x, y : np.ndarray, optional, default None
2D array containing the coordinates of the polygons
z : np.ndarray
2D array containing the value which will be maped into the polygons
colors.
If x and y is None, the polygons will be displaced on a grid
otherwise x and y will be used as polygons vertices coordinates as:
(x[i+1, j], y[i+1, j]) (x[i+1, j+1], y[i+1, j+1])
+---------+
| z[i, j] |
+---------+
(x[i, j], y[i, j]) (x[i, j+1], y[i, j+1])
"ASCII from: https://matplotlib.org/3.2.1/api/_as_gen/
matplotlib.pyplot.pcolormesh.html".
"""
# Prepare data
cd = self._prepareData(args)
# Has the view bounds changed
shapeChanged = False
if self.qpicture is None:
shapeChanged = True
elif len(args)==1:
if args[0].shape[0] != self.x[:,1][-1] or args[0].shape[1] != self.y[0][-1]:
shapeChanged = True
elif len(args)==3:
if np.any(self.x != args[0]) or np.any(self.y != args[1]):
shapeChanged = True
profile = debug.Profiler() profile = debug.Profiler()
@ -102,13 +177,13 @@ class PColorMeshItem(GraphicsObject):
cmap = ColorMap(pos, color) cmap = ColorMap(pos, color)
lut = cmap.getLookupTable(0.0, 1.0, 256) lut = cmap.getLookupTable(0.0, 1.0, 256)
# Second we associate each z value, that we normalize, to the lut # Second we associate each z value, that we normalize, to the lut
norm = z - z.min() norm = self.z - self.z.min()
norm = norm/norm.max() norm = norm/norm.max()
norm = (norm*(len(lut)-1)).astype(int) norm = (norm*(len(lut)-1)).astype(int)
# Go through all the data and draw the polygons accordingly # Go through all the data and draw the polygons accordingly
for xi in range(z.shape[0]): for xi in range(self.z.shape[0]):
for yi in range(z.shape[1]): for yi in range(self.z.shape[1]):
# Set the color of the polygon first # Set the color of the polygon first
# print(xi, yi, norm[xi][yi]) # print(xi, yi, norm[xi][yi])
@ -116,15 +191,18 @@ class PColorMeshItem(GraphicsObject):
p.setBrush(QtGui.QColor(c[0], c[1], c[2])) p.setBrush(QtGui.QColor(c[0], c[1], c[2]))
# DrawConvexPlygon is faster # DrawConvexPlygon is faster
p.drawConvexPolygon(QtCore.QPointF(x[xi][yi], y[xi][yi]), p.drawConvexPolygon(QtCore.QPointF(self.x[xi][yi], self.y[xi][yi]),
QtCore.QPointF(x[xi+1][yi], y[xi+1][yi]), QtCore.QPointF(self.x[xi+1][yi], self.y[xi+1][yi]),
QtCore.QPointF(x[xi+1][yi+1], y[xi+1][yi+1]), QtCore.QPointF(self.x[xi+1][yi+1], self.y[xi+1][yi+1]),
QtCore.QPointF(x[xi][yi+1], y[xi][yi+1])) QtCore.QPointF(self.x[xi][yi+1], self.y[xi][yi+1]))
p.end() p.end()
self.update() self.update()
if shapeChanged:
self.informViewBoundsChanged()
def paint(self, p, *args): def paint(self, p, *args):
@ -159,7 +237,6 @@ class PColorMeshItem(GraphicsObject):
def boundingRect(self): def boundingRect(self):
if self.qpicture is None:
if self.z is None:
return QtCore.QRectF(0., 0., 0., 0.) return QtCore.QRectF(0., 0., 0., 0.)
return QtCore.QRectF(0., 0., float(self.width()), float(self.height())) return QtCore.QRectF(0., 0., float(self.width()), float(self.height()))