Unit 5: Visualization Using Matplotlib¶
Overview and Learning Objectives
In areas such as data sciences, multimedia engineering, and signal processing, one typically transforms, analyzes, and classifies large amounts of complex data. Following the famous saying "A Picture Is Worth a Thousand Words," visualization techniques can be crucial for gaining a deeper understanding of the data to start with, the processing pipeline, the intermediate feature representations, and the final results. Therefore, we consider learning how to generate and use suitable visualizations central to science education. Python provides powerful functionalities for generating visualizations and plotting figures. In this unit, we give a short introduction to data visualization using the Python library Matplotlib. Rather than being comprehensive, we discuss in this notebook concrete examples on how to plot the graph of one-dimensional functions (e.g., the waveform of an audio signal), how to visualize data on a regular two-dimensional raster (e.g., an image), and how to plot surfaces in a three-dimensional space. In particular, we show how one can adapt the plots' sizes, colors, axes, and labels. In Exercise 1, you learn alternatives for plotting a one-dimensional function. With Exercise 2, we prepare you for more advanced topics such as roots on unity (Unit 7) and sampling (Unit 8). In Exercise 3, you will learn how to switch to a logarithmic axis so that numerical data distributed over a wide range is displayed in a compact format. The main learning objective of Exercise 4 is to better understand the concept of grids and their generation using the NumPy function np.meshgrid
. Once having this data structure, one can easily define a function over the grid and plot its graph as a surface. Finally, we hope you will have some fun with Exercise 5, where you will apply different Python functions to load, manipulate, and save an image. In the subsequent units of the PCP notebooks, we will make massive use of visualizations to explain concepts in signal processing applied to concrete examples. For further example, we refer to the following websites:
- Matplotlib Gallery of Examples (along with Python code) that illustrate the many things one can do with
matplotlib
. - FMP Notebooks that contain an a unit on Python Visualization, a unit on Annotation Visualization, and numerous visualization examples in the context of audio and music processing.
Python Packages for Visualization¶
The library matplotlib
is a widely used Python package for graphics, which allows a user to produce high-quality figures in a variety of formats as well as interactive environments across platforms. The main website contains detailed documentation and links to illustrative code examples. In particular, we recommend having a look at the gallery, which contains numerous examples of the many things one can do with matplotlib
. An alternative to matplotlib
is seaborn
(main website), which is a library mainly targeting on visualizing statistical data.
In this notebook, we focus on matplotlib.pyplot
, which provides a plotting framework similar to MATLAB. In the following, we import matplotlib.pyplot
using the abbreviation plt
(following general conventions) and use the Python command dir(plt)
that lists all names contained in the module.
import matplotlib.pyplot as plt
list_plt_names = dir(plt)
print(list_plt_names)
Basic Plotting Function (1D)¶
We start with some basic examples that show how the library matplotlib
works. First, we import all Python packages required in this notebook. The command %matplotlib inline
ensures that the backend of matplotlib
is set to inline
such that figures are displayed within the Jupyter notebook.
import os
import numpy as np
from matplotlib import pyplot as plt
%matplotlib inline
We start with the plotting function plt.plot
. Given two real-valued vectors x
and y
of the same length, plt.plot(x,y)
plots y
against x
as lines and/or markers. In the following example, we generate a (discrete) time axis t
ranging from $-\pi$ and $\pi$. Then, we plot the graph of the sine and cosine function over this time axis in the same figure using the default setting of the plot
-function.
- The command
plt.figure(figsize=(6, 2))
is used to create a new figure of a specific size determined byfigsize
. - The command
tight_layout()
is used to automatically adjust the final layout of the generated figure.
t = np.linspace(-np.pi, np.pi, 256, endpoint=True)
f_cos = np.cos(t)
f_sin = np.sin(t)
plt.figure(figsize=(6, 2))
plt.plot(t, f_cos)
plt.plot(t, f_sin)
plt.tight_layout()
Next, we show how one may deviate from the plotting default settings. In particular, we modify the figure by changing colors, adding a legend and labels, and modifying the axes. Furthermore, we export the figure as PNG
file.
plt.figure(figsize=(6, 2.5))
plt.title('Title of Figure', fontsize=12)
t = np.linspace(-np.pi, np.pi, 256, endpoint=True)
f_cos = np.cos(t)
f_sin = np.sin(t)
plt.plot(t, f_cos, color='blue', linewidth=2.0, linestyle='-', label='cos')
plt.plot(t, f_sin, color='red', linewidth=2.0, linestyle='-', label='sin')
plt.legend(loc='upper left', fontsize=12)
plt.xlim(-np.pi, np.pi)
plt.xticks([-np.pi, -np.pi/2, 0, np.pi/2, np.pi],
[r'$-\pi$', r'$-\pi/2$', r'$0$', r'$+\pi/2$', r'$+\pi$'], fontsize=10)
plt.ylim(f_cos.min() * 1.1, f_cos.max() * 1.1)
plt.yticks([-1, 0, 1], fontsize=10)
plt.xlabel('Angle (radians)')
plt.ylabel('Amplitude')
plt.tight_layout()
# This requires that the output folder exists
output_path_filename = os.path.join('.', 'output', 'Figure_CosSin.png')
plt.savefig(output_path_filename)
plt.figure(figsize=(8, 3))
plt.subplot(1, 3, 1)
plt.yticks(())
plt.title('Using subplots', fontsize=12)
plt.subplot(1, 3, 3)
plt.yticks(())
plt.title('Using subplots', fontsize=12)
plt.axes([0.3, 0.3, 0.5, 0.5]) # [left, bottom, width, height]
plt.title('Using axes', fontsize=12)
plt.xlim(0, 5)
plt.ylim(-2, 2)
plt.grid()
ax = plt.gca()
plt.setp(ax.get_xticklabels(), rotation='vertical', fontsize=10)
plt.text(1, -1, 'Text', ha='center', va='center', size=30, color='red')
output_path_filename = os.path.join('.', 'output', 'Figure_Subplots.png')
plt.savefig(output_path_filename)
Plotting Figures (2D)¶
Next, we consider the function plt.imshow
, which can be used to visualize data on a 2D regular raster. In particular, the function can be used to visualize a real-valued matrix (two-dimensional array), where the matrix values are shown in a color-coded form. Some basic functionalities and parameters are illustrated by the next example.
A = np.arange(5*5).reshape(5, 5)
plt.figure(figsize=(10, 4))
plt.subplot(2, 3, 1)
plt.imshow(A)
plt.title('Default')
plt.subplot(2, 3, 2)
plt.imshow(A)
plt.colorbar()
plt.title('Add colorbar')
plt.subplot(2, 3, 3)
plt.imshow(A, aspect='auto')
plt.colorbar()
plt.title('Modify aspect ratio')
plt.subplot(2, 3, 4)
plt.imshow(A, aspect='auto', cmap='gray')
plt.colorbar()
plt.title('Change colormap')
plt.subplot(2, 3, 5)
plt.imshow(A, aspect='auto', cmap='gray', origin='lower')
plt.colorbar()
plt.title('Change origin')
plt.subplot(2, 3, 6)
plt.imshow(A, aspect='auto', cmap='gray', origin='lower')
plt.colorbar(orientation='horizontal')
plt.title('Rotate colorbar')
plt.tight_layout()
As can be seen in the previous figure, the image is positioned such that the pixel centers fall on zero-based (row, column) indices. To have tick labels with some semantic meaning (e.g., a linear axis given in seconds or centimeters rather than in pixels or samples), one can use the extent
keyword argument to specify the data coordinates [left, right, lower, upper]
, where left
and right
correspond to the range of the horizontal axis, and lower
and upper
to the range of the vertical axis. Furthermore, one can use the functions plt.xlim
and plt.ylim
to zoom into a specific part of the figure. Note that the limits used in plt.xlim
and plt.ylim
refer to the coordinates specified by the extent
coordinates. This is illustrated by the following code example.
X, Y = np.meshgrid(np.arange(-2, 3.1, 0.1), np.arange(-1, 2.1, 0.1))
A = np.sin(X*3) * np.cos(Y*3) * np.exp(-Y**2)
A[10, 15] = 1.2
plt.figure(figsize=(8, 4))
plt.subplot(2, 2, 1)
plt.imshow(A, aspect='auto', cmap='gray', origin='lower')
plt.colorbar()
plt.title('Original')
plt.subplot(2, 2, 2)
plt.imshow(A, aspect='auto', cmap='gray', origin='lower')
plt.colorbar()
plt.xlim((10, 30))
plt.ylim((6, 14))
plt.title('Original (zoom)')
plt.subplot(2, 2, 3)
plt.imshow(A, aspect='auto', cmap='gray', origin='lower', extent=[-2, 3, -1, 2])
plt.colorbar()
plt.title('Apply extent')
plt.subplot(2, 2, 4)
plt.imshow(A, aspect='auto', cmap='gray', origin='lower', extent=[-2, 3, -1, 2])
plt.colorbar()
plt.xlim((-1, 0))
plt.ylim((-0.3, 0.3))
plt.title('Apply extent (zoom)')
plt.tight_layout()
Plotting Surfaces (3D)¶
The library matplotlib
also offers various possibilities for creating 3D plots. Technically one needs to create a figure and add a new axis of type Axes3D
to it. For details, we refer to the mplot3d tutorial and consider only one example. Based on the previously visualized matrix, the following plot shows a 3D representation of the same data.
from mpl_toolkits.mplot3d import Axes3D
X, Y = np.meshgrid(np.arange(-2, 3.1, 0.1), np.arange(-1, 2.1, 0.1))
A = np.sin(X*3) * np.cos(Y*3) * np.exp(-Y**2)
A[10, 15] = 1.2
fig = plt.figure(figsize=(10, 6))
ax = fig.add_subplot(projection = '3d')
ax.plot_surface(X, Y, A, cmap='coolwarm');
Exercises and Results¶
import libpcp.vis
show_result = True
- Create a vector
t
that represents a discrete time axis (given in seconds) covering the interval $[0, 1]$ sampled with a sampling rate of $F_\mathrm{s}=100~\mathrm{Hz}$. (In other wordst[0]=0, t[1]=0.01, ...,t[100]=1
.) - Plot the graph of the sinusoid $x(t) = \mathrm{sin}(2\pi \omega t)$ using frequency $\omega = 5~\mathrm{Hz}$ over the time points specified by
t
. - Label the horizontal axis (
Time (seconds)
) and vertical axis (Amplitude
) and add a title. Display grid lines. - Modify the appearance of the sinusoid by varying the arguments
color
,linewidth
,linestyle
, andmarker
in theplt.plot
function. You can find a table of all possible arguments here. - Set the limits of the horizontal axis such that only one period of the sinusoid is shown.
- Use a figure with three subplots and plot one period of the sinusoid using the Python functions
plt.stem
,plt.step
, andplt.bar
. Explain the differences.
#<solution>
# Your Solution
#</solution>
libpcp.vis.exercise_vis1D(show_result=show_result)
- Create a vector
t
that represents a discrete time axis (given in seconds) covering the interval $[0, 1]$ sampled with a sampling rate of $F_\mathrm{s}$ given in $\mathrm{Hz}$. (See Exercise 1: Plotting 1D Function.) - Specify two functions $f_1(t)$ and $f_2(t)$ such that one obtains a unit circle when plotting the values $f_1(t)$ against the values of $f_2(t)$ using
np.plot
. - Write a function
plot_circle
that plots a circle in this way with an argument that specifies the sampling rate $F_\mathrm{s}$. - Apply the function using different sampling rates $F_\mathrm{s}\in\{4,8,16,32\}$
#<solution>
# Your Solution
#</solution>
libpcp.vis.exercise_circle(show_result=show_result)
Consider functions $f(x) = e^x$, $g(x) = x$, and $h(x)=1.1 + \mathrm{sin}(10\cdot x)$ for $x\in\mathbb{R}$. For a given sampling rate
Fs
, let x = np.arange(1/Fs, 10+1/Fs, 1/Fs)
be the discretized axis covering the interval [1/Fs, 10]
. Furthermore, let f
, g
, and h
be the vectors obtained by evaluating $f$, $g$, and $h$ on x
, respectively. Using the sampling rate Fs=100
, plot the graphs for all three functions in the same figure (using different colors). Switch on the grid-line option (using plt.grid()
). Besides using plt.plot
, also use the functions plt.semilogy
, plt.semilogx
, plt.loglog
. What do you observe?
#<solution>
# Your Solution
#</solution>
libpcp.vis.exercise_logaxis(show_result=show_result)
The $\mathrm{sinc}$-function (
np.sinc
) is defined by $\mathrm{sinc}(t) := \frac{\mathrm{sin}(\pi t)}{\pi t}$ for $t\in\mathbb{R}$ and $\mathrm{sinc}(0):=1$. In the following, we want to visualize the surface of the function $f(x,y) = \mathrm{sinc}(3x) + \mathrm{sinc}(3y)$ for variables $x, y \in [-1,\, 1]$. To this end, we represent the area $[-1,\, 1]$ by an equidistant 2D-grid with a resolution specified by parameters $\Delta x = \Delta y = 0.01$. The grid points are represented by two equal-sized matrices $\mathbf{X}$ and $\mathbf{Y}$, where $\mathbf{X}$ contains all $x$-coordinates and $\mathbf{Y}$ all $y$-coordinates:
\begin{align*} \mathbf{X} & = \begin{bmatrix} -1 & -0.99 & -0.98 & \cdots & & 0.98 & 0.99 & 1\cr -1 & -0.99 & -0.98 & \cdots & & 0.98 & 0.99 & 1\cr \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots\cr -1 & -0.99 & -0.98 & \cdots & & 0.98 & 0.99 & 1\cr -1 & -0.99 & -0.98 & \cdots & & 0.98 & 0.99 & 1\cr \end{bmatrix} \\[1mm] \mathbf{Y} & = \begin{bmatrix} -1 & -1 & -1 & \cdots & & -1 & -1 & -1\cr -0.99 & -0.99 & -0.99 & \cdots & & -0.99 & -0.99 & -0.99\cr \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots\cr 0.99 & 0.99 & 0.99 & \cdots & & 0.99 & 0.99 & 0.99\cr 1 & 1 & 1 & \cdots & & 1 & 1 & 1\cr \end{bmatrix} \end{align*}
- Create the matrices
X
andY
that have data points with a spacing of $\Delta x = \Delta y = 0.01$ using the functionnp.meshgrid
. - Evaluate the function $f(x,y)$ on the grid defined by
X
andY
. - Plot the 3D-surface using the function
plot_surface
. - Experiment with different colormaps of the plot. All available colormaps can be found here.
- Use the function
plot_wireframe
to visualize the function. What is the difference toplot_surface
?
#<solution>
# Your Solution
#</solution>
libpcp.vis.exercise_plot3d(show_result=show_result)
In this exercise we study how one may represent and manipulate digital images using
matplotlib
.
- First, import
matplotlib.image
asmpimg
. - Using
mpimg.imread
, load the photo'./data/PCP_fig_erlangen.png'
, which shows the castle of the city of Erlangen (home of FAU). This results in an NumPy array of size(274, 400, 3)
. Check this! While the first two dimensions specify the number of pixels in horizontal and vertical direction, the third dimension are the color channels of the RGB (red, green, blue) color space. - Display the image using
plt.imshow
. - Rotate the image by $180^\circ$ using the function
np.rot90
. - Convert the color image into a black–with figure by summing over the channels. Furthermore, set
cmap='gray'
inplt.imshow
. - Try to extract edges, e.g., by applying
np.diff
. - Be creative to further modify the image, e.g., by manipulating the channels and the changing colormap specified by
cmap
. - Save your modified images using
mpimg.imsave('./output/PCP_fig_erlangen_mod.png', img_mod)
Picture by Selby. Licensed under CC BY-SA 3.0.
#<solution>
# Your Solution
#</solution>
libpcp.vis.exercise_erlangen(show_result=show_result)