1# README
2
3This is a Python wrapper for the Arm open source [CMSIS-DSP](https://github.com/ARM-software/CMSIS-DSP) and it is compatible with `NumPy`.
4
5The CMSIS-DSP is available on our [GitHub](https://github.com/ARM-software/CMSIS-DSP) or as a [CMSIS Pack](https://github.com/ARM-software/CMSIS-DSP/releases).
6
7The idea is to follow as closely as possible the C [CMSIS-DSP](https://github.com/ARM-software/CMSIS-DSP) API to ease the migration to the final implementation on a board.
8
9The signal processing chain can thus be tested and developed in a Python environment and then easily converted to a C implementation running on a Cortex-M or Cortex-A board.
10
11A tutorial is also available but with less details than this README:
12https://developer.arm.com/documentation/102463/latest/
13
14An history of the changes to this wrapper is available at the end of the README.
15
16# How to build and install
17
18## Tested configurations
19
20The building of this package has been tested on Windows with the Python install from python.org and Microsoft Visual Studio 2022 and on Ubuntu 22.04.
21
22It has also been tested with `cygwin`. In that case, `python-devel` must be installed too. On Mac, it was tested with standard XCode installation.
23
24To run the examples, `scipy` and `matplotlib` must also be installed.
25
26Other configurations should work but the `setup.py` file would have to be improved.
27
28Python 3 must be used.
29
30## Installing and Building
31
32### Installing
33
34It is advised to do it in a Python virtual environment. Then, in the virtual environment you can just do:
35
36    pip install cmsisdsp
37
38You must have a recent `pip` (to automatically install the dependencies like `NumPy`) and you should have a compiler which can be found by Python when building the package.
39
40DSP examples are available in the [CMSIS-DSP PythonWrapper examples](https://github.com/ARM-software/CMSIS-DSP/tree/main/PythonWrapper/examples) folder.
41
42You can also install and run it from [Google colab](https://colab.research.google.com/):
43
44This [link](https://colab.research.google.com/github/ARM-software/CMSIS-DSP/blob/main/PythonWrapper/examples/cmsisdsp_tests.ipynb) will open a Jupyter notebook in [Google colab](https://colab.research.google.com/) for testing. This notebook is from the [examples](https://github.com/ARM-software/CMSIS-DSP/tree/main/PythonWrapper/examples) in the CMSIS-DSP GitHub repository.
45
46### Building
47
48It it is not working (because it is not possible for us to test all configurations), you can then try to build and install the wrapper manually.
49
50It is advised to do this it into a virtualenv
51
52
53Since the [CMSIS-DSP](https://github.com/ARM-software/CMSIS-DSP) wrapper is using `NumPy`, you must first install it in the virtual environment.
54
55    > pip install numpy
56
57Once `NumPy` is installed, you can build the [CMSIS-DSP](https://github.com/ARM-software/CMSIS-DSP) python wrapper. Go to folder `CMSIS/DSP`.
58
59Now, you can install the cmsisdsp package in editable mode:
60
61    > pip install -e .
62
63Before using this command, you need to rebuild the CMSIS-DSP library which is no more built by the `setup.py` script.
64
65There is a `CMakeLists.txt` in the `PythonWrapper` folder for this. The `build` folders in `PythonWrapper` are giving some examples of the options to use with the `cmake` command to generate the `Makefile` and build the library.
66
67This library is then used by the `setup.py` script to build the Python extension.
68
69## Running the examples
70
71Install some packages to be able to run the examples
72
73    > pip install numpy
74    > pip install scipy
75    > pip install matplotlib
76
77Depending on the example, you may have to install more packages.
78
79The examples are in the  [CMSIS-DSP PythonWrapper examples](https://github.com/ARM-software/CMSIS-DSP/tree/main/PythonWrapper/examples) folder.
80
81You can test the scripts `testdsp.py` and `example.py` and try to run them from this virtual environment. `example.py` is requiring a data file to be downloaded from the web. See below in this document for the link.
82
83Note that due to the great number of possible configurations (OS, Compiler, Python), we can't give any support if you have problems compiling the `PythonWrapper` on your specific configuration. But, generally people manage to do it and solve all the problems.
84
85# Usage
86
87The idea is to follow as closely as possible the [CMSIS-DSP](https://github.com/ARM-software/CMSIS-DSP) API to ease the migration to the final implementation on a board.
88
89First you need to import the module
90
91    > import cmsisdsp as dsp
92
93If you use numpy:
94
95    > import numpy as np
96
97If you use scipy signal processing functions:
98
99    > from scipy import signal
100
101## Functions with no instance arguments
102
103You can use a [CMSIS-DSP](https://github.com/ARM-software/CMSIS-DSP) function with numpy arrays:
104
105    > r = dsp.arm_add_f32(np.array([1.,2,3]),np.array([4.,5,7]))
106
107The function can also be called more simply with
108
109    > r = dsp.arm_add_f32([1.,2,3],[4.,5,7])
110
111The result of a [CMSIS-DSP](https://github.com/ARM-software/CMSIS-DSP) function will always be a numpy array whatever the arguments were (numpy array or list).
112
113## Functions with instance arguments
114
115When the [CMSIS-DSP](https://github.com/ARM-software/CMSIS-DSP) function is requiring an instance data structure, it is just a bit more complex to use it:
116
117First you need to create this instance:
118
119    > firf32 = dsp.arm_fir_instance_f32()
120
121Then, you need to call an init function:
122
123    > dsp.arm_fir_init_f32(firf32,3,[1.,2,3],[0,0,0,0,0,0,0])
124
125The third argument in this function is the state. Since all arguments (except the instance ones) are read-only in this Python API, this state will never be changed ! It is just used to communicate the length of the state array which must be allocated by the init function. This argument is required because it is present in the [CMSIS-DSP](https://github.com/ARM-software/CMSIS-DSP) API and in the final C implementation you'll need to allocate a state array with the right dimension.
126
127Since the goal is to be as close as possible to the C API, the API is forcing the use of this argument.
128
129The only change compared to the C API is that the size variables (like blockSize for filter) are computed automatically from the other arguments. This choice was made to make it a bit easier the use of numpy array with the API.
130
131Now, you can check that the instance was initialized correctly.
132
133    > print(firf32.numTaps())
134
135Then, you can filter with CMSIS-DSP:
136
137    > print(dsp.arm_fir_f32(firf32,[1,2,3,4,5]))
138
139The size of this signal should be `blockSize`. `blockSize` was inferred from the size of the state array : `numTaps + blockSize - 1` according to [CMSIS-DSP.](https://github.com/ARM-software/CMSIS-DSP) So here the signal must have 5 samples.
140
141If you want to filter more than 5 samples, then you can just call the function again. The state variable inside firf32 will ensure that it works like in the [CMSIS-DSP](https://github.com/ARM-software/CMSIS-DSP) C code.
142
143    > print(dsp.arm_fir_f32(firf32,[6,7,8,9,10]))
144
145If you want to compare with scipy it is easy but warning : coefficients for the filter are in opposite order in scipy :
146
147    > filtered_x = signal.lfilter([3,2,1.], 1.0, [1,2,3,4,5,6,7,8,9,10])
148    > print(filtered_x)
149
150The principles are the same for all other APIs.
151
152## FFT
153
154Here is an example for using FFT from the Python interface:
155
156Let's define a signal you will use for the FFT.
157
158    > nb = 16
159    > signal = np.cos(2 * np.pi * np.arange(nb) / nb)
160
161The [CMSIS-DSP](https://github.com/ARM-software/CMSIS-DSP) cfft is requiring complex signals with a specific layout in memory.
162
163To remain as close as possible to the C API, we are not using complex numbers in the wrapper. So a complex signal must be converted into a real one. The function imToReal1D is defined in testdsp.py
164
165    > signalR = imToReal1D(signal)
166
167Then, you create the FFT instance with:
168
169    > cfftf32=dsp.arm_cfft_instance_f32()
170
171You initialize the instance with the init function provided by the wrapper:
172
173    > status=dsp.arm_cfft_init_f32(cfftf32, nb)
174    > print(status)
175
176You compute the FFT of the signal with:
177
178    > resultR = dsp.arm_cfft_f32(cfftf32,signalR,0,1)
179
180You convert back to a complex format to compare with scipy:
181
182    > resultI = realToIm1D(resultR)
183    > print(resultI)
184
185## Matrix
186
187For matrix, the instance variables are masked by the Python API. We decided that for matrix only there was no use for having the [CMSIS-DSP](https://github.com/ARM-software/CMSIS-DSP) instance visibles since they contain the same information as the numpy array (samples and dimension).
188
189So to use a [CMSIS-DSP](https://github.com/ARM-software/CMSIS-DSP) matrix function, it is very simple:
190
191    > a=np.array([[1.,2,3,4],[5,6,7,8],[9,10,11,12]])
192    > b=np.array([[1.,2,3],[5.1,6,7],[9.1,10,11],[5,8,4]])
193
194`NumPy` result as reference:
195
196    > print(np.dot(a , b))
197
198[CMSIS-DSP](https://github.com/ARM-software/CMSIS-DSP) result:
199
200    > v=dsp.arm_mat_mult_f32(a,b)
201    > print(v)
202
203In a real C code, a pointer to a data structure for the result `v` would have to be passed as argument of the function.
204
205## example.py
206
207This example depends on a data file which can be downloaded here:
208
209https://archive.physionet.org/pn3/ecgiddb/Person_87/rec_2.dat
210
211This signal was created for a master thesis:
212
213Lugovaya T.S. Biometric human identification based on electrocardiogram. [Master's thesis] Faculty of Computing Technologies and Informatics, Electrotechnical University "LETI", Saint-Petersburg, Russian Federation; June 2005.
214
215and it is part of the PhysioNet database
216
217Goldberger AL, Amaral LAN, Glass L, Hausdorff JM, Ivanov PCh, Mark RG, Mietus JE, Moody GB, Peng C-K, Stanley HE. PhysioBank, PhysioToolkit, and PhysioNet: Components of a New Research Resource for Complex Physiologic Signals. Circulation 101(23):e215-e220 [Circulation Electronic Pages; http://circ.ahajournals.org/cgi/content/full/101/23/e215]; 2000 (June 13).
218
219## Submodules
220
221The Python wrapper is containing three submodules : `fixedpoint` , `mfcc` and `datatype`
222
223`fixedpoint` is proving some tools to help generating the fixedpoint values expected
224by [CMSIS-DSP](https://github.com/ARM-software/CMSIS-DSP).
225
226`mfcc` is generating some tools to generate the MEL filters, DCT and window coefficients
227expected by the [CMSIS-DSP](https://github.com/ARM-software/CMSIS-DSP) MFCC implementation.
228
229MEL filters are represented as 3 arrays to encode a sparse array.
230
231`datatype` is an API on top of `fixedpoint` to provide more reuse when converting between data formats.
232
233
234
235# Change history
236
237## Version 1.9.9:
238* Supports Python 3.12
239* Works with Numpy 2.0
240* Corrections on Cholesky
241
242## Version 1.9.8:
243* Compute graph API has been removed
244* Dependency on numpy 1.22 has been lifted, tested through numpy 1.26
245* Inconsistencies in distance and window modules have been fixed.
246
247## Version 1.9.7:
248
249* Upgrade for compatibility with google colab
250* Change to compute graph API for structured datatype
251* Corrected distance issues when using wrapper on aarch64
252
253## Version 1.9.6:
254
255* Corrections to the RFFTs APIs
256* More flexibility in the compute graph to specify the additional arguments of the scheduler and nodes
257* Possibility to set the FIFO scaling factor at FIFO level (in asynchronous mode)
258
259## Version 1.9.5:
260
261Same as 1.9.4 but will work in Google Colab.
262
263## Version 1.9.4:
264
265* Dynamic Time Warping API
266* Window functions for FFT
267* New asynchronous mode for the compute graph
268(see [compute graph documentation](https://github.com/ARM-software/CMSIS-DSP/tree/main/ComputeGraph) for more details.
269
270## Version 1.9.3:
271
272* Corrected real FFTs in the wrapper
273* Corrected arm_fir_decimate and arm_fir_interpolate
274* Possibility to customize the FIFO class on a connection for the Python wrapper
275
276## Version 1.9.2:
277
278* New customization options for the compute graph:
279  * CAPI
280  * CMSISDSP
281  * postCustomCName
282
283## Version 1.9.1:
284
285* Small fix to the compute graph generator. The `#ifdef` at beginning of the custom header should be different for different scheduler names
286* Improve `addLiteralArg` and `addVariableArg` in compute graph to use variable number of arguments
287
288## Version 1.9.0:
289
290* New scheduling mode, in the compute graph generator, giving priority to sinks in the scheduling. The idea is to try to decrease the latency between sinks and sources.
291* More customization options (Macros to be defined) in the C++ code generated by the compute graph generator
292