diff --git a/.github/workflows/run_unix.yml b/.github/workflows/run_unix.yml index db6accc74..26289be4d 100644 --- a/.github/workflows/run_unix.yml +++ b/.github/workflows/run_unix.yml @@ -411,6 +411,7 @@ jobs: sudo -H python3 $GITHUB_WORKSPACE/python_package/examples/tests/decomposition_regression.py sudo -H python3 $GITHUB_WORKSPACE/python_package/examples/tests/signal_metrics_regression.py sudo -H python3 $GITHUB_WORKSPACE/python_package/examples/tests/binding_validation_regression.py + sudo -H python3 $GITHUB_WORKSPACE/python_package/examples/tests/rereference_channels.py - name: Signal Processing Regression Cpp run: $GITHUB_WORKSPACE/cpp_package/examples/signal_processing/build/signal_processing_regression env: diff --git a/.github/workflows/run_windows.yml b/.github/workflows/run_windows.yml index f636a769d..34e15d9a8 100644 --- a/.github/workflows/run_windows.yml +++ b/.github/workflows/run_windows.yml @@ -250,6 +250,7 @@ jobs: python python_package\examples\tests\decomposition_regression.py python python_package\examples\tests\signal_metrics_regression.py python python_package\examples\tests\binding_validation_regression.py + python python_package\examples\tests\rereference_channels.py shell: cmd - name: Signal Quality Regression Python run: python python_package\examples\tests\signal_quality_regression.py diff --git a/cpp_package/examples/signal_processing/src/signal_processing_regression.cpp b/cpp_package/examples/signal_processing/src/signal_processing_regression.cpp index f76082fd2..ed4fa3dd0 100644 --- a/cpp_package/examples/signal_processing/src/signal_processing_regression.cpp +++ b/cpp_package/examples/signal_processing/src/signal_processing_regression.cpp @@ -113,6 +113,42 @@ try lengths[0]++; reject ( [&] { DataFilter::perform_inverse_wavelet_transform (coefficients, lengths, 256, 3, 3); }); + + BrainFlowArray reref_data (4, 3); + double values[12] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; + for (int i = 0; i < 4; i++) + for (int j = 0; j < 3; j++) + reref_data.at (i, j) = values[i * 3 + j]; + DataFilter::rereference_channels (reref_data, {0, 1}, {0, 1}); + for (int j = 0; j < 3; j++) + if (std::abs (reref_data.at (0, j) + 1.5) > 1e-12 || + std::abs (reref_data.at (1, j) - 1.5) > 1e-12 || + std::abs (reref_data.at (2, j) - values[6 + j]) > 1e-12) + throw std::runtime_error ("Two-channel average reference failed"); + + for (int i = 0; i < 4; i++) + for (int j = 0; j < 3; j++) + reref_data.at (i, j) = values[i * 3 + j]; + DataFilter::rereference_channels (reref_data, {0, 1, 2, 3}, {1}); + for (int j = 0; j < 3; j++) + if (std::abs (reref_data.at (1, j)) > 1e-12 || + std::abs (reref_data.at (0, j) - (values[j] - values[3 + j])) > 1e-12) + throw std::runtime_error ("Single-channel reference failed"); + + for (int i = 0; i < 4; i++) + for (int j = 0; j < 3; j++) + reref_data.at (i, j) = values[i * 3 + j]; + DataFilter::rereference_channels (reref_data, {0, 1, 2, 3}, {0, 1, 2, 3}); + for (int j = 0; j < 3; j++) + { + double mean = (values[j] + values[3 + j] + values[6 + j] + values[9 + j]) / 4.0; + for (int i = 0; i < 4; i++) + if (std::abs (reref_data.at (i, j) - (values[i * 3 + j] - mean)) > 1e-12) + throw std::runtime_error ("Common average reference failed"); + } + reject ([&] { DataFilter::rereference_channels (reref_data, {0}, {4}); }); + reject ([&] { DataFilter::rereference_channels (reref_data, {0, 0}, {1}); }); + std::cout << "Signal-processing binding validation passed." << std::endl; return 0; } diff --git a/cpp_package/src/data_filter.cpp b/cpp_package/src/data_filter.cpp index bd8cd9aec..f5240b0d2 100644 --- a/cpp_package/src/data_filter.cpp +++ b/cpp_package/src/data_filter.cpp @@ -780,6 +780,28 @@ double *DataFilter::get_activity_index (const double *accel_x, const double *acc return output; } +void DataFilter::rereference_channels (BrainFlowArray &data, + const std::vector &channels, const std::vector &reference_channels) +{ + validate_channels (data, channels); + validate_channels (data, reference_channels); + if (channels.size () > (size_t)std::numeric_limits::max () || + reference_channels.size () > (size_t)std::numeric_limits::max ()) + { + throw BrainFlowException ( + "invalid channel dimensions", (int)BrainFlowExitCodes::INVALID_ARGUMENTS_ERROR); + } + std::vector channels_copy (channels.begin (), channels.end ()); + std::vector reference_copy (reference_channels.begin (), reference_channels.end ()); + int res = ::rereference_channels (data.get_raw_ptr (), data.get_size (0), data.get_size (1), + channels_copy.data (), (int)channels_copy.size (), reference_copy.data (), + (int)reference_copy.size ()); + if (res != (int)BrainFlowExitCodes::STATUS_OK) + { + throw BrainFlowException ("failed to rereference channels", res); + } +} + 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 7d15391f6..92358edda 100644 --- a/cpp_package/src/inc/data_filter.h +++ b/cpp_package/src/inc/data_filter.h @@ -314,6 +314,16 @@ class DataFilter double noise_var_x = 0.0, double noise_var_y = 0.0, double noise_var_z = 0.0, int *output_len = NULL); + /** + * re-reference selected EEG/EXG channels in-place + * @param data 2d array with shape (channels, samples), modified in-place + * @param channels zero-based channel indices to update + * @param reference_channels zero-based channel indices used to compute the mean reference + * Overlap between channels and reference_channels is allowed (common average reference). + */ + static void rereference_channels (BrainFlowArray &data, + const std::vector &channels, const std::vector &reference_channels); + /// 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 def7a8b37..4c5230c22 100644 --- a/csharp_package/brainflow/brainflow/data_filter.cs +++ b/csharp_package/brainflow/brainflow/data_filter.cs @@ -833,6 +833,34 @@ public static Tuple perform_ica (dou return return_data; } + public static void rereference_channels (double[,] data, int[] channels, int[] reference_channels) + { + if (data == null || channels == null || reference_channels == null) + { + throw new BrainFlowError ((int)BrainFlowExitCodes.INVALID_ARGUMENTS_ERROR); + } + int rows = data.GetLength (0); + int cols = data.GetLength (1); + if (rows <= 0 || cols <= 0 || (long)rows * cols > int.MaxValue) + { + throw new BrainFlowError ((int)BrainFlowExitCodes.INVALID_ARGUMENTS_ERROR); + } + double[] data_1d = data.Flatten (); + int res = DataHandlerLibrary.rereference_channels (data_1d, rows, cols, channels, channels.Length, + reference_channels, reference_channels.Length); + if (res != (int)BrainFlowExitCodes.STATUS_OK) + { + throw new BrainFlowError (res); + } + for (int i = 0; i < rows; i++) + { + for (int j = 0; j < cols; j++) + { + data[i, j] = data_1d[i * cols + j]; + } + } + } + /// /// Calculate normalized mean powers for bands 2-4, 4-8, 8-13, 13-30, 30-45 Hz. /// Uses get_custom_band_powers preprocessing and minimum retained data length. diff --git a/csharp_package/brainflow/brainflow/data_handler_library.cs b/csharp_package/brainflow/brainflow/data_handler_library.cs index 971c88f71..a52632502 100644 --- a/csharp_package/brainflow/brainflow/data_handler_library.cs +++ b/csharp_package/brainflow/brainflow/data_handler_library.cs @@ -208,6 +208,8 @@ public static extern int perform_wavelet_denoising (double[] data, int data_len, [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 rereference_channels (double[] data, int rows, int cols, int[] channels, int channels_len, int[] reference_channels, int reference_channels_len); + [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 sampling_rate, int period, double noise_var_x, double noise_var_y, double noise_var_z, double[] output); // unsafe methods working with pointers [DllImport ("DataHandler", SetLastError = true, CallingConvention = CallingConvention.Cdecl)] @@ -340,6 +342,8 @@ public static extern int perform_wavelet_denoising (double[] data, int data_len, [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 rereference_channels (double[] data, int rows, int cols, int[] channels, int channels_len, int[] reference_channels, int reference_channels_len); + [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 sampling_rate, int period, double noise_var_x, double noise_var_y, double noise_var_z, double[] output); // unsafe methods working with pointers [DllImport ("DataHandler32", SetLastError = true, CallingConvention = CallingConvention.Cdecl)] @@ -474,6 +478,19 @@ public static int perform_ica (double[] data, int rows, int cols, int num_compon return (int)BrainFlowExitCodes.GENERAL_ERROR; } + public static int rereference_channels (double[] data, int rows, int cols, int[] channels, int channels_len, int[] reference_channels, int reference_channels_len) + { + switch (PlatformHelper.get_library_environment ()) + { + case LibraryEnvironment.x64: + return DataHandlerLibrary64.rereference_channels (data, rows, cols, channels, channels_len, reference_channels, reference_channels_len); + case LibraryEnvironment.x86: + return DataHandlerLibrary32.rereference_channels (data, rows, cols, channels, channels_len, reference_channels, reference_channels_len); + } + + return (int)BrainFlowExitCodes.GENERAL_ERROR; + } + public static int get_activity_index (double[] accel_x, double[] accel_y, double[] accel_z, int data_len, int sampling_rate, int period, double noise_var_x, double noise_var_y, double noise_var_z, double[] output) { switch (PlatformHelper.get_library_environment ()) diff --git a/docs/UserAPI.rst b/docs/UserAPI.rst index 18116015f..7cf7fddff 100644 --- a/docs/UserAPI.rst +++ b/docs/UserAPI.rst @@ -281,6 +281,14 @@ Data files accept rectangular tab- or comma-separated finite numeric values, inc a final line without a newline. Invalid tokens and insufficient destination capacity return errors. Writing uses enough decimal precision to round-trip doubles. +``DataFilter.rereference_channels`` subtracts the element-wise mean of +``reference_channels`` from the selected ``channels`` of a ``(channels, samples)`` +array in place. Channel indices are zero-based in most bindings; MATLAB and Julia +use one-based indices and convert them before calling native code. Overlap between +the two index lists is allowed and supports common average referencing. MATLAB +returns the updated matrix, so callers must assign the result +(``data = DataFilter.rereference_channels(data, channels, reference_channels)``). + Python API Reference ---------------------- diff --git a/java_package/brainflow/src/main/java/brainflow/DataFilter.java b/java_package/brainflow/src/main/java/brainflow/DataFilter.java index 8c1623053..6ed30f4f1 100644 --- a/java_package/brainflow/src/main/java/brainflow/DataFilter.java +++ b/java_package/brainflow/src/main/java/brainflow/DataFilter.java @@ -181,6 +181,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 rereference_channels (double[] data, int rows, int cols, int[] channels, int channels_len, + int[] reference_channels, int reference_channels_len); + int get_activity_index (double[] accel_x, double[] accel_y, double[] accel_z, int data_len, int sampling_rate, int period, double noise_var_x, double noise_var_y, double noise_var_z, double[] output); @@ -1109,6 +1112,42 @@ public static List perform_ica (double[][] data, int num_components, return res; } + public static void rereference_channels (double[][] data, int[] channels, int[] reference_channels) + throws BrainFlowError + { + if (data == null || data.length == 0 || data[0] == null || channels == null || + reference_channels == null) + { + throw new BrainFlowError ("invalid args for rereference_channels", + BrainFlowExitCode.INVALID_ARGUMENTS_ERROR.get_code ()); + } + int cols = data[0].length; + for (int i = 0; i < data.length; i++) + { + if (data[i] == null || data[i].length != cols) + { + throw new BrainFlowError ("invalid args for rereference_channels", + BrainFlowExitCode.INVALID_ARGUMENTS_ERROR.get_code ()); + } + } + int rows = data.length; + double[] data_1d = new double[checked_product (rows, cols)]; + for (int i = 0; i < rows; i++) + { + System.arraycopy (data[i], 0, data_1d, i * cols, cols); + } + int ec = instance.rereference_channels (data_1d, rows, cols, channels, channels.length, + reference_channels, reference_channels.length); + if (ec != BrainFlowExitCode.STATUS_OK.get_code ()) + { + throw new BrainFlowError ("Failed to rereference_channels", ec); + } + for (int i = 0; i < rows; i++) + { + System.arraycopy (data_1d, i * cols, data[i], 0, cols); + } + } + /** * get PSD * diff --git a/julia_package/brainflow/src/data_filter.jl b/julia_package/brainflow/src/data_filter.jl index a2cceda79..1aa4a5a68 100644 --- a/julia_package/brainflow/src/data_filter.jl +++ b/julia_package/brainflow/src/data_filter.jl @@ -633,6 +633,26 @@ function perform_ica(data, num_components::Integer) return perform_ica_select_channels(data, num_components, channels) end +@brainflow_rethrow function rereference_channels(data, channels, reference_channels) + if !(data isa StridedMatrix{Float64}) + throw(BrainFlowError("In-place processing requires a StridedMatrix{Float64}", Integer(INVALID_ARGUMENTS_ERROR))) + end + shape = size(data) + if !(0 < length(data) <= typemax(Cint)) || prod(shape) > typemax(Cint) + throw(BrainFlowError("Expected a nonempty real matrix", Integer(INVALID_ARGUMENTS_ERROR))) + end + channels = _dsp_channels(data, channels) + reference_channels = _dsp_channels(data, reference_channels) + channels_c = Int32.(channels .- 1) + reference_c = Int32.(reference_channels .- 1) + data_1d = copy(reshape(transpose(data), (1, prod(shape)))) + ccall((:rereference_channels, DATA_HANDLER_INTERFACE), Cint, + (Ptr{Float64}, Cint, Cint, Ptr{Int32}, Cint, Ptr{Int32}, Cint), + data_1d, Int32(shape[1]), Int32(shape[2]), channels_c, length(channels_c), reference_c, length(reference_c)) + copyto!(data, transpose(reshape(data_1d, shape[2], shape[1]))) + return +end + @brainflow_rethrow function get_psd(data, sampling_rate::Integer, window::WinType) data = _dsp_signal(data) diff --git a/matlab_package/brainflow/DataFilter.m b/matlab_package/brainflow/DataFilter.m index b670292dd..226ebbcd6 100644 --- a/matlab_package/brainflow/DataFilter.m +++ b/matlab_package/brainflow/DataFilter.m @@ -730,6 +730,22 @@ function write_file(data, file_name, file_mode) DataFilter.check_ec(exit_code, task_name); output = temp_output.Value; end + + function data = rereference_channels(data, channels, reference_channels) + % re-reference selected channels; assign the returned matrix + channels = DataFilter.checked_channels(data, channels); + reference_channels = DataFilter.checked_channels(data, reference_channels); + task_name = 'rereference_channels'; + transposed = transpose(data); + flatten = transpose(transposed(:)); + temp_input = libpointer('doublePtr', flatten); + temp_channels = libpointer('int32Ptr', int32(channels - 1)); + temp_reference = libpointer('int32Ptr', int32(reference_channels - 1)); + lib_name = DataFilter.load_lib(); + exit_code = calllib(lib_name, task_name, temp_input, size(data, 1), size(data, 2), temp_channels, numel(channels), temp_reference, numel(reference_channels)); + DataFilter.check_ec(exit_code, task_name); + data = transpose(reshape(temp_input.Value, [size(data, 2), size(data, 1)])); + end end diff --git a/nodejs_package/brainflow/data_filter.ts b/nodejs_package/brainflow/data_filter.ts index adae0c22e..a2fa1f378 100644 --- a/nodejs_package/brainflow/data_filter.ts +++ b/nodejs_package/brainflow/data_filter.ts @@ -114,6 +114,7 @@ class DataHandlerDLL extends DataHandlerFunctions 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.rereferenceChannels = this.lib.func(CLike.rereference_channels); this.getCsp = this.lib.func(CLike.get_csp); this.detrend = this.lib.func(CLike.detrend); this.calcStddev = this.lib.func(CLike.calc_stddev); @@ -938,4 +939,29 @@ export class DataFilter } return output; } + + public static rereferenceChannels( + data: number[][], channels: number[], referenceChannels: number[]): void + { + const columns = DataFilter.checkMatrix(data); + DataFilter.checkChannels(data, channels, columns); + DataFilter.checkChannels(data, referenceChannels, columns); + const flat = data.flat(); + const res = DataHandlerDLL.getInstance().rereferenceChannels( + flat, data.length, columns, channels, channels.length, referenceChannels, + referenceChannels.length); + if (res !== BrainFlowExitCodes.STATUS_OK) + { + throw new BrainFlowError (res, 'Could not rereference channels'); + } + var i: number; + var j: number; + for (i = 0; i < data.length; i++) + { + for (j = 0; j < columns; j++) + { + data[i][j] = flat[i * columns + j]; + } + } + } } diff --git a/nodejs_package/brainflow/functions.types.ts b/nodejs_package/brainflow/functions.types.ts index 3d1382374..c432cb1c1 100644 --- a/nodejs_package/brainflow/functions.types.ts +++ b/nodejs_package/brainflow/functions.types.ts @@ -376,6 +376,8 @@ export enum DataHandlerCLikeFunctions { '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 sampling_rate, int period, double noise_var_x, double noise_var_y, double noise_var_z, _Inout_ double *activity_index)', + rereference_channels = + 'int rereference_channels (_Inout_ double *data, int rows, int cols, int *channels, int channels_len, int *reference_channels, int reference_channels_len)', 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 = @@ -464,6 +466,8 @@ export class DataHandlerFunctions getActivityIndex!: (accelX: number[], accelY: number[], accelZ: number[], dataLen: number, samplingRate: number, period: number, noiseVarX: number, noiseVarY: number, noiseVarZ: number, activityIndex: number[]) => BrainFlowExitCodes; + rereferenceChannels!: (data: number[], rows: number, cols: number, channels: number[], + channelsLen: number, referenceChannels: number[], referenceChannelsLen: 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/nodejs_package/tests/binding_validation_regression.ts b/nodejs_package/tests/binding_validation_regression.ts index 03a7f1ca5..a1b83cdb4 100644 --- a/nodejs_package/tests/binding_validation_regression.ts +++ b/nodejs_package/tests/binding_validation_regression.ts @@ -48,6 +48,12 @@ assert.ok(restored.every((value, i) => Math.abs(value - signal[i]) < 1e-10)); assert.throws(() => DataFilter.performInverseWaveletTransform( transformed, 64, WaveletTypes.DB3, 2, WaveletExtensionTypes.SYMMETRIC), BrainFlowError); +const rerefData = [[1, 2], [3, 4], [Number.NaN, Number.NaN]]; +DataFilter.rereferenceChannels(rerefData, [0], [1]); +assert.deepStrictEqual(rerefData[0], [-2, -2]); +assert.deepStrictEqual(rerefData[1], [3, 4]); +assert.ok(Number.isNaN(rerefData[2][0]) && Number.isNaN(rerefData[2][1])); + const rate = 128; const samples = 1024; const contaminated = new Array(samples).fill(0); diff --git a/nodejs_package/tests/package-lock.json b/nodejs_package/tests/package-lock.json new file mode 100644 index 000000000..539e76ce6 --- /dev/null +++ b/nodejs_package/tests/package-lock.json @@ -0,0 +1,270 @@ +{ + "name": "tests", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "tests", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "brainflow": "file:.." + }, + "devDependencies": { + "@types/node": "^20.6.0", + "ts-node": "^10.9.2", + "typescript": "^5.2.2" + } + }, + "..": { + "name": "brainflow", + "version": "0.0.0-development", + "license": "MIT", + "dependencies": { + "koffi": "2.5.7", + "lodash": "4.17.21" + }, + "devDependencies": { + "@types/jest": "29.5.3", + "@types/lodash": "4.14.195", + "@types/node": "20.4.2", + "@typescript-eslint/eslint-plugin": "^5.50.0", + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^8.0.1", + "eslint-config-prettier": "8.8.0", + "eslint-config-standard-with-typescript": "36.1.0", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", + "eslint-plugin-prettier": "5.0.0", + "eslint-plugin-promise": "6.0.0", + "jest": "29.6.1", + "prettier": "3.0.0", + "ts-jest": "29.1.1", + "typescript": "^5.1.6" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.13.tgz", + "integrity": "sha512-gcLdvR9HO1ZJBypsOGqaP6TFEzb6vIta0KSTLt9NAQ6pXQO3cRgSVyCN6pzYqI9DlJgY71XKO0dpDhCf08b3pg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/acorn": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz", + "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/brainflow": { + "resolved": "..", + "link": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + } + } +} diff --git a/nodejs_package/tests/package.json b/nodejs_package/tests/package.json index 3697e0cf5..c25118e99 100644 --- a/nodejs_package/tests/package.json +++ b/nodejs_package/tests/package.json @@ -21,6 +21,7 @@ "license": "ISC", "devDependencies": { "@types/node": "^20.6.0", + "ts-node": "^10.9.2", "typescript": "^5.2.2" }, "dependencies": { diff --git a/python_package/brainflow/data_filter.py b/python_package/brainflow/data_filter.py index 86d1ad7f8..c47226641 100644 --- a/python_package/brainflow/data_filter.py +++ b/python_package/brainflow/data_filter.py @@ -491,6 +491,18 @@ def __init__(self): self.perform_ica_with_options.argtypes = self.perform_ica.argtypes + [ ctypes.c_int, ctypes.c_double, ctypes.c_int] + self.rereference_channels = self.lib.rereference_channels + self.rereference_channels.restype = ctypes.c_int + self.rereference_channels.argtypes = [ + ndpointer(ctypes.c_double, flags=('C_CONTIGUOUS', 'WRITEABLE')), + ctypes.c_int, + ctypes.c_int, + ndpointer(ctypes.c_int32, flags='C_CONTIGUOUS'), + ctypes.c_int, + ndpointer(ctypes.c_int32, flags='C_CONTIGUOUS'), + ctypes.c_int, + ] + self.create_streaming_filter = self.lib.create_streaming_filter self.create_streaming_filter.restype = ctypes.c_int self.create_streaming_filter.argtypes = [ @@ -1789,6 +1801,44 @@ def perform_ica(cls, data, num_components: int, channels=None, *, max_iterations return w, k, a, s + @classmethod + def rereference_channels(cls, data, channels: List, reference_channels: List) -> None: + """re-reference selected EEG/EXG channels in-place + + Computes an element-wise mean across ``reference_channels`` and subtracts that + reference from each channel listed in ``channels``. Overlap between the two lists + is allowed and is the usual pattern for common average referencing. Channel indices + are zero-based. + + :param data: writable float64 2d array with shape (channels, samples) + :type data: NDArray[Shape["*, *"], Float64] + :param channels: zero-based indices of channels to update + :type channels: List + :param reference_channels: zero-based indices used to compute the mean reference + :type reference_channels: List + """ + check_memory_layout_row_major(data, 2) + if data.dtype != numpy.float64 or not data.flags.writeable: + raise BrainFlowError('data must be a writable float64 array', + BrainFlowExitCodes.INVALID_ARGUMENTS_ERROR.value) + channels_arr = cls._validated_channel_indices(channels, data.shape[0], 'channels') + reference_arr = cls._validated_channel_indices( + reference_channels, data.shape[0], 'reference_channels') + res = DataHandlerDLL.get_instance().rereference_channels( + data, data.shape[0], data.shape[1], channels_arr, channels_arr.size, + reference_arr, reference_arr.size) + if res != BrainFlowExitCodes.STATUS_OK.value: + raise BrainFlowError('unable to rereference channels', res) + + @staticmethod + def _validated_channel_indices(channels, num_channels: int, arg_name: str): + indices = numpy.asarray(channels) + if (indices.ndim != 1 or indices.size == 0 or indices.dtype.kind not in 'iu' or + numpy.any(indices < 0) or numpy.any(indices >= num_channels)): + raise BrainFlowError('invalid %s' % arg_name, + BrainFlowExitCodes.INVALID_ARGUMENTS_ERROR.value) + return numpy.ascontiguousarray(indices, dtype=numpy.int32) + @classmethod def perform_ifft(cls, data): """perform inverse fft diff --git a/python_package/examples/tests/rereference_channels.py b/python_package/examples/tests/rereference_channels.py new file mode 100644 index 000000000..c76cf6a5e --- /dev/null +++ b/python_package/examples/tests/rereference_channels.py @@ -0,0 +1,105 @@ +"""Unit tests for DataFilter.rereference_channels.""" + +import unittest + +import numpy as np + +from brainflow.data_filter import DataFilter +from brainflow.exit_codes import BrainFlowError, BrainFlowExitCodes + + +class RereferenceChannelsTest(unittest.TestCase): + def setUp(self): + self.data = np.array([ + [1.0, 2.0, 3.0], + [4.0, 5.0, 6.0], + [7.0, 8.0, 9.0], + [10.0, 11.0, 12.0], + ], dtype=np.float64) + + def assert_invalid(self, data, channels, reference_channels): + with self.assertRaises(BrainFlowError) as error: + DataFilter.rereference_channels(data, channels, reference_channels) + self.assertEqual(error.exception.exit_code, BrainFlowExitCodes.INVALID_ARGUMENTS_ERROR.value) + + def test_average_reference_across_two_channels(self): + data = self.data.copy() + DataFilter.rereference_channels(data, [0, 1, 2, 3], [0, 1]) + reference = np.array([2.5, 3.5, 4.5]) + np.testing.assert_allclose(data, self.data - reference) + + def test_average_reference_across_three_channels(self): + data = self.data.copy() + DataFilter.rereference_channels(data, [0, 1, 2, 3], [0, 1, 2]) + reference = np.array([4.0, 5.0, 6.0]) + np.testing.assert_allclose(data, self.data - reference, atol=1e-12) + + def test_reference_to_single_channel(self): + data = self.data.copy() + DataFilter.rereference_channels(data, [0, 1, 2, 3], [1]) + np.testing.assert_allclose(data, self.data - self.data[1]) + np.testing.assert_array_equal(data[1], np.zeros(3)) + + def test_selected_channels_only(self): + data = self.data.copy() + DataFilter.rereference_channels(data, [0, 1], [0, 1]) + reference = np.array([2.5, 3.5, 4.5]) + expected = self.data.copy() + expected[0] -= reference + expected[1] -= reference + np.testing.assert_allclose(data, expected) + np.testing.assert_array_equal(data[2], self.data[2]) + np.testing.assert_array_equal(data[3], self.data[3]) + + def test_common_average_reference_allows_overlap(self): + data = self.data.copy() + DataFilter.rereference_channels(data, [0, 1, 2, 3], [0, 1, 2, 3]) + reference = np.mean(self.data, axis=0) + np.testing.assert_allclose(data, self.data - reference) + + def test_output_shape_equals_input_shape(self): + data = self.data.copy() + DataFilter.rereference_channels(data, [0, 1], [0, 1]) + self.assertEqual(data.shape, self.data.shape) + + def test_invalid_channel_index_raises(self): + self.assert_invalid(self.data.copy(), [0, 4], [0]) + self.assert_invalid(self.data.copy(), [0], [4]) + self.assert_invalid(self.data.copy(), [-1], [0]) + + def test_empty_channel_lists_raise(self): + self.assert_invalid(self.data.copy(), [], [0]) + self.assert_invalid(self.data.copy(), [0], []) + + def test_duplicate_channel_indices_raise(self): + self.assert_invalid(self.data.copy(), [0, 1, 0], [1]) + self.assert_invalid(self.data.copy(), [0], [1, 1]) + + def test_non_2d_input_raises(self): + self.assert_invalid(np.array([1.0, 2.0, 3.0], dtype=np.float64), [0], [0]) + + def test_sliced_channel_index_arrays(self): + data = self.data.copy() + channels = np.arange(4, dtype=np.int32)[::2] + DataFilter.rereference_channels(data, channels, [3]) + expected = self.data.copy() + expected[[0, 2]] -= self.data[3] + np.testing.assert_allclose(data, expected) + + data = self.data.copy() + reference = np.arange(4, dtype=np.int32)[::2] + DataFilter.rereference_channels(data, [0, 1, 2, 3], reference) + expected = self.data - np.mean(self.data[[0, 2]], axis=0) + np.testing.assert_allclose(data, expected) + + def test_non_integer_and_overflowing_indices_raise(self): + self.assert_invalid(self.data.copy(), [0.9], [0]) + self.assert_invalid(self.data.copy(), [-0.5], [0]) + self.assert_invalid(self.data.copy(), np.array([4294967296], dtype=np.int64), [0]) + self.assert_invalid(self.data.copy(), [0], [0.9]) + self.assert_invalid(self.data.copy(), [0], [-0.5]) + self.assert_invalid(self.data.copy(), [0], np.array([4294967296], dtype=np.int64)) + + +if __name__ == '__main__': + unittest.main() diff --git a/rust_package/brainflow/src/data_filter.rs b/rust_package/brainflow/src/data_filter.rs index 48c36b77c..452ac3027 100644 --- a/rust_package/brainflow/src/data_filter.rs +++ b/rust_package/brainflow/src/data_filter.rs @@ -1229,6 +1229,46 @@ pub fn get_activity_index( Ok(output) } +/// Re-reference selected channels in-place by subtracting the element-wise mean of +/// ``reference_channels`` from each channel listed in ``channels``. +pub fn rereference_channels( + data: &mut Array2, + channels: &[usize], + reference_channels: &[usize], +) -> Result<()> { + let shape = data.shape(); + let (rows, cols) = (shape[0], shape[1]); + for &ch in channels.iter().chain(reference_channels.iter()) { + if ch >= rows { + return Err(invalid_arguments()); + } + } + let channels_i32: Vec = channels + .iter() + .map(|&c| native_int(c)) + .collect::>>()?; + let reference_i32: Vec = reference_channels + .iter() + .map(|&c| native_int(c)) + .collect::>>()?; + let slice = data + .as_slice_mut() + .ok_or_else(invalid_arguments)?; + let res = unsafe { + data_handler::rereference_channels( + slice.as_mut_ptr() as *mut c_double, + native_int(rows)?, + native_int(cols)?, + channels_i32.as_ptr() as *mut c_int, + native_int(channels_i32.len())?, + reference_i32.as_ptr() as *mut c_int, + native_int(reference_i32.len())?, + ) + }; + check_brainflow_exit_code(res)?; + Ok(()) +} + /// 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 fa11031c3..362491a30 100644 --- a/rust_package/brainflow/src/ffi/data_handler.rs +++ b/rust_package/brainflow/src/ffi/data_handler.rs @@ -340,6 +340,17 @@ extern "C" { activity_index: *mut f64, ) -> ::std::os::raw::c_int; } +extern "C" { + pub fn rereference_channels( + data: *mut f64, + rows: ::std::os::raw::c_int, + cols: ::std::os::raw::c_int, + channels: *mut ::std::os::raw::c_int, + channels_len: ::std::os::raw::c_int, + reference_channels: *mut ::std::os::raw::c_int, + reference_channels_len: ::std::os::raw::c_int, + ) -> ::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/inc/data_handler.h b/src/data_handler/inc/data_handler.h index 0ebcae24b..a228b1fce 100644 --- a/src/data_handler/inc/data_handler.h +++ b/src/data_handler/inc/data_handler.h @@ -179,6 +179,10 @@ extern "C" SHARED_EXPORT int CALLING_CONVENTION get_activity_index (const double *accel_x, const double *accel_y, const double *accel_z, int data_len, int sampling_rate, int period, double noise_var_x, double noise_var_y, double noise_var_z, double *activity_index); + // Subtract mean of reference_channels from selected channels in-place. + // data is row-major (channels x samples). Overlap between lists is allowed (CAR). + SHARED_EXPORT int CALLING_CONVENTION rereference_channels (double *data, int rows, int cols, + int *channels, int channels_len, int *reference_channels, int reference_channels_len); // logging methods SHARED_EXPORT int CALLING_CONVENTION set_log_level_data_handler (int log_level); diff --git a/src/data_handler/signal_processing.cpp b/src/data_handler/signal_processing.cpp index fd5ebcc09..2f9017f9c 100644 --- a/src/data_handler/signal_processing.cpp +++ b/src/data_handler/signal_processing.cpp @@ -602,6 +602,57 @@ catch (...) return failure; } +int rereference_channels (double *data, int rows, int cols, int *channels, int channels_len, + int *reference_channels, int reference_channels_len) +try +{ + if (!data || !channels || !reference_channels || rows < 1 || cols < 1 || channels_len < 1 || + reference_channels_len < 1 || (int64_t)rows * cols > std::numeric_limits::max ()) + return invalid; + + std::set seen_channels; + std::set seen_reference; + for (int i = 0; i < channels_len; i++) + { + int channel = channels[i]; + if (channel < 0 || channel >= rows || !seen_channels.insert (channel).second) + return invalid; + } + for (int i = 0; i < reference_channels_len; i++) + { + int channel = reference_channels[i]; + if (channel < 0 || channel >= rows || !seen_reference.insert (channel).second) + return invalid; + } + + for (int channel : seen_channels) + if (!finite_signal (data + (size_t)channel * cols, cols)) + return invalid; + for (int channel : seen_reference) + if (!seen_channels.count (channel) && !finite_signal (data + (size_t)channel * cols, cols)) + return invalid; + + std::vector reference ((size_t)cols, 0.0); + const double scale = 1.0 / reference_channels_len; + for (int i = 0; i < reference_channels_len; i++) + { + const double *row = data + (size_t)reference_channels[i] * cols; + for (int t = 0; t < cols; t++) + reference[(size_t)t] += row[t] * scale; + } + for (int i = 0; i < channels_len; i++) + { + double *row = data + (size_t)channels[i] * cols; + for (int t = 0; t < cols; t++) + row[t] -= reference[(size_t)t]; + } + return ok; +} +catch (...) +{ + return failure; +} + int write_file (const double *data, int rows, int cols, const char *name, const char *mode) try { diff --git a/swift_package/Sources/BrainFlow/DataFilter.swift b/swift_package/Sources/BrainFlow/DataFilter.swift index 45bc775fc..e20fe9bbd 100644 --- a/swift_package/Sources/BrainFlow/DataFilter.swift +++ b/swift_package/Sources/BrainFlow/DataFilter.swift @@ -871,6 +871,47 @@ public enum DataFilter { return output } + public static func rereference_channels( + data: inout [[Double]], + channels: [Int], + reference_channels: [Int] + ) throws { + let (rows, cols) = try BrainFlowArray.validateRectangular(data) + guard !channels.isEmpty, !reference_channels.isEmpty else { + throw invalidArguments("channels and reference_channels must be non-empty") + } + guard channels.allSatisfy({ $0 >= 0 && $0 < rows }) else { + throw invalidArguments("Channel index is out of range") + } + guard reference_channels.allSatisfy({ $0 >= 0 && $0 < rows }) else { + throw invalidArguments("Reference channel index is out of range") + } + var linear = reshape_data_to_1d(num_rows: rows, num_cols: cols, buf: data) + var channelsNative = channels.map { CInt($0) } + var referenceNative = reference_channels.map { CInt($0) } + try linear.withUnsafeMutableBufferPointer { dataPtr in + try channelsNative.withUnsafeMutableBufferPointer { channelsPtr in + try referenceNative.withUnsafeMutableBufferPointer { referencePtr in + try DataFilterNative.withData { native in + try checkBrainFlowExitCode( + native.rereference_channels( + dataPtr.baseAddress, + checkedCInt(rows), + checkedCInt(cols), + channelsPtr.baseAddress, + checkedCInt(channels.count), + referencePtr.baseAddress, + checkedCInt(reference_channels.count) + ), + "Failed to rereference channels" + ) + } + } + } + } + data = BrainFlowArray.reshape_data_to_2d(num_rows: rows, num_cols: cols, linear_buffer: linear) + } + private static func checkedCInt(_ value: Int) throws -> CInt { guard let result = CInt(exactly: value) else { throw invalidArguments("Integer argument exceeds the native integer range") @@ -955,6 +996,7 @@ final class DataFilterNative { 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, CInt, Double, Double, Double, UnsafeMutablePointer?) -> CInt + let rereference_channels: @convention(c) (UnsafeMutablePointer?, CInt, CInt, UnsafeMutablePointer?, CInt, UnsafeMutablePointer?, CInt) -> 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 @@ -1012,6 +1054,7 @@ final class DataFilterNative { 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)) + rereference_channels = try library.symbol("rereference_channels", as: type(of: rereference_channels)) 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))