# -*- coding: utf-8 -*-
r"""This module implements plotting functions for the PyGSP main objects."""
import numpy as np
import uuid
try:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
plt_import = True
except:
plt_import = False
try:
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl
qtg_import = True
except:
qtg_import = False
class plid():
r"""Not so clean way of generating plot_ids."""
def __init__(self):
self.plot_id = 0
plid = plid()
def show(block=False):
r"""
Show created figures.
Equivalent to plt.show(*args, **kw) excepted you don't have to import
matplotlib by youself.
By default, showing plots does not block the prompt.
"""
plt.show(block)
def close(*args):
r"""
Close created figures.
Strictly equivalent to plt.close(*args) excepted you don't have to import
matplotlib by youself.
By default, showing plots does not block the prompt.
"""
plt.close(*args)
[docs]def plot(O, default_qtg=True, **kwargs):
r"""
Main plotting function.
This function should be able to determine the appropriate plot for
the object.
Additionnal kwargs may be given in case of filter plotting.
Parameters
----------
O : object
Should be either a Graph, Filter or PointCloud
default_qtg: boolean
Define the library to use if both are installed.
Default is pyqtgraph (field=True).
Examples
--------
>>> from pygsp import graphs, plotting
>>> G = graphs.Logo()
>>> try:
... plotting.plot(G, default_qtg=False)
... except Exception as e:
... print(e)
"""
from graphs import Graph
from pointsclouds.pointscloud import PointsCloud
from filters import Filter
if issubclass(type(O), Graph):
plot_graph(O, default_qtg, **kwargs)
elif issubclass(type(O), PointsCloud):
plot_pointcloud(O)
elif issubclass(type(O), Filter):
plot_filter(O, **kwargs)
else:
raise TypeError('Your object type is incorrect, be sure it is a '
'PointCloud, a Filter or a Graph.')
[docs]def plot_graph(G, default_qtg=True, **kwargs):
r"""
Plot a graph or an array of graphs with installed libraries.
This function should be able to determine the appropriate plot for
the graph.
Additionnal kwargs may be given in case of filter plotting.
Parameters
----------
G : Graph
Graph object to plot
show_edges : boolean
Set to False to only draw the vertices (default G.Ne < 10000).
default_qtg: boolean
Define the library to use if both are installed.
Default is pyqtgraph (field=True).
Examples
--------
>>> from pygsp import graphs, plotting
>>> G = graphs.Logo()
>>> try:
... plotting.plot_graph(G, default_qtg=False)
... except Exception as e:
... print(e)
"""
if qtg_import and (default_qtg or not plt_import):
pg_plot_graph(G, **kwargs)
elif plt_import and not (default_qtg and qtg_import):
plt_plot_graph(G, **kwargs)
else:
raise ImportError('No drawing library installed. Please '
'install matplotlib or pyqtgraph.')
def plt_plot_graph(G, savefig=False, show_edges=None, plot_name=''):
r"""
Plot a graph or an array of graphs with matplotlib.
See plot_graph for full documentation.
Extra args
----------
savefig : boolean
Determine wether the plot is saved as a PNG file in your\
current directory (True) or shown in a window (False) (default False).
plot_name : str
To give custom names to plots
"""
# TODO handling when G is a list of graphs
# TODO integrate param when G is a clustered graph
if not plot_name:
plot_name = u"Plot of {}".format(G.gtype)
if show_edges is None:
show_edges = G.Ne < 10000
if 'edge_color' not in G.plotting:
G.plotting['edge_color'] = np.array([255, 88, 41])/255.
# Matplotlib graph initialization in 2D and 3D
if G.coords.shape[1] == 2:
fig = plt.figure(plid.plot_id)
plid.plot_id += 1
ax = fig.add_subplot(111)
elif G.coords.shape[1] == 3:
fig = plt.figure(plid.plot_id)
plid.plot_id += 1
ax = fig.add_subplot(111, projection='3d')
if show_edges:
ki, kj = np.nonzero(G.A)
if G.directed:
raise NotImplementedError('TODO')
if G.coords.shape[1] == 2:
raise NotImplementedError('TODO')
else:
raise NotImplementedError('TODO')
else:
if G.coords.shape[1] == 2:
ki, kj = np.nonzero(G.A)
x = np.concatenate((np.expand_dims(G.coords[ki, 0], axis=0),
np.expand_dims(G.coords[kj, 0], axis=0)))
y = np.concatenate((np.expand_dims(G.coords[ki, 1], axis=0),
np.expand_dims(G.coords[kj, 1], axis=0)))
if isinstance(G.plotting['vertex_color'], list):
ax.plot(x, y, linewidth=G.plotting['edge_width'],
color=G.plotting['edge_color'],
linestyle=G.plotting['edge_style'],
marker='', zorder=1)
ax.scatter(G.coords[:, 0], G.coords[:, 1], marker='o',
s=G.plotting['vertex_size'],
c=G.plotting['vertex_color'], zorder=2)
else:
ax.plot(x, y, linewidth=G.plotting['edge_width'],
color=G.plotting['edge_color'],
linestyle=G.plotting['edge_style'],
marker='o', markersize=G.plotting['vertex_size'],
markerfacecolor=G.plotting['vertex_color'])
if G.coords.shape[1] == 3:
# Very dirty way to display a 3d graph
x = np.concatenate((np.expand_dims(G.coords[ki, 0], axis=0),
np.expand_dims(G.coords[kj, 0], axis=0)))
y = np.concatenate((np.expand_dims(G.coords[ki, 1], axis=0),
np.expand_dims(G.coords[kj, 1], axis=0)))
z = np.concatenate((np.expand_dims(G.coords[ki, 2], axis=0),
np.expand_dims(G.coords[kj, 2], axis=0)))
ii = range(0, x.shape[1])
x2 = np.ndarray((0, 1))
y2 = np.ndarray((0, 1))
z2 = np.ndarray((0, 1))
for i in ii:
x2 = np.append(x2, x[:, i])
for i in ii:
y2 = np.append(y2, y[:, i])
for i in ii:
z2 = np.append(z2, z[:, i])
for i in range(0, x.shape[1] * 2, 2):
x3 = x2[i:i + 2]
y3 = y2[i:i + 2]
z3 = z2[i:i + 2]
ax.plot(x3, y3, z3, linewidth=G.plotting['edge_width'],
color=G.plotting['edge_color'],
linestyle=G.plotting['edge_style'],
marker='o', markersize=G.plotting['vertex_size'],
markerfacecolor=G.plotting['vertex_color'])
else:
if G.coords.shape[1] == 2:
ax.scatter(G.coords[:, 0], G.coords[:, 1], marker='o',
s=G.plotting['vertex_size'],
c=G.plotting['vertex_color'])
if G.coords.shape[1] == 3:
ax.scatter(G.coords[:, 0], G.coords[:, 1], G.coords[:, 2],
marker='o', s=G.plotting['vertex_size'],
c=G.plotting['vertex_color'])
# Save plot as PNG or show it in a window
if savefig:
plt.savefig(plot_name + '.png')
plt.savefig(plot_name + '.pdf')
plt.close(fig)
# else:
# plt.show()
# threading.Thread(None, _thread, None, (G, show_edges, savefig)).start()
def pg_plot_graph(G, show_edges=None):
r"""
Plot a graph or an array of graphs.
See plot_graph for full documentation.
"""
# TODO handling when G is a list of graphs
global window_list
if 'window_list' not in globals():
window_list = {}
if show_edges is None:
show_edges = G.Ne < 10000
ki, kj = np.nonzero(G.A)
if G.directed:
raise NotImplementedError('TODO')
if G.coords.shape[1] == 2:
raise NotImplementedError('TODO')
else:
raise NotImplementedError('TODO')
else:
if G.coords.shape[1] == 2:
adj = np.concatenate((np.expand_dims(ki, axis=1),
np.expand_dims(kj, axis=1)), axis=1)
w = pg.GraphicsWindow()
w.setWindowTitle(G.plotting['plot_name'] or G.gtype if 'plot_name' in G.plotting else G.gtype)
v = w.addViewBox()
v.setAspectLocked()
extra_args = {}
if isinstance(G.plotting['vertex_color'], list):
extra_args['symbolPen'] = [pg.mkPen(v_col) for v_col in G.plotting['vertex_color']]
extra_args['brush'] = [pg.mkBrush(v_col) for v_col in G.plotting['vertex_color']]
elif isinstance(G.plotting['vertex_color'], int):
extra_args['symbolPen'] = G.plotting['vertex_color']
extra_args['brush'] = G.plotting['vertex_color']
# Define syntaxic sugar mapping keywords for the display options
for plot_args, pg_args in [('vertex_size', 'size'), ('vertex_mask', 'mask'), ('edge_color', 'pen')]:
if plot_args in G.plotting:
G.plotting[pg_args] = G.plotting.pop(plot_args)
for pg_args in ['size', 'mask', 'pen', 'symbolPen']:
if pg_args in G.plotting:
extra_args[pg_args] = G.plotting[pg_args]
if not show_edges:
extra_args['pen'] = None
g = pg.GraphItem(pos=G.coords, adj=adj, **extra_args)
v.addItem(g)
window_list[str(uuid.uuid4())] = w
elif G.coords.shape[1] == 3:
app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.opts['distance'] = 10
w.show()
w.setWindowTitle(G.plotting['plot_name'] or G.gtype if 'plot_name' in G.plotting else G.gtype)
# Very dirty way to display a 3d graph
x = np.concatenate((np.expand_dims(G.coords[ki, 0], axis=0),
np.expand_dims(G.coords[kj, 0], axis=0)))
y = np.concatenate((np.expand_dims(G.coords[ki, 1], axis=0),
np.expand_dims(G.coords[kj, 1], axis=0)))
z = np.concatenate((np.expand_dims(G.coords[ki, 2], axis=0),
np.expand_dims(G.coords[kj, 2], axis=0)))
ii = range(0, x.shape[1])
x2 = np.ndarray((0, 1))
y2 = np.ndarray((0, 1))
z2 = np.ndarray((0, 1))
for i in ii:
x2 = np.append(x2, x[:, i])
for i in ii:
y2 = np.append(y2, y[:, i])
for i in ii:
z2 = np.append(z2, z[:, i])
pts = np.concatenate((np.expand_dims(x2, axis=1),
np.expand_dims(y2, axis=1),
np.expand_dims(z2, axis=1)), axis=1)
extra_args = {'color': (1., 0., 0., 1)}
if 'vertex_color' in G.plotting:
if isinstance(G.plotting['vertex_color'], list):
extra_args['color'] = np.array([pg.glColor(pg.mkPen(v_col).color()) for v_col in G.plotting['vertex_color']])
elif isinstance(G.plotting['vertex_color'], int):
extra_args['color'] = pg.glColor(pg.mkPen(G.plotting['vertex_color']).color())
else:
extra_args['color'] = G.plotting['vertex_color']
# Define syntaxic sugar mapping keywords for the display options
for plot_args, pg_args in [('vertex_size', 'size')]:
if plot_args in G.plotting:
G.plotting[pg_args] = G.plotting.pop(plot_args)
for pg_args in ['size']:
if pg_args in G.plotting:
extra_args[pg_args] = G.plotting[pg_args]
if show_edges:
g = gl.GLLinePlotItem(pos=pts, mode='lines', color=G.plotting['edge_color'])
w.addItem(g)
gp = gl.GLScatterPlotItem(pos=G.coords, **extra_args)
w.addItem(gp)
window_list[str(uuid.uuid4())] = app
[docs]def plot_pointcloud(P):
r"""
Plot the coordinates of a pointcloud.
Parameters
----------
P : PointsClouds object
Examples
--------
>>> from pygsp import plotting, pointsclouds
>>> logo = pointsclouds.PointsCloud('logo')
>>> try:
... plotting.plot_pointcloud(logo)
... except:
... pass
"""
if P.coords.shape[1] == 2:
fig = plt.figure(plid.plot_id)
plid.plot_id += 1
ax = fig.add_subplot(111)
ax.plot(P.coords[:, 0], P.coords[:, 1], 'bo')
# plt.show()
if P.coords.shape[1] == 3:
fig = plt.figure(plid.plot_id)
plid.plot_id += 1
ax = fig.add_subplot(111, projection='3d')
ax.plot(P.coords[:, 0], P.coords[:, 1], P.coords[:, 2], 'bo')
# plt.show()
[docs]def plot_filter(filters, npoints=1000, line_width=4, x_width=3,
x_size=10, plot_eigenvalues=None, show_sum=None,
savefig=False, plot_name=None):
r"""
Plot a system of graph spectral filters.
Parameters
----------
filters : filter object
npoints : int
Number of point where the filters are evaluated.
line_width : int
Width of the filters plots.
x_width : int
Width of the X marks representing the eigenvalues.
x_size : int
Size of the X marks representing the eigenvalues.
plot_eigenvalues : boolean
To plot black X marks at all eigenvalues of the graph (You need to \
compute the Fourier basis to use this option). By default the \
eigenvalues are plot if they are contained in the Graph.
show_sum : boolean
To plot an extra line showing the sum of the squared magnitudes\
of the filters (default True if there is multiple filters).
savefig : boolean
Determine wether the plot is saved as a PNG file in your\
current directory (True) or shown in a window (False) (default False).
plot_name : str
To give custom names to plots
Examples
--------
>>> from pygsp import filters, plotting, graphs
>>> G = graphs.Logo()
>>> mh = filters.MexicanHat(G)
>>> try:
... plotting.plot_filter(mh)
... except:
... pass
"""
G = filters.G
if not isinstance(filters.g, list):
filters.g = [filters.g]
if plot_eigenvalues is None:
plot_eigenvalues = hasattr(G, 'e')
if show_sum is None:
show_sum = len(filters.g) > 1
if plot_name is None:
plot_name = "Filter plot of {}".format(G.gtype)
lambdas = np.linspace(0, G.lmax, npoints)
# Apply the filter
fd = filters.evaluate(lambdas)
# Plot the filter
size = len(fd)
fig = plt.figure(plid.plot_id)
plid.plot_id += 1
ax = fig.add_subplot(111)
if len(filters.g) == 1:
ax.plot(lambdas, fd, linewidth=line_width)
elif len(filters.g) > 1:
for i in range(size):
ax.plot(lambdas, fd[i], linewidth=line_width)
# Plot eigenvalues
if plot_eigenvalues:
ax.plot(G.e, np.zeros(G.N), 'xk', markeredgewidth=x_width,
markersize=x_size)
# Plot highlighted eigenvalues TODO
# Plot the sum
if show_sum:
test_sum = np.sum(np.power(fd, 2), 0)
ax.plot(lambdas, test_sum, 'k', linewidth=line_width)
# Save plot as PNG or show it in a window
if savefig:
plt.savefig(plot_name + '.png')
plt.savefig(plot_name + '.pdf')
plt.close(fig)
# else:
# plt.show()
[docs]def plot_signal(G, signal, default_qtg=True, **kwargs):
r"""
Plot a graph signal in 2D or 3D with installed libraries.
Parameters
----------
G : Graph object
If not specified it will take the one used to create the filter.
signal : array of int
Signal applied to the graph.
show_edges : boolean
Set to False to only draw the vertices (default G.Ne < 10000).
cp : List of int
Camera position for a 3D graph.
vertex_size : int
Size of circle representing each signal component.
vertex_highlight : boolean
Vector of indices of vertices to be highlighted.
climits : array of int
Limits of the colorbar.
colorbar : boolean
To plot an extra line showing the sum of the squared magnitudes
of the filters (default True if there is multiple filters).
bar : boolean
NOT IMPLEMENTED: False display color, True display bar for the graph
(default False).
bar_width : int
Width of the bar (default 1).
default_qtg: boolean
Define the library to use if both are installed.
Default is pyqtgraph (field=True).
Examples
--------
>>> import numpy as np
>>> from pygsp import graphs, filters, plotting
>>> G = graphs.Ring(15)
>>> signal = np.sin((np.arange(1, 16)*2*np.pi/15))
>>> try:
... plotting.plot_signal(G, signal, default_qtg=False)
... except:
... pass
"""
if qtg_import and (default_qtg or not plt_import):
pg_plot_signal(G, signal, **kwargs)
elif plt_import and not (default_qtg and qtg_import):
plt_plot_signal(G, signal, **kwargs)
else:
raise ImportError('No drawing library installed. Please '
'install matplotlib or pyqtgraph.')
def plt_plot_signal(G, signal, show_edges=None, cp=[-6, -3, 160],
vertex_size=None, vertex_highlight=False, climits=None,
colorbar=True, bar=False, bar_width=1, savefig=False,
plot_name=None):
r"""
Plot a graph signal in 2D or 3D using matplotlib.
See plot_signal for full documentation.
Extra args
----------
savefig : boolean
Determine whether the plot is saved as a PNG file in your
current directory (True) or shown in a window (False) (default False).
plot_name : str
To give custom names to plots
"""
fig = plt.figure(plid.plot_id)
plid.plot_id += 1
if np.sum(np.abs(signal.imag)) > 1e-10:
raise ValueError("Can't display complex signal.")
if show_edges is None:
show_edges = G.Ne < 10000
if vertex_size is None:
vertex_size = 100
if climits is None:
cmin = 1.01 * np.min(signal)
cmax = 1.01 * np.max(signal)
climits = [cmin, cmax]
if plot_name is None:
plot_name = "Signal plot of " + G.gtype
# Matplotlib graph initialization in 2D and 3D
if G.coords.shape[1] == 2:
fig = plt.figure()
ax = fig.add_subplot(111)
elif G.coords.shape[1] == 3:
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# Plot edges
if show_edges:
ki, kj = np.nonzero(G.A)
if G.directed:
raise NotImplementedError('TODO')
if G.coords.shape[1] == 2:
raise NotImplementedError('TODO')
else:
raise NotImplementedError('TODO')
else:
if G.coords.shape[1] == 2:
x = np.concatenate((np.expand_dims(G.coords[ki, 0], axis=0),
np.expand_dims(G.coords[kj, 0], axis=0)))
y = np.concatenate((np.expand_dims(G.coords[ki, 1], axis=0),
np.expand_dims(G.coords[kj, 1], axis=0)))
ax.plot(x, y, color='grey', zorder=1)
# plt.show()
if G.coords.shape[1] == 3:
# Very dirty way to display 3D graph edges
x = np.concatenate((np.expand_dims(G.coords[ki, 0], axis=0),
np.expand_dims(G.coords[kj, 0], axis=0)))
y = np.concatenate((np.expand_dims(G.coords[ki, 1], axis=0),
np.expand_dims(G.coords[kj, 1], axis=0)))
z = np.concatenate((np.expand_dims(G.coords[ki, 2], axis=0),
np.expand_dims(G.coords[kj, 2], axis=0)))
ii = range(0, x.shape[1])
x2 = np.ndarray((0, 1))
y2 = np.ndarray((0, 1))
z2 = np.ndarray((0, 1))
for i in ii:
x2 = np.append(x2, x[:, i])
for i in ii:
y2 = np.append(y2, y[:, i])
for i in ii:
z2 = np.append(z2, z[:, i])
for i in range(0, x.shape[1] * 2, 2):
x3 = x2[i:i + 2]
y3 = y2[i:i + 2]
z3 = z2[i:i + 2]
ax.plot(x3, y3, z3, color='grey', marker='o',
markerfacecolor='blue', zorder=1)
# Plot signal
if G.coords.shape[1] == 2:
ax.scatter(G.coords[:, 0], G.coords[:, 1], s=vertex_size, c=signal,
zorder=2)
if G.coords.shape[1] == 3:
ax.scatter(G.coords[:, 0], G.coords[:, 1], G.coords[:, 2],
s=vertex_size, c=signal, zorder=2)
# Save plot as PNG or show it in a window
if savefig:
plt.savefig(plot_name + '.png')
plt.savefig(plot_name + '.pdf')
plt.close(fig)
# else:
# plt.show()
def pg_plot_signal(G, signal, show_edges=None, cp=[-6, -3, 160],
vertex_size=None, vertex_highlight=False, climits=None,
colorbar=True, bar=False, bar_width=1):
r"""
Plot a graph signal in 2D or 3D, with pyqtgraph.
See plot_signal for full documentation.
"""
if np.sum(np.abs(signal.imag)) > 1e-10:
raise ValueError("Can't display complex signal.")
if show_edges is None:
show_edges = G.Ne < 10000
if vertex_size is None:
vertex_size = 15
if climits is None:
cmin = 1.01 * np.min(signal)
cmax = 1.01 * np.max(signal)
climits = [cmin, cmax]
# pygtgraph window initialization in 2D and 3D
global window_list
if 'window_list' not in globals():
window_list = {}
if G.coords.shape[1] == 2:
w = pg.GraphicsWindow()
w.setWindowTitle(G.gtype)
v = w.addViewBox()
elif G.coords.shape[1] == 3:
app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.opts['distance'] = 10
w.show()
w.setWindowTitle(G.gtype)
# Plot signal
pos = np.array([0., 1., 0.5, 0.25, 0.75])
color = np.array([[0, 255, 255, 255], [255, 255, 0, 255], [0, 0, 0, 255],
(0, 0, 255, 255), (255, 0, 0, 255)], dtype=np.ubyte)
cmap = pg.ColorMap(pos, color)
mininum = min(signal)
maximum = max(signal)
normalized_signal = [(float(x) - mininum) / (maximum - mininum) for x in signal]
if G.coords.shape[1] == 2:
gp = pg.ScatterPlotItem(G.coords[:, 0],
G.coords[:, 1],
size=vertex_size,
brush=cmap.map(normalized_signal, 'qcolor'))
v.addItem(gp)
if G.coords.shape[1] == 3:
gp = gl.GLScatterPlotItem(G.coords[:, 0], G.coords[:, 1],
G.coords[:, 2], size=vertex_size, c=signal)
w.addItem(gp)
# Plot edges
if show_edges:
ki, kj = np.nonzero(G.A)
if G.directed:
raise NotImplementedError('TODO')
if G.coords.shape[1] == 2:
raise NotImplementedError('TODO')
else:
raise NotImplementedError('TODO')
else:
if G.coords.shape[1] == 2:
adj = np.concatenate((np.expand_dims(ki, axis=1),
np.expand_dims(kj, axis=1)), axis=1)
g = pg.GraphItem(pos=G.coords, adj=adj, symbolBrush=None,
symbolPen=None)
v.addItem(g)
if G.coords.shape[1] == 3:
app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.opts['distance'] = 10
w.show()
w.setWindowTitle(G.gtype)
# Very dirty way to display a 3d graph
x = np.concatenate((np.expand_dims(G.coords[ki, 0], axis=0),
np.expand_dims(G.coords[kj, 0], axis=0)))
y = np.concatenate((np.expand_dims(G.coords[ki, 1], axis=0),
np.expand_dims(G.coords[kj, 1], axis=0)))
z = np.concatenate((np.expand_dims(G.coords[ki, 2], axis=0),
np.expand_dims(G.coords[kj, 2], axis=0)))
ii = range(0, x.shape[1])
x2 = np.ndarray((0, 1))
y2 = np.ndarray((0, 1))
z2 = np.ndarray((0, 1))
for i in ii:
x2 = np.append(x2, x[:, i])
for i in ii:
y2 = np.append(y2, y[:, i])
for i in ii:
z2 = np.append(z2, z[:, i])
pts = np.concatenate((np.expand_dims(x2, axis=1),
np.expand_dims(y2, axis=1),
np.expand_dims(z2, axis=1)), axis=1)
g = gl.GLLinePlotItem(pos=pts, mode='lines')
gp = gl.GLScatterPlotItem(pos=G.coords, color=(1., 0., 0., 1))
w.addItem(g)
w.addItem(gp)
# Multiple windows handling
if G.coords.shape[1] == 2:
window_list[str(uuid.uuid4())] = w
elif G.coords.shape[1] == 3:
window_list[str(uuid.uuid4())] = app