Skip to content

Commit 3522969

Browse files
Add learning path "get started with CMSIS-DSP using Python"
1 parent f60e17c commit 3522969

File tree

16 files changed

+1313
-0
lines changed

16 files changed

+1313
-0
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
---
2+
title: Learn how to get started with CMSIS-DSP using Python
3+
4+
minutes_to_complete: 10
5+
6+
who_is_this_for: Developers writing DSP/AI software
7+
8+
learning_objectives:
9+
- Understand how to use the CMSIS-DSP Python package
10+
- Understand how the Python implementation maps to the C one
11+
- Develop a complex application with CMSIS-DSP
12+
13+
prerequisites:
14+
- Some familiarity with DSP programming
15+
- Some familiarity with Python programming
16+
- Knowledge of C
17+
- Some familiarity with CMSIS-DSP
18+
- Python installed on your computer
19+
20+
author: Christophe Favergeon
21+
22+
### Tags
23+
skilllevels: Advanced
24+
subjects: Libraries
25+
armips:
26+
- Cortex-M
27+
- Cortex-A
28+
tools_software_languages:
29+
- VS Code
30+
- CMSIS-DSP
31+
- Python
32+
- C
33+
- Jupyter Notebook
34+
operatingsystems:
35+
- Linux
36+
- Windows
37+
- macOS
38+
39+
40+
41+
42+
43+
further_reading:
44+
- resource:
45+
title: Biquad filters with CMSIS-DSP Python package
46+
link: https://developer.arm.com/documentation/102463/latest/
47+
type: documentation
48+
- resource:
49+
title: CMSIS-DSP library
50+
link: https://github.com/ARM-software/CMSIS-DSP
51+
type: Open-source project
52+
- resource:
53+
title: CMSIS-DSP python package
54+
link: https://pypi.org/project/cmsisdsp/
55+
type: Open-source project
56+
- resource:
57+
title: CMSIS-DSP Python package examples and tests
58+
link: https://github.com/ARM-software/CMSIS-DSP/tree/main/PythonWrapper/examples
59+
type: Open-source project
60+
- resource:
61+
title: CMSIS-Stream
62+
link: https://github.com/ARM-software/CMSIS-Stream
63+
type: Open-source project
64+
65+
66+
### FIXED, DO NOT MODIFY
67+
# ================================================================================
68+
weight: 1 # _index.md always has weight of 1 to order correctly
69+
layout: "learningpathall" # All files under learning paths have this same wrapper
70+
learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content.
71+
---
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
# ================================================================================
3+
# FIXED, DO NOT MODIFY THIS FILE
4+
# ================================================================================
5+
weight: 21 # Set to always be larger than the content in this path to be at the end of the navigation.
6+
title: "Next Steps" # Always the same, html page title.
7+
layout: "learningpathall" # All files under learning paths have this same wrapper for Hugo processing.
8+
---
8.1 KB
Loading
23 KB
Loading
24.1 KB
Loading
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
title: What is the CMSIS-DSP Python package ?
3+
weight: 2
4+
5+
### FIXED, DO NOT MODIFY
6+
layout: learningpathall
7+
---
8+
9+
## What is CMSIS-DSP ?
10+
11+
CMSIS-DSP is a general-purpose compute library with a focus on DSP. It was initially developed for Cortex-M processors and has recently been upgraded to also support Cortex-A.
12+
13+
On each processor, CMSIS-DSP is optimized for the architecture: DSP extensions on M4 and M7; Helium on M55 and M85; Neon on A55, etc.
14+
15+
## What is the CMSIS-DSP Python package ?
16+
17+
The CMSIS-DSP Python package is a Python API for CMSIS-DSP. Its goal is to make it easier to develop a C solution using CMSIS-DSP by decreasing the gap between a design environment like Python and the final C implementation.
18+
19+
For this reason, the Python API is as close as possible to the C one.
20+
21+
Fixed point arithmetic is not often provided by Python packages which generally focus on floating-point operations. The CMSIS-DSP Python package provides the same fixed point arithmetic functions as the C version : Q31, Q15 and Q7. The package is also providing float functions and in the future will also provide half-precision floats like the C API.
22+
23+
Finally, the CMSIS-DSP Python package is compatible with NumPy and can be used with all other scientific and AI Python packages such as SciPy or PyTorch.
24+
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
---
2+
title: Install the Python packages
3+
weight: 3
4+
5+
### FIXED, DO NOT MODIFY
6+
layout: learningpathall
7+
---
8+
9+
## Installing the Python packages
10+
The application you will develop with CMSIS-DSP requires a few additional Python packages besides CMSIS-DSP. These need to be installed before you start writing code.
11+
12+
Activate the Python environment you have chosen.
13+
14+
The first package to install is CMSIS-DSP:
15+
16+
```bash
17+
pip install cmsisdsp
18+
```
19+
It will also install `NumPy`, which is a dependency of the CMSIS-DSP Python package.
20+
21+
You'll be working with a Jupyter notebook, so the jupyter package must also be installed:
22+
23+
```bash
24+
pip install jupyter
25+
```
26+
27+
In the Jupyter notebook, you'll be using widgets to play sound, so you'll need to install some additional Jupyter widgets.
28+
29+
```bash
30+
pip install ipywidgets
31+
```
32+
33+
Finally, you'll need packages to read sound files and display plots:
34+
35+
36+
```bash
37+
pip install soundfile
38+
pip install matplotlib
39+
```
40+
41+
you can now launch the Jupyter notebook:
42+
43+
```bash
44+
jupyter notebook
45+
```
46+
Create a new Jupyter notebook by clicking `new` and selecting `Python 3 (ipykernel)`.
47+
48+
The new notebook will be named `Untitled`. Rename it to something more descriptive.
49+
50+
You can now import all the required packages.
51+
52+
Type the following Python code into your notebook and run the cell (shift-enter).
53+
All the Python code in this learning path is intended to be executed in the same Jupyter notebook.
54+
55+
```python
56+
import cmsisdsp as dsp
57+
import cmsisdsp.fixedpoint as fix
58+
import numpy as np
59+
from numpy.lib.stride_tricks import sliding_window_view
60+
61+
# Package for plotting
62+
import matplotlib.pyplot as plt
63+
64+
# Package to display audio widgets in the notebook and upload sound files
65+
import ipywidgets
66+
from IPython.display import display,Audio
67+
68+
# To convert a sound file to a NumPy array
69+
import io
70+
import soundfile as sf
71+
72+
# To load test patterns from the Arm Virtual Hardware Echo Canceller dem
73+
from urllib.request import urlopen
74+
```
75+
76+
You're now ready to move on to the next steps.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
---
2+
title: Load an audio file
3+
weight: 4
4+
5+
### FIXED, DO NOT MODIFY
6+
layout: learningpathall
7+
---
8+
9+
## Load an audio file
10+
11+
Load an audio file from one of the Arm demo repositories on GitHub.
12+
13+
14+
```python
15+
test_pattern_url="https://github.com/ARM-software/VHT-SystemModeling/blob/main/EchoCanceller/sounds/yesno.wav?raw=true"
16+
f = urlopen(test_pattern_url)
17+
filedata = f.read()
18+
```
19+
20+
You can now play and listen to the audio:
21+
```python
22+
audio=Audio(data=filedata,autoplay=False)
23+
audio
24+
```
25+
26+
An audio widget will appear in your Jupyter notebook. It will look like this:
27+
28+
![audio widget alt-text#center](audiowidget.png "Figure 1. Audio widget")
29+
30+
You can use it to listen to the audio.
31+
32+
You'll hear a sequence of the words "yes" and "no", with some noise between them.
33+
The goal of this learning path is to design an algorithm to remove the noise.
34+
35+
36+
Next, convert the audio into a NumPy array so that it can be processed using CMSIS-DSP:
37+
38+
```python
39+
data, samplerate = sf.read(io.BytesIO(filedata))
40+
if len(data.shape)>1:
41+
data=data[:,0]
42+
data = data.astype(np.float32)
43+
data=data/np.max(np.abs(data))
44+
dataQ15 = fix.toQ15(data)
45+
```
46+
47+
The code above does the following:
48+
- Converts the audio into a NumPy array
49+
- If the audio is stereo, only one channel is kept
50+
- Normalizes the audio to ensure no value exceeds 1
51+
- Converts the audio to Q15 fixed-point representation to enable the use of CMSIS-DSP fixed-point functions
52+
53+
Now, plot the audio waveform:
54+
55+
```python
56+
plt.plot(data)
57+
plt.show()
58+
```
59+
60+
You'll get the following output:
61+
62+
![audio signal alt-text#center](signal.png "Figure 2. Audio signal")
63+
64+
In the picture, you can see a sequence of words. Between the words, the signal is not zero: there is some noise.
65+
66+
In a real application, you don't wait for the entire signal to be received. The signal is continuous. The samples are processed as they are received. Processing can either be sample-based or block-based. For this learning path, the processing will be block-based.
67+
68+
Before you can move to the next step, this signal must be split into blocks. The processing will occur on small blocks of samples of a given duration.
69+
70+
71+
72+
```python
73+
winDuration=30e-3/6
74+
winOverlap=15e-3/6
75+
76+
winLength=int(np.floor(samplerate*winDuration))
77+
winOverlap=int(np.floor(samplerate*winOverlap))
78+
slices=sliding_window_view(data,winLength)[::winOverlap,:]
79+
slices_q15=sliding_window_view(dataQ15,winLength)[::winOverlap,:]
80+
```
81+
82+
Refer to the [NumPy documentation](https://numpy.org/doc/stable/reference/generated/numpy.lib.stride_tricks.sliding_window_view.html) for details about `sliding_window_view`. It's not the most efficient function, but it is sufficient for this tutorial.
83+
84+
The signal is split into overlapping blocks: each block reuses half of the samples from the previous block as defined by the `winOverlap` variable.
85+
86+
You are now ready to move on to the next step: you have an audio signal that has been split into overlapping blocks, and processing will occur on those blocks.
87+
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
---
2+
title: Write a simple VAD
3+
weight: 5
4+
5+
### FIXED, DO NOT MODIFY
6+
layout: learningpathall
7+
---
8+
9+
## Write a simple voice activity detection
10+
11+
To remove the noise between speech segments, you need to detect when voice is present.
12+
13+
Voice activity detection can be complex, but for this learning path, you'll implement a very simple and naive approach based on energy. The idea is that if the environment isn't too noisy, speech should have more energy than the noise.
14+
15+
The detection will rely on a comparison with a threshold that must be manually tuned.
16+
17+
You'll first implement a version of the voice activity detection (VAD) with NumPy, which will serve as a reference.
18+
19+
Then you'll implement the same version using CMSIS-DSP with the Q15 fixed-point format.
20+
21+
### NumPy VAD
22+
23+
First, you need to compute the energy of the signal within a block of samples. You'll ignore any constant component and focus only on the varying part of the signal:
24+
25+
```python
26+
# Energy of the window
27+
def signal_energy(window):
28+
w = window - np.mean(window)
29+
return(10*np.log10(np.sum(window * window)))
30+
```
31+
Then, compare the energy to a threshold to determine whether the block of audio is speech or noise:
32+
33+
```python
34+
def signal_vad(window):
35+
if signal_energy(window)>-11:
36+
return(1)
37+
else:
38+
return(0)
39+
```
40+
41+
The threshold is hard-coded. It's not a very clean solution, but it's sufficient for a tutorial.
42+
43+
When using such a detector, you'll quickly find that it is not sufficient. You'll need another pass to clean up the detection signal.
44+
45+
```python
46+
def clean_vad(v):
47+
v = np.hstack([[0],v,[0]])
48+
# Remove isolated peak
49+
vmin=[np.min(l) for l in sliding_window_view(v,3)]
50+
vmin = np.hstack([[0,0],vmin,[0]])
51+
# Remove isolated hole
52+
vmax=[np.max(l) for l in sliding_window_view(vmin,4)]
53+
return(vmax)
54+
```
55+
56+
Now you can apply this algorithm to the audio signal and plot the VAD detection over it to see if it's working:
57+
58+
```python
59+
_,ax=plt.subplots(1,1)
60+
cleaned=clean_vad([signal_vad(w) for w in slices])
61+
vad = np.array([[w]*(winLength-winOverlap) for w in cleaned]).flatten()
62+
ax.plot(data)
63+
ax.plot(vad)
64+
```
65+
The reference implementation works. You can now implement the same version using CMSIS-DSP.
66+
67+
![vad alt-text#center](vad.png "Figure 3. VAD")
68+
69+
70+
### CMSIS-DSP Q15 VAD
71+
72+
First, you need to compute the signal energy from audio in Q15 format using CMSIS-DSP.
73+
74+
If you look at the CMSIS-DSP documentation, you'll see that the power and log functions don't produce results in Q15 format. Tracking the fixed-point format throughout all lines of an algorithm can be challenging.
75+
76+
For this tutorial, instead of trying to determine the exact fixed-point format of the output and applying the necessary shift to adjust the output's fixed-point format, we'll simply tune the threshold of the detection function.
77+
78+
```python
79+
def signal_energy_q15(window):
80+
mean=dsp.arm_mean_q15(window)
81+
# Subtracting the mean won't cause saturation
82+
# So we use the CMSIS-DSP negate function on an array containing a single sample.
83+
neg_mean=dsp.arm_negate_q15([mean])[0]
84+
window=dsp.arm_offset_q15(window,neg_mean)
85+
energy=dsp.arm_power_q15(window)
86+
# Energy is not in Q15 format (refer to the CMSIS-DSP documentation).
87+
energy=dsp.ssat(energy>>20,16)
88+
dB=dsp.arm_vlog_q15([energy])
89+
# The output of the `vlog` is not in q15
90+
# The multiplication by 10 is missing compared to the NumPy
91+
# reference implementation.
92+
# The result of this function is not equivalent to the float implementation due to different
93+
# formats used in intermediate computations.
94+
# As a consequence, a different threshold must be used to compensate for these differences.
95+
return(dB[0])
96+
```
97+
98+
The comparison function is very similar to the NumPy reference, but the threshold is different:
99+
100+
```python
101+
def signal_vad_q15(window):
102+
# The threshold is not directly comparable to the float implementation
103+
# due to the different intermediate formats used in the fixed-point implementation.
104+
if signal_energy_q15(window)>fix.toQ15(-0.38):
105+
return(1)
106+
else:
107+
return(0)
108+
```
109+
110+
Note that in a C code, you would use the output of `fix.toQ15(-0.38)`.
111+
112+
`fix.toQ15` is a utility of the Python package to convert float to fixed-point. It is not available in the CMSIS-DSP C implementation.
113+
CMSIS-DSP C has functions like `arm_float_to_q15` which work on arrays and are meant to be used at runtime. If you need a precomputed constant, you can use a utility function like `fix.toQ15` and use the resulting value in the code.
114+
115+
The clean VAD function is the same for both the NumPy and Q15 versions.
116+
117+
Now you can check whether the Q15 version is working by plotting the signal and the output of the Q15 VAD algorithm.
118+
119+
```python
120+
_,ax=plt.subplots(1,1)
121+
cleaned=clean_vad([signal_vad_q15(w) for w in slices_q15])
122+
vad_q15 = np.array([[w]*winOverlap for w in cleaned]).flatten()
123+
ax.plot(data)
124+
ax.plot(vad_q15)
125+
126+
```

0 commit comments

Comments
 (0)