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
14This wrapper is also containing the scripts for the new [CMSIS-DSP compute graph framework](https://github.com/ARM-software/CMSIS-DSP/tree/main/ComputeGraph) (CG).
15
16CG is also including some nodes to communicate with Modelica using the VHT Modelica blocks developed as part of our [VHT-SystemModeling](https://github.com/ARM-software/VHT-SystemModeling) demos.
17
18An history of the changes to this wrapper is available at the end of the README.
19
20# How to build and install
21
22## Tested configurations
23
24The building of this package has been tested on Windows with the Python install from python.org and Microsoft Visual 2017.
25
26It has also been tested with `cygwin`. In that case, `python-devel` must be installed too. On Mac, it was tested with standard XCode installation.
27
28To run the examples, `scipy` and `matplotlib` must also be installed.
29
30Other configurations should work but the `setup.py` file would have to be improved.
31
32Python 3 must be used.
33
34## Installing and Building
35
36### Installing
37
38It is advised to do it in a Python virtual environment. Then, in the virtual environment you can just do:
39
40    pip install cmsisdsp
41
42You 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.
43
44DSP examples are available in the [CMSIS-DSP PythonWrapper examples](https://github.com/ARM-software/CMSIS-DSP/tree/main/PythonWrapper/examples) folder.
45
46Synchronous Data Flow examples are available in the [ComputeGraph](https://github.com/ARM-software/CMSIS-DSP/tree/main/ComputeGraph) folder of [CMSIS-DSP](https://github.com/ARM-software/CMSIS-DSP) .
47
48You can also install and run it from [Google colab](https://colab.research.google.com/):
49
50This [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.
51
52### Building
53
54It 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.
55
56It is advised to do this it into a virtualenv
57
58
59Since the [CMSIS-DSP](https://github.com/ARM-software/CMSIS-DSP) wrapper is using `NumPy`, you must first install it in the virtual environment.
60
61    > pip install numpy
62
63Once `NumPy` is installed, you can build the [CMSIS-DSP](https://github.com/ARM-software/CMSIS-DSP) python wrapper. Go to folder `CMSIS/DSP`.
64
65Now, you can install the cmsisdsp package in editable mode:
66
67    > pip install -e .
68
69Before using this command, you need to rebuild the CMSIS-DSP library which is no more built by the `setup.py` script.
70
71There 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.
72
73This library is then used by the `setup.py` script to build the Python extension.
74
75## Running the examples
76
77Install some packages to be able to run the examples
78
79    > pip install numpy
80    > pip install scipy
81    > pip install matplotlib
82
83Depending on the example, you may have to install more packages.
84
85The examples are in the  [CMSIS-DSP PythonWrapper examples](https://github.com/ARM-software/CMSIS-DSP/tree/main/PythonWrapper/examples) folder.
86
87You 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.
88
89Note 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.
90
91# Usage
92
93The 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.
94
95First you need to import the module
96
97    > import cmsisdsp as dsp
98
99If you use numpy:
100
101    > import numpy as np
102
103If you use scipy signal processing functions:
104
105    > from scipy import signal
106
107## Functions with no instance arguments
108
109You can use a [CMSIS-DSP](https://github.com/ARM-software/CMSIS-DSP) function with numpy arrays:
110
111    > r = dsp.arm_add_f32(np.array([1.,2,3]),np.array([4.,5,7]))
112
113The function can also be called more simply with
114
115    > r = dsp.arm_add_f32([1.,2,3],[4.,5,7])
116
117The 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).
118
119## Functions with instance arguments
120
121When 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:
122
123First you need to create this instance:
124
125    > firf32 = dsp.arm_fir_instance_f32()
126
127Then, you need to call an init function:
128
129    > dsp.arm_fir_init_f32(firf32,3,[1.,2,3],[0,0,0,0,0,0,0])
130
131The 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.
132
133Since the goal is to be as close as possible to the C API, the API is forcing the use of this argument.
134
135The 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.
136
137Now, you can check that the instance was initialized correctly.
138
139    > print(firf32.numTaps())
140
141Then, you can filter with CMSIS-DSP:
142
143    > print(dsp.arm_fir_f32(firf32,[1,2,3,4,5]))
144
145The 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.
146
147If 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.
148
149    > print(dsp.arm_fir_f32(firf32,[6,7,8,9,10]))
150
151If you want to compare with scipy it is easy but warning : coefficients for the filter are in opposite order in scipy :
152
153    > filtered_x = signal.lfilter([3,2,1.], 1.0, [1,2,3,4,5,6,7,8,9,10])
154    > print(filtered_x)
155
156The principles are the same for all other APIs.
157
158## FFT
159
160Here is an example for using FFT from the Python interface:
161
162Let's define a signal you will use for the FFT.
163
164    > nb = 16
165    > signal = np.cos(2 * np.pi * np.arange(nb) / nb)
166
167The [CMSIS-DSP](https://github.com/ARM-software/CMSIS-DSP) cfft is requiring complex signals with a specific layout in memory.
168
169To 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
170
171    > signalR = imToReal1D(signal)
172
173Then, you create the FFT instance with:
174
175    > cfftf32=dsp.arm_cfft_instance_f32()
176
177You initialize the instance with the init function provided by the wrapper:
178
179    > status=dsp.arm_cfft_init_f32(cfftf32, nb)
180    > print(status)
181
182You compute the FFT of the signal with:
183
184    > resultR = dsp.arm_cfft_f32(cfftf32,signalR,0,1)
185
186You convert back to a complex format to compare with scipy:
187
188    > resultI = realToIm1D(resultR)
189    > print(resultI)
190
191## Matrix
192
193For 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).
194
195So to use a [CMSIS-DSP](https://github.com/ARM-software/CMSIS-DSP) matrix function, it is very simple:
196
197    > a=np.array([[1.,2,3,4],[5,6,7,8],[9,10,11,12]])
198    > b=np.array([[1.,2,3],[5.1,6,7],[9.1,10,11],[5,8,4]])
199
200`NumPy` result as reference:
201
202    > print(np.dot(a , b))
203
204[CMSIS-DSP](https://github.com/ARM-software/CMSIS-DSP) result:
205
206    > v=dsp.arm_mat_mult_f32(a,b)
207    > print(v)
208
209In a real C code, a pointer to a data structure for the result `v` would have to be passed as argument of the function.
210
211## example.py
212
213This example depends on a data file which can be downloaded here:
214
215https://archive.physionet.org/pn3/ecgiddb/Person_87/rec_2.dat
216
217This signal was created for a master thesis:
218
219Lugovaya 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.
220
221and it is part of the PhysioNet database
222
223Goldberger 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).
224
225## Submodules
226
227The Python wrapper is containing three submodules : `fixedpoint` , `mfcc` and `datatype`
228
229`fixedpoint` is proving some tools to help generating the fixedpoint values expected
230by [CMSIS-DSP](https://github.com/ARM-software/CMSIS-DSP).
231
232`mfcc` is generating some tools to generate the MEL filters, DCT and window coefficients
233expected by the [CMSIS-DSP](https://github.com/ARM-software/CMSIS-DSP) MFCC implementation.
234
235MEL filters are represented as 3 arrays to encode a sparse array.
236
237`datatype` is an API on top of `fixedpoint` to provide more reuse when converting between data formats.
238
239The wrapper is now containing the compute graph Python scripts and you should refer the the documentation in `DSP/ComputeGraph` folder to know how to use those tools.
240
241
242
243# Change history
244
245## Version 1.9.7:
246
247* Upgrade for compatibility with google colab
248* Change to compute graph API for structured datatype
249* Corrected distance issues when using wrapper on aarch64
250
251## Version 1.9.6:
252
253* Corrections to the RFFTs APIs
254* More flexibility in the compute graph to specify the additional arguments of the scheduler and nodes
255* Possibility to set the FIFO scaling factor at FIFO level (in asynchronous mode)
256
257## Version 1.9.5:
258
259Same as 1.9.4 but will work in Google Colab.
260
261## Version 1.9.4:
262
263* Dynamic Time Warping API
264* Window functions for FFT
265* New asynchronous mode for the compute graph
266(see [compute graph documentation](https://github.com/ARM-software/CMSIS-DSP/tree/main/ComputeGraph) for more details.
267
268## Version 1.9.3:
269
270* Corrected real FFTs in the wrapper
271* Corrected arm_fir_decimate and arm_fir_interpolate
272* Possibility to customize the FIFO class on a connection for the Python wrapper
273
274## Version 1.9.2:
275
276* New customization options for the compute graph:
277  * CAPI
278  * CMSISDSP
279  * postCustomCName
280
281## Version 1.9.1:
282
283* Small fix to the compute graph generator. The `#ifdef` at beginning of the custom header should be different for different scheduler names
284* Improve `addLiteralArg` and `addVariableArg` in compute graph to use variable number of arguments
285
286## Version 1.9.0:
287
288* 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.
289* More customization options (Macros to be defined) in the C++ code generated by the compute graph generator
290