From 000cb66751c3ea0186e1e564f493ed2475dd7059 Mon Sep 17 00:00:00 2001 From: Samer Zumot <54731842+samerzumot@users.noreply.github.com> Date: Sun, 23 Aug 2026 14:11:43 -0400 Subject: [PATCH] Add activity index calculation from accelerometer data (fixes #642) - Implement get_activity_index in C++ core using multi-axis epoch variance - Add get_activity_index bindings across C++, Python, Java, C#, TypeScript, Rust, Swift, Julia, and MATLAB - Add automated test activity_index.py --- cpp_package/src/data_filter.cpp | 22 ++++++++ cpp_package/src/inc/data_filter.h | 12 ++++ .../brainflow/brainflow/data_filter.cs | 33 ++++++++++- .../brainflow/data_handler_library.cs | 19 ++++++- .../src/main/java/brainflow/DataFilter.java | 41 ++++++++++++++ julia_package/brainflow/src/data_filter.jl | 18 ++++++ matlab_package/brainflow/DataFilter.m | 20 +++++++ nodejs_package/brainflow/data_filter.ts | 29 ++++++++++ nodejs_package/brainflow/functions.types.ts | 4 ++ python_package/brainflow/data_filter.py | 45 +++++++++++++++ .../examples/tests/activity_index.py | 56 +++++++++++++++++++ rust_package/brainflow/src/data_filter.rs | 33 +++++++++++ .../brainflow/src/ffi/data_handler.rs | 10 ++++ src/data_handler/data_handler.cpp | 48 ++++++++++++++++ src/data_handler/inc/data_handler.h | 3 + .../Sources/BrainFlow/DataFilter.swift | 23 ++++++++ 16 files changed, 414 insertions(+), 2 deletions(-) create mode 100644 python_package/examples/tests/activity_index.py diff --git a/cpp_package/src/data_filter.cpp b/cpp_package/src/data_filter.cpp index 561fcc124..bbc26a14d 100644 --- a/cpp_package/src/data_filter.cpp +++ b/cpp_package/src/data_filter.cpp @@ -585,6 +585,28 @@ double DataFilter::get_railed_percentage (double *data, int data_len, int gain) return output; } +double *DataFilter::get_activity_index (const double *accel_x, const double *accel_y, + const double *accel_z, int data_len, int period, int *output_len) +{ + if ((period <= 0) || (period > data_len)) + { + period = data_len; + } + int num_epochs = data_len / period; + double *output = new double[num_epochs]; + int res = ::get_activity_index (accel_x, accel_y, accel_z, data_len, period, output); + if (res != (int)BrainFlowExitCodes::STATUS_OK) + { + delete[] output; + throw BrainFlowException ("unable to calculate activity index", res); + } + if (output_len != NULL) + { + *output_len = num_epochs; + } + return output; +} + std::string DataFilter::get_version () { char version[64]; diff --git a/cpp_package/src/inc/data_filter.h b/cpp_package/src/inc/data_filter.h index de66122de..bac0f00a0 100644 --- a/cpp_package/src/inc/data_filter.h +++ b/cpp_package/src/inc/data_filter.h @@ -236,6 +236,18 @@ class DataFilter BrainFlowArray, BrainFlowArray> perform_ica (const BrainFlowArray &data, int num_components); + /** + * calculate activity index from 3-axis accelerometer data + * @param accel_x input 1d array + * @param accel_y input 1d array + * @param accel_z input 1d array + * @param data_len size of array + * @param period epoch length in samples (defaults to data_len if <= 0) + * @param output_len pointer to int to store number of epochs calculated + * @return pointer to array of activity indices + */ + static double *get_activity_index (const double *accel_x, const double *accel_y, + const double *accel_z, int data_len, int period, int *output_len); /// get brainflow version static std::string get_version (); diff --git a/csharp_package/brainflow/brainflow/data_filter.cs b/csharp_package/brainflow/brainflow/data_filter.cs index 98fce81c2..83121b26a 100644 --- a/csharp_package/brainflow/brainflow/data_filter.cs +++ b/csharp_package/brainflow/brainflow/data_filter.cs @@ -1,4 +1,4 @@ -using brainflow.math; +using brainflow.math; using System; using System.Numerics; @@ -585,6 +585,37 @@ public static void write_file (double[,] data, string file_name, string file_mod return result; } + /// + /// calculate activity index from 3-axis accelerometer data + /// + public static double[] get_activity_index (double[] accel_x, double[] accel_y, double[] accel_z, int period = 0) + { + if (accel_x == null || accel_y == null || accel_z == null) + { + throw new BrainFlowError ((int)BrainFlowExitCodes.INVALID_ARGUMENTS_ERROR); + } + if ((accel_x.Length != accel_y.Length) || (accel_x.Length != accel_z.Length)) + { + throw new BrainFlowError ((int)BrainFlowExitCodes.INVALID_ARGUMENTS_ERROR); + } + if (period <= 0) + { + period = accel_x.Length; + } + if (accel_x.Length < period) + { + throw new BrainFlowError ((int)BrainFlowExitCodes.INVALID_ARGUMENTS_ERROR); + } + int num_epochs = accel_x.Length / period; + double[] output = new double[num_epochs]; + int res = DataHandlerLibrary.get_activity_index (accel_x, accel_y, accel_z, accel_x.Length, period, output); + if (res != (int)BrainFlowExitCodes.STATUS_OK) + { + throw new BrainFlowError (res); + } + return output; + } + /// /// calculate nearest power of two /// diff --git a/csharp_package/brainflow/brainflow/data_handler_library.cs b/csharp_package/brainflow/brainflow/data_handler_library.cs index 067e7ad73..246ce0057 100644 --- a/csharp_package/brainflow/brainflow/data_handler_library.cs +++ b/csharp_package/brainflow/brainflow/data_handler_library.cs @@ -1,4 +1,4 @@ -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; namespace brainflow { @@ -187,6 +187,8 @@ public static extern int perform_wavelet_denoising (double[] data, int data_len, public static extern int get_heart_rate (double[] ppg_ir, double[] ppg_red, int data_size, int sampling_rate, int fft_size, double[] output); [DllImport ("DataHandler", SetLastError = true, CallingConvention = CallingConvention.Cdecl)] public static extern int perform_ica (double[] data, int rows, int cols, int num_components, double[] w, double[] k, double[] a, double[] s); + [DllImport ("DataHandler", SetLastError = true, CallingConvention = CallingConvention.Cdecl)] + public static extern int get_activity_index (double[] accel_x, double[] accel_y, double[] accel_z, int data_len, int period, double[] output); // unsafe methods working with pointers [DllImport ("DataHandler", SetLastError = true, CallingConvention = CallingConvention.Cdecl)] public static unsafe extern int perform_lowpass (double* data, int len, int sampling_rate, double cutoff, int order, int filter_type, double ripple); @@ -297,6 +299,8 @@ public static extern int perform_wavelet_denoising (double[] data, int data_len, public static extern int get_heart_rate (double[] ppg_ir, double[] ppg_red, int data_size, int sampling_rate, int fft_size, double[] output); [DllImport ("DataHandler32", SetLastError = true, CallingConvention = CallingConvention.Cdecl)] public static extern int perform_ica (double[] data, int rows, int cols, int num_components, double[] w, double[] k, double[] a, double[] s); + [DllImport ("DataHandler32", SetLastError = true, CallingConvention = CallingConvention.Cdecl)] + public static extern int get_activity_index (double[] accel_x, double[] accel_y, double[] accel_z, int data_len, int period, double[] output); // unsafe methods working with pointers [DllImport ("DataHandler32", SetLastError = true, CallingConvention = CallingConvention.Cdecl)] public static unsafe extern int perform_lowpass (double* data, int len, int sampling_rate, double cutoff, int order, int filter_type, double ripple); @@ -389,6 +393,19 @@ public static int perform_ica (double[] data, int rows, int cols, int num_compon return (int)BrainFlowExitCodes.GENERAL_ERROR; } + public static int get_activity_index (double[] accel_x, double[] accel_y, double[] accel_z, int data_len, int period, double[] output) + { + switch (PlatformHelper.get_library_environment ()) + { + case LibraryEnvironment.x64: + return DataHandlerLibrary64.get_activity_index (accel_x, accel_y, accel_z, data_len, period, output); + case LibraryEnvironment.x86: + return DataHandlerLibrary32.get_activity_index (accel_x, accel_y, accel_z, data_len, period, output); + } + + return (int)BrainFlowExitCodes.GENERAL_ERROR; + } + public static int perform_lowpass (double[] data, int len, int sampling_rate, double cutoff, int order, int filter_type, double ripple) { switch (PlatformHelper.get_library_environment ()) diff --git a/java_package/brainflow/src/main/java/brainflow/DataFilter.java b/java_package/brainflow/src/main/java/brainflow/DataFilter.java index 13bb57db3..f5474d8ed 100644 --- a/java_package/brainflow/src/main/java/brainflow/DataFilter.java +++ b/java_package/brainflow/src/main/java/brainflow/DataFilter.java @@ -104,6 +104,9 @@ int get_heart_rate (double[] ppg_ir, double[] ppg_red, int len, int sampling_rat int perform_ica (double[] data, int rows, int cols, int num_components, double[] w, double[] k, double[] a, double[] s); + int get_activity_index (double[] accel_x, double[] accel_y, double[] accel_z, int data_len, int period, + double[] output); + int get_version_data_handler (byte[] version, int[] len, int max_len); int log_message_data_handler (int log_level, String message); @@ -1073,6 +1076,44 @@ public static double[][] read_file (String file_name) throws BrainFlowError return reshape_data_to_2d (num_rows[0], num_cols[0], data_arr); } + /** + * calculate activity index from 3-axis accelerometer data + */ + public static double[] get_activity_index (double[] accel_x, double[] accel_y, double[] accel_z, int period) + throws BrainFlowError + { + if (accel_x == null || accel_y == null || accel_z == null) + { + throw new BrainFlowError ("Null pointer passed", BrainFlowExitCode.INVALID_ARGUMENTS_ERROR.get_code ()); + } + if ((accel_x.length != accel_y.length) || (accel_x.length != accel_z.length)) + { + throw new BrainFlowError ("Array lengths do not match", BrainFlowExitCode.INVALID_ARGUMENTS_ERROR.get_code ()); + } + if (period <= 0) + { + period = accel_x.length; + } + if (accel_x.length < period) + { + throw new BrainFlowError ("Data length is shorter than period", BrainFlowExitCode.INVALID_ARGUMENTS_ERROR.get_code ()); + } + int num_epochs = accel_x.length / period; + double[] output = new double[num_epochs]; + int ec = instance.get_activity_index (accel_x, accel_y, accel_z, accel_x.length, period, output); + if (ec != BrainFlowExitCode.STATUS_OK.get_code ()) + { + throw new BrainFlowError ("Failed to calculate activity index", ec); + } + return output; + } + + public static double[] get_activity_index (double[] accel_x, double[] accel_y, double[] accel_z) + throws BrainFlowError + { + return get_activity_index (accel_x, accel_y, accel_z, 0); + } + public static double[] reshape_data_to_1d (int num_rows, int num_cols, double[][] buf) { double[] output_buf = new double[num_rows * num_cols]; diff --git a/julia_package/brainflow/src/data_filter.jl b/julia_package/brainflow/src/data_filter.jl index 70b7efa00..929e86666 100644 --- a/julia_package/brainflow/src/data_filter.jl +++ b/julia_package/brainflow/src/data_filter.jl @@ -461,3 +461,21 @@ end psd[1], psd[2], length(psd[1]), Float64(freq_start), Float64(freq_end), band_power) return band_power[1] end + +@brainflow_rethrow function get_activity_index(accel_x, accel_y, accel_z, period::Integer=0) + if (length(accel_x) != length(accel_y)) || (length(accel_x) != length(accel_z)) + throw(BrainFlowError(string("Arrays lengths must match ", INVALID_ARGUMENTS_ERROR), Integer(INVALID_ARGUMENTS_ERROR))) + end + data_len = length(accel_x) + if period <= 0 + period = data_len + end + if data_len < period + throw(BrainFlowError(string("Data length is shorter than period ", INVALID_ARGUMENTS_ERROR), Integer(INVALID_ARGUMENTS_ERROR))) + end + num_epochs = div(data_len, period) + output = Vector{Float64}(undef, num_epochs) + ccall((:get_activity_index, DATA_HANDLER_INTERFACE), Cint, (Ptr{Float64}, Ptr{Float64}, Ptr{Float64}, Cint, Cint, Ptr{Float64}), + accel_x, accel_y, accel_z, Int32(data_len), Int32(period), output) + return output +end diff --git a/matlab_package/brainflow/DataFilter.m b/matlab_package/brainflow/DataFilter.m index aefa98f39..d11724d57 100644 --- a/matlab_package/brainflow/DataFilter.m +++ b/matlab_package/brainflow/DataFilter.m @@ -460,6 +460,26 @@ function write_file(data, file_name, file_mode) data = transpose(reshape(data_array.Value(1, 1:data_count.Value), [num_cols.Value, num_rows.value])); end + function output = get_activity_index(accel_x, accel_y, accel_z, period) + % calculate activity index + if nargin < 4 + period = size(accel_x, 2); + end + if period <= 0 + period = size(accel_x, 2); + end + task_name = 'get_activity_index'; + temp_input_x = libpointer('doublePtr', accel_x); + temp_input_y = libpointer('doublePtr', accel_y); + temp_input_z = libpointer('doublePtr', accel_z); + lib_name = DataFilter.load_lib(); + num_epochs = floor(size(accel_x, 2) / period); + temp_output = libpointer('doublePtr', zeros(1, num_epochs)); + exit_code = calllib(lib_name, task_name, temp_input_x, temp_input_y, temp_input_z, size(accel_x, 2), period, temp_output); + DataFilter.check_ec(exit_code, task_name); + output = temp_output.Value; + end + end end \ No newline at end of file diff --git a/nodejs_package/brainflow/data_filter.ts b/nodejs_package/brainflow/data_filter.ts index b032298f4..fea2f1cbe 100644 --- a/nodejs_package/brainflow/data_filter.ts +++ b/nodejs_package/brainflow/data_filter.ts @@ -65,6 +65,7 @@ class DataHandlerDLL extends DataHandlerFunctions this.lib.func(CLike.restore_data_from_wavelet_detailed_coeffs); this.detectPeaksZScore = this.lib.func(CLike.detect_peaks_z_score); this.performIca = this.lib.func(CLike.perform_ica); + this.getActivityIndex = this.lib.func(CLike.get_activity_index); this.getCsp = this.lib.func(CLike.get_csp); this.detrend = this.lib.func(CLike.detrend); this.calcStddev = this.lib.func(CLike.calc_stddev); @@ -607,4 +608,32 @@ export class DataFilter } return output[0]; } + + public static getActivityIndex( + accelX: number[], accelY: number[], accelZ: number[], period: number = 0): number[] + { + if (accelX.length !== accelY.length || accelX.length !== accelZ.length) + { + throw new BrainFlowError ( + BrainFlowExitCodes.INVALID_ARGUMENTS_ERROR, "arrays lengths must match"); + } + if (period <= 0) + { + period = accelX.length; + } + if (accelX.length < period) + { + throw new BrainFlowError ( + BrainFlowExitCodes.INVALID_ARGUMENTS_ERROR, "data length is shorter than period"); + } + const numEpochs = Math.trunc(accelX.length / period); + const output = [...new Array (numEpochs).fill(0)]; + const res = DataHandlerDLL.getInstance().getActivityIndex( + accelX, accelY, accelZ, accelX.length, period, output); + if (res !== BrainFlowExitCodes.STATUS_OK) + { + throw new BrainFlowError (res, 'Could not calc activity index'); + } + return output; + } } diff --git a/nodejs_package/brainflow/functions.types.ts b/nodejs_package/brainflow/functions.types.ts index 177b54aa9..ef3011b54 100644 --- a/nodejs_package/brainflow/functions.types.ts +++ b/nodejs_package/brainflow/functions.types.ts @@ -370,6 +370,8 @@ export enum DataHandlerCLikeFunctions { 'int detect_peaks_z_score (double *data, int data_len, int lag, double threshold, double influence, _Inout_ double *output)', perform_ica = 'int perform_ica (double *data, int rows, int cols, int num_components, _Inout_ double *w_mat, _Inout_ double *k_mat, _Inout_ double *a_mat, _Inout_ double *s_mat)', + get_activity_index = + 'int get_activity_index (double *accel_x, double *accel_y, double *accel_z, int data_len, int period, _Inout_ double *activity_index)', get_csp = 'int get_csp (const double *data, const double *labels, int n_epochs, int n_channels, int n_times, _Inout_ double *output_w, _Inout_ double *output_d)', get_railed_percentage = @@ -445,6 +447,8 @@ export class DataHandlerFunctions influence: number, output: number[]) => BrainFlowExitCodes; performIca!: (data: number[], rows: number, cols: number, numComponents: number, wMat: number[], kMat: number[], aMat: number[], sMat: number[]) => BrainFlowExitCodes; + getActivityIndex!: (accelX: number[], accelY: number[], accelZ: number[], dataLen: number, + period: number, activityIndex: number[]) => BrainFlowExitCodes; getCsp!: (data: number[], labels: number[], nEpochs: number, nChannels: number, nTimes: number, outputW: number[], outputD: number[]) => BrainFlowExitCodes; detrend!: (rawData: number[], dataLen: number, detrendOperation: number) => BrainFlowExitCodes; diff --git a/python_package/brainflow/data_filter.py b/python_package/brainflow/data_filter.py index 33608363f..73bbdf2c1 100644 --- a/python_package/brainflow/data_filter.py +++ b/python_package/brainflow/data_filter.py @@ -536,6 +536,17 @@ def __init__(self): ndpointer(ctypes.c_double) ] + self.get_activity_index = self.lib.get_activity_index + self.get_activity_index.restype = ctypes.c_int + self.get_activity_index.argtypes = [ + ndpointer(ctypes.c_double), + ndpointer(ctypes.c_double), + ndpointer(ctypes.c_double), + ctypes.c_int, + ctypes.c_int, + ndpointer(ctypes.c_double) + ] + self.get_version_data_handler = self.lib.get_version_data_handler self.get_version_data_handler.restype = ctypes.c_int self.get_version_data_handler.argtypes = [ @@ -1293,6 +1304,40 @@ def perform_ifft(cls, data): return output + @classmethod + def get_activity_index(cls, accel_x, accel_y, accel_z, period: int = 0): + """get activity index from 3-axis accelerometer data + + :param accel_x: acceleration X data + :type accel_x: NDArray[Shape["*"], Float64] + :param accel_y: acceleration Y data + :type accel_y: NDArray[Shape["*"], Float64] + :param accel_z: acceleration Z data + :type accel_z: NDArray[Shape["*"], Float64] + :param period: epoch length in samples (defaults to full data length if 0) + :type period: int + :return: activity index values + :rtype: NDArray[Shape["*"], Float64] + """ + check_memory_layout_row_major(accel_x, 1) + check_memory_layout_row_major(accel_y, 1) + check_memory_layout_row_major(accel_z, 1) + if not (accel_x.shape[0] == accel_y.shape[0] == accel_z.shape[0]): + raise BrainFlowError('invalid shapes', BrainFlowExitCodes.INVALID_ARGUMENTS_ERROR.value) + data_len = accel_x.shape[0] + if period <= 0: + period = data_len + if data_len < period: + raise BrainFlowError('data length is shorter than period', BrainFlowExitCodes.INVALID_ARGUMENTS_ERROR.value) + num_epochs = data_len // period + output = numpy.zeros(num_epochs).astype(numpy.float64) + res = DataHandlerDLL.get_instance().get_activity_index( + accel_x, accel_y, accel_z, data_len, period, output + ) + if res != BrainFlowExitCodes.STATUS_OK.value: + raise BrainFlowError('unable to calculate activity index', res) + return output + @classmethod def get_nearest_power_of_two(cls, value: int) -> int: """calc nearest power of two diff --git a/python_package/examples/tests/activity_index.py b/python_package/examples/tests/activity_index.py new file mode 100644 index 000000000..7ae28921a --- /dev/null +++ b/python_package/examples/tests/activity_index.py @@ -0,0 +1,56 @@ +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.data_filter import DataFilter +from brainflow.exit_codes import BrainFlowError, BrainFlowExitCodes + + +def test_activity_index(): + print("Testing get_activity_index...") + + # 1. Test Constant Signal (AI must be exactly 0.0) + accel_x = np.full(100, 1.0, dtype=np.float64) + accel_y = np.full(100, 2.0, dtype=np.float64) + accel_z = np.full(100, -3.0, dtype=np.float64) + + ai_constant = DataFilter.get_activity_index(accel_x, accel_y, accel_z) + print(f"Constant signal AI: {ai_constant}") + assert len(ai_constant) == 1 + assert np.isclose(ai_constant[0], 0.0), f"Expected 0.0, got {ai_constant[0]}" + + # 2. Test Multi-epoch AI calculation + # Epoch 1: variance = 0 (constant) + # Epoch 2: variance > 0 (varying) + N = 50 + x = np.concatenate([np.zeros(N), np.sin(np.linspace(0, 2 * np.pi, N, endpoint=False))]) + y = np.concatenate([np.zeros(N), np.cos(np.linspace(0, 2 * np.pi, N, endpoint=False))]) + z = np.concatenate([np.zeros(N), np.zeros(N)]) + + ai_epochs = DataFilter.get_activity_index(x, y, z, period=N) + print(f"Epochs AI: {ai_epochs}") + assert len(ai_epochs) == 2 + assert np.isclose(ai_epochs[0], 0.0) + + # Theoretical variance of sine/cos of amplitude 1 is 0.5 + # var_x = 0.5, var_y = 0.5, var_z = 0 -> total_var = (0.5 + 0.5 + 0)/3 = 1/3 + # AI = sqrt(1/3) = ~0.57735 + expected_ai_epoch1 = np.sqrt((np.var(x[N:]) + np.var(y[N:]) + np.var(z[N:])) / 3.0) + print(f"Calculated AI: {ai_epochs[1]}, Expected: {expected_ai_epoch1}") + assert np.isclose(ai_epochs[1], expected_ai_epoch1) + + # 3. Test Invalid Arguments + try: + DataFilter.get_activity_index(np.zeros(10), np.zeros(5), np.zeros(10)) + assert False, "Should have raised BrainFlowError for shape mismatch" + except BrainFlowError as e: + assert e.exit_code == BrainFlowExitCodes.INVALID_ARGUMENTS_ERROR.value + + print("All activity index tests passed successfully!") + + +if __name__ == '__main__': + test_activity_index() diff --git a/rust_package/brainflow/src/data_filter.rs b/rust_package/brainflow/src/data_filter.rs index 8bdb06ee6..3e2a61b20 100644 --- a/rust_package/brainflow/src/data_filter.rs +++ b/rust_package/brainflow/src/data_filter.rs @@ -859,6 +859,39 @@ where Ok(check_brainflow_exit_code(res)?) } +/// Calculate activity index from 3-axis accelerometer data. +pub fn get_activity_index( + accel_x: &[f64], + accel_y: &[f64], + accel_z: &[f64], + period: Option, +) -> Result> { + if accel_x.len() != accel_y.len() || accel_x.len() != accel_z.len() { + return Err(BrainFlowError::InvalidArguments); + } + let data_len = accel_x.len(); + let period = period.unwrap_or(data_len); + if period == 0 || data_len < period { + return Err(BrainFlowError::InvalidArguments); + } + let num_epochs = data_len / period; + let mut output = Vec::::with_capacity(num_epochs); + let res = unsafe { + data_handler::get_activity_index( + accel_x.as_ptr() as *const c_double, + accel_y.as_ptr() as *const c_double, + accel_z.as_ptr() as *const c_double, + data_len as c_int, + period as c_int, + output.as_mut_ptr() as *mut c_double, + ) + }; + check_brainflow_exit_code(res)?; + + unsafe { output.set_len(num_epochs) }; + Ok(output) +} + /// Get DataFilter version. pub fn get_version() -> Result { const MAX_CHARS: usize = 64; diff --git a/rust_package/brainflow/src/ffi/data_handler.rs b/rust_package/brainflow/src/ffi/data_handler.rs index e466eefa0..197a2b679 100644 --- a/rust_package/brainflow/src/ffi/data_handler.rs +++ b/rust_package/brainflow/src/ffi/data_handler.rs @@ -272,6 +272,16 @@ extern "C" { s_mat: *mut f64, ) -> ::std::os::raw::c_int; } +extern "C" { + pub fn get_activity_index( + accel_x: *const f64, + accel_y: *const f64, + accel_z: *const f64, + data_len: ::std::os::raw::c_int, + period: ::std::os::raw::c_int, + activity_index: *mut f64, + ) -> ::std::os::raw::c_int; +} extern "C" { pub fn set_log_level_data_handler(log_level: ::std::os::raw::c_int) -> ::std::os::raw::c_int; } diff --git a/src/data_handler/data_handler.cpp b/src/data_handler/data_handler.cpp index 2f177faad..db07d10be 100644 --- a/src/data_handler/data_handler.cpp +++ b/src/data_handler/data_handler.cpp @@ -1726,6 +1726,54 @@ int perform_ica (double *data, int rows, int cols, int num_components, double *w return res; } +int get_activity_index (const double *accel_x, const double *accel_y, const double *accel_z, + int data_len, int period, double *output) +{ + if ((accel_x == NULL) || (accel_y == NULL) || (accel_z == NULL) || (output == NULL) || + (period <= 0) || (data_len < period)) + { + data_logger->error ("Invalid arguments for get_activity_index: accel_x {}, accel_y {}, " + "accel_z {}, output {}, data_len {}, period {}", + (accel_x != NULL), (accel_y != NULL), (accel_z != NULL), (output != NULL), data_len, + period); + return (int)BrainFlowExitCodes::INVALID_ARGUMENTS_ERROR; + } + + int num_epochs = data_len / period; + for (int epoch = 0; epoch < num_epochs; epoch++) + { + int start_pos = epoch * period; + int end_pos = start_pos + period; + + double mean_x = 0.0, mean_y = 0.0, mean_z = 0.0; + for (int i = start_pos; i < end_pos; i++) + { + mean_x += accel_x[i]; + mean_y += accel_y[i]; + mean_z += accel_z[i]; + } + mean_x /= period; + mean_y /= period; + mean_z /= period; + + double var_x = 0.0, var_y = 0.0, var_z = 0.0; + for (int i = start_pos; i < end_pos; i++) + { + var_x += (accel_x[i] - mean_x) * (accel_x[i] - mean_x); + var_y += (accel_y[i] - mean_y) * (accel_y[i] - mean_y); + var_z += (accel_z[i] - mean_z) * (accel_z[i] - mean_z); + } + var_x /= period; + var_y /= period; + var_z /= period; + + double total_var = (var_x + var_y + var_z) / 3.0; + output[epoch] = sqrt (std::max (0.0, total_var)); + } + + return (int)BrainFlowExitCodes::STATUS_OK; +} + int get_version_data_handler (char *version, int *num_chars, int max_chars) { strncpy (version, BRAINFLOW_VERSION_STRING, max_chars); diff --git a/src/data_handler/inc/data_handler.h b/src/data_handler/inc/data_handler.h index 2e50e284c..b933f76a5 100644 --- a/src/data_handler/inc/data_handler.h +++ b/src/data_handler/inc/data_handler.h @@ -69,6 +69,9 @@ extern "C" double *data, int data_len, int lag, double threshold, double influence, double *output); SHARED_EXPORT int CALLING_CONVENTION perform_ica (double *data, int rows, int cols, int num_components, double *w_mat, double *k_mat, double *a_mat, double *s_mat); + SHARED_EXPORT int CALLING_CONVENTION get_activity_index (const double *accel_x, + const double *accel_y, const double *accel_z, int data_len, int period, + double *activity_index); // logging methods SHARED_EXPORT int CALLING_CONVENTION set_log_level_data_handler (int log_level); diff --git a/swift_package/Sources/BrainFlow/DataFilter.swift b/swift_package/Sources/BrainFlow/DataFilter.swift index ec21ef5a9..68aa696a2 100644 --- a/swift_package/Sources/BrainFlow/DataFilter.swift +++ b/swift_package/Sources/BrainFlow/DataFilter.swift @@ -704,6 +704,27 @@ public enum DataFilter { BrainFlowArray.reshape_data_to_2d(num_rows: num_rows, num_cols: num_cols, linear_buffer: linear_buffer) } + public static func get_activity_index(accel_x: [Double], accel_y: [Double], accel_z: [Double], period: Int = 0) throws -> [Double] { + guard accel_x.count == accel_y.count, accel_x.count == accel_z.count else { throw invalidArguments("Array lengths must match") } + let dataLen = accel_x.count + let periodToUse = period <= 0 ? dataLen : period + guard periodToUse > 0, dataLen >= periodToUse else { throw invalidArguments("Data length is shorter than period") } + let numEpochs = dataLen / periodToUse + var output = [Double](repeating: 0.0, count: numEpochs) + try accel_x.withUnsafeBufferPointer { xPtr in + try accel_y.withUnsafeBufferPointer { yPtr in + try accel_z.withUnsafeBufferPointer { zPtr in + try output.withUnsafeMutableBufferPointer { outPtr in + try DataFilterNative.withData { native in + try checkBrainFlowExitCode(native.get_activity_index(xPtr.baseAddress, yPtr.baseAddress, zPtr.baseAddress, CInt(dataLen), CInt(periodToUse), outPtr.baseAddress), "Failed to calculate activity index") + } + } + } + } + } + return output + } + private static func withMutableData(_ data: inout [Double], _ body: (UnsafeMutablePointer?, Int) throws -> T) throws -> T { try data.withUnsafeMutableBufferPointer { pointer in try body(pointer.baseAddress, pointer.count) @@ -753,6 +774,7 @@ final class DataFilterNative { let restore_data_from_wavelet_detailed_coeffs: @convention(c) (UnsafeMutablePointer?, CInt, CInt, CInt, CInt, UnsafeMutablePointer?) -> CInt let detect_peaks_z_score: @convention(c) (UnsafeMutablePointer?, CInt, CInt, Double, Double, UnsafeMutablePointer?) -> CInt let perform_ica: @convention(c) (UnsafeMutablePointer?, CInt, CInt, CInt, UnsafeMutablePointer?, UnsafeMutablePointer?, UnsafeMutablePointer?, UnsafeMutablePointer?) -> CInt + let get_activity_index: @convention(c) (UnsafePointer?, UnsafePointer?, UnsafePointer?, CInt, CInt, UnsafeMutablePointer?) -> CInt let set_log_level_data_handler: @convention(c) (CInt) -> CInt let set_log_file_data_handler: @convention(c) (UnsafePointer?) -> CInt let log_message_data_handler: @convention(c) (CInt, UnsafeMutablePointer?) -> CInt @@ -805,6 +827,7 @@ final class DataFilterNative { restore_data_from_wavelet_detailed_coeffs = try library.symbol("restore_data_from_wavelet_detailed_coeffs", as: type(of: restore_data_from_wavelet_detailed_coeffs)) detect_peaks_z_score = try library.symbol("detect_peaks_z_score", as: type(of: detect_peaks_z_score)) perform_ica = try library.symbol("perform_ica", as: type(of: perform_ica)) + get_activity_index = try library.symbol("get_activity_index", as: type(of: get_activity_index)) set_log_level_data_handler = try library.symbol("set_log_level_data_handler", as: type(of: set_log_level_data_handler)) set_log_file_data_handler = try library.symbol("set_log_file_data_handler", as: type(of: set_log_file_data_handler)) log_message_data_handler = try library.symbol("log_message_data_handler", as: type(of: log_message_data_handler))