Skip to content

Commit

Permalink
add peaks detection (#539)
Browse files Browse the repository at this point in the history
Signed-off-by: Andrey Parfenov <[email protected]>

Signed-off-by: Andrey Parfenov <[email protected]>
  • Loading branch information
Andrey1994 authored Aug 23, 2022
1 parent 7064ad5 commit 8b8f804
Show file tree
Hide file tree
Showing 21 changed files with 663 additions and 9 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/valgrind.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ jobs:
run: valgrind --error-exitcode=1 --track-origins=yes --leak-check=full $GITHUB_WORKSPACE/cpp_package/examples/signal_processing/build/downsampling
env:
LD_LIBRARY_PATH: $GITHUB_WORKSPACE/installed/lib
- name: Peaks Cpp
run: valgrind --error-exitcode=1 --track-origins=yes --leak-check=full $GITHUB_WORKSPACE/cpp_package/examples/signal_processing/build/peaks_detection
env:
LD_LIBRARY_PATH: $GITHUB_WORKSPACE/installed/lib
- name: CSP Cpp
run: valgrind --error-exitcode=1 --track-origins=yes --leak-check=full $GITHUB_WORKSPACE/cpp_package/examples/signal_processing/build/csp
env:
Expand Down
23 changes: 23 additions & 0 deletions cpp_package/examples/signal_processing/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -246,3 +246,26 @@ target_link_libraries (
${DataHandlerPath}
${BoardControllerPath}
)


##########################################
## Demo for band powers across channels ##
##########################################
add_executable (
peaks_detection
src/peaks_detection.cpp
)

target_include_directories (
peaks_detection PUBLIC
${brainflow_INCLUDE_DIRS}
)

target_link_libraries (
peaks_detection PUBLIC
# for some systems(ubuntu for example) order matters
${BrainflowPath}
${MLModulePath}
${DataHandlerPath}
${BoardControllerPath}
)
69 changes: 69 additions & 0 deletions cpp_package/examples/signal_processing/src/peaks_detection.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#include <iostream>
#include <stdlib.h>
#include <string>

#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif

#include "board_shim.h"
#include "data_filter.h"

using namespace std;

void print_one_row (double *data, int num_data_points);


int main (int argc, char *argv[])
{
BoardShim::enable_dev_board_logger ();

struct BrainFlowInputParams params;
int res = 0;
int board_id = (int)BoardIds::SYNTHETIC_BOARD;
// use synthetic board for demo
BoardShim *board = new BoardShim (board_id, params);

try
{
board->prepare_session ();
board->start_stream ();

#ifdef _WIN32
Sleep (5000);
#else
sleep (5);
#endif

board->stop_stream ();
BrainFlowArray<double, 2> data = board->get_board_data ();
board->release_session ();

double *peaks = new double[data.get_size (1)];
std::vector<int> eeg_channels = BoardShim::get_eeg_channels (board_id);

for (int i = 0; i < eeg_channels.size (); i++)
{
DataFilter::restore_data_from_wavelet_detailed_coeffs (
data.get_address (eeg_channels[i]), data.get_size (1), (int)WaveletTypes::DB4, 6, 4,
peaks);
DataFilter::detect_peaks_z_score (peaks, data.get_size (1), 20, 3.5, 0.0, peaks);
}
delete[] peaks;
}
catch (const BrainFlowException &err)
{
BoardShim::log_message ((int)LogLevels::LEVEL_ERROR, err.what ());
res = err.exit_code;
if (board->is_prepared ())
{
board->release_session ();
}
}

delete board;

return res;
}
21 changes: 21 additions & 0 deletions cpp_package/src/data_filter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,27 @@ void DataFilter::remove_environmental_noise (
}
}

void DataFilter::restore_data_from_wavelet_detailed_coeffs (double *data, int data_len, int wavelet,
int decomposition_level, int level_to_restore, double *output)
{
int res = ::restore_data_from_wavelet_detailed_coeffs (
data, data_len, wavelet, decomposition_level, level_to_restore, output);
if (res != (int)BrainFlowExitCodes::STATUS_OK)
{
throw BrainFlowException ("failed to restore", res);
}
}

void DataFilter::detect_peaks_z_score (
double *data, int data_len, int lag, double threshold, double influence, double *output)
{
int res = ::detect_peaks_z_score (data, data_len, lag, threshold, influence, output);
if (res != (int)BrainFlowExitCodes::STATUS_OK)
{
throw BrainFlowException ("failed to detect", res);
}
}

