|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifndef _EIDSP_SPECTRAL_PROCESSING_H_ |
|
|
#define _EIDSP_SPECTRAL_PROCESSING_H_ |
|
|
|
|
|
#include "edge-impulse-sdk/dsp/ei_vector.h" |
|
|
#include <algorithm> |
|
|
#include "../numpy.hpp" |
|
|
#include "filters.hpp" |
|
|
|
|
|
namespace ei { |
|
|
namespace spectral { |
|
|
|
|
|
namespace processing { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class scale { |
|
|
public: |
|
|
scale(ei_signal_t *signal, float scaling = 1.0f) |
|
|
: _signal(signal), _scaling(scaling) |
|
|
{ |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int get_data(size_t offset, size_t length, float *out_buffer) { |
|
|
if (offset + length > _signal->total_length) { |
|
|
EIDSP_ERR(EIDSP_OUT_OF_BOUNDS); |
|
|
} |
|
|
|
|
|
int ret = _signal->get_data(offset, length, out_buffer); |
|
|
if (ret != 0) { |
|
|
EIDSP_ERR(ret); |
|
|
} |
|
|
|
|
|
EI_DSP_MATRIX_B(temp, 1, length, out_buffer); |
|
|
return numpy::scale(&temp, _scaling); |
|
|
} |
|
|
|
|
|
private: |
|
|
ei_signal_t *_signal; |
|
|
float _scaling; |
|
|
}; |
|
|
} |
|
|
|
|
|
namespace processing { |
|
|
typedef struct { |
|
|
float freq; |
|
|
float amplitude; |
|
|
} freq_peak_t; |
|
|
|
|
|
typedef struct { |
|
|
EIDSP_i32 freq; |
|
|
EIDSP_i32 amplitude; |
|
|
} freq_peak_i32_t; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
__attribute__((unused)) static int scale(float *signal, size_t signal_size, float scale = 1) |
|
|
{ |
|
|
EI_DSP_MATRIX_B(temp, 1, signal_size, signal); |
|
|
return numpy::scale(&temp, scale); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int butterworth_lowpass_filter( |
|
|
matrix_t *matrix, |
|
|
float sampling_frequency, |
|
|
float filter_cutoff, |
|
|
uint8_t filter_order) |
|
|
{ |
|
|
for (size_t row = 0; row < matrix->rows; row++) { |
|
|
filters::butterworth_lowpass( |
|
|
filter_order, |
|
|
sampling_frequency, |
|
|
filter_cutoff, |
|
|
matrix->buffer + (row * matrix->cols), |
|
|
matrix->buffer + (row * matrix->cols), |
|
|
matrix->cols); |
|
|
} |
|
|
|
|
|
return EIDSP_OK; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int butterworth_highpass_filter( |
|
|
matrix_t *matrix, |
|
|
float sampling_frequency, |
|
|
float filter_cutoff, |
|
|
uint8_t filter_order) |
|
|
{ |
|
|
for (size_t row = 0; row < matrix->rows; row++) { |
|
|
filters::butterworth_highpass( |
|
|
filter_order, |
|
|
sampling_frequency, |
|
|
filter_cutoff, |
|
|
matrix->buffer + (row * matrix->cols), |
|
|
matrix->buffer + (row * matrix->cols), |
|
|
matrix->cols); |
|
|
} |
|
|
|
|
|
return EIDSP_OK; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int find_peak_indexes( |
|
|
matrix_t *input_matrix, |
|
|
matrix_t *output_matrix, |
|
|
float threshold, |
|
|
uint16_t *peaks_found) |
|
|
{ |
|
|
if (input_matrix->rows != 1) { |
|
|
EIDSP_ERR(EIDSP_MATRIX_SIZE_MISMATCH); |
|
|
} |
|
|
|
|
|
if (output_matrix->cols != 1) { |
|
|
EIDSP_ERR(EIDSP_MATRIX_SIZE_MISMATCH); |
|
|
} |
|
|
|
|
|
uint16_t out_ix = 0; |
|
|
size_t in_size = input_matrix->cols; |
|
|
float *in = input_matrix->buffer; |
|
|
size_t out_size = output_matrix->rows; |
|
|
float *out = output_matrix->buffer; |
|
|
|
|
|
|
|
|
float min = FLT_MAX, max = 0.0f; |
|
|
for (size_t ix = 0; ix < in_size - 1; ix++) { |
|
|
if (in[ix] < min) { |
|
|
min = in[ix]; |
|
|
} |
|
|
if (in[ix] > max) { |
|
|
max = in[ix]; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
float prev = in[0]; |
|
|
|
|
|
|
|
|
for (size_t ix = 1; ix < in_size - 1; ix++) { |
|
|
|
|
|
if (in[ix] > prev && in[ix] > in[ix+1]) { |
|
|
|
|
|
float height = (in[ix] - prev) + (in[ix] - in[ix + 1]); |
|
|
|
|
|
if (height > threshold) { |
|
|
out[out_ix] = ix; |
|
|
out_ix++; |
|
|
if (out_ix == out_size) break; |
|
|
} |
|
|
} |
|
|
|
|
|
prev = in[ix]; |
|
|
} |
|
|
|
|
|
*peaks_found = out_ix; |
|
|
|
|
|
return EIDSP_OK; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int find_fft_peaks( |
|
|
matrix_t *fft_matrix, |
|
|
matrix_t *output_matrix, |
|
|
float sampling_freq, |
|
|
float threshold, |
|
|
uint16_t fft_length) |
|
|
{ |
|
|
if (fft_matrix->rows != 1) { |
|
|
EIDSP_ERR(EIDSP_MATRIX_SIZE_MISMATCH); |
|
|
} |
|
|
|
|
|
if (output_matrix->cols != 2) { |
|
|
EIDSP_ERR(EIDSP_MATRIX_SIZE_MISMATCH); |
|
|
} |
|
|
|
|
|
if (output_matrix->rows == 0) { |
|
|
return EIDSP_OK; |
|
|
} |
|
|
|
|
|
int ret; |
|
|
|
|
|
int N = static_cast<int>(fft_length); |
|
|
float T = 1.0f / sampling_freq; |
|
|
|
|
|
EI_DSP_MATRIX(freq_space, 1, fft_matrix->cols); |
|
|
ret = numpy::linspace(0.0f, 1.0f / (2.0f * T), floor(N / 2), freq_space.buffer); |
|
|
if (ret != EIDSP_OK) { |
|
|
EIDSP_ERR(ret); |
|
|
} |
|
|
|
|
|
EI_DSP_MATRIX(peaks_matrix, output_matrix->rows * 10, 1); |
|
|
|
|
|
uint16_t peak_count; |
|
|
ret = find_peak_indexes(fft_matrix, &peaks_matrix, 0.0f, &peak_count); |
|
|
if (ret != EIDSP_OK) { |
|
|
EIDSP_ERR(ret); |
|
|
} |
|
|
|
|
|
|
|
|
ei_vector<freq_peak_t> peaks; |
|
|
for (uint8_t ix = 0; ix < peak_count; ix++) { |
|
|
freq_peak_t d; |
|
|
|
|
|
d.freq = freq_space.buffer[static_cast<uint32_t>(peaks_matrix.buffer[ix])]; |
|
|
d.amplitude = fft_matrix->buffer[static_cast<uint32_t>(peaks_matrix.buffer[ix])]; |
|
|
|
|
|
if (d.amplitude < threshold) { |
|
|
d.freq = 0.0f; |
|
|
d.amplitude = 0.0f; |
|
|
} |
|
|
peaks.push_back(d); |
|
|
} |
|
|
sort(peaks.begin(), peaks.end(), |
|
|
[](const freq_peak_t & a, const freq_peak_t & b) -> bool |
|
|
{ |
|
|
return a.amplitude > b.amplitude; |
|
|
}); |
|
|
|
|
|
|
|
|
for (size_t ix = peaks.size(); ix < output_matrix->rows; ix++) { |
|
|
freq_peak_t d; |
|
|
d.freq = 0; |
|
|
d.amplitude = 0; |
|
|
peaks.push_back(d); |
|
|
} |
|
|
|
|
|
for (size_t row = 0; row < output_matrix->rows; row++) { |
|
|
|
|
|
output_matrix->buffer[row * output_matrix->cols + 0] = peaks[row].freq; |
|
|
output_matrix->buffer[row * output_matrix->cols + 1] = peaks[row].amplitude; |
|
|
} |
|
|
|
|
|
return EIDSP_OK; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int spectral_power_edges( |
|
|
matrix_t *fft_matrix, |
|
|
matrix_t *freq_matrix, |
|
|
matrix_t *edges_matrix, |
|
|
matrix_t *output_matrix, |
|
|
float sampling_freq |
|
|
) { |
|
|
if (fft_matrix->rows != 1 || freq_matrix->rows != 1) { |
|
|
EIDSP_ERR(EIDSP_MATRIX_SIZE_MISMATCH); |
|
|
} |
|
|
|
|
|
if (edges_matrix->cols != 1) { |
|
|
EIDSP_ERR(EIDSP_MATRIX_SIZE_MISMATCH); |
|
|
} |
|
|
|
|
|
if (output_matrix->rows != edges_matrix->rows - 1 || output_matrix->cols != edges_matrix->cols) { |
|
|
EIDSP_ERR(EIDSP_MATRIX_SIZE_MISMATCH); |
|
|
} |
|
|
|
|
|
if (fft_matrix->cols != freq_matrix->cols) { |
|
|
EIDSP_ERR(EIDSP_MATRIX_SIZE_MISMATCH); |
|
|
} |
|
|
|
|
|
EI_DSP_MATRIX(buckets, 1, edges_matrix->rows - 1); |
|
|
EI_DSP_MATRIX(bucket_count, 1, edges_matrix->rows - 1); |
|
|
|
|
|
for (uint16_t ix = 0; ix < freq_matrix->cols; ix++) { |
|
|
float t = freq_matrix->buffer[ix]; |
|
|
float v = fft_matrix->buffer[ix]; |
|
|
|
|
|
|
|
|
for (uint16_t ex = 0; ex < edges_matrix->rows - 1; ex++) { |
|
|
if (t >= edges_matrix->buffer[ex] && t < edges_matrix->buffer[ex + 1]) { |
|
|
buckets.buffer[ex] += v; |
|
|
bucket_count.buffer[ex]++; |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
for (uint16_t ex = 0; ex < edges_matrix->rows - 1; ex++) { |
|
|
if (bucket_count.buffer[ex] == 0.0f) { |
|
|
output_matrix->buffer[ex] = 0.0f; |
|
|
} |
|
|
else { |
|
|
output_matrix->buffer[ex] = buckets.buffer[ex] / bucket_count.buffer[ex]; |
|
|
} |
|
|
} |
|
|
|
|
|
return EIDSP_OK; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int periodogram(matrix_t *input_matrix, matrix_t *out_fft_matrix, matrix_t *out_freq_matrix, float sampling_freq, uint16_t n_fft) |
|
|
{ |
|
|
if (input_matrix->rows != 1) { |
|
|
EIDSP_ERR(EIDSP_MATRIX_SIZE_MISMATCH); |
|
|
} |
|
|
|
|
|
if (out_fft_matrix->rows != 1 || out_fft_matrix->cols != static_cast<uint32_t>(n_fft / 2 + 1)) { |
|
|
EIDSP_ERR(EIDSP_MATRIX_SIZE_MISMATCH); |
|
|
} |
|
|
|
|
|
if (out_freq_matrix->rows != 1 || out_freq_matrix->cols != static_cast<uint32_t>(n_fft / 2 + 1)) { |
|
|
EIDSP_ERR(EIDSP_MATRIX_SIZE_MISMATCH); |
|
|
} |
|
|
|
|
|
if (input_matrix->buffer == NULL) { |
|
|
EIDSP_ERR(EIDSP_OUT_OF_MEM); |
|
|
} |
|
|
|
|
|
if (out_fft_matrix->buffer == NULL) { |
|
|
EIDSP_ERR(EIDSP_OUT_OF_MEM); |
|
|
} |
|
|
|
|
|
if (out_freq_matrix->buffer == NULL) { |
|
|
EIDSP_ERR(EIDSP_OUT_OF_MEM); |
|
|
} |
|
|
|
|
|
|
|
|
EI_DSP_MATRIX_B(welch_matrix, input_matrix->rows, input_matrix->cols, input_matrix->buffer); |
|
|
|
|
|
uint16_t nperseg = n_fft; |
|
|
|
|
|
if (n_fft > input_matrix->cols) { |
|
|
nperseg = input_matrix->cols; |
|
|
} |
|
|
|
|
|
else if (n_fft < input_matrix->cols) { |
|
|
welch_matrix.cols = n_fft; |
|
|
} |
|
|
|
|
|
EI_DSP_MATRIX(triage_segments, 1, nperseg); |
|
|
for (uint16_t ix = 0; ix < nperseg; ix++) { |
|
|
triage_segments.buffer[ix] = 1.0f; |
|
|
} |
|
|
|
|
|
float scale = 1.0f / (sampling_freq * nperseg); |
|
|
|
|
|
for (uint16_t ix = 0; ix < n_fft / 2 + 1; ix++) { |
|
|
out_freq_matrix->buffer[ix] = static_cast<float>(ix) * (1.0f / (n_fft * (1.0f / sampling_freq))); |
|
|
} |
|
|
|
|
|
int ret; |
|
|
|
|
|
|
|
|
EI_DSP_MATRIX(mean_matrix, 1, 1); |
|
|
ret = numpy::mean(&welch_matrix, &mean_matrix); |
|
|
if (ret != EIDSP_OK) { |
|
|
EIDSP_ERR(ret); |
|
|
} |
|
|
|
|
|
ret = numpy::subtract(&welch_matrix, &mean_matrix); |
|
|
if (ret != EIDSP_OK) { |
|
|
EIDSP_ERR(ret); |
|
|
} |
|
|
|
|
|
fft_complex_t *fft_output = (fft_complex_t*)ei_dsp_calloc((n_fft / 2 + 1) * sizeof(fft_complex_t), 1); |
|
|
ret = numpy::rfft(welch_matrix.buffer, welch_matrix.cols, fft_output, n_fft / 2 + 1, n_fft); |
|
|
if (ret != EIDSP_OK) { |
|
|
ei_dsp_free(fft_output, (n_fft / 2 + 1) * sizeof(fft_complex_t)); |
|
|
EIDSP_ERR(ret); |
|
|
} |
|
|
|
|
|
|
|
|
for (uint16_t ix = 0; ix < n_fft / 2 + 1; ix++) { |
|
|
fft_output[ix].r = (fft_output[ix].r * fft_output[ix].r) + |
|
|
(abs(fft_output[ix].i * fft_output[ix].i)); |
|
|
fft_output[ix].i = 0.0f; |
|
|
|
|
|
fft_output[ix].r *= scale; |
|
|
|
|
|
if (ix != n_fft / 2) { |
|
|
fft_output[ix].r *= 2; |
|
|
} |
|
|
|
|
|
|
|
|
out_fft_matrix->buffer[ix] = fft_output[ix].r; |
|
|
} |
|
|
|
|
|
ei_dsp_free(fft_output, (n_fft / 2 + 1) * sizeof(fft_complex_t)); |
|
|
|
|
|
return EIDSP_OK; |
|
|
} |
|
|
|
|
|
static int subtract_mean(matrix_t* input_matrix) { |
|
|
|
|
|
EI_DSP_MATRIX(mean_matrix, input_matrix->rows, 1); |
|
|
int ret = numpy::mean(input_matrix, &mean_matrix); |
|
|
if (ret != EIDSP_OK) { |
|
|
EIDSP_ERR(EIDSP_MATRIX_SIZE_MISMATCH); |
|
|
} |
|
|
|
|
|
|
|
|
ret = numpy::subtract(input_matrix, &mean_matrix); |
|
|
if (ret != EIDSP_OK) { |
|
|
EIDSP_ERR(EIDSP_MATRIX_SIZE_MISMATCH); |
|
|
} |
|
|
|
|
|
return EIDSP_OK; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
#endif |
|
|
|