ulab utilities¶
There might be cases, when the format of your data does not conform to
ulab
, i.e., there is no obvious way to map the data to any of the
five supported dtype
s. A trivial example is an ADC or microphone
signal with 32-bit resolution. For such cases, ulab
defines the
utils
module, which, at the moment, has four functions that are not
numpy
compatible, but which should ease interfacing ndarray
s
to peripheral devices.
The utils
module can be enabled by setting the
ULAB_HAS_UTILS_MODULE
constant to 1 in
ulab.h:
#ifndef ULAB_HAS_UTILS_MODULE
#define ULAB_HAS_UTILS_MODULE (1)
#endif
This still does not compile any functions into the firmware. You can add a function by setting the corresponding pre-processor constant to 1. E.g.,
#ifndef ULAB_UTILS_HAS_FROM_INT16_BUFFER
#define ULAB_UTILS_HAS_FROM_INT16_BUFFER (1)
#endif
from_int32_buffer, from_uint32_buffer¶
With the help of utils.from_int32_buffer
, and
utils.from_uint32_buffer
, it is possible to convert 32-bit integer
buffers to ndarrays
of float type. These functions have a syntax
similar to numpy.frombuffer
; they support the count=-1
, and
offset=0
keyword arguments. However, in addition, they also accept
out=None
, and byteswap=False
.
Here is an example without keyword arguments
# code to be run in micropython
from ulab import numpy as np
from ulab import utils
a = bytearray([1, 1, 0, 0, 0, 0, 0, 255])
print('a: ', a)
print()
print('unsigned integers: ', utils.from_uint32_buffer(a))
b = bytearray([1, 1, 0, 0, 0, 0, 0, 255])
print('\nb: ', b)
print()
print('signed integers: ', utils.from_int32_buffer(b))
a: bytearray(b'x01x01x00x00x00x00x00xff') unsigned integers: array([257.0, 4278190080.000001], dtype=float64) b: bytearray(b'x01x01x00x00x00x00x00xff') signed integers: array([257.0, -16777216.0], dtype=float64)
The meaning of count
, and offset
is similar to that in
numpy.frombuffer
. count
is the number of floats that will be
converted, while offset
would discard the first offset
number of
bytes from the buffer before the conversion.
In the example above, repeated calls to either of the functions returns
a new ndarray
. You can save RAM by supplying the out
keyword
argument with a pre-defined ndarray
of sufficient size, in which
case the results will be inserted into the ndarray
. If the dtype
of out
is not float
, a TypeError
exception will be raised.
# code to be run in micropython
from ulab import numpy as np
from ulab import utils
a = np.array([1, 2], dtype=np.float)
b = bytearray([1, 0, 1, 0, 0, 1, 0, 1])
print('b: ', b)
utils.from_uint32_buffer(b, out=a)
print('a: ', a)
b: bytearray(b'x01x00x01x00x00x01x00x01') a: array([65537.0, 16777472.0], dtype=float64)
Finally, since there is no guarantee that the endianness of a particular
peripheral device supplying the buffer is the same as that of the
microcontroller, from_(u)intbuffer
allows a conversion via the
byteswap
keyword argument.
# code to be run in micropython
from ulab import numpy as np
from ulab import utils
a = bytearray([1, 0, 0, 0, 0, 0, 0, 1])
print('a: ', a)
print('buffer without byteswapping: ', utils.from_uint32_buffer(a))
print('buffer with byteswapping: ', utils.from_uint32_buffer(a, byteswap=True))
a: bytearray(b'x01x00x00x00x00x00x00x01') buffer without byteswapping: array([1.0, 16777216.0], dtype=float64) buffer with byteswapping: array([16777216.0, 1.0], dtype=float64)
from_int16_buffer, from_uint16_buffer¶
These two functions are identical to utils.from_int32_buffer
, and
utils.from_uint32_buffer
, with the exception that they convert
16-bit integers to floating point ndarray
s.
spectrogram¶
In addition to the Fourier transform and its inverse, ulab
also
sports a function called spectrogram
, which returns the absolute
value of the Fourier transform, also known as the power spectrum. This
could be used to find the dominant spectral component in a time series.
The arguments are treated in the same way as in fft
, and ifft
.
This means that, if the firmware was compiled with complex support, the
input can also be a complex array.
# code to be run in micropython
from ulab import numpy as np
from ulab import utils as utils
x = np.linspace(0, 10, num=1024)
y = np.sin(x)
a = utils.spectrogram(y)
print('original vector:\n', y)
print('\nspectrum:\n', a)
original vector:
array([0.0, 0.009775015390171337, 0.01954909674625918, ..., -0.5275140569487312, -0.5357931822978732, -0.5440211108893697], dtype=float64)
spectrum:
array([187.8635087634579, 315.3112063607119, 347.8814873399374, ..., 84.45888934298905, 347.8814873399374, 315.3112063607118], dtype=float64)
As such, spectrogram
is really just a shorthand for
np.sqrt(a*a + b*b)
, however, it saves significant amounts of RAM:
the expression a*a + b*b
has to allocate memory for a*a
,
b*b
, and finally, their sum. In contrast, spectrogram
calculates
the spectrum internally, and stores it in the memory segment that was
reserved for the real part of the Fourier transform.
# code to be run in micropython
from ulab import numpy as np
from ulab import utils as utils
x = np.linspace(0, 10, num=1024)
y = np.sin(x)
a, b = np.fft.fft(y)
print('\nspectrum calculated the hard way:\n', np.sqrt(a*a + b*b))
a = utils.spectrogram(y)
print('\nspectrum calculated the lazy way:\n', a)
spectrum calculated the hard way:
array([187.8635087634579, 315.3112063607119, 347.8814873399374, ..., 84.45888934298905, 347.8814873399374, 315.3112063607118], dtype=float64)
spectrum calculated the lazy way:
array([187.8635087634579, 315.3112063607119, 347.8814873399374, ..., 84.45888934298905, 347.8814873399374, 315.3112063607118], dtype=float64)