From 1c0a9cfbcd66156e1c6d8585321658462bbacebd Mon Sep 17 00:00:00 2001 From: francovaro Date: Wed, 10 Jul 2024 16:14:37 +0200 Subject: [PATCH] SDK release v1.53.12 --- EdgeImpulse.EI-SDK.pdsc | 12 +- EdgeImpulse.pidx | 4 +- .../classifier/ei_classifier_types.h | 73 +++---- .../classifier/ei_fill_result_struct.h | 11 +- .../classifier/ei_run_classifier.h | 185 +++++++++--------- .../classifier/inferencing_engines/anomaly.h | 10 +- .../inferencing_engines/tflite_eon.h | 4 - .../inferencing_engines/tflite_micro.h | 4 - edgeimpulse/edge-impulse-sdk/dsp/ei_hr.hpp | 112 ++++++++--- .../edge-impulse-sdk/dsp/spectral/signal.hpp | 68 +++---- 10 files changed, 278 insertions(+), 205 deletions(-) diff --git a/EdgeImpulse.EI-SDK.pdsc b/EdgeImpulse.EI-SDK.pdsc index 825849b..86c5777 100644 --- a/EdgeImpulse.EI-SDK.pdsc +++ b/EdgeImpulse.EI-SDK.pdsc @@ -5,13 +5,16 @@ EI-SDK LICENSE-apache-2.0.txt Edge Impulse SDK - https://github.com/edgeimpulse/edge-impulse-sdk-pack/releases/download/v1.53.7/ + https://github.com/edgeimpulse/edge-impulse-sdk-pack/releases/download/v1.53.12/ hello@edgeimpulse.com https://github.com/edgeimpulse/edge-impulse-sdk-pack.git - + EI-SDK + + EI-SDK + EI-SDK @@ -98,9 +101,6 @@ EI-SDK - - - EI-SDK @@ -146,7 +146,7 @@ - + Edge Impulse SDK diff --git a/EdgeImpulse.pidx b/EdgeImpulse.pidx index ca0eb11..9203f30 100644 --- a/EdgeImpulse.pidx +++ b/EdgeImpulse.pidx @@ -2,8 +2,8 @@ EdgeImpulse https://raw.githubusercontent.com/edgeimpulse/edge-impulse-sdk-pack/main/ - 2024-07-05 09:15:15 + 2024-07-10 16:14:04 - + diff --git a/edgeimpulse/edge-impulse-sdk/classifier/ei_classifier_types.h b/edgeimpulse/edge-impulse-sdk/classifier/ei_classifier_types.h index b9404ea..b61141d 100644 --- a/edgeimpulse/edge-impulse-sdk/classifier/ei_classifier_types.h +++ b/edgeimpulse/edge-impulse-sdk/classifier/ei_classifier_types.h @@ -28,32 +28,32 @@ /** * @defgroup ei_structs Structs - * + * * Public-facing structs for Edge Impulse C++ SDK. - * + * * @addtogroup ei_structs * @{ */ /** * @brief Holds the output of inference, anomaly results, and timing information. - * + * * `ei_impulse_result_t` holds the output of `run_classifier()`. If object detection is * enabled, then the output results is a * pointer to an array of bounding boxes of size `bounding_boxes_count`, as given by * [ei_impulse_result_bounding_box_t](https://docs.edgeimpulse.com/reference/ei_impulse_result_bounding_box_t). * Otherwise, results are stored as an array of classification scores, as given by * [ei_impulse_result_classification_t](https://docs.edgeimpulse.com/reference/ei_impulse_result_classification_t). - * + * * If anomaly detection is enabled (e.g. `EI_CLASSIFIER_HAS_ANOMALY == 1`), then the * anomaly score will be stored as a floating point value in `anomaly`. - * - * Timing information is stored in an + * + * Timing information is stored in an * [ei_impulse_result_timing_t](https://docs.edgeimpulse.com/reference/ei_impulse_result_timing_t) * struct. - * + * * **Source**: [classifier/ei_classifier_types.h](https://github.com/edgeimpulse/inferencing-sdk-cpp/blob/master/classifier/ei_classifier_types.h) - * + * * **Example**: [standalone inferencing main.cpp](https://github.com/edgeimpulse/example-standalone-inferencing/blob/master/source/main.cpp) */ typedef struct { @@ -70,16 +70,16 @@ typedef struct { /** * @brief Holds the output of visual anomaly detection (FOMO-AD) - * + * * If visual anomaly detection is enabled (e.g. `EI_CLASSIFIER_HAS_VISUAL_ANOMALY == - * 1`), then the output results will be a pointer to an array of grid cells of size - * `visual_ad_count`, as given by + * 1`), then the output results will be a pointer to an array of grid cells of size + * `visual_ad_count`, as given by * [ei_impulse_result_bounding_box_t](https://docs.edgeimpulse.com/reference/ei_impulse_result_bounding_box_t). - * + * * The visual anomaly detection result is stored in `visual_ad_result`, which contains the mean and max values of the grid cells. - * + * * **Source**: [classifier/ei_classifier_types.h](https://github.com/edgeimpulse/inferencing-sdk-cpp/blob/master/classifier/ei_classifier_types.h) - * + * * **Example**: [standalone inferencing main.cpp](https://github.com/edgeimpulse/example-standalone-inferencing/blob/master/source/main.cpp) */ typedef struct { @@ -96,7 +96,7 @@ typedef struct { /** * @brief Holds information for a single bounding box. - * + * * If object detection is enabled (i.e. `EI_CLASSIFIER_OBJECT_DETECTION == 1`), then * inference results will be one or more bounding boxes. The bounding boxes with the * highest confidence scores (assuming those scores are equal to or greater than @@ -105,20 +105,20 @@ typedef struct { * least `EI_CLASSIFIER_OBJECT_DETECTION_COUNT`. The exact number of bounding boxes * is stored in `bounding_boxes_count` field of [ei_impulse_result_t]/C++ Inference * SDK Library/structs/ei_impulse_result_t.md). - * - * A bounding box is a rectangle that ideally surrounds the identified object. The + * + * A bounding box is a rectangle that ideally surrounds the identified object. The * (`x`, `y`) coordinates in the struct identify the top-left corner of the box. * `label` is the predicted class with the highest confidence score. `value` is the * confidence score between [0.0..1.0] of the given `label`. - * + * * **Source**: [classifier/ei_classifier_types.h](https://github.com/edgeimpulse/inferencing-sdk-cpp/blob/master/classifier/ei_classifier_types.h) - * + * * **Example**: [standalone inferencing main.cpp](https://github.com/edgeimpulse/example-standalone-inferencing/blob/master/source/main.cpp) */ typedef struct { /** - * Pointer to a character array describing the associated class of the given - * bounding box. Taken from one of the elements of + * Pointer to a character array describing the associated class of the given + * bounding box. Taken from one of the elements of * `ei_classifier_inferencing_categories[]`. */ const char *label; @@ -151,19 +151,19 @@ typedef struct { /** * @brief Holds timing information about the processing (DSP) and inference blocks. - * + * * Records timing information during the execution of the preprocessing (DSP) and * inference blocks. Can be used to determine if inference will meet timing requirements * on your particular platform. - * + * * **Source**: [classifier/ei_classifier_types.h](https://github.com/edgeimpulse/inferencing-sdk-cpp/blob/master/classifier/ei_classifier_types.h) - * + * * **Example**: [standalone inferencing main.cpp](https://github.com/edgeimpulse/example-standalone-inferencing/blob/master/source/main.cpp) */ typedef struct { /** * If using `run_impulse()` to perform sampling and inference, it is the amount of - * time (in milliseconds) it took to fetch raw samples. Not used for + * time (in milliseconds) it took to fetch raw samples. Not used for * `run_classifier()`. */ int sampling; @@ -203,23 +203,23 @@ typedef struct { /** * @brief Holds the output of inference, anomaly results, and timing information. - * + * * `ei_impulse_result_t` holds the output of `run_classifier()`. If object detection is * enabled (e.g. `EI_CLASSIFIER_OBJECT_DETECTION == 1`), then the output results is a * pointer to an array of bounding boxes of size `bounding_boxes_count`, as given by - * [ei_impulse_result_bounding_box_t](https://docs.edgeimpulse.com/reference/ei_impulse_result_bounding_box_t). + * [ei_impulse_result_bounding_box_t](https://docs.edgeimpulse.com/reference/ei_impulse_result_bounding_box_t). * Otherwise, results are stored as an array of classification scores, as given by * [ei_impulse_result_classification_t](https://docs.edgeimpulse.com/reference/ei_impulse_result_classification_t). - * + * * If anomaly detection is enabled (e.g. `EI_CLASSIFIER_HAS_ANOMALY == 1`), then the * anomaly score will be stored as a floating point value in `anomaly`. - * - * Timing information is stored in an - * [ei_impulse_result_timing_t](https://docs.edgeimpulse.com/reference/ei_impulse_result_timing_t) + * + * Timing information is stored in an + * [ei_impulse_result_timing_t](https://docs.edgeimpulse.com/reference/ei_impulse_result_timing_t) * struct. - * + * * **Source**: [classifier/ei_classifier_types.h](https://github.com/edgeimpulse/inferencing-sdk-cpp/blob/master/classifier/ei_classifier_types.h) - * + * * **Example**: [standalone inferencing main.cpp](https://github.com/edgeimpulse/example-standalone-inferencing/blob/master/source/main.cpp) */ typedef struct { @@ -238,13 +238,18 @@ typedef struct { * Array of classification results. If object detection is enabled, this will be * empty. */ +#ifdef EI_DSP_RESULT_OVERRIDE + // For CI only. We will create the array to hold results + ei_impulse_result_classification_t* classification; +#else #if EI_CLASSIFIER_LABEL_COUNT == 0 // EI_CLASSIFIER_LABEL_COUNT can be 0 for anomaly only models // to prevent compiler warnings/errors, we need to have at least one element ei_impulse_result_classification_t classification[1]; #else ei_impulse_result_classification_t classification[EI_CLASSIFIER_LABEL_COUNT]; -#endif +#endif // EI_CLASSIFIER_LABEL_COUNT == 0 +#endif // EI_DSP_RESULT_OVERRIDE else /** * Anomaly score. If anomaly detection is not enabled, this will be 0. A higher diff --git a/edgeimpulse/edge-impulse-sdk/classifier/ei_fill_result_struct.h b/edgeimpulse/edge-impulse-sdk/classifier/ei_fill_result_struct.h index b6f2b32..e3bfd04 100644 --- a/edgeimpulse/edge-impulse-sdk/classifier/ei_fill_result_struct.h +++ b/edgeimpulse/edge-impulse-sdk/classifier/ei_fill_result_struct.h @@ -374,7 +374,13 @@ __attribute__((unused)) static EI_IMPULSE_ERROR fill_result_struct_f32(const ei_ ei_impulse_result_t *result, float *data, bool debug) { - for (uint32_t ix = 0; ix < impulse->label_count; ix++) { +#ifdef EI_DSP_RESULT_OVERRIDE + uint32_t stop_count = EI_DSP_RESULT_OVERRIDE; +#else + uint32_t stop_count = impulse->label_count; +#endif + for (uint32_t ix = 0; ix < stop_count; ix++) { + float value = data[ix]; if (debug) { @@ -382,7 +388,10 @@ __attribute__((unused)) static EI_IMPULSE_ERROR fill_result_struct_f32(const ei_ ei_printf_float(value); ei_printf("\n"); } +// For testing purposes, we will have more values than labels +#ifndef EI_DSP_RESULT_OVERRIDE result->classification[ix].label = impulse->categories[ix]; +#endif result->classification[ix].value = value; } diff --git a/edgeimpulse/edge-impulse-sdk/classifier/ei_run_classifier.h b/edgeimpulse/edge-impulse-sdk/classifier/ei_run_classifier.h index 02a3523..d9b0e6f 100644 --- a/edgeimpulse/edge-impulse-sdk/classifier/ei_run_classifier.h +++ b/edgeimpulse/edge-impulse-sdk/classifier/ei_run_classifier.h @@ -237,7 +237,10 @@ extern "C" EI_IMPULSE_ERROR process_impulse(ei_impulse_handle_t *handle, } #endif +#ifndef EI_DSP_RESULT_OVERRIDE + // Don't wipe in CI, as we store a pointer memset(result, 0, sizeof(ei_impulse_result_t)); +#endif uint32_t block_num = handle->impulse->dsp_blocks_size + handle->impulse->learning_blocks_size; // smart pointer to features array @@ -745,25 +748,25 @@ will be documented by Doxygen. */ /** * @defgroup ei_functions Functions - * - * Public-facing functions for running inference using the Edge Impulse C++ library. - * + * + * Public-facing functions for running inference using the Edge Impulse C++ library. + * * **Source**: [classifier/ei_run_classifier.h](https://github.com/edgeimpulse/inferencing-sdk-cpp/blob/master/classifier/ei_run_classifier.h) - * + * * @addtogroup ei_functions * @{ */ /** - * @brief Initialize static variables for running preprocessing and inference + * @brief Initialize static variables for running preprocessing and inference * continuously. - * + * * Initializes and clears any internal static variables needed by `run_classifier_continuous()`. * This includes the moving average filter (MAF). This function should be called prior to * calling `run_classifier_continuous()`. - * + * * **Blocking**: yes - * + * * **Example**: [nano_ble33_sense_microphone_continuous.ino](https://github.com/edgeimpulse/example-lacuna-ls200/blob/main/nano_ble33_sense_microphone_continous/nano_ble33_sense_microphone_continuous.ino) */ extern "C" void run_classifier_init(void) @@ -786,17 +789,17 @@ extern "C" void run_classifier_init(void) } /** - * @brief Initialize static variables for running preprocessing and inference + * @brief Initialize static variables for running preprocessing and inference * continuously. - * + * * Initializes and clears any internal static variables needed by `run_classifier_continuous()`. * This includes the moving average filter (MAF). This function should be called prior to * calling `run_classifier_continuous()`. - * + * * **Blocking**: yes - * + * * **Example**: [nano_ble33_sense_microphone_continuous.ino](https://github.com/edgeimpulse/example-lacuna-ls200/blob/main/nano_ble33_sense_microphone_continous/nano_ble33_sense_microphone_continuous.ino) - * + * * @param[in] handle struct with information about model and DSP */ __attribute__((unused)) void run_classifier_init(ei_impulse_handle_t *handle) @@ -818,13 +821,13 @@ __attribute__((unused)) void run_classifier_init(ei_impulse_handle_t *handle) /** * @brief Deletes static variables when running preprocessing and inference continuously. - * + * * Deletes internal static variables used by `run_classifier_continuous()`, which * includes the moving average filter (MAF). This function should be called when you * are done running continuous classification. - * + * * **Blocking**: yes - * + * * **Example**: [ei_run_audio_impulse.cpp](https://github.com/edgeimpulse/firmware-nordic-thingy53/blob/main/src/inference/ei_run_audio_impulse.cpp) */ extern "C" void run_classifier_deinit(void) @@ -835,18 +838,18 @@ extern "C" void run_classifier_deinit(void) } /** - * @brief Run preprocessing (DSP) on new slice of raw features. Add output features + * @brief Run preprocessing (DSP) on new slice of raw features. Add output features * to rolling matrix and run inference on full sample. * - * Accepts a new slice of features give by the callback defined in the `signal` parameter. - * It performs preprocessing (DSP) on this new slice of features and appends the output to + * Accepts a new slice of features give by the callback defined in the `signal` parameter. + * It performs preprocessing (DSP) on this new slice of features and appends the output to * a sliding window of pre-processed features (stored in a static features matrix). The matrix - * stores the new slice and as many old slices as necessary to make up one full sample for + * stores the new slice and as many old slices as necessary to make up one full sample for * performing inference. - * - * `run_classifier_init()` must be called before making any calls to + * + * `run_classifier_init()` must be called before making any calls to * `run_classifier_continuous().` - * + * * For example, if you are doing keyword spotting on 1-second slices of audio and you want to * perform inference 4 times per second (given by `EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW`), you * would collect 0.25 seconds of audio and call run_classifier_continuous(). The function would @@ -854,33 +857,33 @@ extern "C" void run_classifier_deinit(void) * drop the oldest 0.25 seconds' worth of MFCCs from its internal matrix, and append the newest * slice of MFCCs. This process allows the library to keep track of the pre-processed features * (e.g. MFCCs) in the window instead of the entire set of raw features (e.g. raw audio data), - * which can potentially save a lot of space in RAM. After updating the static matrix, - * inference is performed using the whole matrix, which acts as a sliding window of + * which can potentially save a lot of space in RAM. After updating the static matrix, + * inference is performed using the whole matrix, which acts as a sliding window of * pre-processed features. - * - * Additionally, a moving average filter (MAF) can be enabled for `run_classifier_continuous()`, - * which averages (arithmetic mean) the last *n* inference results for each class. *n* is - * `EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW / 2`. In our example above, if we enabled the MAF, the + * + * Additionally, a moving average filter (MAF) can be enabled for `run_classifier_continuous()`, + * which averages (arithmetic mean) the last *n* inference results for each class. *n* is + * `EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW / 2`. In our example above, if we enabled the MAF, the * values in `result` would contain predictions averaged from the previous 2 inferences. - * - * To learn more about `run_classifier_continuous()`, see - * [this guide](https://docs.edgeimpulse.com/docs/tutorials/advanced-inferencing/continuous-audio-sampling) + * + * To learn more about `run_classifier_continuous()`, see + * [this guide](https://docs.edgeimpulse.com/docs/tutorials/advanced-inferencing/continuous-audio-sampling) * on continuous audio sampling. While the guide is written for audio signals, the concepts of continuous sampling and inference can be extrapolated to any time-series data. - * + * * **Blocking**: yes - * + * * **Example**: [nano_ble33_sense_microphone_continuous.ino](https://github.com/edgeimpulse/example-lacuna-ls200/blob/main/nano_ble33_sense_microphone_continous/nano_ble33_sense_microphone_continuous.ino) - * - * @param[in] signal Pointer to a signal_t struct that contains the number of elements in the - * slice of raw features (e.g. `EI_CLASSIFIER_SLICE_SIZE`) and a pointer to a callback that reads + * + * @param[in] signal Pointer to a signal_t struct that contains the number of elements in the + * slice of raw features (e.g. `EI_CLASSIFIER_SLICE_SIZE`) and a pointer to a callback that reads * in the slice of raw features. - * @param[out] result Pointer to an `ei_impulse_result_t` struct that contains the various output + * @param[out] result Pointer to an `ei_impulse_result_t` struct that contains the various output * results from inference after run_classifier() returns. - * @param[in] debug Print internal preprocessing and inference debugging information via + * @param[in] debug Print internal preprocessing and inference debugging information via * `ei_printf()`. * @param[in] enable_maf Enable the moving average filter (MAF) for the classifier. * - * @return Error code as defined by `EI_IMPULSE_ERROR` enum. Will be `EI_IMPULSE_OK` if inference + * @return Error code as defined by `EI_IMPULSE_ERROR` enum. Will be `EI_IMPULSE_OK` if inference * completed successfully. */ extern "C" EI_IMPULSE_ERROR run_classifier_continuous( @@ -894,18 +897,18 @@ extern "C" EI_IMPULSE_ERROR run_classifier_continuous( } /** - * @brief Run preprocessing (DSP) on new slice of raw features. Add output features + * @brief Run preprocessing (DSP) on new slice of raw features. Add output features * to rolling matrix and run inference on full sample. * - * Accepts a new slice of features give by the callback defined in the `signal` parameter. - * It performs preprocessing (DSP) on this new slice of features and appends the output to + * Accepts a new slice of features give by the callback defined in the `signal` parameter. + * It performs preprocessing (DSP) on this new slice of features and appends the output to * a sliding window of pre-processed features (stored in a static features matrix). The matrix - * stores the new slice and as many old slices as necessary to make up one full sample for + * stores the new slice and as many old slices as necessary to make up one full sample for * performing inference. - * - * `run_classifier_init()` must be called before making any calls to + * + * `run_classifier_init()` must be called before making any calls to * `run_classifier_continuous().` - * + * * For example, if you are doing keyword spotting on 1-second slices of audio and you want to * perform inference 4 times per second (given by `EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW`), you * would collect 0.25 seconds of audio and call run_classifier_continuous(). The function would @@ -913,34 +916,34 @@ extern "C" EI_IMPULSE_ERROR run_classifier_continuous( * drop the oldest 0.25 seconds' worth of MFCCs from its internal matrix, and append the newest * slice of MFCCs. This process allows the library to keep track of the pre-processed features * (e.g. MFCCs) in the window instead of the entire set of raw features (e.g. raw audio data), - * which can potentially save a lot of space in RAM. After updating the static matrix, - * inference is performed using the whole matrix, which acts as a sliding window of + * which can potentially save a lot of space in RAM. After updating the static matrix, + * inference is performed using the whole matrix, which acts as a sliding window of * pre-processed features. - * - * Additionally, a moving average filter (MAF) can be enabled for `run_classifier_continuous()`, - * which averages (arithmetic mean) the last *n* inference results for each class. *n* is - * `EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW / 2`. In our example above, if we enabled the MAF, the + * + * Additionally, a moving average filter (MAF) can be enabled for `run_classifier_continuous()`, + * which averages (arithmetic mean) the last *n* inference results for each class. *n* is + * `EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW / 2`. In our example above, if we enabled the MAF, the * values in `result` would contain predictions averaged from the previous 2 inferences. - * - * To learn more about `run_classifier_continuous()`, see - * [this guide](https://docs.edgeimpulse.com/docs/tutorials/advanced-inferencing/continuous-audio-sampling) + * + * To learn more about `run_classifier_continuous()`, see + * [this guide](https://docs.edgeimpulse.com/docs/tutorials/advanced-inferencing/continuous-audio-sampling) * on continuous audio sampling. While the guide is written for audio signals, the concepts of continuous sampling and inference can be extrapolated to any time-series data. - * + * * **Blocking**: yes - * + * * **Example**: [nano_ble33_sense_microphone_continuous.ino](https://github.com/edgeimpulse/example-lacuna-ls200/blob/main/nano_ble33_sense_microphone_continous/nano_ble33_sense_microphone_continuous.ino) - * + * * @param[in] impulse `ei_impulse_handle_t` struct with information about preprocessing and model. - * @param[in] signal Pointer to a signal_t struct that contains the number of elements in the - * slice of raw features (e.g. `EI_CLASSIFIER_SLICE_SIZE`) and a pointer to a callback that reads + * @param[in] signal Pointer to a signal_t struct that contains the number of elements in the + * slice of raw features (e.g. `EI_CLASSIFIER_SLICE_SIZE`) and a pointer to a callback that reads * in the slice of raw features. - * @param[out] result Pointer to an `ei_impulse_result_t` struct that contains the various output + * @param[out] result Pointer to an `ei_impulse_result_t` struct that contains the various output * results from inference after run_classifier() returns. - * @param[in] debug Print internal preprocessing and inference debugging information via + * @param[in] debug Print internal preprocessing and inference debugging information via * `ei_printf()`. * @param[in] enable_maf Enable the moving average filter (MAF) for the classifier. * - * @return Error code as defined by `EI_IMPULSE_ERROR` enum. Will be `EI_IMPULSE_OK` if inference + * @return Error code as defined by `EI_IMPULSE_ERROR` enum. Will be `EI_IMPULSE_OK` if inference * completed successfully. */ __attribute__((unused)) EI_IMPULSE_ERROR run_classifier_continuous( @@ -955,19 +958,19 @@ __attribute__((unused)) EI_IMPULSE_ERROR run_classifier_continuous( /** * @brief Run the classifier over a raw features array. - * - * + * + * * Overloaded function [run_classifier()](#run_classifier-1) that defaults to the single impulse. - * + * * **Blocking**: yes - * - * @param[in] signal Pointer to a `signal_t` struct that contains the total length of the raw + * + * @param[in] signal Pointer to a `signal_t` struct that contains the total length of the raw * feature array, which must match EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, and a pointer to a callback * that reads in the raw features. - * @param[out] result Pointer to an ei_impulse_result_t struct that will contain the various output + * @param[out] result Pointer to an ei_impulse_result_t struct that will contain the various output * results from inference after `run_classifier()` returns. * @param[in] debug Print internal preprocessing and inference debugging information via `ei_printf()`. - * + * * @return Error code as defined by `EI_IMPULSE_ERROR` enum. Will be `EI_IMPULSE_OK` if inference * completed successfully. */ @@ -981,26 +984,26 @@ extern "C" EI_IMPULSE_ERROR run_classifier( /** * @brief Run the classifier over a raw features array. - * - * - * Accepts a `signal_t` input struct pointing to a callback that reads in pages of raw features. - * `run_classifier()` performs any necessary preprocessing on the raw features (e.g. DSP, cropping - * of images, etc.) before performing inference. Results from inference are stored in an + * + * + * Accepts a `signal_t` input struct pointing to a callback that reads in pages of raw features. + * `run_classifier()` performs any necessary preprocessing on the raw features (e.g. DSP, cropping + * of images, etc.) before performing inference. Results from inference are stored in an * `ei_impulse_result_t` struct. - * + * * **Blocking**: yes - * + * * **Example**: [standalone inferencing main.cpp](https://github.com/edgeimpulse/example-standalone-inferencing/blob/master/source/main.cpp) - * + * * @param[in] impulse Pointer to an `ei_impulse_handle_t` struct that contains the model and * preprocessing information. - * @param[in] signal Pointer to a `signal_t` struct that contains the total length of the raw + * @param[in] signal Pointer to a `signal_t` struct that contains the total length of the raw * feature array, which must match EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, and a pointer to a callback * that reads in the raw features. - * @param[out] result Pointer to an ei_impulse_result_t struct that will contain the various output + * @param[out] result Pointer to an ei_impulse_result_t struct that will contain the various output * results from inference after `run_classifier()` returns. * @param[in] debug Print internal preprocessing and inference debugging information via `ei_printf()`. - * + * * @return Error code as defined by `EI_IMPULSE_ERROR` enum. Will be `EI_IMPULSE_OK` if inference * completed successfully. */ @@ -1023,17 +1026,17 @@ Do not use these - if possible, change your code to reflect the upcoming changes #if EIDSP_SIGNAL_C_FN_POINTER == 0 /** - * @brief Run the impulse, if you provide an instance of sampler it will also persist + * @brief Run the impulse, if you provide an instance of sampler it will also persist * the data for you. - * - * @deprecated This function is deprecated and will be removed in future versions. Use + * + * @deprecated This function is deprecated and will be removed in future versions. Use * `run_classifier()` instead. - * + * * @param[in] sampler Instance to an **initialized** sampler * @param[out] result Object to store the results in * @param[in] data_fn Callback function to retrieve data from sensors * @param[in] debug Whether to log debug messages (default false) - * + * * @return Error code as defined by `EI_IMPULSE_ERROR` enum. Will be `EI_IMPULSE_OK` if inference * completed successfully. */ @@ -1099,14 +1102,14 @@ __attribute__((unused)) EI_IMPULSE_ERROR run_impulse( #if (defined(EI_CLASSIFIER_HAS_SAMPLER) && EI_CLASSIFIER_HAS_SAMPLER == 1) || defined(__DOXYGEN__) /** * @brief Run the impulse, does not persist data. - * - * @deprecated This function is deprecated and will be removed in future versions. Use + * + * @deprecated This function is deprecated and will be removed in future versions. Use * `run_classifier()` instead. - * + * * @param[out] result Object to store the results in * @param[in] data_fn Callback function to retrieve data from sensors * @param[out] debug Whether to log debug messages (default false) - * + * * @return Error code as defined by `EI_IMPULSE_ERROR` enum. Will be `EI_IMPULSE_OK` if inference * completed successfully. */ diff --git a/edgeimpulse/edge-impulse-sdk/classifier/inferencing_engines/anomaly.h b/edgeimpulse/edge-impulse-sdk/classifier/inferencing_engines/anomaly.h index 5a800eb..7e595bf 100644 --- a/edgeimpulse/edge-impulse-sdk/classifier/inferencing_engines/anomaly.h +++ b/edgeimpulse/edge-impulse-sdk/classifier/inferencing_engines/anomaly.h @@ -163,7 +163,7 @@ EI_IMPULSE_ERROR run_kmeans_anomaly( { ei_learning_block_config_anomaly_kmeans_t *block_config = (ei_learning_block_config_anomaly_kmeans_t*)config_ptr; - uint64_t anomaly_start_ms = ei_read_timer_ms(); + uint64_t anomaly_start_us = ei_read_timer_us(); float *input = (float*)ei_malloc(block_config->anom_axes_size * sizeof(float)); if (!input) { @@ -177,15 +177,16 @@ EI_IMPULSE_ERROR run_kmeans_anomaly( float anomaly = get_min_distance_to_cluster( input, block_config->anom_axes_size, block_config->anom_clusters, block_config->anom_cluster_count); - uint64_t anomaly_end_ms = ei_read_timer_ms(); + uint64_t anomaly_end_us = ei_read_timer_us(); if (debug) { - ei_printf("Anomaly score (time: %d ms.): ", static_cast(anomaly_end_ms - anomaly_start_ms)); + ei_printf("Anomaly score (time: %d ms.): ", static_cast(anomaly_end_us - anomaly_start_us)); ei_printf_float(anomaly); ei_printf("\n"); } - result->timing.anomaly = anomaly_end_ms - anomaly_start_ms; + result->timing.anomaly_us = anomaly_end_us - anomaly_start_us; + result->timing.anomaly = (int)(result->timing.anomaly_us/1000); result->anomaly = anomaly; ei_free(input); @@ -258,6 +259,7 @@ EI_IMPULSE_ERROR run_gmm_anomaly( ei_printf("\n"); } + result->timing.anomaly_us = anomaly_result.timing.classification_us; result->timing.anomaly = anomaly_result.timing.classification; if (block_config->classification_mode == EI_CLASSIFIER_CLASSIFICATION_MODE_VISUAL_ANOMALY) { diff --git a/edgeimpulse/edge-impulse-sdk/classifier/inferencing_engines/tflite_eon.h b/edgeimpulse/edge-impulse-sdk/classifier/inferencing_engines/tflite_eon.h index c1053e0..2eacaa9 100644 --- a/edgeimpulse/edge-impulse-sdk/classifier/inferencing_engines/tflite_eon.h +++ b/edgeimpulse/edge-impulse-sdk/classifier/inferencing_engines/tflite_eon.h @@ -261,8 +261,6 @@ EI_IMPULSE_ERROR run_nn_inference( graph_config->model_reset(ei_aligned_free); - result->timing.classification_us = ei_read_timer_us() - ctx_start_us; - if (run_res != EI_IMPULSE_OK) { return run_res; } @@ -361,8 +359,6 @@ EI_IMPULSE_ERROR run_nn_inference_image_quantized( return run_res; } - result->timing.classification_us = ei_read_timer_us() - ctx_start_us; - return EI_IMPULSE_OK; } #endif // EI_CLASSIFIER_QUANTIZATION_ENABLED == 1 diff --git a/edgeimpulse/edge-impulse-sdk/classifier/inferencing_engines/tflite_micro.h b/edgeimpulse/edge-impulse-sdk/classifier/inferencing_engines/tflite_micro.h index fd3cb26..4710b74 100644 --- a/edgeimpulse/edge-impulse-sdk/classifier/inferencing_engines/tflite_micro.h +++ b/edgeimpulse/edge-impulse-sdk/classifier/inferencing_engines/tflite_micro.h @@ -331,8 +331,6 @@ EI_IMPULSE_ERROR run_nn_inference( } } - result->timing.classification_us = ei_read_timer_us() - ctx_start_us; - if (run_res != EI_IMPULSE_OK) { return run_res; } @@ -427,8 +425,6 @@ EI_IMPULSE_ERROR run_nn_inference_image_quantized( return run_res; } - result->timing.classification_us = ei_read_timer_us() - ctx_start_us; - return EI_IMPULSE_OK; } #endif // EI_CLASSIFIER_QUANTIZATION_ENABLED == 1 diff --git a/edgeimpulse/edge-impulse-sdk/dsp/ei_hr.hpp b/edgeimpulse/edge-impulse-sdk/dsp/ei_hr.hpp index 6570f9b..816191e 100644 --- a/edgeimpulse/edge-impulse-sdk/dsp/ei_hr.hpp +++ b/edgeimpulse/edge-impulse-sdk/dsp/ei_hr.hpp @@ -26,6 +26,28 @@ #include "edge-impulse-sdk/dsp/numpy.hpp" #include "edge-impulse-sdk/dsp/ei_dsp_handle.h" #include "edge-impulse-enterprise/hr/hr_ppg.hpp" +#include "edge-impulse-enterprise/hr/hrv.hpp" +#include "edge-impulse-sdk/dsp/memory.hpp" + +// Need a wrapper to get ei_malloc used +// cppyy didn't like this override for some reason +class hrv_wrap : public ei::hrv::beats_to_hrv { +public: + // Boilerplate below here + void* operator new(size_t size) { + // Custom memory allocation logic here + return ei_malloc(size); + } + + void operator delete(void* ptr) { + // Custom memory deallocation logic here + ei_free(ptr); + } + // end boilerplate + + // Use the same constructor as the parent + using ei::hrv::beats_to_hrv::beats_to_hrv; +}; class hr_class : public DspHandle { public: @@ -37,32 +59,71 @@ class hr_class : public DspHandle { int extract(ei::signal_t *signal, ei::matrix_t *output_matrix, void *config_ptr, const float frequency) override { using namespace ei; - // Don't need just yet - // ei_dsp_config_hr_t config = *((ei_dsp_config_hr_t*)config_ptr); - - - // TODO fix for axes / accel - size_t samples_per_inc = ppg.win_inc_samples; - // TODO go in a loop for the full window size, once I can actually test this vs studio - if(signal->total_length != samples_per_inc) { - return EIDSP_BUFFER_SIZE_MISMATCH; + // Using reference avoids a copy + ei_dsp_config_hr_t& config = *((ei_dsp_config_hr_t*)config_ptr); + size_t floats_per_inc = ppg.win_inc_samples * ppg.axes; + size_t hrv_inc_samples = config.hrv_update_interval_s * frequency * ppg.axes; + // Greater than b/c can have multiple increments (HRs) per window + assert(signal->total_length >= floats_per_inc); + + int out_idx = 0; + size_t hrv_count = 0; + for(size_t i = 0; i < signal->total_length; i += floats_per_inc) { + // TODO ask for smaller increments and bp them into place + // Copy into the end of the buffer + matrix_t temp(ppg.win_inc_samples, ppg.axes); + signal->get_data(i, floats_per_inc, temp.buffer); + auto hr = ppg.stream(&temp); + if(!hrv || (hrv && config.include_hr)) { + output_matrix->buffer[out_idx++] = hr; + } + if(hrv) { + auto peaks = ppg.get_peaks(); + hrv->add_streaming_beats(peaks); + hrv_count += floats_per_inc; + if(hrv_count >= hrv_inc_samples) { + fvec features = hrv->get_hrv_features(0); + for(size_t j = 0; j < features.size(); j++) { + output_matrix->buffer[out_idx++] = features[j]; + } + hrv_count = 0; + } + } } - - // TODO ask for smaller increments and bp them into place - // Copy into the end of the buffer - matrix_t temp(ppg.axes, samples_per_inc); - signal->get_data(0, samples_per_inc, temp.buffer); - - - output_matrix->buffer[0] = ppg.stream(&temp); - - output_matrix->rows = 1; - output_matrix->cols = 1; return EIDSP_OK; } - // TODO: actually read in config: axes too! - hr_class(float frequency) : ppg(frequency, 1, 8*50, 2*50, true) { + hr_class(ei_dsp_config_hr_t* config, float frequency) + : ppg(frequency, + config->axes, + frequency * 8, // TODO variable window + frequency * 2, // TODO variable overlap + config->filter_preset, + config->acc_resting_std, + config->sensitivity, + true), hrv(nullptr) + { + auto table = config->named_axes; + for( size_t i = 0; i < config->named_axes_size; i++ ) { + ppg.set_offset_table(i, table[i].axis); + } + // if not "none" + if(strcmp(config->hrv_features,"none") != 0) { + // new is overloaded to use ei_malloc + hrv = new hrv_wrap( + frequency, + config->hrv_features, + config->hrv_update_interval_s, + config->hrv_win_size_s, + 2); // TODO variable window? + } + } + + ~hr_class() { + if(hrv) { + // delete is overloaded to use ei_free + delete hrv; + } } // Boilerplate below here @@ -80,13 +141,12 @@ class hr_class : public DspHandle { // end boilerplate private: ei::hr_ppg ppg; + hrv_wrap* hrv; // pointer b/c we don't always need it }; DspHandle* hr_class::create(void* config_in, float frequency) { // NOLINT def in header is OK at EI - // Don't need just yet - // auto config = reinterpret_cast(config_in); - // TODO: actually read in config - return new hr_class(frequency); + auto config = reinterpret_cast(config_in); + return new hr_class(config, frequency); }; /* diff --git a/edgeimpulse/edge-impulse-sdk/dsp/spectral/signal.hpp b/edgeimpulse/edge-impulse-sdk/dsp/spectral/signal.hpp index 37fb0e9..4827181 100644 --- a/edgeimpulse/edge-impulse-sdk/dsp/spectral/signal.hpp +++ b/edgeimpulse/edge-impulse-sdk/dsp/spectral/signal.hpp @@ -38,7 +38,7 @@ class signal { public: using fvec = ei_vector; - static void scale(fvec &x, float a) + static void scale(fvec& x, float a) { for (size_t ix = 0; ix < x.size(); ix++) { x[ix] *= a; @@ -58,12 +58,12 @@ class signal { * @param zi Initial conditions */ static void decimate_simple( - const fvec &input, - fvec &output, + const fvec& input, + fvec& output, size_t factor, - const fvec &b, - const fvec &a, - const fvec &zi) + const fvec& b, + const fvec& a, + const fvec& zi) { fvec d = zi; scale(d, input[0]); @@ -85,19 +85,18 @@ class signal { } struct sosfilt { - const float *coeff; // 6 * num_sections coefficients - float* zi; + const float* coeff; // 6 * num_sections coefficients fvec zi_vec; // 2 * num_sections initial conditions size_t num_sections; - sosfilt(const float *coeff_, const float *zi_, size_t num_sections_) + sosfilt(const float* coeff_, const float* zi_, size_t num_sections_) : coeff(coeff_), - zi_vec(zi_, zi_ + (num_sections_ * 2)), - num_sections(num_sections_) + zi_vec(zi_, zi_ + (num_sections_ * 2)), + num_sections(num_sections_) { } - void update(const float *coeff_, const float *zi_) + void update(const float* coeff_, const float* zi_) { coeff = coeff_; zi_vec.assign(zi_, zi_ + (num_sections * 2)); @@ -110,7 +109,7 @@ class signal { * @param output Output signal. Can be the same as input for in place * @param x_size Minimum size of input and output signal */ - void run(const float *input, const size_t size, float* output) + void run(const float* input, const size_t size, float* output) { assert(num_sections > 0); @@ -130,8 +129,8 @@ class signal { void init(float x0) { for (size_t sect = 0; sect < num_sections; sect++) { - zi_vec.data()[sect * 2] *= x0; - zi_vec.data()[sect * 2 + 1] *= x0; + zi_vec[sect * 2] *= x0; + zi_vec[sect * 2 + 1] *= x0; } } }; @@ -145,12 +144,12 @@ class signal { * @param sos Second-order section */ static void decimate_simple( - const float *input, + const float* input, const size_t input_size, - float *output, + float* output, const size_t output_size, size_t factor, - sosfilt &sos) + sosfilt& sos) { sos.init(input[0]); @@ -176,7 +175,7 @@ class signal { * @param a Denominator coefficients * @param zi Initial conditions */ - static void lfilter(const fvec &b, const fvec &a, const fvec &x, fvec &y, fvec &d) + static void lfilter(const fvec& b, const fvec& a, const fvec& x, fvec& y, fvec& d) { /* a[0]*y[n] = b[0] * x[n] + d[0][n-1] @@ -205,7 +204,7 @@ class signal { } } - static void iir2(const float *x, float *y, size_t n, const float *b, const float *a, float *d) + static void iir2(const float* x, float* y, size_t n, const float* b, const float* a, float* d) { /* a[0]*y[n] = b[0] * x[n] + d[0][n-1] @@ -236,7 +235,7 @@ class signal { * @param y Output signal * @param h FIR coefficients */ - static void upfirdn(const float * x, size_t x_size, fvec &y, int up, int down, const fvec &h) + static void upfirdn(const float* x, size_t x_size, fvec& y, int up, int down, const fvec& h) { assert(up > 0); assert(down > 0); @@ -296,7 +295,7 @@ class signal { * @param output Output signal, will be moved from an internal vector sized correctly. * @param window FIR coefficients. e.g. signal.firwin(2 * half_len + 1, f_c, window=('kaiser', 5.0)) */ - static void resample_poly(const float* input, size_t input_size, fvec &output, int up, int down, const fvec &window) + static void resample_poly(const float* input, size_t input_size, fvec& output, int up, int down, const fvec& window) { assert(up > 0); assert(down > 0); @@ -323,28 +322,31 @@ class signal { } static void calc_decimation_ratios( - const char *filter_type, + const char* filter_type, float filter_cutoff, float sample_rate, - std::vector &ratios) + std::vector& ratios) { if (strcmp(filter_type, "low") == 0) { - ratios = {1}; + ratios = { 1 }; return; } - static const std::vector supported = {1000, 100, 30, 10, 3}; + static const std::vector supported = { 1000, 100, 30, 10, 3 }; for (size_t i = 0; i < supported.size(); i++) { const int r = supported[i]; if (sample_rate * 0.5f / r > filter_cutoff) { if (r == 3 || r == 10) { - ratios = {r}; - } else if (r == 30) { - ratios = {3, 10}; - } else if (r == 100) { - ratios = {10, 10}; - } else if (r == 1000) { - ratios = {10, 10, 10}; + ratios = { r }; + } + else if (r == 30) { + ratios = { 3, 10 }; + } + else if (r == 100) { + ratios = { 10, 10 }; + } + else if (r == 1000) { + ratios = { 10, 10, 10 }; } return; }