void DataFilter::perform_rolling_filter (double *data, int data_len, int period, int agg_operation)
{
int res = ::perform_rolling_filter (data, data_len, period, agg_operation);
Expand Down
6 changes: 6 additions & 0 deletions cpp_package/src/inc/data_filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ class DataFilter
int threshold = (int)ThresholdTypes::HARD,
int extenstion_type = (int)WaveletExtensionTypes::SYMMETRIC,
int noise_level = (int)NoiseEstimationLevelTypes::FIRST_LEVEL);
/// restore data from selected detailed coeffs
static void restore_data_from_wavelet_detailed_coeffs (double *data, int data_len, int wavelet,
int decomposition_level, int level_to_restore, double *output);
/// z score peak detection, more info https://stackoverflow.com/a/22640362
static void detect_peaks_z_score (
double *data, int data_len, int lag, double threshold, double influence, double *output);
// clang-format off
/**
* calculate filters and the corresponding eigenvalues using the Common Spatial Patterns
Expand Down
14 changes: 14 additions & 0 deletions csharp_package/brainflow/brainflow/data_filter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,20 @@ public static double[] perform_downsampling (double[] data, int period, int oper
return downsampled_data;
}

/// <summary>
/// detect peaks using z score algorithm
/// </summary>
public static double[] detect_peaks_z_score (double[] data, int lag, double threshold, double influence)
{
double[] peaks = new double[data.Length];
int res = DataHandlerLibrary.detect_peaks_z_score (data, data.Length, lag, threshold, influence, peaks);
if (res != (int)BrainFlowExitCodes.STATUS_OK)
{
throw new BrainFlowError (res);
}
return peaks;
}

/// <summary>
/// calc stddev
/// </summary>
Expand Down
50 changes: 50 additions & 0 deletions csharp_package/brainflow/brainflow/data_handler_library.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@ public static extern int perform_wavelet_denoising (double[] data, int data_len,
public static extern int get_version_data_handler (byte[] version, int[] len, int max_len);
[DllImport ("DataHandler.dll", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
public static extern int get_oxygen_level (double[] ppg_ir, double[] ppg_red, int data_size, int sampling_rate, double coef1, double coef2, double coef3, double[] output);
[DllImport ("DataHandler.dll", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
public static extern int restore_data_from_wavelet_detailed_coeffs (double[] data, int data_len, int wavelet, int decomposition_level, int level_to_restore, double[] output);
[DllImport ("DataHandler.dll", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
public static extern int detect_peaks_z_score (double[] data, int data_len, int lag, double threshold, double influence, double[] output);
}

class DataHandlerLibrary32
Expand Down Expand Up @@ -242,6 +246,10 @@ public static extern int perform_wavelet_denoising (double[] data, int data_len,
public static extern int get_railed_percentage (double[] data, int len, int gain, double[] output);
[DllImport ("DataHandler32.dll", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
public static extern int get_oxygen_level (double[] ppg_ir, double[] ppg_red, int data_size, int sampling_rate, double coef1, double coef2, double coef3, double[] output);
[DllImport ("DataHandler32.dll", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
public static extern int restore_data_from_wavelet_detailed_coeffs (double[] data, int data_len, int wavelet, int decomposition_level, int level_to_restore, double[] output);
[DllImport ("DataHandler32.dll", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
public static extern int detect_peaks_z_score (double[] data, int data_len, int lag, double threshold, double influence, double[] output);
}

class DataHandlerLibraryLinux
Expand Down Expand Up @@ -308,6 +316,10 @@ public static extern int perform_wavelet_denoising (double[] data, int data_len,
public static extern int get_railed_percentage (double[] data, int len, int gain, double[] output);
[DllImport ("libDataHandler.so", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
public static extern int get_oxygen_level (double[] ppg_ir, double[] ppg_red, int data_size, int sampling_rate, double coef1, double coef2, double coef3, double[] output);
[DllImport ("libDataHandler.so", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
public static extern int restore_data_from_wavelet_detailed_coeffs (double[] data, int data_len, int wavelet, int decomposition_level, int level_to_restore, double[] output);
[DllImport ("libDataHandler.so", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
public static extern int detect_peaks_z_score (double[] data, int data_len, int lag, double threshold, double influence, double[] output);
}

class DataHandlerLibraryMac
Expand Down Expand Up @@ -374,6 +386,10 @@ public static extern int perform_wavelet_denoising (double[] data, int data_len,
public static extern int get_railed_percentage (double[] data, int len, int gain, double[] output);
[DllImport ("libDataHandler.dylib", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
public static extern int get_oxygen_level (double[] ppg_ir, double[] ppg_red, int data_size, int sampling_rate, double coef1, double coef2, double coef3, double[] output);
[DllImport ("libDataHandler.dylib", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
public static extern int restore_data_from_wavelet_detailed_coeffs (double[] data, int data_len, int wavelet, int decomposition_level, int level_to_restore, double[] output);
[DllImport ("libDataHandler.dylib", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
public static extern int detect_peaks_z_score (double[] data, int data_len, int lag, double threshold, double influence, double[] output);
}

class DataHandlerLibrary
Expand Down Expand Up @@ -515,6 +531,40 @@ public static int perform_rolling_filter (double[] data, int len, int period, in
return (int)BrainFlowExitCodes.GENERAL_ERROR;
}

public static int restore_data_from_wavelet_detailed_coeffs (double[] data, int len, int wavelet, int decomposition_level, int level_to_restore, double[] output)
{
switch (PlatformHelper.get_library_environment ())
{
case LibraryEnvironment.x64:
return DataHandlerLibrary64.restore_data_from_wavelet_detailed_coeffs (data, len, wavelet, decomposition_level, level_to_restore, output);
case LibraryEnvironment.x86:
return DataHandlerLibrary32.restore_data_from_wavelet_detailed_coeffs (data, len, wavelet, decomposition_level, level_to_restore, output);
case LibraryEnvironment.Linux:
return DataHandlerLibraryLinux.restore_data_from_wavelet_detailed_coeffs (data, len, wavelet, decomposition_level, level_to_restore, output);
case LibraryEnvironment.MacOS:
return DataHandlerLibraryMac.restore_data_from_wavelet_detailed_coeffs (data, len, wavelet, decomposition_level, level_to_restore, output);
}

return (int)BrainFlowExitCodes.GENERAL_ERROR;
}

public static int detect_peaks_z_score (double[] data, int len, int lag, double threshold, double influence, double[] output)
{
switch (PlatformHelper.get_library_environment ())
{
case LibraryEnvironment.x64:
return DataHandlerLibrary64.detect_peaks_z_score (data, len, lag, threshold, influence, output);
case LibraryEnvironment.x86:
return DataHandlerLibrary32.detect_peaks_z_score (data, len, lag, threshold, influence, output);
case LibraryEnvironment.Linux:
return DataHandlerLibraryLinux.detect_peaks_z_score (data, len, lag, threshold, influence, output);
case LibraryEnvironment.MacOS:
return DataHandlerLibraryMac.detect_peaks_z_score (data, len, lag, threshold, influence, output);
}

return (int)BrainFlowExitCodes.GENERAL_ERROR;
}

public static int detrend (double[] data, int len, int operation)
{
switch (PlatformHelper.get_library_environment ())
Expand Down
47 changes: 47 additions & 0 deletions java_package/brainflow/src/main/java/brainflow/DataFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ int perform_bandstop (double[] data, int data_len, int sampling_rate, double sta

int perform_downsampling (double[] data, int data_len, int period, int operation, double[] filtered_data);

int restore_data_from_wavelet_detailed_coeffs (double[] data, int data_len, int wavelet,
int decomposition_level, int level_to_restore, double[] output);

int detect_peaks_z_score (double[] data, int data_len, int lag, double threshold, double influence,
double[] output);

int remove_environmental_noise (double[] data, int data_len, int sampling_rate, int noise_type);

int perform_wavelet_transform (double[] data, int data_len, int wavelet, int decomposition_level, int extention,
Expand Down Expand Up @@ -433,6 +439,47 @@ public static double[] perform_downsampling (double[] data, int period, int oper
return downsampled_data;
}

/**
* restore data from a single wavelet coeff
*/
public static double[] restore_data_from_wavelet_detailed_coeffs (double[] data, int wavelet,
int decomposition_level, int level_to_restore) throws BrainFlowError
{
double[] restored_data = new double[data.length];
int ec = instance.restore_data_from_wavelet_detailed_coeffs (data, data.length, wavelet, decomposition_level,
level_to_restore, restored_data);
if (ec != BrainFlowExitCode.STATUS_OK.get_code ())
{
throw new BrainFlowError ("Failed to perform restore_data_from_wavelet_detailed_coeffs", ec);
}
return restored_data;
}

