#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Some data related viewer."""
import numpy as np
import pygimli as pg
from matplotlib.colors import LogNorm, Normalize
from matplotlib.patches import Wedge, Rectangle
from matplotlib.collections import PatchCollection
from .utils import updateAxes as updateAxes_
[docs]
def generateMatrix(xvec, yvec, vals, **kwargs):
"""Generate a data matrix from x/y and value vectors.
Parameters
----------
xvec, yvec, vals : iterables (list, np.array, pg.Vector) of same length
full: bool [False]
generate a fully symmetric matrix containing all unique xvec+yvec
otherwise A is squeezed to the individual unique vectors
Returns
-------
A : np.ndarray(2d)
matrix containing the values sorted according to unique xvec/yvec
xmap/ymap : dict {key: num}
dictionaries for accessing matrix position (row/col number from x/y[i])
"""
verbose = kwargs.get("verbose", True)
if kwargs.pop('full', False):
xymap = {xy: ii
for ii, xy in enumerate(np.unique(np.hstack((xvec, yvec))))}
xmap = xymap
ymap = xymap
else:
xu, yu = np.unique(xvec), np.unique(yvec)
if kwargs.pop('fillx', False):
if verbose:
pg.info('filling x', len(xu))
dx = np.median(np.diff(xu)).round(1)
xu = np.arange(0, xu[-1] - xu[0] + dx * 0.5, dx) + xu[0]
if verbose:
pg.info(len(xu))
if kwargs.pop('filly', False):
dy = np.median(np.diff(yu)).round(1)
yu = np.arange(0, yu[-1] - yu[0] + dy * 0.5, dy) + yu[0]
xmap = {xx: ii for ii, xx in enumerate(xu)}
ymap = {yy: ii for ii, yy in enumerate(yu)}
A = np.zeros((len(ymap), len(xmap)))
inot = []
nshow = min([len(xvec), len(yvec), len(vals)])
for i in range(nshow):
xi, yi = xvec[i], yvec[i]
if A[ymap[yi], xmap[xi]]:
inot.append(i)
A[ymap[yi], xmap[xi]] = vals[i]
if len(inot) > 0:
if verbose:
pg.info(len(inot), "data of", nshow, "not shown")
if len(inot) < 30:
if verbose:
pg.info(inot)
return A, xmap, ymap
[docs]
def showValMapPatches(vals, xVec=None, yVec=None, dx=1, dy=None, **kwargs):
"""Show values as patches over x and y vector.
Parameters
----------
vals : iterable
values to plot
xVec/yVec : iterable
x/y axis values
dx/dy : float
patch width
circular : bool
assume circular (cyclic) positions
"""
ax, _ = pg.show(ax=kwargs.pop('ax', None))
gci, ymap = drawValMapPatches(ax, vals, xVec=xVec, yVec=yVec, dx=dx, dy=dy,
**kwargs)
cbar = pg.viewer.mpl.createColorBar(
gci, **kwargs, onlyColorSet=not kwargs.pop('colorBar', True))
return ax, cbar, ymap
[docs]
def drawValMapPatches(ax, vals, xVec=None, yVec=None, dx=1, dy=None, **kwargs):
"""Show values as patches over x and y vector.
Parameters
----------
vals : iterable
values to plot
xVec/yVec : iterable
x/y axis values
dx/dy : float
patch width
circular : bool
assume circular (cyclic) positions
"""
recs = []
circular = kwargs.pop('circular', False)
if circular:
recs = [None] * len(xVec)
if dy is None: # map y values to unique
ymap = {xy: ii for ii, xy in enumerate(np.unique(yVec))}
xyMap = {}
for i, y in enumerate(yVec):
if y not in xyMap:
xyMap[y] = []
xyMap[y].append(i)
# maxR = max(ymap.values()) # what's that for? not used
dR = 1 / (len(ymap.values())+1)
# dOff = np.pi / 2 # what's that for? not used
for y, xIds in xyMap.items():
r = 1. - dR*(ymap[y]+1)
# ax.plot(r * np.cos(xvec[xIds]),
# r * np.sin(xvec[xIds]), 'o')
# print(y, ymap[y])
for i in xIds:
phi = xVec[i]
# x = r * np.cos(phi) # what's that for? not used
y = r * np.sin(phi)
dPhi = (xVec[1] - xVec[0])
recs[i] = Wedge((0., 0.), r + dR/1.5,
(phi - dPhi)*360/(2*np.pi),
(phi + dPhi)*360/(2*np.pi),
width=dR,
zorder=1+r)
# if i < 5:
# ax.text(x, y, str(i))
# pg.wait()
else:
raise("Implementme")
else:
if dy is None: # map y values to unique
ymap = {xy: ii for ii, xy in enumerate(np.unique(yVec))}
for i in range(len(vals)):
recs.append(Rectangle((xVec[i] - dx / 2, ymap[yVec[i]] - 0.5),
dx, 1))
else:
for i in range(len(vals)):
recs.append(Rectangle((xVec[i] - dx / 2, yVec[i] - dy / 2),
dx, dy))
ax.set_xlim(min(xVec) - dx / 2, max(xVec) + dx / 2)
ax.set_ylim(len(ymap) - 0.5, -0.5)
pp = PatchCollection(recs)
pp.set_edgecolor(None)
pp.set_linewidths(0.0)
pp.set_array(vals)
gci = ax.add_collection(pp)
if circular:
pp.set_edgecolor('black')
pp.set_linewidths(0.1)
return gci, ymap
[docs]
def patchValMap(vals, xvec=None, yvec=None, ax=None, cMin=None, cMax=None,
logScale=None, label=None, dx=1, dy=None, cTrim=0, **kwargs):
"""Plot previously generated (generateVecMatrix) y map (category).
Parameters
----------
vals : iterable
Data values to show.
xvec : dict {i:num}
dict (must match vals.shape[0])
ymap : iterable
vector for x axis (must match vals.shape[0])
ax : mpl.axis
axis to plot, if not given a new figure is created
cMin/cMax : float
minimum/maximum color values
cTrim : float [0]
use trim value to exclude outer cTrim percent of data from color scale
logScale : bool
logarithmic colour scale [min(vals)>0]
label : string
colorbar label
** kwargs:
* circular : bool
Plot in polar coordinates.
"""
if cMin is None:
cMin = np.min(vals)
# cMin = np.nanquantile(vals, cTrim/100)
if cMax is None:
cMax = np.max(vals)
# cMin = np.nanquantile(vals, 1-cTrim/100)
if logScale is None:
logScale = (cMin > 0.0)
norm = None
if logScale and cMin > 0:
norm = LogNorm(vmin=cMin, vmax=cMax)
else:
norm = Normalize(vmin=cMin, vmax=cMax)
if ax is None:
ax = pg.plt.subplots()[1]
recs = []
circular = kwargs.pop('circular', False)
if circular:
recs = [None] * len(xvec)
if dy is None: # map y values to unique
ymap = {xy: ii for ii, xy in enumerate(np.unique(yvec))}
xyMap = {}
for i, y in enumerate(yvec):
if y not in xyMap:
xyMap[y] = []
xyMap[y].append(i)
# maxR = max(ymap.values()) # what's that for? not used
dR = 1 / (len(ymap.values())+1)
# dOff = np.pi / 2 # what's that for? not used
for y, xIds in xyMap.items():
r = 1. - dR*(ymap[y]+1)
# ax.plot(r * np.cos(xvec[xIds]),
# r * np.sin(xvec[xIds]), 'o')
# print(y, ymap[y])
for i in xIds:
phi = xvec[i]
# x = r * np.cos(phi) # what's that for? not used
y = r * np.sin(phi)
dPhi = (xvec[1] - xvec[0])
recs[i] = Wedge((0., 0.), r + dR/1.5,
(phi - dPhi)*360/(2*np.pi),
(phi + dPhi)*360/(2*np.pi),
width=dR,
zorder=1+r)
else:
raise("Implementme")
else:
if dy is None: # map y values to unique
ymap = {xy: ii for ii, xy in enumerate(np.unique(yvec))}
for i in range(len(vals)):
recs.append(Rectangle((xvec[i] - dx / 2, ymap[yvec[i]] - 0.5),
dx, 1))
else:
for i in range(len(vals)):
recs.append(Rectangle((xvec[i] - dx / 2, yvec[i] - dy / 2),
dx, dy))
ax.set_xlim(min(xvec) - dx / 2, max(xvec) + dx / 2)
ax.set_ylim(len(ymap) - 0.5, -0.5)
pp = PatchCollection(recs)
# ax.clear()
col = ax.add_collection(pp)
pp.set_edgecolor(None)
pp.set_linewidths(0.0)
if 'alpha' in kwargs:
pp.set_alpha(kwargs['alpha'])
if circular:
pp.set_edgecolor('black')
pp.set_linewidths(0.1)
cmap = pg.viewer.mpl.cmapFromName(**kwargs)
if kwargs.pop('markOutside', False):
cmap.set_bad('grey')
cmap.set_under('darkgrey')
cmap.set_over('lightgrey')
cmap.set_bad('black')
pp.set_cmap(cmap)
pp.set_norm(norm)
pp.set_array(vals)
pp.set_clim(cMin, cMax)
updateAxes_(ax)
cbar = kwargs.pop('colorBar', True)
ori = kwargs.pop('orientation', 'horizontal')
if cbar in ['horizontal', 'vertical']:
ori = cbar
cbar = True
if cbar is True: # not for cbar=1, which is really confusing!
cbar = pg.viewer.mpl.createColorBar(col, cMin=cMin, cMax=cMax,
nLevs=5, label=label,
orientation=ori)
elif cbar is not False:
# .. cbar is an already existing cbar .. so we update its values
pg.viewer.mpl.updateColorBar(cbar, cMin=cMin, cMax=cMax,
nLevs=5, label=label)
updateAxes_(ax)
return ax, cbar, ymap
[docs]
def patchMatrix(mat, xmap=None, ymap=None, ax=None, cMin=None, cMax=None,
logScale=None, label=None, dx=1, **kwargs):
"""Plot previously generated (generateVecMatrix) matrix.
Parameters
----------
mat : numpy.array2d
matrix to show
xmap : dict {i:num}
dict (must match A.shape[0])
ymap : iterable
vector for x axis (must match A.shape[0])
ax : mpl.axis
axis to plot, if not given a new figure is created
cMin/cMax : float
minimum/maximum color values
logScale : bool
logarithmic colour scale [min(A)>0]
label : string
colorbar label
dx : float
width of the matrix elements (by default 1)
"""
mat = np.ma.masked_where(mat == 0.0, mat, False)
if cMin is None:
cMin = np.min(mat)
if cMax is None:
cMax = np.max(mat)
if logScale is None:
logScale = (cMin > 0.0)
if logScale:
norm = LogNorm(vmin=cMin, vmax=cMax)
else:
norm = Normalize(vmin=cMin, vmax=cMax)
if ax is None:
ax = pg.plt.subplots()[1]
iy, ix = np.nonzero(mat) # != 0)
recs = []
vals = []
for i, _ in enumerate(ix):
recs.append(Rectangle((ix[i] - dx / 2, iy[i] - 0.5), dx, 1))
vals.append(mat[iy[i], ix[i]])
pp = PatchCollection(recs)
col = ax.add_collection(pp)
pp.set_edgecolor(None)
pp.set_linewidths(0.0)
if 'cmap' in kwargs:
pp.set_cmap(kwargs.pop('cmap'))
if 'cMap' in kwargs:
pp.set_cmap(kwargs.pop('cMap'))
pp.set_norm(norm)
pp.set_array(np.array(vals))
pp.set_clim(cMin, cMax)
xval = [k for k in xmap.keys()]
ax.set_xlim(min(xval) - dx / 2, max(xval) + dx / 2)
ax.set_ylim(len(ymap) + 0.5, -0.5)
updateAxes_(ax)
cb = None
if kwargs.pop('colorBar', True):
ori = kwargs.pop('orientation', 'horizontal')
cb = pg.viewer.mpl.createColorBar(col, cMin=cMin, cMax=cMax, nLevs=5,
label=label, orientation=ori)
return ax, cb
[docs]
def plotMatrix(mat, *args, **kwargs):
"""Naming conventions. Use drawDataMatrix or showDataMatrix instead."""
pg.deprecated("use drawDataMatrix or showMatrix")
return showDataMatrix(*args, **kwargs)
[docs]
def showDataMatrix(mat, xmap=None, ymap=None, **kwargs):
"""Show value map as matrix.
Returns
-------
ax : matplotlib axes object
axes object
cb : matplotlib colorbar
colorbar object
"""
ax, _ = pg.show(ax=kwargs.pop('ax', None))
gci = drawDataMatrix(ax, mat, xmap=xmap, ymap=ymap, **kwargs)
cb = None
if kwargs.pop('colorBar', True):
ori = kwargs.pop('orientation', 'horizontal')
cMin = kwargs.pop('cMin', None)
cMax = kwargs.pop('cMax', None)
label = kwargs.pop('label', None)
cb = pg.viewer.mpl.createColorBar(gci, cMin=cMin, cMax=cMax, nLevs=5,
label=label, orientation=ori)
return ax, cb
[docs]
def drawDataMatrix(ax, mat, xmap=None, ymap=None, cMin=None, cMax=None,
logScale=None, label=None, **kwargs):
"""Draw previously generated (generateVecMatrix) matrix.
Parameters
----------
ax : mpl.axis
axis to plot, if not given a new figure is created
mat : numpy.array2d
matrix to show
xmap : dict {i:num}
dict (must match A.shape[0])
ymap : iterable
vector for x axis (must match A.shape[0])
cMin/cMax : float
minimum/maximum color values
logScale : bool
logarithmic colour scale [min(A)>0]
label : string
colorbar label
"""
from matplotlib.colors import LogNorm, Normalize
if xmap is None:
xmap = {i: i for i in range(mat.shape[0])}
if ymap is None:
ymap = {i: i for i in range(mat.shape[1])}
if isinstance(mat, np.ma.MaskedArray):
mat_ = mat
else:
mat_ = np.ma.masked_where(mat == 0.0, mat, False)
if cMin is None:
cMin = np.min(mat_)
if cMax is None:
cMax = np.max(mat_)
if logScale is None:
logScale = (cMin > 0.0)
if logScale:
norm = LogNorm(vmin=cMin, vmax=cMax)
else:
norm = Normalize(vmin=cMin, vmax=cMax)
gci = ax.imshow(mat_, norm=norm, interpolation='nearest')
if 'cmap' in kwargs:
pg.deprecated('use cMap') # 190422
gci.set_cmap(kwargs.pop('cmap'))
if 'cMap' in kwargs:
gci.set_cmap(kwargs.pop('cMap'))
ax.set_aspect(kwargs.pop('aspect', 1))
ax.grid(True)
xt = np.unique(ax.get_xticks().clip(0, len(xmap) - 1))
yt = np.unique(ax.get_xticks().clip(0, len(ymap) - 1))
if kwargs.pop('showally', False):
yt = np.arange(len(ymap))
else:
yt = np.round(np.linspace(0, len(ymap) - 1, 5))
xx = np.sort([k for k in xmap])
ax.set_xticks(xt)
ax.set_xticklabels(['{:g}'.format(round(xx[int(ti)], 2)) for ti in xt])
yy = np.unique([k for k in ymap])
ax.set_yticks(yt)
ax.set_yticklabels(['{:g}'.format(round(yy[int(ti)], 2)) for ti in yt])
return gci
[docs]
def plotVecMatrix(xvec, yvec, vals, full=False, **kwargs):
"""Plot vectors as matrix (deprecated)."""
pg.deprecated("use showVecMatrix")
return showVecMatrix(xvec, yvec, vals, full, **kwargs)
[docs]
def showVecMatrix(xvec, yvec, vals, full=False, **kwargs):
"""Plot three vectors as matrix.
Parameters
----------
xvec, yvec : iterable (e.g. list, np.array, pg.Vector) of identical length
vectors defining the indices into the matrix
vals : iterable of same length as xvec/yvec
vector containing the values to show
full: bool [False]
use a fully symmetric matrix containing all unique xvec+yvec
otherwise A is squeezed to the individual unique xvec/yvec values
**kwargs: forwarded to plotMatrix
* ax : mpl.axis
Axis to plot, if not given a new figure is created
* cMin/cMax : float
Minimum/maximum color values
* logScale : bool
Lgarithmic colour scale [min(A)>0]
* label : string
Colorbar label
Returns
-------
ax : matplotlib axes object
axes object
cb : matplotlib colorbar
colorbar object
"""
A, xmap, ymap = generateMatrix(xvec, yvec, vals, full=full,
verbose=kwargs.get("verbose", True))
return showDataMatrix(A, xmap=xmap, ymap=ymap, **kwargs)
[docs]
def drawVecMatrix(ax, xvec, yvec, vals, full=False, **kwargs):
"""Draw x, y, v vectors in form of a matrix."""
A, xmap, ymap = generateMatrix(xvec, yvec, vals, full=full)
return drawDataMatrix(ax, A, xmap=xmap, ymap=ymap, **kwargs)
[docs]
def plotDataContainerAsMatrix(*args, **kwargs):
"""Plot datacontainer as matrix (deprecated)."""
pg.deprecated('plotDataContainerAsMatrix', 'showDataContainerAsMatrix')
return showDataContainerAsMatrix(*args, **kwargs)
[docs]
def showDataContainerAsMatrix(data, x=None, y=None, v=None, **kwargs):
"""Plot data container as matrix (cross-plot).
for each x, y and v token strings or vectors should be given
"""
xToken = ''
yToken = ''
mul = kwargs.pop('mul', 10**int(np.ceil(np.log10(data.sensorCount()))))
plus = kwargs.pop('plus', 1) # add 1 to count
verbose = kwargs.get('verbose', True)
if isinstance(x, str):
xToken = x
x = data(x)
elif hasattr(x, '__iter__') and isinstance(x[0], str):
num = np.zeros(data.size())
for token in x:
num *= mul
num += data(token) + plus
xToken += token + ' '
x = num.copy()
if verbose:
pg.info("found " + str(len(np.unique(x))) + " x values")
if isinstance(y, str):
yToken = y
y = data(y)
elif hasattr(y, '__iter__') and isinstance(y[0], str):
num = np.zeros(data.size())
for token in y:
num *= mul
num += data(token) + plus
yToken += token + ' '
y = num.copy()
if isinstance(v, str):
v = data(v)
# kwargs.setdefault('ymap', {n: i for i, n in enumerate(np.unique(y))})
if verbose:
pg.info("found " + str(len(np.unique(y))) + " y values")
pg.info("x vector length: {:d}".format(len(x)))
pg.info("y vector length: {:d}".format(len(y)))
pg.info("v vector length: {:d}".format(len(v)))
if x is None or y is None or v is None:
raise Exception("Vectors or strings must be given")
if len(x) != len(y) or len(x) != len(v):
raise Exception("lengths x/y/v not matching: {:d}!={:d}!={:d}".format(
len(x), len(y), len(v)))
ax, cbar = showVecMatrix(x, y, v, **kwargs)
try:
ax.set_xlabel(xToken)
ax.set_ylabel(yToken)
except Exception:
print("Could not set x/y label: ", xToken, yToken)
return ax, cbar
[docs]
def drawSensorAsMarker(ax, data):
"""Draw Sensor marker, these marker are pickable."""
elecsX = []
elecsY = []
for i in range(len(data.sensorPositions())):
elecsX.append(data.sensorPositions()[i][0])
elecsY.append(data.sensorPositions()[i][1])
electrodeMarker, = ax.plot(elecsX, elecsY, 'x', color='black', picker=5.)
ax.set_xlim([data.sensorPositions()[0][0] - 1.,
data.sensorPositions()[data.sensorCount() - 1][0] + 1.])
return electrodeMarker