%matplotlib inline
from scipy import linalg
import numpy as np
import matplotlib.pyplot as plt
Disclaimer: this course is adapted from the notebooks by
Introduction
SciPy is a scientific library that builds upon NumPy. Among others, SciPy deals with:
- Integration (scipy.integrate)
- Optimization (scipy.optimize)
- Interpolation (scipy.interpolate)
- Fourier Transform (scipy.fftpack)
- Signal Processing (scipy.signal)
- Linear Algebra (scipy.linalg)
- Sparse matrices (scipy.sparse)
- Statistics (scipy.stats)
- Image processing (scipy.ndimage)
- IO (input/output) (scipy.io)
References:
- Matplotlib Animations / JavaScript Widgets by Louis Tiao
Linear algebra
scipy
for linear algebra : use linalg
. It includes functions for solving linear systems, eigenvalues decomposition, SVD, Gaussian elimination (LU, Cholesky), etc.
References:
Solving linear systems:
Find x such that: A x = b for specified matrix A and vector b.
= np.array([[1, 0, 3], [4, 5, 12], [7, 8, 9]], dtype=float)
A = np.array([[1, 2, 3]], dtype=np.float64).T
b
print(A, b)
= linalg.solve(A, b)
x print(x, x.shape, b.shape)
[[ 1. 0. 3.]
[ 4. 5. 12.]
[ 7. 8. 9.]] [[1.]
[2.]
[3.]]
[[ 0.8 ]
[-0.4 ]
[ 0.06666667]] (3, 1) (3, 1)
Check the result at a given precision (different from ==)
@ x, b, atol=1e-14, rtol=1e-15) np.allclose(A
True
Remark: NEVER (or you should really know why) invert a matrix. ALWAYS solve linear systems instead!
Eigenvalues/ Eigenvectors
A v_n = \lambda_n v_n with v_n the n-th eigen vector and \lambda_n the n-th eigen value. The associated python
functions are eigvals
and eig
:
= np.random.randn(3, 3)
A = A + A.T
A = linalg.eig(A)
evals, evecs print(evals, "\n ------\n", evecs)
@ np.diag(evals) @ evecs.T) np.allclose(A, evecs
[-6.17397487+0.j 0.69404863+0.j -0.0671204 +0.j]
------
[[-0.06085611 -0.97923121 0.19339793]
[-0.92915285 0.1263613 0.3474303 ]
[ 0.36465261 0.15855298 0.91754533]]
True
Symmetric matrices
If A is symmetric you should use eigvalsh
(H for Hermitian) instead: This is more robust and leverages the structures (you know they are real!)
Matrix operations
linalg.trace(A)
# tracelinalg.det(A)
# determinantlinalg.inv(A)
# Inverse, consider NEVER using it though :)
Norms
print(linalg.norm(A, ord="fro")) # fro for Frobenius
print((np.sum(A ** 2)) ** 0.5)
print(linalg.norm(A, ord=2))
print((linalg.eigvalsh(A.T @ A) ** 0.5))
print(linalg.norm(A, ord=np.inf))
6.213225759077945
6.213225759077946
6.173974868689911
[0.0671204 0.69404863 6.17397487]
7.851023676894788
= np.random.randn(3, 3)
A print(linalg.norm(A, ord=np.inf))
4.585394537975905
Random generation, distributions, etc.
References:
- Good practices with numpy random number generators by Albert Thomas
- Numpy documentation on RandomState
- Random Widgets, by Joseph Salmon: Visualization of various popular distributions.
= 12345
seed = np.random.default_rng(seed) # can be called without a seed
rng rng.random()
0.22733602246716966
Optimization
Goal: find functions minima or maxima
References:
- Scipy Lectures on mathematical optimization.
from scipy import optimize
Finding (local!) minima
def f(x):
return 4 * x ** 3 + (x - 2) ** 2 + x ** 4
def mf(x):
return -(4 * x ** 3 + (x - 2) ** 2 + x ** 4)
= np.linspace(-5, 3, 100)
xs
plt.figure()
plt.plot(xs, f(xs)) plt.show()
Default solver for minimization/maximization: fmin_bfgs
(see Wikipedia on BFGS)
= optimize.fmin_bfgs(f, x0=-4)
x_min = optimize.fmin_bfgs(mf, x0=-2)
x_max = optimize.fmin_bfgs(f, x0=2)
x_min2
plt.figure()
plt.plot(xs, f(xs))"o", markersize=10, color="orange")
plt.plot(x_min, f(x_min), "o", markersize=10, color="red")
plt.plot(x_min2, f(x_min2), "|", markersize=20)
plt.plot(x_max, f(x_max), plt.show()
Optimization terminated successfully.
Current function value: -3.506641
Iterations: 7
Function evaluations: 16
Gradient evaluations: 8
Optimization terminated successfully.
Current function value: -6.201654
Iterations: 5
Function evaluations: 12
Gradient evaluations: 6
Optimization terminated successfully.
Current function value: 2.804988
Iterations: 7
Function evaluations: 16
Gradient evaluations: 8
Find the zeros of a function
Find x such that f(x) = 0, with fsolve
.
= 3.0
omega_c
def f(omega):
return np.tan(2 * np.pi * omega) - omega_c / omega
= np.linspace(1e-8, 3.2, 1000)
x = f(x)
y
# Remove vertical lines when the function flips signs
= np.where(np.abs(y) > 50)
mask = y[mask] = np.nan
x[mask]
plt.plot(x, y)0, 3.3], [0, 0], "k")
plt.plot([-5, 5)
plt.ylim(
0.72)
optimize.fsolve(f, 1.1)
optimize.fsolve(f, 0.001, 3, 20))
optimize.fsolve(f, np.linspace(round(optimize.fsolve(f, np.linspace(0.2, 3, 20)), 3))
np.unique(np.
= (
my_zeros 0.2, 3, 20)) * 1000).astype(int)) / 1000.0
np.unique((optimize.fsolve(f, np.linspace(
)
plt.figure()="$f$")
plt.plot(x, y, label0, 3.3], [0, 0], "k")
plt.plot(["o", label="$x : f(x)=0$")
plt.plot(my_zeros, np.zeros(my_zeros.shape),
plt.legend() plt.show()
Parameters estimation
from scipy.optimize import curve_fit
def f(x, a, b, c):
"""f(x) = a exp(-bx) + c."""
return a * np.exp(-b * x) + c
= np.linspace(0, 4, 50)
x = f(x, 2.5, 1.3, 0.5) # true signal
y = y + 0.2 * np.random.randn(len(x)) # noisy added
yn
plt.figure()".")
plt.plot(x, yn, "k", label="$f$")
plt.plot(x, y,
plt.legend()
plt.show()
= curve_fit(f, x, yn)
(a, b, c), _ print(a,"\n", b,"\n", c)
2.467235117119903
1.1951985840398656
0.45844145995955415
Displaying
plt.figure()".", label="data")
plt.plot(x, yn, "k", label="True $f$")
plt.plot(x, y, "--k", label="Estimated $\hat{f}$")
plt.plot(x, f(x, a, b, c),
plt.legend() plt.show()
<>:4: SyntaxWarning:
invalid escape sequence '\h'
<>:4: SyntaxWarning:
invalid escape sequence '\h'
/tmp/ipykernel_14934/1241871388.py:4: SyntaxWarning:
invalid escape sequence '\h'
For polynomial fitting, one can directly use numpy
functionsL
= np.linspace(0, 1, 10)
x = np.sin(x * np.pi / 2.0)
y = np.polyfit(x, y, deg=10)
line
plt.figure()".", label="data")
plt.plot(x, y, "k--", label="polynomial approximation")
plt.plot(x, np.polyval(line, x),
plt.legend() plt.show()
/tmp/ipykernel_14934/4158621946.py:3: RankWarning:
Polyfit may be poorly conditioned
Interpolation
from scipy.interpolate import interp1d, CubicSpline
def f(x):
return np.sin(x)
= np.arange(0, 10)
n = np.linspace(0, 9, 100)
x
= f(n) + 0.1 * np.random.randn(len(n)) # add noise
y_meas = f(x)
y_real
= interp1d(n, y_meas)
linear_interpolation = linear_interpolation(x)
y_interp1
= CubicSpline(n, y_meas)
cubic_interpolation = cubic_interpolation(x)
y_interp2
plt.figure()"bs", label="noisy data")
plt.plot(n, y_meas, "k", lw=2, label="true function")
plt.plot(x, y_real, "r", label="linear interp")
plt.plot(x, y_interp1, "g", label="CubicSpline interp")
plt.plot(x, y_interp2, =3)
plt.legend(loc plt.show()
Images
RGB decomposition
First, discuss the color decomposition in RGB. The RGB color model is an additive color model[1] in which the red, green and blue primary colors of light are added together in various ways to reproduce a broad array of colors. Hence, each channel (R, G or B) represents a grayscale image, usually coded on [0,1] or [0,255].
from scipy import ndimage, datasets
= datasets.face()
img print(type(img), img.dtype, img.ndim, img.shape)
print(2 ** 8) # uint8-> code sur 256 niveau.
= img.shape
n_1, n_2, n_3 print(n_1, n_2, n_3)
# True image
plt.figure()
plt.imshow(img)"off")
plt.axis( plt.show()
<class 'numpy.ndarray'> uint8 3 (768, 1024, 3)
256
768 1024 3
= plt.subplots(3, 2)
fig, ax 7, 4.5)
fig.set_size_inches(= img.shape
n_1, n_2, n_3
# add subplot titles
0, 0].set_title("Red channel")
ax[0, 0].imshow(img[:, :, 0], cmap=plt.cm.Reds)
ax[0, 1].set_title("Pixel values histogram (red channel)")
ax[0, 1].hist(img[:, :, 0].reshape(n_1 * n_2), np.arange(0, 256))
ax[
1, 0].set_title("Green channel")
ax[1, 0].imshow(img[:, :, 1], cmap=plt.cm.Greens)
ax[1, 1].set_title("Pixel values histogram (green channel)")
ax[1, 1].hist(img[:, :, 1].reshape(n_1 * n_2), np.arange(0, 256))
ax[
2, 0].set_title("Blue channel")
ax[2, 0].imshow(img[:, :, 2], cmap=plt.cm.Blues)
ax[2, 1].set_title("Pixel values histogram (blue channel)")
ax[2, 1].hist(img[:, :, 2].reshape(n_1 * n_2), np.arange(0, 256))
ax[
plt.tight_layout()
print(img.flags) # cannot edit...
= img.copy()
img_compressed =1)
img_compressed.setflags(writeprint(img_compressed.flags) # can edit now
< 70] = 50
img_compressed[img_compressed >= 70) & (img_compressed < 110)] = 100
img_compressed[(img_compressed >= 110) & (img_compressed < 180)] = 150
img_compressed[(img_compressed >= 180)] = 200
img_compressed[(img_compressed
plt.figure()=plt.cm.gray)
plt.imshow(img_compressed, cmap"off")
plt.axis( plt.show()
C_CONTIGUOUS : True
F_CONTIGUOUS : False
OWNDATA : False
WRITEABLE : False
ALIGNED : True
WRITEBACKIFCOPY : False
C_CONTIGUOUS : True
F_CONTIGUOUS : False
OWNDATA : True
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
Convert a color image in grayscale
plt.figure()=2), cmap=plt.cm.gray)
plt.imshow(np.mean(img, axis plt.show()
Changing colors in an image
import pooch
import os
= "https://upload.wikimedia.org/wikipedia/en/thumb/0/05/Flag_of_Brazil.svg/486px-Flag_of_Brazil.svg.png"
url =pooch.retrieve(url, known_hash=None)
name_img
= (255 * plt.imread(name_img)).astype(int)
img = img.copy()
img
plt.figure()
plt.imshow(img)
= plt.subplots(3, 2)
fig, ax 7, 4.5)
fig.set_size_inches(= img.shape
n_1, n_2, n_3
0, 0].imshow(img[:, :, 0], cmap=plt.cm.Reds)
ax[0, 0].set_title("Red channel")
ax[0, 1].hist(img[:, :, 0].reshape(n_1 * n_2), np.arange(0, 256), density=True)
ax[0, 1].set_title("Pixel values histogram (red channel)")
ax[
1, 0].imshow(img[:, :, 1], cmap=plt.cm.Greens)
ax[1, 0].set_title("Green channel")
ax[1, 1].hist(img[:, :, 1].reshape(n_1 * n_2), np.arange(0, 256), density=True)
ax[1, 1].set_title("Pixel values histogram (green channel)")
ax[
2, 0].imshow(img[:, :, 2], cmap=plt.cm.Blues)
ax[2, 0].set_title("Blue channel")
ax[2, 1].hist(img[:, :, 2].reshape(n_1 * n_2), np.arange(0, 256), density=True)
ax[2, 1].set_title("Pixel values histogram (Blue channel)")
ax[ plt.tight_layout()
RGBA
RGBA stands for red (R), green (G), blue (B) and alpha (A). Alpha indicates how the transparency allows an image to be combined over others using alpha compositing, with transparent areas.
Hexadecimal decomposition
Often, colors are represented not with an RGB triplet, say (255, 0, 0), but with a hexadecimal code (say #FF0000). To get a hexadecimal decomposition, transform each 8-bit RGB channel (i.e., 2^8=256) into a 2-digit hexadecimal number (i.e., 16^2=256). This requires letters for representing 10: A, 11: B,\dots, 15: FF (see https://www.rgbtohex.net/ for an online converter)
CMYK decomposition
This is rather a subtractive color model, where the primary colors are cyan (C), magenta (M), yellow (Y), and black (B). For a good source to go from RGB to CMYK (and back), see https://fr.wikipedia.org/wiki/Quadrichromie.
HSL (hue, saturation, lightness)
XXX TODO.
Here, you can find a simple online converter for all popular color models: https://www.myfixguide.com/color-converter/.
Image files formats
Bitmap formats: - PNG (raw, uncompressed format, opens with Gimp) - JPG (compressed format) - GIF (compressed, animated format)
Vector formats: - PDF (recommended for your documents) - SVG (easily modifiable with Inkscape) - EPS - etc.
= np.linspace(0.0, 5.0, num=50)
x1 = np.linspace(0.0, 2.0, num=50)
x2 = np.cos(2 * np.pi * x1) * np.exp(-x1)
y1 = np.cos(2 * np.pi * x2)
y2
= plt.figure(figsize=(5, 4))
fig1
plt.plot(x1, y1)0, 6)
plt.xlim(-1, 1) plt.ylim(
Then, we can save the figure in various formats:
"ma_figure_pas_belle.png", format='png', dpi=90)
fig1.savefig("ma_figure_plus_belle.svg",format='svg', dpi=90) fig1.savefig(
Now that the images have been saved, we can visualize the difference between the PNG and SVG formats.
PNG (zoom on hover):
SVG (zoom on hover):
Some additional effects to produce the above zoom-on-hover effect can be found here: https://www.notuxedo.com/effet-de-zoom-image-css/
References: