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