Skip to content

Commit c2aa039

Browse files
authored
Additional documentation for cwt (issue #676) (#678)
Closes gh-676
1 parent ec9338f commit c2aa039

File tree

5 files changed

+233
-3
lines changed

5 files changed

+233
-3
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import matplotlib.pyplot as plt
2+
import numpy as np
3+
4+
import pywt
5+
6+
# plot complex morlet wavelets with different center frequencies and bandwidths
7+
wavelets = [f"cmor{x:.1f}-{y:.1f}" for x in [0.5, 1.5, 2.5] for y in [0.5, 1.0, 1.5]]
8+
fig, axs = plt.subplots(3, 3, figsize=(10, 10), sharex=True, sharey=True)
9+
for ax, wavelet in zip(axs.flatten(), wavelets):
10+
[psi, x] = pywt.ContinuousWavelet(wavelet).wavefun(10)
11+
ax.plot(x, np.real(psi), label="real")
12+
ax.plot(x, np.imag(psi), label="imag")
13+
ax.set_title(wavelet)
14+
ax.set_xlim([-5, 5])
15+
ax.set_ylim([-0.8, 1])
16+
ax.legend()
17+
plt.suptitle("Complex Morlet Wavelets with different center frequencies and bandwidths")
18+
plt.show()
19+
20+
21+
def gaussian(x, x0, sigma):
22+
return np.exp(-np.power((x - x0) / sigma, 2.0) / 2.0)
23+
24+
25+
def make_chirp(t, t0, a):
26+
frequency = (a * (t + t0)) ** 2
27+
chirp = np.sin(2 * np.pi * frequency * t)
28+
return chirp, frequency
29+
30+
31+
def plot_wavelet(time, data, wavelet, title, ax):
32+
widths = np.geomspace(1, 1024, num=75)
33+
cwtmatr, freqs = pywt.cwt(
34+
data, widths, wavelet, sampling_period=np.diff(time).mean()
35+
)
36+
cwtmatr = np.abs(cwtmatr[:-1, :-1])
37+
pcm = ax.pcolormesh(time, freqs, cwtmatr)
38+
ax.set_yscale("log")
39+
ax.set_xlabel("Time (s)")
40+
ax.set_ylabel("Frequency (Hz)")
41+
ax.set_title(title)
42+
plt.colorbar(pcm, ax=ax)
43+
return ax
44+
45+
46+
# generate signal
47+
time = np.linspace(0, 1, 1000)
48+
chirp1, frequency1 = make_chirp(time, 0.2, 9)
49+
chirp2, frequency2 = make_chirp(time, 0.1, 5)
50+
chirp = chirp1 + 0.6 * chirp2
51+
chirp *= gaussian(time, 0.5, 0.2)
52+
53+
# perform CWT with different wavelets on same signal and plot results
54+
wavelets = [f"cmor{x:.1f}-{y:.1f}" for x in [0.5, 1.5, 2.5] for y in [0.5, 1.0, 1.5]]
55+
fig, axs = plt.subplots(3, 3, figsize=(10, 10), sharex=True)
56+
for ax, wavelet in zip(axs.flatten(), wavelets):
57+
plot_wavelet(time, chirp, wavelet, wavelet, ax)
58+
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
59+
plt.suptitle("Scaleograms of the same signal with different wavelets")
60+
plt.show()
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import matplotlib.pyplot as plt
2+
import numpy as np
3+
4+
import pywt
5+
6+
7+
def gaussian(x, x0, sigma):
8+
return np.exp(-np.power((x - x0) / sigma, 2.0) / 2.0)
9+
10+
11+
def make_chirp(t, t0, a):
12+
frequency = (a * (t + t0)) ** 2
13+
chirp = np.sin(2 * np.pi * frequency * t)
14+
return chirp, frequency
15+
16+
17+
# generate signal
18+
time = np.linspace(0, 1, 2000)
19+
chirp1, frequency1 = make_chirp(time, 0.2, 9)
20+
chirp2, frequency2 = make_chirp(time, 0.1, 5)
21+
chirp = chirp1 + 0.6 * chirp2
22+
chirp *= gaussian(time, 0.5, 0.2)
23+
24+
# plot signal
25+
fig, axs = plt.subplots(2, 1, sharex=True)
26+
axs[0].plot(time, chirp)
27+
axs[1].plot(time, frequency1)
28+
axs[1].plot(time, frequency2)
29+
axs[1].set_yscale("log")
30+
axs[1].set_xlabel("Time (s)")
31+
axs[0].set_ylabel("Signal")
32+
axs[1].set_ylabel("True frequency (Hz)")
33+
plt.suptitle("Input signal")
34+
plt.show()
35+
36+
# perform CWT
37+
wavelet = "cmor1.5-1.0"
38+
# logarithmic scale for scales, as suggested by Torrence & Compo:
39+
widths = np.geomspace(1, 1024, num=100)
40+
sampling_period = np.diff(time).mean()
41+
cwtmatr, freqs = pywt.cwt(chirp, widths, wavelet, sampling_period=sampling_period)
42+
# absolute take absolute value of complex result
43+
cwtmatr = np.abs(cwtmatr[:-1, :-1])
44+
45+
# plot result using matplotlib's pcolormesh (image with annoted axes)
46+
fig, axs = plt.subplots(2, 1)
47+
pcm = axs[0].pcolormesh(time, freqs, cwtmatr)
48+
axs[0].set_yscale("log")
49+
axs[0].set_xlabel("Time (s)")
50+
axs[0].set_ylabel("Frequency (Hz)")
51+
axs[0].set_title("Continuous Wavelet Transform (Scaleogram)")
52+
fig.colorbar(pcm, ax=axs[0])
53+
54+
# plot fourier transform for comparison
55+
from numpy.fft import rfft, rfftfreq
56+
57+
yf = rfft(chirp)
58+
xf = rfftfreq(len(chirp), sampling_period)
59+
plt.semilogx(xf, np.abs(yf))
60+
axs[1].set_xlabel("Frequency (Hz)")
61+
axs[1].set_title("Fourier Transform")
62+
plt.tight_layout()

doc/source/pyplots/plot_wavelets.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import matplotlib.pyplot as plt
2+
import numpy as np
3+
4+
import pywt
5+
6+
wavlist = pywt.wavelist(kind="continuous")
7+
cols = 3
8+
rows = (len(wavlist) + cols - 1) // cols
9+
fig, axs = plt.subplots(rows, cols, figsize=(10, 10),
10+
sharex=True, sharey=True)
11+
for ax, wavelet in zip(axs.flatten(), wavlist):
12+
# A few wavelet families require parameters in the string name
13+
if wavelet in ['cmor', 'shan']:
14+
wavelet += '1-1'
15+
elif wavelet == 'fbsp':
16+
wavelet += '1-1.5-1.0'
17+
18+
[psi, x] = pywt.ContinuousWavelet(wavelet).wavefun(10)
19+
ax.plot(x, np.real(psi), label="real")
20+
ax.plot(x, np.imag(psi), label="imag")
21+
ax.set_title(wavelet)
22+
ax.set_xlim([-5, 5])
23+
ax.set_ylim([-0.8, 1])
24+
25+
ax.legend(loc="upper right")
26+
plt.suptitle("Available wavelets for CWT")
27+
plt.tight_layout()
28+
plt.show()

doc/source/ref/cwt.rst

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,82 @@
66
Continuous Wavelet Transform (CWT)
77
==================================
88

9-
This section describes functions used to perform single continuous wavelet
10-
transforms.
9+
This section focuses on the one-dimensional Continuous Wavelet Transform. It
10+
introduces the main function ``cwt`` alongside several helper function, and
11+
also gives an overview over the available wavelets for this transfom.
1112

12-
Single level - ``cwt``
13+
14+
Introduction
15+
------------
16+
17+
In simple terms, the Continuous Wavelet Transform is an analysis tool similar
18+
to the Fourier Transform, in that it takes a time-domain signal and returns
19+
the signal's components in the frequency domain. However, in contrast to the
20+
Fourier Transform, the Continuous Wavelet Transform returns a two-dimensional
21+
result, providing information in the frequency- as well as in time-domain.
22+
Therefore, it is useful for periodic signals which change over time, such as
23+
audio, seismic signals and many others (see below for examples).
24+
25+
For more background and an in-depth guide to the application of the Continuous
26+
Wavelet Transform, including topics such as statistical significance, the
27+
following well-known article is highly recommended:
28+
29+
`C. Torrence and G. Compo: "A Practical Guide to Wavelet Analysis", Bulletin of the American Meteorological Society, vol. 79, no. 1, pp. 61-78, January 1998 <https://paos.colorado.edu/research/wavelets/bams_79_01_0061.pdf>`_
30+
31+
32+
The ``cwt`` Function
1333
----------------------
1434

35+
This is the main function, which calculates the Continuous Wavelet Transform
36+
of a one-dimensional signal.
37+
1538
.. autofunction:: cwt
1639

40+
A comprehensive example of the CWT
41+
----------------------------------
42+
43+
Here is a simple end-to-end example of how to calculate the CWT of a simple
44+
signal, and how to plot it using ``matplotlib``.
45+
46+
First, we generate an artificial signal to be analyzed. We are
47+
using the sum of two sine functions with increasing frequency, known as "chirp".
48+
For reference, we also generate a plot of the signal and the two time-dependent
49+
frequency components it contains.
50+
51+
We then apply the Continuous Wavelet Transform
52+
using a complex Morlet wavlet with a given center frequency and bandwidth
53+
(namely ``cmor1.5-1.0``). We then plot the so-called "scaleogram", which is the
54+
2D plot of the signal strength vs. time and frequency.
55+
56+
.. plot:: pyplots/plot_cwt_scaleogram.py
57+
58+
The Continuous Wavelet Transform can resolve the two frequency components clearly,
59+
which is an obvious advantage over the Fourier Transform in this case. The scales
60+
(widths) are given on a logarithmic scale in the example. The scales determine the
61+
frequency resolution of the scaleogram. However, it is not straightforward to
62+
convert them to frequencies, but luckily, ``cwt`` calculates the correct frequencies
63+
for us. There are also helper functions, that perform this conversion in both ways.
64+
For more information, see :ref:`Choosing scales` and :ref:`Converting frequency`.
65+
66+
Also note, that the raw output of ``cwt`` is complex if a complex wavelet is used.
67+
For visualization, it is therefore necessary to use the absolute value.
68+
69+
70+
Wavelet bandwidth and center frequencies
71+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
72+
73+
This example shows how the Complex Morlet Wavelet can be configured for optimum
74+
results using the ``center_frequency`` and ``bandwidth_frequency`` parameters,
75+
which can simply be appended to the wavelet's string identifier ``cmor`` for
76+
convenience. It also demonstrates the importance of choosing appropriate values
77+
for the wavelet's center frequency and bandwidth. The right values will depend
78+
on the signal being analyzed. As shown below, bad values may lead to poor
79+
resolution or artifacts.
80+
81+
.. plot:: pyplots/cwt_wavelet_frequency_bandwidth_demo.py
82+
.. Sphinx seems to take a long time to generate this plot, even though the
83+
.. corresponding script is relatively fast when run on its own.
84+
1785
1886
Continuous Wavelet Families
1987
---------------------------
@@ -25,6 +93,12 @@ wavelet names compatible with ``cwt`` can be obtained by:
2593

2694
wavlist = pywt.wavelist(kind='continuous')
2795

96+
Here is an overview of all available wavelets for ``cwt``. Note, that they can be
97+
customized by passing parameters such as ``center_frequency`` and ``bandwidth_frequency``
98+
(see :ref:`ContinuousWavelet` for details).
99+
100+
.. plot:: pyplots/plot_wavelets.py
101+
28102

29103
Mexican Hat Wavelet
30104
^^^^^^^^^^^^^^^^^^^
@@ -104,6 +178,8 @@ where :math:`M` is the spline order, :math:`B` is the bandwidth and :math:`C` is
104178
the center frequency.
105179

106180

181+
.. _Choosing scales:
182+
107183
Choosing the scales for ``cwt``
108184
-------------------------------
109185

@@ -147,6 +223,8 @@ scales. The right column are the corresponding Fourier power spectra of each
147223
filter.. For scales 1 and 2 it can be seen that aliasing due to violation of
148224
the Nyquist limit occurs.
149225

226+
.. _Converting frequency:
227+
150228
Converting frequency to scale for ``cwt``
151229
-----------------------------------------
152230

doc/source/ref/wavelets.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,8 @@ from plain Python lists of filter coefficients and a *filter bank-like* object.
257257
>>> myOtherWavelet = pywt.Wavelet(name="myHaarWavelet", filter_bank=filter_bank)
258258

259259

260+
.. _ContinuousWavelet:
261+
260262
``ContinuousWavelet`` object
261263
----------------------------
262264

0 commit comments

Comments
 (0)