This notebook presents the basic concepts behind Menpo's PointGraph
class and subclasses.
PointGraph
is basically Graph
s with geometry (PointCloud
). This means that apart from the edge connections between vertices, a PointGraph
also defines spatial location coordinates for each vertex. The PointGraph
subclasses are:
PointUndirectedGraph
: graph with undirected edge connectionsPointDirectedGraph
: graph with directed edge connectionsPointTree
: directed graph in which any two vertices are connected with exactly one pathFor a tutorial on the basics of the Graph
class, pease refer to the Graph
notebook.
This presentation contains the following:
First, let's make all the necessary imports:
In [1]:
%matplotlib inline
import numpy as np
from scipy.sparse import csr_matrix
import matplotlib.pyplot as plt
from menpo.shape import PointUndirectedGraph, PointDirectedGraph, PointTree
The following undirected graph:
|---0---|
| |
| |
1-------2
| |
| |
3-------4
|
|
5
can be defined as:
In [2]:
points = np.array([[10, 30], [0, 20], [20, 20], [0, 10], [20, 10], [0, 0]])
adj_undirected = np.array([[0, 1, 1, 0, 0, 0],
[1, 0, 1, 1, 0, 0],
[1, 1, 0, 0, 1, 0],
[0, 1, 0, 0, 1, 1],
[0, 0, 1, 1, 0, 0],
[0, 0, 0, 1, 0, 0]])
undirected_graph = PointUndirectedGraph(points, adj_undirected)
and printed and visualized as:
In [3]:
print(undirected_graph)
undirected_graph.view(image_view=False, render_axes=False, line_width=2);
Similarly, the following directed graph with isolated vertices:
|-->0<--|
| |
| |
1<----->2
| |
v v
3------>4
|
v
5 6
can be defined as:
In [4]:
points = np.array([[10, 30], [0, 20], [20, 20], [0, 10], [20, 10], [0, 0], [20, 0]])
adj_directed = np.array([[0, 0, 0, 0, 0, 0, 0],
[1, 0, 1, 1, 0, 0, 0],
[1, 1, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0]])
directed_graph = PointDirectedGraph(points, adj_directed)
In [5]:
print(directed_graph)
directed_graph.view(image_view=False, render_axes=False, line_width=2);
A Tree in Menpo is defined as a directed graph, thus PointTree
is a subclass of PointDirectedGraph
. The following tree:
0
|
___|___
1 2
| |
_|_ |
3 4 5
| | |
| | |
6 7 8
can be defined as:
In [6]:
points = np.array([[30, 30], [10, 20], [50, 20], [0, 10], [20, 10], [50, 10], [0, 0], [20, 0], [50, 0]])
adj_tree = csr_matrix(([1] * 8, ([0, 0, 1, 1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6, 7, 8])), shape=(9, 9))
tree = PointTree(points, adj_tree, root_vertex=0)
In [7]:
print(tree)
tree.view(image_view=False, render_axes=False, line_width=2);
For the basic properties of graphs and trees, please refer to the Graph
notebook. Herein, we present some more advanced functionality, such as shortest paths and minimum spanning tree.
All PointGraph
subclasses are constructed using the adjacency matrix. However, it is possible to also create a graph (or tree) using the edges, i.e. a matrix of size (n_edges, 2, )
that contains all the pairs of vertices that are connected with an edge. For example, the following undirected graph:
0---|
|
|
1 2
|
|
3-------4
5
can be defined as:
In [8]:
points = np.array([[10, 30], [0, 20], [20, 20], [0, 10], [20, 10], [0, 0]])
edges = [[0, 2], [2, 4], [3, 4]]
graph = PointUndirectedGraph.init_from_edges(points, edges)
print(graph)
graph.view(image_view=False, render_axes=False, line_width=2);
In [9]:
v1 = 2
v2 = 5
print("The path between {} and {} in the undirected_graph is {}.".format(v1, v2, undirected_graph.find_path(v1, v2)))
print("The path between {} and {} in the directed_graph is {}.".format(v1, v2, directed_graph.find_path(v1, v2)))
print("The path between {} and {} in the tree is {}.".format(v1, v2, tree.find_path(v1, v2)))
Or get all the possible paths between two vertices:
In [10]:
v1 = 2
v2 = 4
print("Vertices {} and {} in the directed_graph "
"are connected with the following paths: {}.".format(v1, v2, directed_graph.find_all_paths(v1, v2)))
The paths can be easily visualized as:
In [11]:
all_paths = directed_graph.find_all_paths(v1, v2)
paths = []
points = np.array([[10, 30], [0, 20], [20, 20], [0, 10], [20, 10], [0, 0], [20, 0]])
for path_list in all_paths:
path = csr_matrix(([1] * len(path_list[:-1]),
(path_list[:-1], path_list[1:])), shape=(7, 7))
paths.append(PointDirectedGraph(points, path))
In [12]:
renderer = directed_graph.view(new_figure=True, image_view=False, line_width=2);
paths[0].view(figure_id=renderer.figure_id, image_view=False, line_colour='b', line_width=2, render_markers=False);
paths[1].view(figure_id=renderer.figure_id, image_view=False, render_axes=False, line_colour='g', line_width=2);
plt.legend(['Graph edges', 'Graph vertices', 'First path connecting 2 and 4', 'Second path connecting 2 and 4'], loc=8);
In [13]:
v1 = 2
v2 = 4
shortest_path, distance = directed_graph.find_shortest_path(v1, v2)
print("The shortest path connecting vertices {} and {} "
"in the directed_graph is {} and costs {}.".format(v1, v2, shortest_path, distance))
Similarly:
In [14]:
v1 = 0
v2 = 4
shortest_path, distance = undirected_graph.find_shortest_path(v1, v2)
print("The shortest path connecting vertices {} and {} "
"in the undirected_graph is {} and costs {}.".format(v1, v2, shortest_path, distance))
Of course there may be no path:
In [15]:
v1 = 0
v2 = 6
shortest_path, distance = directed_graph.find_shortest_path(v1, v2)
print("The shortest path connecting vertices {} and {} "
"in the directed_graph is {} and costs {}.".format(v1, v2, shortest_path, distance))
In [16]:
points = np.array([[0, 10], [10, 20], [20, 20], [30, 20], [40, 10], [30, 0], [20, 0], [10, 0], [20, 10]])
adj = csr_matrix(([4, 4, 8, 8, 8, 8, 11, 11, 7, 7, 4, 4, 2, 2, 9, 9, 14, 14, 10, 10, 2, 2, 1, 1, 6, 6, 7, 7],
([0, 1, 0, 7, 1, 2, 1, 7, 2, 3, 2, 5, 2, 8, 3, 4, 3, 5, 4, 5, 5, 6, 6, 7, 6, 8, 7, 8],
[1, 0, 7, 0, 2, 1, 7, 1, 3, 2, 5, 2, 8, 2, 4, 3, 5, 3, 5, 4, 6, 5, 7, 6, 8, 6, 8, 7])), shape=(9, 9))
graph = PointUndirectedGraph(points, adj)
In [17]:
print(graph)
graph.view(image_view=False, render_axes=False, axes_x_limits=[-1, 41], axes_y_limits=[-10, 30], line_width=2);
# vertices numbering
for k, p in enumerate(graph.points):
plt.gca().annotate(str(k), xy=(p[0], p[1]),
horizontalalignment='center',
verticalalignment='bottom',
fontsize=14)
The minimum spanning tree of the above graph is:
In [18]:
mst = graph.minimum_spanning_tree(root_vertex=0)
print(mst)
mst.view(image_view=False, render_axes=False, axes_x_limits=[-1, 41], axes_y_limits=[-10, 30], line_width=2);
# vertices numbering
for k, p in enumerate(mst.points):
plt.gca().annotate(str(k), xy=(p[0], p[1]),
horizontalalignment='center',
verticalalignment='top',
fontsize=14)
In [19]:
# Create mask that removes vertices 2, 6 and 8
mask = np.array([True, True, False, True, True, True, False, True, False])
# Mask the graph
masked_graph = graph.from_mask(mask)
# Visualize
print(masked_graph)
masked_graph.view(image_view=False, render_axes=False, axes_x_limits=[-1, 41], axes_y_limits=[-10, 30], line_width=2);
# vertices numbering
for k, p in enumerate(masked_graph.points):
plt.gca().annotate(str(k), xy=(p[0], p[1]),
horizontalalignment='center',
verticalalignment='bottom',
fontsize=14)
PointGraphs are useful when defining landmarks with different semantic groups. For example, let us load and visualize the lenna
image that has landmarks in the LJSON
format.
In [20]:
import menpo.io as mio
im = mio.import_builtin_asset.lenna_png()
im.view_landmarks(render_legend=True);
The landmarks are actually a PointUndirectedGraph
that can be visualized as normal.
In [21]:
print(im.landmarks['LJSON'].lms)
im.landmarks['LJSON'].lms.view(render_axes=False);
And each one of the subgroups is a PointUndirectedGraph
:
In [22]:
print(im.landmarks['LJSON']['mouth'])
im.landmarks['LJSON']['mouth'].view(render_axes=False);
All PointGraphs can be visualized using widgets. Note that Jupyter widget functionality is provided by the menpowidgets package and must be installed prior to using widgets (conda install -c menpo menpowidgets
).
In [23]:
tree.view_widget()
This basically calls visualize_pointclouds()
widget, which can accept a list of different PointGraphs. For example:
In [24]:
from menpowidgets import visualize_pointclouds
visualize_pointclouds([undirected_graph, directed_graph, tree, mst])