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