diff --git a/csharp_package/brainflow/brainflow/ml_module_library.cs b/csharp_package/brainflow/brainflow/ml_module_library.cs index cb6de268c..bdfa6862c 100644 --- a/csharp_package/brainflow/brainflow/ml_module_library.cs +++ b/csharp_package/brainflow/brainflow/ml_module_library.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; @@ -19,7 +19,8 @@ public enum BrainFlowClassifiers { DEFAULT_CLASSIFIER = 0, DYN_LIB_CLASSIFIER = 1, - ONNX_CLASSIFIER = 2 + ONNX_CLASSIFIER = 2, + MOVING_AVERAGE_CLASSIFIER = 3 }; public static class MLModuleLibrary64 diff --git a/java_package/brainflow/src/main/java/brainflow/BrainFlowClassifiers.java b/java_package/brainflow/src/main/java/brainflow/BrainFlowClassifiers.java index e2233f7b5..9ed62c004 100644 --- a/java_package/brainflow/src/main/java/brainflow/BrainFlowClassifiers.java +++ b/java_package/brainflow/src/main/java/brainflow/BrainFlowClassifiers.java @@ -8,7 +8,8 @@ public enum BrainFlowClassifiers DEFAULT_CLASSIFIER (0), DYN_LIB_CLASSIFIER (1), - ONNX_CLASSIFIER (2); + ONNX_CLASSIFIER (2), + MOVING_AVERAGE_CLASSIFIER (3); private final int protocol; private static final Map cl_map = new HashMap (); diff --git a/julia_package/brainflow/src/ml_model.jl b/julia_package/brainflow/src/ml_model.jl index cc59e9849..e066e5156 100644 --- a/julia_package/brainflow/src/ml_model.jl +++ b/julia_package/brainflow/src/ml_model.jl @@ -16,6 +16,7 @@ MetricType = Union{BrainFlowMetrics, Integer} DEFAULT_CLASSIFIER = 0 DYN_LIB_CLASSIFIER = 1 ONNX_CLASSIFIER = 2 + MOVING_AVERAGE_CLASSIFIER = 3 end diff --git a/matlab_package/brainflow/BrainFlowClassifiers.m b/matlab_package/brainflow/BrainFlowClassifiers.m index ee4028d91..3aa1fab51 100644 --- a/matlab_package/brainflow/BrainFlowClassifiers.m +++ b/matlab_package/brainflow/BrainFlowClassifiers.m @@ -4,5 +4,6 @@ DEFAULT_CLASSIFIER(0) DYN_LIB_CLASSIFIER(1) ONNX_CLASSIFIER(2) + MOVING_AVERAGE_CLASSIFIER(3) end end \ No newline at end of file diff --git a/nodejs_package/brainflow/brainflow.types.ts b/nodejs_package/brainflow/brainflow.types.ts index 5aabeaed5..d1683f842 100644 --- a/nodejs_package/brainflow/brainflow.types.ts +++ b/nodejs_package/brainflow/brainflow.types.ts @@ -241,6 +241,7 @@ export enum BrainFlowClassifiers { DEFAULT_CLASSIFIER = 0, USER_DEFINED = 1, ONNX_CLASSIFIER = 2, + MOVING_AVERAGE_CLASSIFIER = 3, } export interface IBrainFlowInputParams { diff --git a/python_package/brainflow/ml_model.py b/python_package/brainflow/ml_model.py index fe9480e33..a20d40353 100644 --- a/python_package/brainflow/ml_model.py +++ b/python_package/brainflow/ml_model.py @@ -27,6 +27,7 @@ class BrainFlowClassifiers(enum.IntEnum): DEFAULT_CLASSIFIER = 0 #: DYN_LIB_CLASSIFIER = 1 #: ONNX_CLASSIFIER = 2 #: + MOVING_AVERAGE_CLASSIFIER = 3 #: class BrainFlowModelParams(object): diff --git a/python_package/examples/tests/moving_average_classifier.py b/python_package/examples/tests/moving_average_classifier.py new file mode 100644 index 000000000..270d5bbc8 --- /dev/null +++ b/python_package/examples/tests/moving_average_classifier.py @@ -0,0 +1,73 @@ +import numpy as np +import sys +import os + +# add python_package to sys.path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))) + +from brainflow.ml_model import MLModel, BrainFlowMetrics, BrainFlowClassifiers, BrainFlowModelParams +from brainflow.exit_codes import BrainFlowError, BrainFlowExitCodes + + +def test_moving_average_classifier(): + print("Testing MovingAverageClassifier...") + + # 1. Test USER_DEFINED stream with window_len = 3 + params = BrainFlowModelParams( + BrainFlowMetrics.USER_DEFINED.value, + BrainFlowClassifiers.MOVING_AVERAGE_CLASSIFIER.value + ) + params.other_info = "3" + + model = MLModel(params) + model.prepare() + + # Step 1: Input 10.0 -> Avg: 10.0 + out1 = model.predict(np.array([10.0], dtype=np.float64)) + print(f"Step 1: In=10.0, Out={out1[0]}") + assert np.isclose(out1[0], 10.0) + + # Step 2: Input 20.0 -> Avg: (10 + 20) / 2 = 15.0 + out2 = model.predict(np.array([20.0], dtype=np.float64)) + print(f"Step 2: In=20.0, Out={out2[0]}") + assert np.isclose(out2[0], 15.0) + + # Step 3: Input 30.0 -> Avg: (10 + 20 + 30) / 3 = 20.0 + out3 = model.predict(np.array([30.0], dtype=np.float64)) + print(f"Step 3: In=30.0, Out={out3[0]}") + assert np.isclose(out3[0], 20.0) + + # Step 4: Input 40.0 -> Avg: (20 + 30 + 40) / 3 = 30.0 (oldest 10.0 dropped) + out4 = model.predict(np.array([40.0], dtype=np.float64)) + print(f"Step 4: In=40.0, Out={out4[0]}") + assert np.isclose(out4[0], 30.0) + + model.release() + + # 2. Test MINDFULNESS metric with MOVING_AVERAGE_CLASSIFIER + mf_params = BrainFlowModelParams( + BrainFlowMetrics.MINDFULNESS.value, + BrainFlowClassifiers.MOVING_AVERAGE_CLASSIFIER.value + ) + mf_params.other_info = '{"window_len": 4}' + + mf_model = MLModel(mf_params) + mf_model.prepare() + + # 5 band powers input + feature_vector = np.array([0.1, 0.2, 0.3, 0.2, 0.2], dtype=np.float64) + mf_out1 = mf_model.predict(feature_vector) + print(f"Mindfulness moving avg 1: {mf_out1[0]}") + assert 0.0 <= mf_out1[0] <= 1.0 + + mf_out2 = mf_model.predict(feature_vector) + print(f"Mindfulness moving avg 2: {mf_out2[0]}") + assert np.isclose(mf_out1[0], mf_out2[0]) + + mf_model.release() + + print("All MovingAverageClassifier tests passed successfully!") + + +if __name__ == '__main__': + test_moving_average_classifier() diff --git a/rust_package/brainflow/src/ffi/constants.rs b/rust_package/brainflow/src/ffi/constants.rs index 37d78ddfa..a9637ea53 100644 --- a/rust_package/brainflow/src/ffi/constants.rs +++ b/rust_package/brainflow/src/ffi/constants.rs @@ -157,6 +157,7 @@ pub enum BrainFlowClassifiers { DefaultClassifier = 0, DynLibClassifier = 1, OnnxClassifier = 2, + MovingAverageClassifier = 3, } #[repr(i32)] #[derive(FromPrimitive, ToPrimitive, Debug, Copy, Clone, Hash, PartialEq, Eq)] diff --git a/src/ml/build.cmake b/src/ml/build.cmake index bddbbe91c..e03054739 100644 --- a/src/ml/build.cmake +++ b/src/ml/build.cmake @@ -29,6 +29,7 @@ SET (ML_MODULE_SRC ${CMAKE_CURRENT_LIST_DIR}/base_classifier.cpp ${CMAKE_CURRENT_LIST_DIR}/mindfulness_classifier.cpp ${CMAKE_CURRENT_LIST_DIR}/generated/mindfulness_model.cpp + ${CMAKE_CURRENT_LIST_DIR}/moving_average_classifier.cpp ) add_library ( diff --git a/src/ml/inc/moving_average_classifier.h b/src/ml/inc/moving_average_classifier.h new file mode 100644 index 000000000..7c3c7fd0d --- /dev/null +++ b/src/ml/inc/moving_average_classifier.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +#include "base_classifier.h" +#include "brainflow_constants.h" +#include "brainflow_model_params.h" + + +class MovingAverageClassifier : public BaseClassifier +{ +protected: + int window_len; + std::deque buffer; + double sum; + std::shared_ptr base_classifier; + + int parse_window_len (); + +public: + MovingAverageClassifier (struct BrainFlowModelParams params); + ~MovingAverageClassifier (); + + int prepare () override; + int predict (double *data, int data_len, double *output, int *output_len) override; + int release () override; +}; diff --git a/src/ml/ml_module.cpp b/src/ml/ml_module.cpp index 55e8ebaaf..45f8c33c8 100644 --- a/src/ml/ml_module.cpp +++ b/src/ml/ml_module.cpp @@ -11,6 +11,7 @@ #include "dyn_lib_classifier.h" #include "mindfulness_classifier.h" #include "ml_module.h" +#include "moving_average_classifier.h" #include "onnx_classifier.h" #include "restfulness_classifier.h" @@ -62,6 +63,10 @@ int prepare (const char *json_params) { model = std::shared_ptr (new RestfulnessClassifier (key)); } + else if (key.classifier == (int)BrainFlowClassifiers::MOVING_AVERAGE_CLASSIFIER) + { + model = std::shared_ptr (new MovingAverageClassifier (key)); + } else { return (int)BrainFlowExitCodes::UNSUPPORTED_CLASSIFIER_AND_METRIC_COMBINATION_ERROR; diff --git a/src/ml/moving_average_classifier.cpp b/src/ml/moving_average_classifier.cpp new file mode 100644 index 000000000..535346891 --- /dev/null +++ b/src/ml/moving_average_classifier.cpp @@ -0,0 +1,138 @@ +#include +#include +#include + +#include "brainflow_constants.h" +#include "json.hpp" +#include "mindfulness_classifier.h" +#include "moving_average_classifier.h" +#include "restfulness_classifier.h" + +using json = nlohmann::json; + + +MovingAverageClassifier::MovingAverageClassifier (struct BrainFlowModelParams model_params) + : BaseClassifier (model_params) +{ + window_len = 5; + sum = 0.0; + base_classifier = NULL; + + if (params.metric == (int)BrainFlowMetrics::MINDFULNESS) + { + base_classifier = std::shared_ptr (new MindfulnessClassifier (params)); + } + else if (params.metric == (int)BrainFlowMetrics::RESTFULNESS) + { + base_classifier = std::shared_ptr (new RestfulnessClassifier (params)); + } +} + +MovingAverageClassifier::~MovingAverageClassifier () +{ + buffer.clear (); + sum = 0.0; + base_classifier = NULL; +} + +int MovingAverageClassifier::parse_window_len () +{ + int len = 5; + if (!params.other_info.empty ()) + { + try + { + if (params.other_info.find ("{") != std::string::npos) + { + json j = json::parse (params.other_info); + if (j.contains ("window_len")) + { + len = j["window_len"].get (); + } + else if (j.contains ("period")) + { + len = j["period"].get (); + } + } + else + { + len = std::stoi (params.other_info); + } + } + catch (...) + { + safe_logger (spdlog::level::warn, + "Unable to parse window_len from other_info: {}. Using default value of 5.", + params.other_info); + len = 5; + } + } + if (len <= 0) + { + len = 5; + } + return len; +} + +int MovingAverageClassifier::prepare () +{ + buffer.clear (); + sum = 0.0; + window_len = parse_window_len (); + + if (base_classifier != NULL) + { + return base_classifier->prepare (); + } + return (int)BrainFlowExitCodes::STATUS_OK; +} + +int MovingAverageClassifier::predict ( + double *data, int data_len, double *output, int *output_len) +{ + if ((data == NULL) || (output == NULL) || (data_len <= 0)) + { + safe_logger (spdlog::level::err, "Incorrect arguments for predict."); + return (int)BrainFlowExitCodes::INVALID_ARGUMENTS_ERROR; + } + + double raw_score = 0.0; + if (base_classifier != NULL) + { + double base_output = 0.0; + int base_output_len = 0; + int res = base_classifier->predict (data, data_len, &base_output, &base_output_len); + if (res != (int)BrainFlowExitCodes::STATUS_OK) + { + return res; + } + raw_score = base_output; + } + else + { + raw_score = data[0]; + } + + buffer.push_back (raw_score); + sum += raw_score; + if ((int)buffer.size () > window_len) + { + sum -= buffer.front (); + buffer.pop_front (); + } + + *output = sum / buffer.size (); + *output_len = 1; + return (int)BrainFlowExitCodes::STATUS_OK; +} + +int MovingAverageClassifier::release () +{ + buffer.clear (); + sum = 0.0; + if (base_classifier != NULL) + { + return base_classifier->release (); + } + return (int)BrainFlowExitCodes::STATUS_OK; +} diff --git a/src/utils/inc/brainflow_constants.h b/src/utils/inc/brainflow_constants.h index 8b31b0df2..de51574ee 100644 --- a/src/utils/inc/brainflow_constants.h +++ b/src/utils/inc/brainflow_constants.h @@ -152,7 +152,8 @@ enum class BrainFlowClassifiers : int { DEFAULT_CLASSIFIER = 0, DYN_LIB_CLASSIFIER = 1, - ONNX_CLASSIFIER = 2 + ONNX_CLASSIFIER = 2, + MOVING_AVERAGE_CLASSIFIER = 3 }; enum class BrainFlowPresets : int diff --git a/swift_package/Sources/BrainFlow/BrainFlowEnums.swift b/swift_package/Sources/BrainFlow/BrainFlowEnums.swift index 9e98fa9a0..f3031bdbd 100644 --- a/swift_package/Sources/BrainFlow/BrainFlowEnums.swift +++ b/swift_package/Sources/BrainFlow/BrainFlowEnums.swift @@ -126,6 +126,7 @@ public enum BrainFlowClassifiers: Int, CaseIterable, Sendable { case DEFAULT_CLASSIFIER = 0 case DYN_LIB_CLASSIFIER = 1 case ONNX_CLASSIFIER = 2 + case MOVING_AVERAGE_CLASSIFIER = 3 public var code: Int { rawValue } }