{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", " \n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Unit 5: Visualization Using Matplotlib\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " \n", "
\n", "

## Overview and Learning Objectives

\n", " \n", "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:\n", " \n", "
\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Python Packages for Visualization\n", "\n", "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](https://matplotlib.org/) contains detailed documentation and links to illustrative code examples. In particular, we recommend having a look at the [gallery](https://matplotlib.org/stable/gallery/index.html), which contains numerous examples of the many things one can do with matplotlib. An alternative to matplotlib is seaborn ([main website](https://seaborn.pydata.org/)), which is a library mainly targeting on visualizing statistical data. \n", "\n", "In this notebook, we focus on [matplotlib.pyplot](https://matplotlib.org/api/pyplot_api.html), 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." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "list_plt_names = dir(plt)\n", "print(list_plt_names)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " \n", "## Basic Plotting Function (1D)\n", "\n", "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. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os\n", "import numpy as np\n", "from matplotlib import pyplot as plt\n", "%matplotlib inline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We start with the plotting function [plt.plot](https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.plot.html). 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. \n", "\n", "* The command [plt.figure(figsize=(6, 2))](https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.figure.html) is used to create a new figure of a specific size determined by figsize.\n", "* The command [tight_layout()](https://matplotlib.org/users/tight_layout_guide.html) is used to automatically adjust the final layout of the generated figure. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t = np.linspace(-np.pi, np.pi, 256, endpoint=True)\n", "f_cos = np.cos(t)\n", "f_sin = np.sin(t)\n", "\n", "plt.figure(figsize=(6, 2))\n", "plt.plot(t, f_cos)\n", "plt.plot(t, f_sin)\n", "plt.tight_layout()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plt.figure(figsize=(6, 2.5))\n", "plt.title('Title of Figure', fontsize=12)\n", "t = np.linspace(-np.pi, np.pi, 256, endpoint=True)\n", "f_cos = np.cos(t)\n", "f_sin = np.sin(t)\n", "plt.plot(t, f_cos, color='blue', linewidth=2.0, linestyle='-', label='cos')\n", "plt.plot(t, f_sin, color='red', linewidth=2.0, linestyle='-', label='sin')\n", "plt.legend(loc='upper left', fontsize=12)\n", "plt.xlim(-np.pi, np.pi)\n", "plt.xticks([-np.pi, -np.pi/2, 0, np.pi/2, np.pi],\n", " [r'$-\\pi$', r'$-\\pi/2$', r'$0$', r'$+\\pi/2$', r'$+\\pi$'], fontsize=10)\n", "plt.ylim(f_cos.min() * 1.1, f_cos.max() * 1.1)\n", "plt.yticks([-1, 0, 1], fontsize=10)\n", "plt.xlabel('Angle (radians)')\n", "plt.ylabel('Amplitude')\n", "plt.tight_layout()\n", "\n", "# This requires that the output folder exists\n", "output_path_filename = os.path.join('.', 'output', 'Figure_CosSin.png')\n", "plt.savefig(output_path_filename)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The next example demonstrates how to create subplots using [subplot ](https://matplotlib.org/api/_as_gen/matplotlib.pyplot.subplot.html) and [axes ](https://matplotlib.org/api/_as_gen/matplotlib.pyplot.axes.html). " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plt.figure(figsize=(8, 3))\n", "\n", "plt.subplot(1, 3, 1)\n", "plt.yticks(())\n", "plt.title('Using subplots', fontsize=12)\n", "\n", "plt.subplot(1, 3, 3)\n", "plt.yticks(())\n", "plt.title('Using subplots', fontsize=12)\n", "\n", "plt.axes([0.3, 0.3, 0.5, 0.5]) # [left, bottom, width, height]\n", "plt.title('Using axes', fontsize=12)\n", "plt.xlim(0, 5)\n", "plt.ylim(-2, 2)\n", "plt.grid()\n", "ax = plt.gca()\n", "plt.setp(ax.get_xticklabels(), rotation='vertical', fontsize=10)\n", "plt.text(1, -1, 'Text', ha='center', va='center', size=30, color='red')\n", "\n", "output_path_filename = os.path.join('.', 'output', 'Figure_Subplots.png')\n", "plt.savefig(output_path_filename)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " \n", "## Plotting Figures (2D)\n", "\n", "Next, we consider the function [plt.imshow](https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.imshow.html), 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. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "A = np.arange(5*5).reshape(5, 5)\n", "\n", "plt.figure(figsize=(10, 4))\n", "plt.subplot(2, 3, 1)\n", "plt.imshow(A)\n", "plt.title('Default')\n", "\n", "plt.subplot(2, 3, 2)\n", "plt.imshow(A)\n", "plt.colorbar()\n", "plt.title('Add colorbar')\n", "\n", "plt.subplot(2, 3, 3)\n", "plt.imshow(A, aspect='auto')\n", "plt.colorbar()\n", "plt.title('Modify aspect ratio')\n", "\n", "plt.subplot(2, 3, 4)\n", "plt.imshow(A, aspect='auto', cmap='gray')\n", "plt.colorbar()\n", "plt.title('Change colormap')\n", "\n", "plt.subplot(2, 3, 5)\n", "plt.imshow(A, aspect='auto', cmap='gray', origin='lower')\n", "plt.colorbar()\n", "plt.title('Change origin')\n", "\n", "plt.subplot(2, 3, 6)\n", "plt.imshow(A, aspect='auto', cmap='gray', origin='lower')\n", "plt.colorbar(orientation='horizontal')\n", "plt.title('Rotate colorbar')\n", "plt.tight_layout()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X, Y = np.meshgrid(np.arange(-2, 3.1, 0.1), np.arange(-1, 2.1, 0.1))\n", "A = np.sin(X*3) * np.cos(Y*3) * np.exp(-Y**2)\n", "A[10, 15] = 1.2\n", "\n", "plt.figure(figsize=(8, 4))\n", "plt.subplot(2, 2, 1)\n", "plt.imshow(A, aspect='auto', cmap='gray', origin='lower')\n", "plt.colorbar()\n", "plt.title('Original')\n", "\n", "plt.subplot(2, 2, 2)\n", "plt.imshow(A, aspect='auto', cmap='gray', origin='lower')\n", "plt.colorbar()\n", "plt.xlim((10, 30))\n", "plt.ylim((6, 14))\n", "plt.title('Original (zoom)')\n", "\n", "plt.subplot(2, 2, 3)\n", "plt.imshow(A, aspect='auto', cmap='gray', origin='lower', extent=[-2, 3, -1, 2])\n", "plt.colorbar()\n", "plt.title('Apply extent')\n", "\n", "plt.subplot(2, 2, 4)\n", "plt.imshow(A, aspect='auto', cmap='gray', origin='lower', extent=[-2, 3, -1, 2])\n", "plt.colorbar()\n", "plt.xlim((-1, 0))\n", "plt.ylim((-0.3, 0.3))\n", "plt.title('Apply extent (zoom)')\n", "\n", "plt.tight_layout()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " \n", "## Plotting Surfaces (3D)\n", "\n", "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](https://matplotlib.org/mpl_toolkits/mplot3d/tutorial.html) and consider only one example. Based on the previously visualized matrix, the following plot shows a 3D representation of the same data." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from mpl_toolkits.mplot3d import Axes3D\n", "\n", "X, Y = np.meshgrid(np.arange(-2, 3.1, 0.1), np.arange(-1, 2.1, 0.1))\n", "A = np.sin(X*3) * np.cos(Y*3) * np.exp(-Y**2)\n", "A[10, 15] = 1.2\n", "\n", "fig = plt.figure(figsize=(10, 6))\n", "ax = fig.add_subplot(projection = '3d')\n", "ax.plot_surface(X, Y, A, cmap='coolwarm');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercises and Results" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import libpcp.vis\n", "show_result = True" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " \n", "
\n", "Exercise 1: Plotting 1D Function
\n", "
\n", "
• 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 words t=0, t=0.01, ...,t=1 .)
• \n", "
• 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.
• \n", "
• Label the horizontal axis (Time (seconds)) and vertical axis (Amplitude) and add a title. Display grid lines.
• \n", "
• Modify the appearance of the sinusoid by varying the arguments color, linewidth, linestyle, and marker in the plt.plot function. You can find a table of all possible arguments here.\n", "
• Set the limits of the horizontal axis such that only one period of the sinusoid is shown.
• \n", "
• Use a figure with three subplots and plot one period of the sinusoid using the Python functions plt.stem, plt.step, and plt.bar. Explain the differences.\n", "
\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#\n", "# Your Solution\n", "#" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "libpcp.vis.exercise_vis1D(show_result=show_result)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " \n", "
\n", "Exercise 2: Plotting Circle
\n", "
\n", "
• 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.)
• \n", "
• 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.
• \n", "
• Write a function plot_circle that plots a circle in this way with an argument that specifies the sampling rate $F_\\mathrm{s}$.
• \n", "
• Apply the function using different sampling rates $F_\\mathrm{s}\\in\\{4,8,16,32\\}$
\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#\n", "# Your Solution\n", "#" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "libpcp.vis.exercise_circle(show_result=show_result)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " \n", "
\n", "Exercise 3: Plotting with Logarithmic Axes
\n", "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?\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#\n", "# Your Solution\n", "#" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "libpcp.vis.exercise_logaxis(show_result=show_result)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " \n", "
\n", "Exercise 4: Plotting 3D Surface (sinc)
\n", "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:\n", " \n", "\\begin{align*}\n", "\\mathbf{X} & = \n", "\\begin{bmatrix}\n", "\t-1 & -0.99 & -0.98 & \\cdots & & 0.98 & 0.99 & 1\\cr\n", "\t-1 & -0.99 & -0.98 & \\cdots & & 0.98 & 0.99 & 1\\cr\n", "\t\\vdots & \\vdots & \\vdots & \\vdots & \\vdots & \\vdots & \\vdots &\n", "\t\t\t\t\t\t\\vdots\\cr\n", "\t-1 & -0.99 & -0.98 & \\cdots & & 0.98 & 0.99 & 1\\cr\n", "\t-1 & -0.99 & -0.98 & \\cdots & & 0.98 & 0.99 & 1\\cr\n", "\\end{bmatrix}\n", "\\\\[1mm]\n", "\\mathbf{Y} & =\n", "\\begin{bmatrix}\n", "\t-1 & -1 & -1 & \\cdots & & -1 & -1 & -1\\cr\n", "\t-0.99 & -0.99 & -0.99 & \\cdots & & -0.99 & -0.99 & -0.99\\cr\n", "\t\\vdots & \\vdots & \\vdots & \\vdots & \\vdots & \\vdots & \\vdots &\n", "\t\t\t\t\t\t\\vdots\\cr\n", "\t0.99 & 0.99 & 0.99 & \\cdots & & 0.99 & 0.99 & 0.99\\cr\n", "\t1 & 1 & 1 & \\cdots & & 1 & 1 & 1\\cr\n", "\\end{bmatrix}\n", "\\end{align*}\n", "\n", "
\n", "
• Create the matrices X and Y that have data points with a spacing of $\\Delta x = \\Delta y = 0.01$ using the function np.meshgrid.
• \n", "
• Evaluate the function $f(x,y)$ on the grid defined by X and Y.
• \n", "
• Plot the 3D-surface using the function plot_surface.
• \n", "\n", "
• Experiment with different colormaps of the plot. All available colormaps can be found here.
• \n", "
• Use the function plot_wireframe to visualize the function. What is the difference to plot_surface?
• \n", "
\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#\n", "# Your Solution\n", "#" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "libpcp.vis.exercise_plot3d(show_result=show_result)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " \n", "
\n", "Exercise 5: Photo Manipulation (Erlangen)
\n", "In this exercise we study how one may represent and manipulate digital images using matplotlib. \n", "
\n", "
• First, import matplotlib.image as mpimg.
• \n", "
• 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.
• \n", "
• Display the image using plt.imshow.
• \n", "
• Rotate the image by $180^\\circ$ using the function np.rot90.
• \n", "
• Convert the color image into a black–with figure by summing over the channels. Furthermore, set cmap='gray' in plt.imshow.
• \n", "
• Try to extract edges, e.g., by applying np.diff.
• \n", "
• Be creative to further modify the image, e.g., by manipulating the channels and the changing colormap specified by cmap.
• \n", "
• Save your modified images using mpimg.imsave('./output/PCP_fig_erlangen_mod.png', img_mod)
• \n", "
\n", "
\n", "Picture by Selby. Licensed under CC BY-SA 3.0.\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#\n", "# Your Solution\n", "#" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "libpcp.vis.exercise_erlangen(show_result=show_result)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", " \n", "
" ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.17" } }, "nbformat": 4, "nbformat_minor": 1 }