| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| #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 |
|
|