#!/usr/bin/env python
# coding: utf-8
# # Animations with Matplotlib
#
# This notebook shows how to use the `FuncAnimation` function from the `matplotlib.animation` module to create animated plots.
# ## Animation Basics
#
# Before we dive into more complex example. it is helpful to understand the basics of matplotlib animation.
# Let's define 3 positions and we will create an animation of a point moving between them.
# In[1]:
points = [(0.1, 0.5), (0.5, 0.5), (0.9, 0.5)]
# Then we use the `FuncAnimation` class which makes an animation by repeatedly calling a function and saving the output as a frame in the animation.
# In[2]:
get_ipython().run_line_magic('matplotlib', 'inline')
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
# We need to define a function that takes the frame number and generates a plot from it. Here we define a function `animation` that takes the frame index and creates a plot from the point at the same index in the `points` list. So at frame 0, it will display the first point, frame 1 the second point and so on.
# In[3]:
fig, ax = plt.subplots(1, 1)
fig.set_size_inches(5,5)
fig.tight_layout(rect=[0, 0.03, 1, 0.95])
def animate(i):
ax.clear()
# Get the point from the points list at index i
point = points[i]
# Plot that point using the x and y coordinates
ax.plot(point[0], point[1], color='green',
label='original', marker='o')
# Set the x and y axis to display a fixed range
ax.set_xlim([0, 1])
ax.set_ylim([0, 1])
ani = FuncAnimation(fig, animate, frames=len(points),
interval=500, repeat=False)
plt.close()
# The animation is now contained in the `ani` object. We can call `save()` and save the result as an animated GIF. We need to specify a `writer` that supports the output format.
# In[4]:
from matplotlib.animation import PillowWriter
# Save the animation as an animated GIF
ani.save("simple_animation.gif", dpi=300,
writer=PillowWriter(fps=1))
#
# We can also use the `to_jshtml()` function to create an HTML representation of the animation and display in a Jupyter notebook.
# In[5]:
from IPython.display import HTML
HTML(ani.to_jshtml())
# ## Animating Plots
#
# Now that you understand the basics, it's time to apply this technique to animate plots. I will show 2 different approaches for animating matplotlib plots. I find it very helpful in visualizing iterative algorithms. We will take examples of two popular geometry simplification algorithms and visualize how they work.
#
# 1. Visvalingam and Whyatt algorithm
# 2. Douglas-Peucker algorithm
# Let's take a line segment defined by a list of (x,y) coordinates as below.
# In[6]:
point_list = [(0,3), (1,0), (2,1), (3, 2), (5, 3),
(7,4), (8,4), (10,3), (11,1), (12,1),
(15,2), (17, 3), (18, 3), (20, 2)]
# We will use the `shapely` library to create a `LineString` object and plot it.
# In[7]:
import matplotlib.pyplot as plt
from shapely.geometry import LineString
original = LineString(point_list)
fig, ax = plt.subplots(1, 1)
fig.set_size_inches(20,5)
ax.plot(*original.xy, color='red',
label='input', marker='o')
ax.set_title('Original Line Segment', fontsize=20)
ax.legend()
plt.savefig('line.png', bbox_inches='tight', dpi=300)
plt.show()
# ### Visualizing Visvalingam and Whyatt Algorithm
#
# This algorithm simplifies a line segment by successively removing vertices till the required number of vertices remain. It calculates the areas of triangle formed by three consecutive points and then removes the vertex with the smallest area. [Learn more](https://en.wikipedia.org/wiki/Visvalingam%E2%80%93Whyatt_algorithm).
# Below is a python implementation of this algorithm. We use the `shapely` library to calculate polygon areas. This implementation uses a recursive approach to achieve the result.
# In[8]:
import math
from shapely.geometry import Polygon
def visvalingam_whyatt_recursive(point_list, required_points):
# Copy the original list since we will be modifying it
points = point_list.copy()
if len(points) == required_points:
return points
# Calculate traingle areas of each point
areas = []
for index, point in enumerate(points):
if index == 0 or index == len(points)-1:
areas.append(math.inf)
else:
p1 = points[index-1]
p2 = point
p3 = points[index+1]
polygon = Polygon([p1, p2, p3])
areas.append(polygon.area)
min_area = min(areas)
remove_index = areas.index(min_area)
# Remove the vertex with smallest area
points.pop(remove_index)
if len(points) == required_points:
return points
else:
# Call the function recursively with updated point list
return visvalingam_whyatt_recursive(
points, required_points)
# Let's test the function and simplify the line to retain only 50% of the original vertices.
# In[9]:
required_points = int(len(point_list)/2)
simplified_list = visvalingam_whyatt_recursive(
point_list, required_points)
print('n={}'.format(required_points))
print('original={}'.format(point_list))
print('result={}'.format(simplified_list))
# In[10]:
fig, ax = plt.subplots(1, 1)
fig.set_size_inches(20,5)
original = LineString(point_list)
simplified = LineString(simplified_list)
ax.plot(*original.xy, color='red',
label='original', marker='o')
ax.plot(*simplified.xy, color='blue',
linestyle='dashed', label='simplified', marker='o')
ax.set_title('Visvalingam and Whyatt Simplification',
fontsize=20)
ax.legend()
plt.savefig('visvalingam_whyatt.png',
bbox_inches='tight', dpi=300)
plt.show()
# ### Animating the Algorithm
#
# This algorithm removes 1 point at a time. We can visualize how the algorithm works by plotting the results of simplifying the original segment with successively smaller number of points. The `animate()` function gets input as the frame number `i` and we use it to calculate how many points to retain in each iteration. The resulting animation shows how the algorithm chooses the vertex with the smallest area first and removes it - continuing to do the same till only the start and end points remain.
# In[11]:
fig, ax = plt.subplots(1, 1)
fig.set_size_inches(20,5)
fig.tight_layout(rect=[0, 0.03, 1, 0.95])
def animate(i):
ax.clear()
simplified_list = visvalingam_whyatt_recursive(
point_list, len(point_list)-i)
original = LineString(point_list)
simplified = LineString(simplified_list)
ax.plot(*original.xy, color='red',
label='original', marker='o')
ax.plot(*simplified.xy, color='blue',
linestyle='dashed', label='simplified', marker='o')
ax.set_title(
'Visvalingam and Whyatt Algorithm - '\
'Eliminated {} Points'.format(i), fontsize=20)
ax.legend()
ani = FuncAnimation(fig, animate, frames=len(point_list)-1,
interval=500, repeat=False)
plt.close()
# In[12]:
from matplotlib.animation import PillowWriter
# Save the animation as an animated GIF
ani.save("visvalingam_whyatt.gif",
dpi=300, writer=PillowWriter(fps=2))
#
# ### Visualizing Douglas-Peucker Algorithm
#
# The Douglas-Peucker algorithm successively removes vertices from a segment using the distance between the original segment and simplified segment. It removes points whose distance is less than the specified threshold **e** [Learn more](https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm).
# Below is a python implementation of this algorithm. We use the `shapely` library to calculate distance between a point and a line segment. This implementation uses a recursive approach to achieve the result. The implementation is adapted from https://github.com/fhirschmann/rdp
# In[13]:
from shapely.geometry import Point, LineString
def douglas_peuker_recursive(point_list, e):
dmax = 0
index = -1
for i in range(1, len(point_list)):
point = Point(point_list[i])
line = LineString([point_list[0], point_list[-1]])
d = point.distance(line)
if d > dmax:
index = i
dmax = d
if dmax > e:
r1 = douglas_peuker_recursive(point_list[:index+1], e)
r2 = douglas_peuker_recursive(point_list[index:], e)
return r1[:-1] + r2
else:
return [point_list[0], point_list[-1]]
e = 1
simplified_list = douglas_peuker_recursive(point_list, e)
print('e={}'.format(e))
print('original={}'.format(point_list))
print('result={}'.format(simplified))
# In[14]:
fig, ax = plt.subplots(1, 1)
fig.set_size_inches(20,5)
fig.tight_layout(rect=[0, 0.03, 1, 0.95])
original = LineString(point_list)
simplified = LineString(simplified_list)
ax.plot(*original.xy, color='red',
label='original', marker='o')
ax.plot(*simplified.xy, color='blue',
linestyle='dashed', label='simplified', marker='o')
ax.set_title('Douglas-Peucker Simplification', fontsize=20)
ax.legend()
plt.savefig('douglas_peucker.png',
bbox_inches='tight', dpi=300)
plt.show()
# ### Animating the Algorithm
#
# The Douglas-Peucker algorithm is harder to visualize because each step is done recursively and you need to capture the result of it and plot it. Here I present another strategy to animate each step. We can capture the intermediate results in a separate list and use that list with the `animate()` function. To capture the output of each invocation of the function, we can use a decorator. Since our function is already defined, we can directly call the `track` function instead of the `@track` syntax for decorating the `douglas_peuker_recursive()` function.
#
# Remember that the recursive function may be called multiple times till it before it meets the condition to actually remove a point. So we save the input and output both to our temporary list.
# In[15]:
steps_list = []
# Decorator function to save the input/output from a function
def track(func):
def inner(*args, **kwargs):
# Save the input to the function
steps_list.append(
{'type': 'input', 'line': args[0]})
output = func(*args, **kwargs)
# Save the output from the function
steps_list.append({
'type': 'output', 'line': output
})
return output
return inner
# Decorate the function
douglas_peuker_recursive = track(douglas_peuker_recursive)
# In[16]:
simplified_list = douglas_peuker_recursive(point_list, e)
# We have now captured each step of the algorithm in the `steps_list`. We can now write an animation function to display each step from it.
# In[17]:
fig, ax = plt.subplots(1, 1)
fig.set_size_inches(20,5)
fig.tight_layout(rect=[0, 0.03, 1, 0.95])
def animate(frame):
ax.clear()
step = steps_list[frame]
original = LineString(point_list)
ax.plot(*original.xy, color='red',
label='original', marker='o')
if step['type'] == 'input':
part = LineString(step['line'])
ax.plot(*part.xy, color='yellow',
linestyle='dashed', label='_part', marker='x')
elif step['type'] == 'output':
simplified = LineString(step['line'])
ax.plot(*simplified.xy, color='blue',
label='simplified', marker='o')
ax.set_title('Douglas-Peucker - Step {}'.format(frame),
fontsize=20)
ani = FuncAnimation(fig, animate, frames=len(steps_list),
interval=500, repeat=False)
plt.close()
# In[18]:
from matplotlib.animation import PillowWriter
# Save the animation as an animated GIF
ani.save("douglas_peucker.gif",
dpi=300, writer=PillowWriter(fps=1))
#
# In[19]:
from IPython.display import HTML
HTML(ani.to_jshtml())
# In[ ]: