/* Edge Impulse inferencing library
 * Copyright (c) 2021 EdgeImpulse Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#ifndef _EDGE_IMPULSE_RUN_CLASSIFIER_H_
#define _EDGE_IMPULSE_RUN_CLASSIFIER_H_

#include "model-parameters/model_metadata.h"

#if EI_CLASSIFIER_HAS_ANOMALY == 1
#include "model-parameters/anomaly_clusters.h"
#endif
#include "ei_run_dsp.h"
#include "ei_classifier_types.h"
#include "ei_classifier_smooth.h"
#include "ei_signal_with_axes.h"
#if defined(EI_CLASSIFIER_HAS_SAMPLER) && EI_CLASSIFIER_HAS_SAMPLER == 1
#include "ei_sampler.h"
#endif
#include "edge-impulse-sdk/porting/ei_classifier_porting.h"
#include "model-parameters/dsp_blocks.h"

#if (EI_CLASSIFIER_INFERENCING_ENGINE == EI_CLASSIFIER_TFLITE) && (EI_CLASSIFIER_COMPILED != 1)
#include <cmath>
#include "edge-impulse-sdk/tensorflow/lite/micro/all_ops_resolver.h"
#include "edge-impulse-sdk/tensorflow/lite/micro/micro_error_reporter.h"
#include "edge-impulse-sdk/tensorflow/lite/micro/micro_interpreter.h"
#include "edge-impulse-sdk/tensorflow/lite/schema/schema_generated.h"
#include "edge-impulse-sdk/tensorflow/lite/version.h"
#include "edge-impulse-sdk/classifier/ei_aligned_malloc.h"

#include "tflite-model/tflite-trained.h"
#if defined(EI_CLASSIFIER_HAS_TFLITE_OPS_RESOLVER) && EI_CLASSIFIER_HAS_TFLITE_OPS_RESOLVER == 1
#include "tflite-model/tflite-resolver.h"
#endif // EI_CLASSIFIER_HAS_TFLITE_OPS_RESOLVER

#if defined(EI_CLASSIFIER_ENABLE_DETECTION_POSTPROCESS_OP)
namespace tflite {
namespace ops {
namespace micro {
extern TfLiteRegistration *Register_TFLite_Detection_PostProcess(void);
}  // namespace micro
}  // namespace ops

extern float post_process_boxes[10 * 4 * sizeof(float)];
extern float post_process_classes[10];
extern float post_process_scores[10];

}  // namespace tflite
#endif

static tflite::MicroErrorReporter micro_error_reporter;
static tflite::ErrorReporter* error_reporter = &micro_error_reporter;
#elif EI_CLASSIFIER_COMPILED == 1
#include "edge-impulse-sdk/tensorflow/lite/c/common.h"
#include "edge-impulse-sdk/tensorflow/lite/kernels/internal/tensor_ctypes.h"
#include "tflite-model/trained_model_compiled.h"
#include "edge-impulse-sdk/classifier/ei_aligned_malloc.h"

namespace tflite {
extern float post_process_boxes[10 * 4 * sizeof(float)];
extern float post_process_classes[10];
extern float post_process_scores[10];
} // namespace tflite

#elif EI_CLASSIFIER_INFERENCING_ENGINE == EI_CLASSIFIER_TFLITE_FULL

#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/kernels/register.h"
#include "tensorflow/lite/model.h"
#include "tensorflow/lite/optional_debug_tools.h"
#include "tflite-model/tflite-trained.h"

#elif (EI_CLASSIFIER_INFERENCING_ENGINE == EI_CLASSIFIER_TENSORRT)
#include <stdlib.h>
#include "tflite-model/onnx-trained.h"
#include "tflite/linux-jetson-nano/libeitrt.h"
EiTrt* ei_trt_handle = NULL;

#elif EI_CLASSIFIER_INFERENCING_ENGINE == EI_CLASSIFIER_NONE
// noop
#else
#error "Unknown inferencing engine"
#endif

#if ECM3532
void*   __dso_handle = (void*) &__dso_handle;
#endif

#ifdef __cplusplus
namespace {
#endif // __cplusplus

/* Function prototypes ----------------------------------------------------- */
extern "C" EI_IMPULSE_ERROR run_inference(ei::matrix_t *fmatrix, ei_impulse_result_t *result, bool debug);
extern "C" EI_IMPULSE_ERROR run_classifier_image_quantized(signal_t *signal, ei_impulse_result_t *result, bool debug);
static EI_IMPULSE_ERROR can_run_classifier_image_quantized();
static void calc_cepstral_mean_and_var_normalization_mfcc(ei_matrix *matrix, void *config_ptr);
static void calc_cepstral_mean_and_var_normalization_mfe(ei_matrix *matrix, void *config_ptr);
static void calc_cepstral_mean_and_var_normalization_spectrogram(ei_matrix *matrix, void *config_ptr);

/* Private variables ------------------------------------------------------- */
#if EI_CLASSIFIER_LABEL_COUNT > 0
ei_impulse_maf classifier_maf[EI_CLASSIFIER_LABEL_COUNT] = {{0}};
#else
ei_impulse_maf classifier_maf[0];
#endif

static uint64_t classifier_continuous_features_written = 0;

/* Private functions ------------------------------------------------------- */

/**
 * @brief      Run a moving average filter over the classification result.
 *             The size of the filter determines the response of the filter.
 *             It is now set to the number of slices per window.
 * @param      maf             Pointer to maf object
 * @param[in]  classification  Classification output on current slice
 *
 * @return     Averaged classification value
 */
extern "C" float run_moving_average_filter(ei_impulse_maf *maf, float classification)
{
    maf->running_sum -= maf->maf_buffer[maf->buf_idx];
    maf->running_sum += classification;
    maf->maf_buffer[maf->buf_idx] = classification;

    if (++maf->buf_idx >= (EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW >> 1)) {
        maf->buf_idx = 0;
    }

#if (EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW > 1)
    return maf->running_sum / (float)(EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW >> 1);
#else
    return maf->running_sum;
#endif
}

/**
 * @brief      Reset all values in filter to 0
 *
 * @param      maf   Pointer to maf object
 */
static void clear_moving_average_filter(ei_impulse_maf *maf)
{
    maf->running_sum = 0;

    for (int i = 0; i < (EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW >> 1); i++) {
        maf->maf_buffer[i] = 0.f;
    }
}

/**
 * @brief      Init static vars
 */
extern "C" void run_classifier_init(void)
{
    classifier_continuous_features_written = 0;
    ei_dsp_clear_continuous_audio_state();

    for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
        clear_moving_average_filter(&classifier_maf[ix]);
    }
}

/**
 * @brief      Fill the complete matrix with sample slices. From there, run inference
 *             on the matrix.
 *
 * @param      signal  Sample data
 * @param      result  Classification output
 * @param[in]  debug   Debug output enable boot
 * @param      enable_maf Enables the moving average filter
 *
 * @return     The ei impulse error.
 */
extern "C" EI_IMPULSE_ERROR run_classifier_continuous(signal_t *signal, ei_impulse_result_t *result,
                                                      bool debug = false, bool enable_maf = true)
{
    static ei::matrix_t static_features_matrix(1, EI_CLASSIFIER_NN_INPUT_FRAME_SIZE);
    if (!static_features_matrix.buffer) {
        return EI_IMPULSE_ALLOC_FAILED;
    }

    EI_IMPULSE_ERROR ei_impulse_error = EI_IMPULSE_OK;

    uint64_t dsp_start_ms = ei_read_timer_ms();

    size_t out_features_index = 0;
    bool is_mfcc = false;
    bool is_mfe = false;
    bool is_spectrogram = false;

    for (size_t ix = 0; ix < ei_dsp_blocks_size; ix++) {
        ei_model_dsp_t block = ei_dsp_blocks[ix];

        if (out_features_index + block.n_output_features > EI_CLASSIFIER_NN_INPUT_FRAME_SIZE) {
            ei_printf("ERR: Would write outside feature buffer\n");
            return EI_IMPULSE_DSP_ERROR;
        }

        ei::matrix_t fm(1, block.n_output_features,
                        static_features_matrix.buffer + out_features_index);

        int (*extract_fn_slice)(ei::signal_t *signal, ei::matrix_t *output_matrix, void *config, const float frequency, matrix_size_t *out_matrix_size);

        /* Switch to the slice version of the mfcc feature extract function */
        if (block.extract_fn == extract_mfcc_features) {
            extract_fn_slice = &extract_mfcc_per_slice_features;
            is_mfcc = true;
        }
        else if (block.extract_fn == extract_spectrogram_features) {
            extract_fn_slice = &extract_spectrogram_per_slice_features;
            is_spectrogram = true;
        }
        else if (block.extract_fn == extract_mfe_features) {
            extract_fn_slice = &extract_mfe_per_slice_features;
            is_mfe = true;
        }
        else {
            ei_printf("ERR: Unknown extract function, only MFCC, MFE and spectrogram supported\n");
            return EI_IMPULSE_DSP_ERROR;
        }

        matrix_size_t features_written;

#if EIDSP_SIGNAL_C_FN_POINTER
        if (block.axes_size != EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME) {
            ei_printf("ERR: EIDSP_SIGNAL_C_FN_POINTER can only be used when all axes are selected for DSP blocks\n");
            return EI_IMPULSE_DSP_ERROR;
        }
        int ret = extract_fn_slice(signal, &fm, block.config, EI_CLASSIFIER_FREQUENCY, &features_written);
#else
        SignalWithAxes swa(signal, block.axes, block.axes_size);
        int ret = extract_fn_slice(swa.get_signal(), &fm, block.config, EI_CLASSIFIER_FREQUENCY, &features_written);
#endif

        if (ret != EIDSP_OK) {
            ei_printf("ERR: Failed to run DSP process (%d)\n", ret);
            return EI_IMPULSE_DSP_ERROR;
        }

        if (ei_run_impulse_check_canceled() == EI_IMPULSE_CANCELED) {
            return EI_IMPULSE_CANCELED;
        }

        classifier_continuous_features_written += (features_written.rows * features_written.cols);

        out_features_index += block.n_output_features;
    }

    result->timing.dsp = ei_read_timer_ms() - dsp_start_ms;

    if (debug) {
        ei_printf("\r\nFeatures (%d ms.): ", result->timing.dsp);
        for (size_t ix = 0; ix < static_features_matrix.cols; ix++) {
            ei_printf_float(static_features_matrix.buffer[ix]);
            ei_printf(" ");
        }
        ei_printf("\n");
    }

    if (classifier_continuous_features_written >= EI_CLASSIFIER_NN_INPUT_FRAME_SIZE) {
        dsp_start_ms = ei_read_timer_ms();
        ei::matrix_t classify_matrix(1, EI_CLASSIFIER_NN_INPUT_FRAME_SIZE);

        /* Create a copy of the matrix for normalization */
        for (size_t m_ix = 0; m_ix < EI_CLASSIFIER_NN_INPUT_FRAME_SIZE; m_ix++) {
            classify_matrix.buffer[m_ix] = static_features_matrix.buffer[m_ix];
        }

        if (is_mfcc) {
            calc_cepstral_mean_and_var_normalization_mfcc(&classify_matrix, ei_dsp_blocks[0].config);
        }
        else if (is_spectrogram) {
            calc_cepstral_mean_and_var_normalization_spectrogram(&classify_matrix, ei_dsp_blocks[0].config);
        }
        else if (is_mfe) {
            calc_cepstral_mean_and_var_normalization_mfe(&classify_matrix, ei_dsp_blocks[0].config);
        }
        result->timing.dsp += ei_read_timer_ms() - dsp_start_ms;

#if EI_CLASSIFIER_INFERENCING_ENGINE != EI_CLASSIFIER_NONE
        if (debug) {
            ei_printf("Running neural network...\n");
        }
#endif
        ei_impulse_error = run_inference(&classify_matrix, result, debug);

        if (enable_maf) {
            for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
    #if EI_CLASSIFIER_OBJECT_DETECTION != 1
                result->classification[ix].value =
                    run_moving_average_filter(&classifier_maf[ix], result->classification[ix].value);
    #endif
            }
        }
    }
    return ei_impulse_error;
}

#if EI_CLASSIFIER_OBJECT_DETECTION

/**
 * Fill the result structure from an unquantized output tensor
 * (we don't support quantized here a.t.m.)
 */
__attribute__((unused)) static void fill_result_struct_f32(ei_impulse_result_t *result, float *data, float *scores, float *labels, bool debug) {
    for (size_t ix = 0; ix < EI_CLASSIFIER_OBJECT_DETECTION_COUNT; ix++) {

        float score = scores[ix];
        float label = labels[ix];

#if EI_CLASSIFIER_TFLITE_INPUT_DATATYPE == EI_CLASSIFIER_DATATYPE_INT8
        // so for i8 inputs this seems to be 0.5..1.0 instead of 0.0..1.0
        // let's fix it by hand
        score = (score - 0.5f) * 2;
#endif

        if (score >= EI_CLASSIFIER_OBJECT_DETECTION_THRESHOLD) {
            float ystart = data[(ix * 4) + 0];
            float xstart = data[(ix * 4) + 1];
            float yend = data[(ix * 4) + 2];
            float xend = data[(ix * 4) + 3];

            if (xstart < 0) xstart = 0;
            if (xstart > 1) xstart = 1;
            if (ystart < 0) ystart = 0;
            if (ystart > 1) ystart = 1;
            if (yend < 0) yend = 0;
            if (yend > 1) yend = 1;
            if (xend < 0) xend = 0;
            if (xend > 1) xend = 1;
            if (xend < xstart) xend = xstart;
            if (yend < ystart) yend = ystart;

            if (debug) {
                ei_printf("%s (%f): %f [ %f, %f, %f, %f ]\n",
                    ei_classifier_inferencing_categories[(uint32_t)label], label, score, xstart, ystart, xend, yend);
            }

            result->bounding_boxes[ix].label = ei_classifier_inferencing_categories[(uint32_t)label];
            result->bounding_boxes[ix].x = static_cast<uint32_t>(xstart * static_cast<float>(EI_CLASSIFIER_INPUT_WIDTH));
            result->bounding_boxes[ix].y = static_cast<uint32_t>(ystart * static_cast<float>(EI_CLASSIFIER_INPUT_HEIGHT));
            result->bounding_boxes[ix].width = static_cast<uint32_t>((xend - xstart) * static_cast<float>(EI_CLASSIFIER_INPUT_WIDTH));
            result->bounding_boxes[ix].height = static_cast<uint32_t>((yend - ystart) * static_cast<float>(EI_CLASSIFIER_INPUT_HEIGHT));
            result->bounding_boxes[ix].value = score;
        }
        else {
            result->bounding_boxes[ix].value = 0.0f;
        }
    }
}

#else

/**
 * Fill the result structure from a quantized output tensor
 */
__attribute__((unused)) static void fill_result_struct_i8(ei_impulse_result_t *result, int8_t *data, float zero_point, float scale, bool debug) {
    for (uint32_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
        float value = static_cast<float>(data[ix] - zero_point) * scale;

        if (debug) {
            ei_printf("%s:\t", ei_classifier_inferencing_categories[ix]);
            ei_printf_float(value);
            ei_printf("\n");
        }
        result->classification[ix].label = ei_classifier_inferencing_categories[ix];
        result->classification[ix].value = value;
    }
}

/**
 * Fill the result structure from an unquantized output tensor
 */
__attribute__((unused)) static void fill_result_struct_f32(ei_impulse_result_t *result, float *data, bool debug) {
    for (uint32_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
        float value = data[ix];

        if (debug) {
            ei_printf("%s:\t", ei_classifier_inferencing_categories[ix]);
            ei_printf_float(value);
            ei_printf("\n");
        }
        result->classification[ix].label = ei_classifier_inferencing_categories[ix];
        result->classification[ix].value = value;
    }
}

#endif // EI_CLASSIFIER_OBJECT_DETECTION

#if (EI_CLASSIFIER_INFERENCING_ENGINE == EI_CLASSIFIER_TFLITE)

/**
 * Setup the TFLite runtime
 *
 * @param      ctx_start_ms       Pointer to the start time
 * @param      input              Pointer to input tensor
 * @param      output             Pointer to output tensor
 * @param      micro_interpreter  Pointer to interpreter (for non-compiled models)
 * @param      micro_tensor_arena Pointer to the arena that will be allocated
 *
 * @return  EI_IMPULSE_OK if successful
 */
static EI_IMPULSE_ERROR inference_tflite_setup(uint64_t *ctx_start_ms, TfLiteTensor** input, TfLiteTensor** output,
#if EI_CLASSIFIER_OBJECT_DETECTION
    TfLiteTensor** output_labels,
    TfLiteTensor** output_scores,
#endif
#if (EI_CLASSIFIER_COMPILED != 1)
    tflite::MicroInterpreter** micro_interpreter,
#endif
    uint8_t** micro_tensor_arena) {
#if (EI_CLASSIFIER_COMPILED == 1)
    TfLiteStatus init_status = trained_model_init(ei_aligned_malloc);
    if (init_status != kTfLiteOk) {
        ei_printf("Failed to allocate TFLite arena (error code %d)\n", init_status);
        return EI_IMPULSE_TFLITE_ARENA_ALLOC_FAILED;
    }
#else
    // Create an area of memory to use for input, output, and intermediate arrays.
    uint8_t *tensor_arena = (uint8_t*)ei_aligned_malloc(16, EI_CLASSIFIER_TFLITE_ARENA_SIZE);
    if (tensor_arena == NULL) {
        ei_printf("Failed to allocate TFLite arena (%d bytes)\n", EI_CLASSIFIER_TFLITE_ARENA_SIZE);
        return EI_IMPULSE_TFLITE_ARENA_ALLOC_FAILED;
    }
    *micro_tensor_arena = tensor_arena;
#endif

    *ctx_start_ms = ei_read_timer_ms();

    static bool tflite_first_run = true;

#if (EI_CLASSIFIER_COMPILED != 1)
        static const tflite::Model* model = nullptr;
#endif

#if (EI_CLASSIFIER_COMPILED != 1)
    // ======
    // Initialization code start
    // This part can be run once, but that would require the TFLite arena
    // to be allocated at all times, which is not ideal (e.g. when doing MFCC)
    // ======
    if (tflite_first_run) {
        // Map the model into a usable data structure. This doesn't involve any
        // copying or parsing, it's a very lightweight operation.
        model = tflite::GetModel(trained_tflite);
        if (model->version() != TFLITE_SCHEMA_VERSION) {
            error_reporter->Report(
                "Model provided is schema version %d not equal "
                "to supported version %d.",
                model->version(), TFLITE_SCHEMA_VERSION);
            ei_aligned_free(tensor_arena);
            return EI_IMPULSE_TFLITE_ERROR;
        }
    }
#endif

#if (EI_CLASSIFIER_COMPILED != 1)
#ifdef EI_TFLITE_RESOLVER
    EI_TFLITE_RESOLVER
#else
    tflite::AllOpsResolver resolver;
#endif
#if defined(EI_CLASSIFIER_ENABLE_DETECTION_POSTPROCESS_OP)
    resolver.AddCustom("TFLite_Detection_PostProcess", tflite::ops::micro::Register_TFLite_Detection_PostProcess());
#endif
#endif // EI_CLASSIFIER_COMPILED != 1

#if (EI_CLASSIFIER_COMPILED == 1)
    *input = trained_model_input(0);
    *output = trained_model_output(0);
#if EI_CLASSIFIER_OBJECT_DETECTION
    *output_scores = trained_model_output(EI_CLASSIFIER_TFLITE_OUTPUT_SCORE_TENSOR);
    *output_labels = trained_model_output(EI_CLASSIFIER_TFLITE_OUTPUT_LABELS_TENSOR);
#endif // EI_CLASSIFIER_OBJECT_DETECTION
#else
    // Build an interpreter to run the model with.
    tflite::MicroInterpreter *interpreter = new tflite::MicroInterpreter(
        model, resolver, tensor_arena, EI_CLASSIFIER_TFLITE_ARENA_SIZE, error_reporter);

    *micro_interpreter = interpreter;

    // Allocate memory from the tensor_arena for the model's tensors.
    TfLiteStatus allocate_status = interpreter->AllocateTensors();
    if (allocate_status != kTfLiteOk) {
        error_reporter->Report("AllocateTensors() failed");
        ei_aligned_free(tensor_arena);
        return EI_IMPULSE_TFLITE_ERROR;
    }

    // Obtain pointers to the model's input and output tensors.
    *input = interpreter->input(0);
    *output = interpreter->output(0);
#if EI_CLASSIFIER_OBJECT_DETECTION
    *output_scores = interpreter->output(EI_CLASSIFIER_TFLITE_OUTPUT_SCORE_TENSOR);
    *output_labels = interpreter->output(EI_CLASSIFIER_TFLITE_OUTPUT_LABELS_TENSOR);
#endif // EI_CLASSIFIER_OBJECT_DETECTION
#endif

    // Assert that our quantization parameters match the model
    if (tflite_first_run) {
        assert((*input)->type == EI_CLASSIFIER_TFLITE_INPUT_DATATYPE);
        assert((*output)->type == EI_CLASSIFIER_TFLITE_OUTPUT_DATATYPE);
#if EI_CLASSIFIER_OBJECT_DETECTION
        assert((*output_scores)->type == EI_CLASSIFIER_TFLITE_OUTPUT_DATATYPE);
        assert((*output_labels)->type == EI_CLASSIFIER_TFLITE_OUTPUT_DATATYPE);
#endif
#if defined(EI_CLASSIFIER_TFLITE_INPUT_QUANTIZED) || defined(EI_CLASSIFIER_TFLITE_OUTPUT_QUANTIZED)
        if (EI_CLASSIFIER_TFLITE_INPUT_QUANTIZED) {
            assert((*input)->params.scale == EI_CLASSIFIER_TFLITE_INPUT_SCALE);
            assert((*input)->params.zero_point == EI_CLASSIFIER_TFLITE_INPUT_ZEROPOINT);
        }
        if (EI_CLASSIFIER_TFLITE_OUTPUT_QUANTIZED) {
            assert((*output)->params.scale == EI_CLASSIFIER_TFLITE_OUTPUT_SCALE);
            assert((*output)->params.zero_point == EI_CLASSIFIER_TFLITE_OUTPUT_ZEROPOINT);
        }
#endif
        tflite_first_run = false;
    }
    return EI_IMPULSE_OK;
}

/**
 * Run TFLite model
 *
 * @param   ctx_start_ms    Start time of the setup function (see above)
 * @param   output          Output tensor
 * @param   interpreter     TFLite interpreter (non-compiled models)
 * @param   tensor_arena    Allocated arena (will be freed)
 * @param   result          Struct for results
 * @param   debug           Whether to print debug info
 *
 * @return  EI_IMPULSE_OK if successful
 */
static EI_IMPULSE_ERROR inference_tflite_run(uint64_t ctx_start_ms,
    TfLiteTensor* output,
#if EI_CLASSIFIER_OBJECT_DETECTION
    TfLiteTensor* labels_tensor,
    TfLiteTensor* scores_tensor,
#endif
#if (EI_CLASSIFIER_COMPILED != 1)
    tflite::MicroInterpreter* interpreter,
#endif
    uint8_t* tensor_arena,
    ei_impulse_result_t *result,
    bool debug) {
#if (EI_CLASSIFIER_COMPILED == 1)
    trained_model_invoke();
#else
    // Run inference, and report any error
    TfLiteStatus invoke_status = interpreter->Invoke();
    if (invoke_status != kTfLiteOk) {
        error_reporter->Report("Invoke failed (%d)\n", invoke_status);
        ei_aligned_free(tensor_arena);
        return EI_IMPULSE_TFLITE_ERROR;
    }
    delete interpreter;
#endif

    uint64_t ctx_end_ms = ei_read_timer_ms();

    result->timing.classification = ctx_end_ms - ctx_start_ms;

    // Read the predicted y value from the model's output tensor
    if (debug) {
        ei_printf("Predictions (time: %d ms.):\n", result->timing.classification);
    }
#if EI_CLASSIFIER_OBJECT_DETECTION == 1
    fill_result_struct_f32(result, tflite::post_process_boxes, tflite::post_process_scores, tflite::post_process_classes, debug);
    // fill_result_struct_f32(result, output->data.f, scores_tensor->data.f, labels_tensor->data.f, debug);
#else
    bool int8_output = output->type == TfLiteType::kTfLiteInt8;
    if (int8_output) {
        fill_result_struct_i8(result, output->data.int8, output->params.zero_point, output->params.scale, debug);
    }
    else {
        fill_result_struct_f32(result, output->data.f, debug);
    }
#endif

#if (EI_CLASSIFIER_COMPILED == 1)
    trained_model_reset(ei_aligned_free);
#else
    ei_aligned_free(tensor_arena);
#endif

    if (ei_run_impulse_check_canceled() == EI_IMPULSE_CANCELED) {
        return EI_IMPULSE_CANCELED;
    }

    return EI_IMPULSE_OK;
}
#endif // (EI_CLASSIFIER_INFERENCING_ENGINE == EI_CLASSIFIER_TFLITE)

/**
 * @brief      Do inferencing over the processed feature matrix
 *
 * @param      fmatrix  Processed matrix
 * @param      result   Output classifier results
 * @param[in]  debug    Debug output enable
 *
 * @return     The ei impulse error.
 */
extern "C" EI_IMPULSE_ERROR run_inference(
    ei::matrix_t *fmatrix,
    ei_impulse_result_t *result,
    bool debug = false)
{
#if (EI_CLASSIFIER_INFERENCING_ENGINE == EI_CLASSIFIER_TFLITE)
    {
        uint64_t ctx_start_ms;
        TfLiteTensor* input;
        TfLiteTensor* output;
#if EI_CLASSIFIER_OBJECT_DETECTION
        TfLiteTensor* output_scores;
        TfLiteTensor* output_labels;
#endif
        uint8_t* tensor_arena;

#if (EI_CLASSIFIER_COMPILED == 1)
        EI_IMPULSE_ERROR init_res = inference_tflite_setup(&ctx_start_ms, &input, &output,
    #if EI_CLASSIFIER_OBJECT_DETECTION
            &output_labels,
            &output_scores,
    #endif
            &tensor_arena);
#else
        tflite::MicroInterpreter* interpreter;
        EI_IMPULSE_ERROR init_res = inference_tflite_setup(&ctx_start_ms, &input, &output,
    #if EI_CLASSIFIER_OBJECT_DETECTION
            &output_labels,
            &output_scores,
    #endif
            &interpreter, &tensor_arena);
#endif
        if (init_res != EI_IMPULSE_OK) {
            return init_res;
        }

        // Place our calculated x value in the model's input tensor
#if EI_CLASSIFIER_OBJECT_DETECTION
        bool uint8_input = input->type == TfLiteType::kTfLiteUInt8;
        for (size_t ix = 0; ix < fmatrix->rows * fmatrix->cols; ix++) {
            if (uint8_input) {
                float pixel = (float)fmatrix->buffer[ix];
                input->data.uint8[ix] = static_cast<uint8_t>((pixel / EI_CLASSIFIER_TFLITE_INPUT_SCALE) + EI_CLASSIFIER_TFLITE_INPUT_ZEROPOINT);
            }
            else {
                input->data.f[ix] = fmatrix->buffer[ix];
            }
        }
#else
        bool int8_input = input->type == TfLiteType::kTfLiteInt8;
        for (size_t ix = 0; ix < fmatrix->rows * fmatrix->cols; ix++) {
            // Quantize the input if it is int8
            if (int8_input) {
                input->data.int8[ix] = static_cast<int8_t>(round(fmatrix->buffer[ix] / input->params.scale) + input->params.zero_point);
                // printf("float %ld : %d\r\n", ix, input->data.int8[ix]);
            } else {
                input->data.f[ix] = fmatrix->buffer[ix];
            }
        }
#endif

#if (EI_CLASSIFIER_COMPILED == 1)
        EI_IMPULSE_ERROR run_res = inference_tflite_run(ctx_start_ms, output,
    #if EI_CLASSIFIER_OBJECT_DETECTION
            output_labels,
            output_scores,
    #endif
            tensor_arena, result, debug);
#else
        EI_IMPULSE_ERROR run_res = inference_tflite_run(ctx_start_ms, output,
    #if EI_CLASSIFIER_OBJECT_DETECTION
            output_labels,
            output_scores,
    #endif
            interpreter, tensor_arena, result, debug);
#endif

        if (run_res != EI_IMPULSE_OK) {
            return run_res;
        }
    }

#elif EI_CLASSIFIER_INFERENCING_ENGINE == EI_CLASSIFIER_TFLITE_FULL

    {
        static std::unique_ptr<tflite::FlatBufferModel> model = nullptr;
        static std::unique_ptr<tflite::Interpreter> interpreter = nullptr;
        if (!model) {
            model = tflite::FlatBufferModel::BuildFromBuffer((const char*)trained_tflite, trained_tflite_len);
            if (!model) {
                ei_printf("Failed to build TFLite model from buffer\n");
                return EI_IMPULSE_TFLITE_ERROR;
            }

            tflite::ops::builtin::BuiltinOpResolver resolver;
            tflite::InterpreterBuilder builder(*model, resolver);
            builder(&interpreter);

            if (!interpreter) {
                ei_printf("Failed to construct interpreter\n");
                return EI_IMPULSE_TFLITE_ERROR;
            }

            if (interpreter->AllocateTensors() != kTfLiteOk) {
                ei_printf("AllocateTensors failed\n");
                return EI_IMPULSE_TFLITE_ERROR;
            }
        }

        // Obtain pointers to the model's input and output tensors.
    #if EI_CLASSIFIER_OBJECT_DETECTION == 1
        #if EI_CLASSIFIER_TFLITE_INPUT_QUANTIZED == 1
            int8_t* input = interpreter->typed_input_tensor<int8_t>(0);
        #else
            float* input = interpreter->typed_input_tensor<float>(0);
        #endif
    #elif EI_CLASSIFIER_TFLITE_INPUT_QUANTIZED == 1
        int8_t* input = interpreter->typed_input_tensor<int8_t>(0);
    #else
        float* input = interpreter->typed_input_tensor<float>(0);
    #endif

        if (!input) {
            return EI_IMPULSE_INPUT_TENSOR_WAS_NULL;
        }

        for (uint32_t ix = 0; ix < fmatrix->rows * fmatrix->cols; ix++) {
    #if EI_CLASSIFIER_OBJECT_DETECTION == 1
        #if EI_CLASSIFIER_TFLITE_INPUT_QUANTIZED == 1
            float pixel = (float)fmatrix->buffer[ix];
            input[ix] = static_cast<uint8_t>((pixel / EI_CLASSIFIER_TFLITE_INPUT_SCALE) + EI_CLASSIFIER_TFLITE_INPUT_ZEROPOINT);
        #else
            input[ix] = fmatrix->buffer[ix];
        #endif
    #elif EI_CLASSIFIER_TFLITE_INPUT_QUANTIZED == 1
            input[ix] = static_cast<int8_t>(round(fmatrix->buffer[ix] / EI_CLASSIFIER_TFLITE_INPUT_SCALE) + EI_CLASSIFIER_TFLITE_INPUT_ZEROPOINT);
    #else
            input[ix] = fmatrix->buffer[ix];
    #endif
        }

        uint64_t ctx_start_ms = ei_read_timer_ms();

        interpreter->Invoke();

        uint64_t ctx_end_ms = ei_read_timer_ms();

        result->timing.classification = ctx_end_ms - ctx_start_ms;
    #if EI_CLASSIFIER_TFLITE_OUTPUT_QUANTIZED == 1
        int8_t* out_data = interpreter->typed_output_tensor<int8_t>(0);
    #else
        float* out_data = interpreter->typed_output_tensor<float>(0);
    #endif

        if (!out_data) {
            return EI_IMPULSE_OUTPUT_TENSOR_WAS_NULL;
        }

        if (debug) {
            ei_printf("Predictions (time: %d ms.):\n", result->timing.classification);
        }

#if EI_CLASSIFIER_OBJECT_DETECTION == 1
        float *scores_tensor = interpreter->typed_output_tensor<float>(EI_CLASSIFIER_TFLITE_OUTPUT_SCORE_TENSOR);
        float *label_tensor = interpreter->typed_output_tensor<float>(EI_CLASSIFIER_TFLITE_OUTPUT_LABELS_TENSOR);
        if (!scores_tensor) {
            return EI_IMPULSE_SCORE_TENSOR_WAS_NULL;
        }
        if (!label_tensor) {
            return EI_IMPULSE_LABEL_TENSOR_WAS_NULL;
        }
        fill_result_struct_f32(result, out_data, scores_tensor, label_tensor, debug);
#else

    #if EI_CLASSIFIER_TFLITE_OUTPUT_QUANTIZED == 1
        fill_result_struct_i8(result, out_data, EI_CLASSIFIER_TFLITE_OUTPUT_ZEROPOINT, EI_CLASSIFIER_TFLITE_OUTPUT_SCALE, debug);
    #else
        fill_result_struct_f32(result, out_data, debug);
    #endif

#endif

        // on Linux we're not worried about free'ing (for now)
    }

#elif (EI_CLASSIFIER_INFERENCING_ENGINE == EI_CLASSIFIER_TENSAIFLOW)
    {
        uint64_t ctx_start_ms = ei_read_timer_ms();
        int8_t *input;
        int8_t output[EI_CLASSIFIER_LABEL_COUNT];

        input = (int8_t *)ei_malloc(fmatrix->rows * fmatrix->cols);

        if (!input) {
            return EI_IMPULSE_ALLOC_FAILED;
        }

        for (size_t ix = 0; ix < fmatrix->rows * fmatrix->cols; ix++) {
            input[ix] = static_cast<int8_t>(
                round(fmatrix->buffer[ix] / EI_CLASSIFIER_TFLITE_INPUT_SCALE) +
                EI_CLASSIFIER_TFLITE_INPUT_ZEROPOINT);
        }

        /* Run tensaiflow inference */
        infer(input, output);

        for (uint32_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
            float value;
            // Dequantize the output if it is int8
            value = static_cast<float>(output[ix] - EI_CLASSIFIER_TFLITE_OUTPUT_ZEROPOINT) *
                EI_CLASSIFIER_TFLITE_OUTPUT_SCALE;

            if (debug) {
                ei_printf("%s:\t", ei_classifier_inferencing_categories[ix]);
                ei_printf_float(value);
                ei_printf("\n");
            }
            result->classification[ix].label = ei_classifier_inferencing_categories[ix];
            result->classification[ix].value = value;
        }

        result->timing.classification = ei_read_timer_ms() - ctx_start_ms;

        ei_free(input);
    }

#elif (EI_CLASSIFIER_INFERENCING_ENGINE == EI_CLASSIFIER_TENSORRT)
    {
        #if EI_CLASSIFIER_TFLITE_INPUT_QUANTIZED == 1
        #error "TensorRT requires an unquantized network"
        #endif

        static bool first_run = true;
        static char model_file_name[128];

        if (first_run) {
            snprintf(model_file_name, 128, "/tmp/ei-%s", trained_onnx_hash);

            FILE *file = fopen(model_file_name, "w");
            if (!file) {
                ei_printf("ERR: TensorRT init failed to open '%s'\n", model_file_name);
                return EI_IMPULSE_TENSORRT_INIT_FAILED;
            }

            if (fwrite(trained_onnx, trained_onnx_len, 1, file) != 1) {
                ei_printf("ERR: TensorRT init fwrite failed\n");
                return EI_IMPULSE_TENSORRT_INIT_FAILED;
            }

            if (fclose(file) != 0) {
                ei_printf("ERR: TensorRT init fclose failed\n");
                return EI_IMPULSE_TENSORRT_INIT_FAILED;
            }
        }

        float tensorrt_output[EI_CLASSIFIER_LABEL_COUNT];

        // lazy initialize tensorRT context
        if( ei_trt_handle == nullptr ) {
            ei_trt_handle = libeitrt::create_EiTrt(model_file_name, debug);
        }

        uint64_t ctx_start_ms = ei_read_timer_ms();

        libeitrt::infer(ei_trt_handle, fmatrix->buffer, tensorrt_output, EI_CLASSIFIER_LABEL_COUNT);
        uint64_t ctx_end_ms = ei_read_timer_ms();
        result->timing.classification = ctx_end_ms - ctx_start_ms;

        for( int i = 0; i < EI_CLASSIFIER_LABEL_COUNT; ++i) {
            result->classification[i].label = ei_classifier_inferencing_categories[i];
            result->classification[i].value = tensorrt_output[i];
        }
    }
#endif

#if EI_CLASSIFIER_HAS_ANOMALY == 1

    // Anomaly detection
    {
        uint64_t anomaly_start_ms = ei_read_timer_ms();

        float input[EI_CLASSIFIER_ANOM_AXIS_SIZE];
        for (size_t ix = 0; ix < EI_CLASSIFIER_ANOM_AXIS_SIZE; ix++) {
            input[ix] = fmatrix->buffer[EI_CLASSIFIER_ANOM_AXIS[ix]];
        }
        standard_scaler(input, ei_classifier_anom_scale, ei_classifier_anom_mean, EI_CLASSIFIER_ANOM_AXIS_SIZE);
        float anomaly = get_min_distance_to_cluster(
            input, EI_CLASSIFIER_ANOM_AXIS_SIZE, ei_classifier_anom_clusters, EI_CLASSIFIER_ANOM_CLUSTER_COUNT);

        uint64_t anomaly_end_ms = ei_read_timer_ms();

        if (debug) {
            ei_printf("Anomaly score (time: %d ms.): ", static_cast<int>(anomaly_end_ms - anomaly_start_ms));
            ei_printf_float(anomaly);
            ei_printf("\n");
        }

        result->timing.anomaly = anomaly_end_ms - anomaly_start_ms;

        result->anomaly = anomaly;
    }

#endif

    if (ei_run_impulse_check_canceled() == EI_IMPULSE_CANCELED) {
        return EI_IMPULSE_CANCELED;
    }

    return EI_IMPULSE_OK;
}

extern "C" EI_IMPULSE_ERROR run_inference_i16(
    ei::matrix_i32_t *fmatrix,
    ei_impulse_result_t *result,
    bool debug = false)
{
#if EI_CLASSIFIER_OBJECT_DETECTION
    return EI_IMPULSE_NOT_SUPPORTED_WITH_I16;
#else

#if (EI_CLASSIFIER_INFERENCING_ENGINE == EI_CLASSIFIER_TFLITE)
    {
        uint64_t ctx_start_ms;
        TfLiteTensor* input;
        TfLiteTensor* output;
        uint8_t* tensor_arena;

#if (EI_CLASSIFIER_COMPILED == 1)
        EI_IMPULSE_ERROR init_res = inference_tflite_setup(&ctx_start_ms, &input, &output,
            &tensor_arena);
#else
        tflite::MicroInterpreter* interpreter;
        EI_IMPULSE_ERROR init_res = inference_tflite_setup(&ctx_start_ms, &input, &output,
            &interpreter,
            &tensor_arena);
#endif
        if (init_res != EI_IMPULSE_OK) {
            return init_res;
        }

        EIDSP_i16 scale;
        numpy::float_to_int16(&input->params.scale, &scale, 1);

        // Place our calculated x value in the model's input tensor
        bool int8_input = input->type == TfLiteType::kTfLiteInt8;
        for (size_t ix = 0; ix < fmatrix->rows * fmatrix->cols; ix++) {
            // Quantize the input if it is int8
            if (int8_input) {
                int32_t calc = (int32_t)fmatrix->buffer[ix] << 8; // Shift for scaler
                calc /= scale;
                calc += 0x80; // Round by adding 0.5
                calc >>= 8; // Shift to int8_t domain
                input->data.int8[ix] = static_cast<int8_t>(calc + input->params.zero_point);
            } else {
                numpy::int16_to_float((EIDSP_i16 *)&fmatrix->buffer[ix], &input->data.f[ix], 1);
            }
        }

#if (EI_CLASSIFIER_COMPILED == 1)
        EI_IMPULSE_ERROR run_res = inference_tflite_run(ctx_start_ms, output,
            tensor_arena, result, debug);
#else
        EI_IMPULSE_ERROR run_res = inference_tflite_run(ctx_start_ms, output,
            interpreter, tensor_arena, result, debug);
#endif

        if (run_res != EI_IMPULSE_OK) {
            return run_res;
        }
    }

#elif EI_CLASSIFIER_INFERENCING_ENGINE == EI_CLASSIFIER_TFLITE_FULL

    ei_printf("ERR: run_classifier_i16 is not supported with full TensorFlow Lite\n");
    return EI_IMPULSE_TFLITE_ERROR;

#endif

#if EI_CLASSIFIER_HAS_ANOMALY == 1

    // Anomaly detection
    {
        uint64_t anomaly_start_ms = ei_read_timer_ms();

        float input[EI_CLASSIFIER_ANOM_AXIS_SIZE];
        for (size_t ix = 0; ix < EI_CLASSIFIER_ANOM_AXIS_SIZE; ix++) {
            // input[ix] = fmatrix->buffer[EI_CLASSIFIER_ANOM_AXIS[ix]];
            // numpy::int16_to_float(&fmatrix->buffer[EI_CLASSIFIER_ANOM_AXIS[ix]], &input[ix], 1);
            input[ix] = (float)fmatrix->buffer[EI_CLASSIFIER_ANOM_AXIS[ix]] / 32768.f;
        }
        standard_scaler(input, ei_classifier_anom_scale, ei_classifier_anom_mean, EI_CLASSIFIER_ANOM_AXIS_SIZE);
        float anomaly = get_min_distance_to_cluster(
            input, EI_CLASSIFIER_ANOM_AXIS_SIZE, ei_classifier_anom_clusters, EI_CLASSIFIER_ANOM_CLUSTER_COUNT);

        uint64_t anomaly_end_ms = ei_read_timer_ms();

        if (debug) {
            ei_printf("Anomaly score (time: %d ms.): ", static_cast<int>(anomaly_end_ms - anomaly_start_ms));
            ei_printf_float(anomaly);
            ei_printf("\n");
        }

        result->timing.anomaly = anomaly_end_ms - anomaly_start_ms;

        result->anomaly = anomaly;
    }

#endif

    if (ei_run_impulse_check_canceled() == EI_IMPULSE_CANCELED) {
        return EI_IMPULSE_CANCELED;
    }

    return EI_IMPULSE_OK;
#endif // OBJECT_DETECTION
}

/**
 * Run the classifier over a raw features array
 * @param raw_features Raw features array
 * @param raw_features_size Size of the features array
 * @param result Object to store the results in
 * @param debug Whether to show debug messages (default: false)
 */
extern "C" EI_IMPULSE_ERROR run_classifier(
    signal_t *signal,
    ei_impulse_result_t *result,
    bool debug = false)
{
#if EI_CLASSIFIER_TFLITE_INPUT_QUANTIZED == 1 && EI_CLASSIFIER_INFERENCING_ENGINE == EI_CLASSIFIER_TFLITE
    // Shortcut for quantized image models
    if (can_run_classifier_image_quantized() == EI_IMPULSE_OK) {
        return run_classifier_image_quantized(signal, result, debug);
    }
#endif

    // if (debug) {
    // static float buf[1000];
    // printf("Raw data: ");
    // for (size_t ix = 0; ix < 16000; ix += 1000) {
    //     int r = signal->get_data(ix, 1000, buf);
    //     for (size_t jx = 0; jx < 1000; jx++) {
    //         printf("%.0f, ", buf[jx]);
    //     }
    // }
    // printf("\n");
    // }

    memset(result, 0, sizeof(ei_impulse_result_t));

    ei::matrix_t features_matrix(1, EI_CLASSIFIER_NN_INPUT_FRAME_SIZE);

    uint64_t dsp_start_ms = ei_read_timer_ms();

    size_t out_features_index = 0;

    for (size_t ix = 0; ix < ei_dsp_blocks_size; ix++) {
        ei_model_dsp_t block = ei_dsp_blocks[ix];

        if (out_features_index + block.n_output_features > EI_CLASSIFIER_NN_INPUT_FRAME_SIZE) {
            ei_printf("ERR: Would write outside feature buffer\n");
            return EI_IMPULSE_DSP_ERROR;
        }

        ei::matrix_t fm(1, block.n_output_features, features_matrix.buffer + out_features_index);

#if EIDSP_SIGNAL_C_FN_POINTER
        if (block.axes_size != EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME) {
            ei_printf("ERR: EIDSP_SIGNAL_C_FN_POINTER can only be used when all axes are selected for DSP blocks\n");
            return EI_IMPULSE_DSP_ERROR;
        }
        int ret = block.extract_fn(signal, &fm, block.config, EI_CLASSIFIER_FREQUENCY);
#else
        SignalWithAxes swa(signal, block.axes, block.axes_size);
        int ret = block.extract_fn(swa.get_signal(), &fm, block.config, EI_CLASSIFIER_FREQUENCY);
#endif

        if (ret != EIDSP_OK) {
            ei_printf("ERR: Failed to run DSP process (%d)\n", ret);
            return EI_IMPULSE_DSP_ERROR;
        }

        if (ei_run_impulse_check_canceled() == EI_IMPULSE_CANCELED) {
            return EI_IMPULSE_CANCELED;
        }

        out_features_index += block.n_output_features;
    }

    result->timing.dsp = ei_read_timer_ms() - dsp_start_ms;

    if (debug) {
        ei_printf("Features (%d ms.): ", result->timing.dsp);
        for (size_t ix = 0; ix < features_matrix.cols; ix++) {
            ei_printf_float(features_matrix.buffer[ix]);
            ei_printf(" ");
        }
        ei_printf("\n");
    }

#if EI_CLASSIFIER_INFERENCING_ENGINE != EI_CLASSIFIER_NONE
    if (debug) {
        ei_printf("Running neural network...\n");
    }
#endif

    return run_inference(&features_matrix, result, debug);
}

#if defined(EI_CLASSIFIER_USE_QUANTIZED_DSP_BLOCK) && EI_CLASSIFIER_USE_QUANTIZED_DSP_BLOCK == 1