/**
* restore data from a single wavelet coeff
*/
public static double[] restore_data_from_wavelet_detailed_coeffs (double[] data, WaveletTypes wavelet,
int decomposition_level, int level_to_restore) throws BrainFlowError
{
return restore_data_from_wavelet_detailed_coeffs (data, wavelet.get_code (), decomposition_level,
level_to_restore);
}

/**
* peak detection using z score algorithm
*/
public static double[] detect_peaks_z_score (double[] data, int lag, double threshold, double influence)
throws BrainFlowError
{
double[] peaks = new double[data.length];
int ec = instance.detect_peaks_z_score (data, data.length, lag, threshold, influence, peaks);
if (ec != BrainFlowExitCode.STATUS_OK.get_code ())
{
throw new BrainFlowError ("Failed to perform detect_peaks_z_score", ec);
}
return peaks;
}

/**
* perform data downsampling, it doesnt apply lowpass filter for you, it just
* aggregates several data points
Expand Down
18 changes: 18 additions & 0 deletions julia_package/brainflow/src/data_filter.jl
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,24 @@ end
return
end

@brainflow_rethrow function restore_data_from_wavelet_detailed_coeffs(data, wavelet::WaveletType,
decomposition_level::Integer,
level_to_restore::Integer)
restored_data = Vector{Float64}(undef, length(data))
ccall((:restore_data_from_wavelet_detailed_coeffs, DATA_HANDLER_INTERFACE), Cint, (Ptr{Float64}, Cint, Cint, Cint, Cint, Ptr{Float64}),
data, length(data), Int32(wavelet), Int32(decomposition_level), Int32(level_to_restore), restored_data)
return restored_data
end

@brainflow_rethrow function detect_peaks_z_score(data, lag::Integer,
threshold::Float64,
influence::Float64)
peaks = Vector{Float64}(undef, length(data))
ccall((:detect_peaks_z_score, DATA_HANDLER_INTERFACE), Cint, (Ptr{Float64}, Cint, Cint, Float64, Float64, Ptr{Float64}),
data, length(data), Int32(lag), threshold, influence, peaks)
return peaks
end

@brainflow_rethrow function perform_wavelet_denoising(data, wavelet::WaveletType, decomposition_level::Integer,
wavelet_denoising::WaveletDenoisingType,
threshold::ThresholdType,
Expand Down
16 changes: 16 additions & 0 deletions julia_package/brainflow/test/muse_collector.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using BrainFlow

BrainFlow.enable_dev_logger(BrainFlow.BOARD_CONTROLLER)

params = BrainFlowInputParams()
board_shim = BrainFlow.BoardShim(BrainFlow.MUSE_S_BOARD, params)

BrainFlow.prepare_session(board_shim)
BrainFlow.config_board("p61", board_shim) # to enable ppg only use p61, p50 enables aux(5th eeg) channel, ppg and smth else
BrainFlow.add_streamer("file://default4.csv:w", board_shim, BrainFlow.DEFAULT_PRESET)
BrainFlow.add_streamer("file://aux4.csv:w", board_shim, BrainFlow.AUXILIARY_PRESET)
BrainFlow.add_streamer("file://anc4.csv:w", board_shim, BrainFlow.ANCILLARY_PRESET) # this preset contains ppg data and not available for Muse 2016
BrainFlow.start_stream(board_shim)
sleep(24000)
BrainFlow.stop_stream(board_shim)
BrainFlow.release_session(board_shim)
22 changes: 22 additions & 0 deletions julia_package/brainflow/test/peaks_detection.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using BrainFlow

# enable logs
BrainFlow.enable_dev_logger(BrainFlow.BOARD_CONTROLLER)
BrainFlow.enable_dev_logger(BrainFlow.DATA_HANDLER)

params = BrainFlowInputParams()
board_shim = BrainFlow.BoardShim(BrainFlow.SYNTHETIC_BOARD, params)
sampling_rate = BrainFlow.get_sampling_rate(BrainFlow.SYNTHETIC_BOARD)

BrainFlow.prepare_session(board_shim)
BrainFlow.start_stream(board_shim)
sleep(10)
BrainFlow.stop_stream(board_shim)
data = BrainFlow.get_current_board_data(1024, board_shim)
BrainFlow.release_session(board_shim)

eeg_channels = BrainFlow.get_eeg_channels(BrainFlow.SYNTHETIC_BOARD)
data_first_channel = data[eeg_channels[1], :]

restored_data = BrainFlow.restore_data_from_wavelet_detailed_coeffs(data_first_channel, BrainFlow.DB4, 4, 2)
peaks = BrainFlow.detect_peaks_z_score(restored_data, 20, 3.5, 0.0)
Loading

0 comments on commit 8b8f804

Please sign in to comment.