extern "C" EI_IMPULSE_ERROR run_classifier_i16(
    signal_i16_t *signal,
    ei_impulse_result_t *result,
    bool debug = false)
{

    memset(result, 0, sizeof(ei_impulse_result_t));

    ei::matrix_i32_t features_matrix(1, EI_CLASSIFIER_NN_INPUT_FRAME_SIZE);

    uint64_t dsp_start_ms = ei_read_timer_ms();

    size_t out_features_index = 0;

    for (size_t ix = 0; ix < ei_dsp_blocks_size; ix++) {
        ei_model_dsp_i16_t block = ei_dsp_blocks_i16[ix];

        if (out_features_index + block.n_output_features > EI_CLASSIFIER_NN_INPUT_FRAME_SIZE) {
            ei_printf("ERR: Would write outside feature buffer\n");
            return EI_IMPULSE_DSP_ERROR;
        }

        ei::matrix_i32_t fm(1, block.n_output_features, features_matrix.buffer + out_features_index);

#if EIDSP_SIGNAL_C_FN_POINTER
        if (block.axes_size != EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME) {
            ei_printf("ERR: EIDSP_SIGNAL_C_FN_POINTER can only be used when all axes are selected for DSP blocks\n");
            return EI_IMPULSE_DSP_ERROR;
        }
        int ret = block.extract_fn(signal, &fm, block.config, EI_CLASSIFIER_FREQUENCY);
#else
        SignalWithAxesI16 swa(signal, block.axes, block.axes_size);
        int ret = block.extract_fn(swa.get_signal(), &fm, block.config, EI_CLASSIFIER_FREQUENCY);
#endif

        if (ret != EIDSP_OK) {
            ei_printf("ERR: Failed to run DSP process (%d)\n", ret);
            return EI_IMPULSE_DSP_ERROR;
        }

        if (ei_run_impulse_check_canceled() == EI_IMPULSE_CANCELED) {
            return EI_IMPULSE_CANCELED;
        }

        out_features_index += block.n_output_features;
    }

    result->timing.dsp = ei_read_timer_ms() - dsp_start_ms;

    if (debug) {
        ei_printf("Features (%d ms.): ", result->timing.dsp);
        for (size_t ix = 0; ix < features_matrix.cols; ix++) {
            ei_printf_float((float)features_matrix.buffer[ix] / 32768.f);
            ei_printf(" ");
        }
        ei_printf("\n");
    }

#if EI_CLASSIFIER_INFERENCING_ENGINE != EI_CLASSIFIER_NONE
    if (debug) {
        ei_printf("Running neural network...\n");
    }
#endif

    return run_inference_i16(&features_matrix, result, debug);
}
#endif //EI_CLASSIFIER_USE_QUANTIZED_DSP_BLOCK

/**
 * @brief      Calculates the cepstral mean and variable normalization.
 *
 * @param      matrix      Source and destination matrix
 * @param      config_ptr  ei_dsp_config_mfcc_t struct pointer
 */
static void calc_cepstral_mean_and_var_normalization_mfcc(ei_matrix *matrix, void *config_ptr)
{
    ei_dsp_config_mfcc_t *config = (ei_dsp_config_mfcc_t *)config_ptr;

    uint32_t original_matrix_size = matrix->rows * matrix->cols;

    /* Modify rows and colums ration for matrix normalization */
    matrix->rows = original_matrix_size / config->num_cepstral;
    matrix->cols = config->num_cepstral;

    // cepstral mean and variance normalization
    int ret = speechpy::processing::cmvnw(matrix, config->win_size, true, false);
    if (ret != EIDSP_OK) {
        ei_printf("ERR: cmvnw failed (%d)\n", ret);
        return;
    }

    /* Reset rows and columns ratio */
    matrix->rows = 1;
    matrix->cols = original_matrix_size;
}

/**
 * @brief      Calculates the cepstral mean and variable normalization.
 *
 * @param      matrix      Source and destination matrix
 * @param      config_ptr  ei_dsp_config_mfe_t struct pointer
 */
static void calc_cepstral_mean_and_var_normalization_mfe(ei_matrix *matrix, void *config_ptr)
{
    ei_dsp_config_mfe_t *config = (ei_dsp_config_mfe_t *)config_ptr;

    uint32_t original_matrix_size = matrix->rows * matrix->cols;

    /* Modify rows and colums ration for matrix normalization */
    matrix->rows = (original_matrix_size) / config->num_filters;
    matrix->cols = config->num_filters;

    if (config->implementation_version < 3) {
        // cepstral mean and variance normalization
        int ret = speechpy::processing::cmvnw(matrix, config->win_size, false, true);
        if (ret != EIDSP_OK) {
            ei_printf("ERR: cmvnw failed (%d)\n", ret);
            return;
        }
    }
    else {
        // normalization
        int ret = speechpy::processing::mfe_normalization(matrix, config->noise_floor_db);
        if (ret != EIDSP_OK) {
            ei_printf("ERR: normalization failed (%d)\n", ret);
            return;
        }
    }

    /* Reset rows and columns ratio */
    matrix->rows = 1;
    matrix->cols = (original_matrix_size);
}

/**
 * @brief      Calculates the cepstral mean and variable normalization.
 *
 * @param      matrix      Source and destination matrix
 * @param      config_ptr  ei_dsp_config_spectrogram_t struct pointer
 */
static void calc_cepstral_mean_and_var_normalization_spectrogram(ei_matrix *matrix, void *config_ptr)
{
    ei_dsp_config_spectrogram_t *config = (ei_dsp_config_spectrogram_t *)config_ptr;

    uint32_t original_matrix_size = matrix->rows * matrix->cols;

    /* Modify rows and colums ration for matrix normalization */
    matrix->cols = config->fft_length / 2 + 1;
    matrix->rows = (original_matrix_size) / matrix->cols;

    if (config->implementation_version < 3) {
        int ret = numpy::normalize(matrix);
        if (ret != EIDSP_OK) {
            ei_printf("ERR: normalization failed (%d)\n", ret);
            return;
        }
    }
    else {
        // normalization
        int ret = speechpy::processing::spectrogram_normalization(matrix, config->noise_floor_db);
        if (ret != EIDSP_OK) {
            ei_printf("ERR: normalization failed (%d)\n", ret);
            return;
        }
    }

    /* Reset rows and columns ratio */
    matrix->rows = 1;
    matrix->cols = (original_matrix_size);
}

/**
 * Check if the current impulse could be used by 'run_classifier_image_quantized'
 */
__attribute__((unused)) static EI_IMPULSE_ERROR can_run_classifier_image_quantized() {
#if (EI_CLASSIFIER_INFERENCING_ENGINE != EI_CLASSIFIER_TFLITE)
    return EI_IMPULSE_UNSUPPORTED_INFERENCING_ENGINE;
#endif

#if EI_CLASSIFIER_HAS_ANOMALY == 1
    return EI_IMPULSE_ONLY_SUPPORTED_FOR_IMAGES;
#endif

    // Check if we have a quantized NN
#if EI_CLASSIFIER_TFLITE_INPUT_QUANTIZED != 1
    return EI_IMPULSE_ONLY_SUPPORTED_FOR_IMAGES;
#endif

    // And if we have one DSP block which operates on images...
    if (ei_dsp_blocks_size != 1 || ei_dsp_blocks[0].extract_fn != extract_image_features) {
        return EI_IMPULSE_ONLY_SUPPORTED_FOR_IMAGES;
    }

    return EI_IMPULSE_OK;
}

#if EI_CLASSIFIER_TFLITE_INPUT_QUANTIZED == 1 && EI_CLASSIFIER_INFERENCING_ENGINE == EI_CLASSIFIER_TFLITE
/**
 * Special function to run the classifier on images, only works on TFLite models (either interpreter or EON)
 * that allocates a lot less memory by quantizing in place. This only works if 'can_run_classifier_image_quantized'
 * returns EI_IMPULSE_OK.
 */
extern "C" EI_IMPULSE_ERROR run_classifier_image_quantized(
    signal_t *signal,
    ei_impulse_result_t *result,
    bool debug = false)
{
    EI_IMPULSE_ERROR verify_res = can_run_classifier_image_quantized();
    if (verify_res != EI_IMPULSE_OK) {
        return verify_res;
    }

    memset(result, 0, sizeof(ei_impulse_result_t));

#if (EI_CLASSIFIER_INFERENCING_ENGINE != EI_CLASSIFIER_TFLITE)
    return EI_IMPULSE_UNSUPPORTED_INFERENCING_ENGINE;
#else
    uint64_t ctx_start_ms;
    TfLiteTensor* input;
    TfLiteTensor* output;
#if EI_CLASSIFIER_OBJECT_DETECTION
    TfLiteTensor* output_scores;
    TfLiteTensor* output_labels;
#endif
    uint8_t* tensor_arena;

#if (EI_CLASSIFIER_COMPILED == 1)
    EI_IMPULSE_ERROR init_res = inference_tflite_setup(&ctx_start_ms, &input, &output,
    #if EI_CLASSIFIER_OBJECT_DETECTION
        &output_labels,
        &output_scores,
    #endif
        &tensor_arena);
#else
    tflite::MicroInterpreter* interpreter;
    EI_IMPULSE_ERROR init_res = inference_tflite_setup(&ctx_start_ms, &input, &output,
    #if EI_CLASSIFIER_OBJECT_DETECTION
        &output_labels,
        &output_scores,
    #endif
        &interpreter,
        &tensor_arena);
#endif
    if (init_res != EI_IMPULSE_OK) {
        return init_res;
    }

    if (input->type != TfLiteType::kTfLiteInt8) {
        return EI_IMPULSE_ONLY_SUPPORTED_FOR_IMAGES;
    }

    uint64_t dsp_start_ms = ei_read_timer_ms();

    // features matrix maps around the input tensor to not allocate any memory
    ei::matrix_i8_t features_matrix(1, EI_CLASSIFIER_NN_INPUT_FRAME_SIZE, input->data.int8);

    // run DSP process and quantize automatically
    int ret = extract_image_features_quantized(signal, &features_matrix, ei_dsp_blocks[0].config, EI_CLASSIFIER_FREQUENCY);
    if (ret != EIDSP_OK) {
        ei_printf("ERR: Failed to run DSP process (%d)\n", ret);
        return EI_IMPULSE_DSP_ERROR;
    }

    if (ei_run_impulse_check_canceled() == EI_IMPULSE_CANCELED) {
        return EI_IMPULSE_CANCELED;
    }

    result->timing.dsp = ei_read_timer_ms() - dsp_start_ms;

    if (debug) {
        ei_printf("Features (%d ms.): ", result->timing.dsp);
        for (size_t ix = 0; ix < features_matrix.cols; ix++) {
            ei_printf_float((features_matrix.buffer[ix] - EI_CLASSIFIER_TFLITE_INPUT_ZEROPOINT) * EI_CLASSIFIER_TFLITE_INPUT_SCALE);
            ei_printf(" ");
        }
        ei_printf("\n");
    }

    ctx_start_ms = ei_read_timer_ms();

#if (EI_CLASSIFIER_COMPILED == 1)
    EI_IMPULSE_ERROR run_res = inference_tflite_run(ctx_start_ms, output,
    #if EI_CLASSIFIER_OBJECT_DETECTION
        output_labels,
        output_scores,
    #endif
        tensor_arena, result, debug);
#else
    EI_IMPULSE_ERROR run_res = inference_tflite_run(ctx_start_ms, output,
    #if EI_CLASSIFIER_OBJECT_DETECTION
        output_labels,
        output_scores,
    #endif
        interpreter, tensor_arena, result, debug);
#endif

    if (run_res != EI_IMPULSE_OK) {
        return run_res;
    }

    return EI_IMPULSE_OK;
#endif // EI_CLASSIFIER_INFERENCING_ENGINE != EI_CLASSIFIER_TFLITE
}
#endif // #if EI_CLASSIFIER_TFLITE_INPUT_QUANTIZED == 1 && EI_CLASSIFIER_INFERENCING_ENGINE == EI_CLASSIFIER_TFLITE

#if EIDSP_SIGNAL_C_FN_POINTER == 0

/**
 * Run the impulse, if you provide an instance of sampler it will also persist the data for you
 * @param sampler Instance to an **initialized** sampler
 * @param result Object to store the results in
 * @param data_fn Function to retrieve data from sensors
 * @param debug Whether to log debug messages (default false)
 */
__attribute__((unused)) EI_IMPULSE_ERROR run_impulse(
#if defined(EI_CLASSIFIER_HAS_SAMPLER) && EI_CLASSIFIER_HAS_SAMPLER == 1
        EdgeSampler *sampler,
#endif
        ei_impulse_result_t *result,
#ifdef __MBED__
        mbed::Callback<void(float*, size_t)> data_fn,
#else
        std::function<void(float*, size_t)> data_fn,
#endif
        bool debug = false) {

    float *x = (float*)calloc(EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, sizeof(float));
    if (!x) {
        return EI_IMPULSE_OUT_OF_MEMORY;
    }

    uint64_t next_tick = 0;

    uint64_t sampling_us_start = ei_read_timer_us();

    // grab some data
    for (int i = 0; i < EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE; i += EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME) {
        uint64_t curr_us = ei_read_timer_us() - sampling_us_start;

        next_tick = curr_us + (EI_CLASSIFIER_INTERVAL_MS * 1000);

        data_fn(x + i, EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME);
#if defined(EI_CLASSIFIER_HAS_SAMPLER) && EI_CLASSIFIER_HAS_SAMPLER == 1
        if (sampler != NULL) {
            sampler->write_sensor_data(x + i, EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME);
        }
#endif

        if (ei_run_impulse_check_canceled() == EI_IMPULSE_CANCELED) {
            free(x);
            return EI_IMPULSE_CANCELED;
        }

        while (next_tick > ei_read_timer_us() - sampling_us_start);
    }

    result->timing.sampling = (ei_read_timer_us() - sampling_us_start) / 1000;

    signal_t signal;
    int err = numpy::signal_from_buffer(x, EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, &signal);
    if (err != 0) {
        free(x);
        ei_printf("ERR: signal_from_buffer failed (%d)\n", err);
        return EI_IMPULSE_DSP_ERROR;
    }

    EI_IMPULSE_ERROR r = run_classifier(&signal, result, debug);
    free(x);
    return r;
}

#if defined(EI_CLASSIFIER_USE_QUANTIZED_DSP_BLOCK) && EI_CLASSIFIER_USE_QUANTIZED_DSP_BLOCK == 1

__attribute__((unused)) EI_IMPULSE_ERROR run_impulse_i16(
#if defined(EI_CLASSIFIER_HAS_SAMPLER) && EI_CLASSIFIER_HAS_SAMPLER == 1
        EdgeSampler *sampler,
#endif
        ei_impulse_result_t *result,
#ifdef __MBED__
        mbed::Callback<void(signed short*, size_t)> data_fn,
#else
        std::function<void(signed short*, size_t)> data_fn,
#endif
        bool debug = false) {

    int16_t x[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE] = { 0 };

    uint64_t next_tick = 0;

    uint64_t sampling_us_start = ei_read_timer_us();

    // grab some data
    for (int i = 0; i < EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE; i += EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME) {
        uint64_t curr_us = ei_read_timer_us() - sampling_us_start;

        next_tick = curr_us + (EI_CLASSIFIER_INTERVAL_MS * 1000);

        data_fn(x + i, EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME);
#if defined(EI_CLASSIFIER_HAS_SAMPLER) && EI_CLASSIFIER_HAS_SAMPLER == 1
        if (sampler != NULL) {
            sampler->write_sensor_data(x + i, EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME);
        }
#endif

        if (ei_run_impulse_check_canceled() == EI_IMPULSE_CANCELED) {
            return EI_IMPULSE_CANCELED;
        }

        while (next_tick > ei_read_timer_us() - sampling_us_start);
    }

    result->timing.sampling = (ei_read_timer_us() - sampling_us_start) / 1000;

    signal_i16_t signal;
    int err = numpy::signal_from_buffer_i16(x, EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, &signal);
    if (err != 0) {
        ei_printf("ERR: signal_from_buffer failed (%d)\n", err);
        return EI_IMPULSE_DSP_ERROR;
    }

    return run_classifier_i16(&signal, result, debug);
}
#endif //EI_CLASSIFIER_USE_QUANTIZED_DSP_BLOCK

#if defined(EI_CLASSIFIER_HAS_SAMPLER) && EI_CLASSIFIER_HAS_SAMPLER == 1
/**
 * Run the impulse, does not persist data
 * @param result Object to store the results in
 * @param data_fn Function to retrieve data from sensors
 * @param debug Whether to log debug messages (default false)
 */
__attribute__((unused)) EI_IMPULSE_ERROR run_impulse(
        ei_impulse_result_t *result,
#ifdef __MBED__
        mbed::Callback<void(float*, size_t)> data_fn,
#else
        std::function<void(float*, size_t)> data_fn,
#endif
        bool debug = false) {
    return run_impulse(NULL, result, data_fn, debug);
}
#endif

#endif // #if EIDSP_SIGNAL_C_FN_POINTER == 0

#ifdef __cplusplus
}
#endif // __cplusplus

#endif // _EDGE_IMPULSE_RUN_CLASSIFIER_H_
