Skip to content

Commit 8b8f804

Browse files
authored
add peaks detection (#539)
Signed-off-by: Andrey Parfenov <a1994ndrey@gmail.com> Signed-off-by: Andrey Parfenov <a1994ndrey@gmail.com>
1 parent 7064ad5 commit 8b8f804

File tree

21 files changed

+663
-9
lines changed

21 files changed

+663
-9
lines changed

.github/workflows/valgrind.yml

+4
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ jobs:
123123
run: valgrind --error-exitcode=1 --track-origins=yes --leak-check=full $GITHUB_WORKSPACE/cpp_package/examples/signal_processing/build/downsampling
124124
env:
125125
LD_LIBRARY_PATH: $GITHUB_WORKSPACE/installed/lib
126+
- name: Peaks Cpp
127+
run: valgrind --error-exitcode=1 --track-origins=yes --leak-check=full $GITHUB_WORKSPACE/cpp_package/examples/signal_processing/build/peaks_detection
128+
env:
129+
LD_LIBRARY_PATH: $GITHUB_WORKSPACE/installed/lib
126130
- name: CSP Cpp
127131
run: valgrind --error-exitcode=1 --track-origins=yes --leak-check=full $GITHUB_WORKSPACE/cpp_package/examples/signal_processing/build/csp
128132
env:

cpp_package/examples/signal_processing/CMakeLists.txt

+23
Original file line numberDiff line numberDiff line change
@@ -246,3 +246,26 @@ target_link_libraries (
246246
${DataHandlerPath}
247247
${BoardControllerPath}
248248
)
249+
250+
251+
##########################################
252+
## Demo for band powers across channels ##
253+
##########################################
254+
add_executable (
255+
peaks_detection
256+
src/peaks_detection.cpp
257+
)
258+
259+
target_include_directories (
260+
peaks_detection PUBLIC
261+
${brainflow_INCLUDE_DIRS}
262+
)
263+
264+
target_link_libraries (
265+
peaks_detection PUBLIC
266+
# for some systems(ubuntu for example) order matters
267+
${BrainflowPath}
268+
${MLModulePath}
269+
${DataHandlerPath}
270+
${BoardControllerPath}
271+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#include <iostream>
2+
#include <stdlib.h>
3+
#include <string>
4+
5+
#ifdef _WIN32
6+
#include <windows.h>
7+
#else
8+
#include <unistd.h>
9+
#endif
10+
11+
#include "board_shim.h"
12+
#include "data_filter.h"
13+
14+
using namespace std;
15+
16+
void print_one_row (double *data, int num_data_points);
17+
18+
19+
int main (int argc, char *argv[])
20+
{
21+
BoardShim::enable_dev_board_logger ();
22+
23+
struct BrainFlowInputParams params;
24+
int res = 0;
25+
int board_id = (int)BoardIds::SYNTHETIC_BOARD;
26+
// use synthetic board for demo
27+
BoardShim *board = new BoardShim (board_id, params);
28+
29+
try
30+
{
31+
board->prepare_session ();
32+
board->start_stream ();
33+
34+
#ifdef _WIN32
35+
Sleep (5000);
36+
#else
37+
sleep (5);
38+
#endif
39+
40+
board->stop_stream ();
41+
BrainFlowArray<double, 2> data = board->get_board_data ();
42+
board->release_session ();
43+
44+
double *peaks = new double[data.get_size (1)];
45+
std::vector<int> eeg_channels = BoardShim::get_eeg_channels (board_id);
46+
47+
for (int i = 0; i < eeg_channels.size (); i++)
48+
{
49+
DataFilter::restore_data_from_wavelet_detailed_coeffs (
50+
data.get_address (eeg_channels[i]), data.get_size (1), (int)WaveletTypes::DB4, 6, 4,
51+
peaks);
52+
DataFilter::detect_peaks_z_score (peaks, data.get_size (1), 20, 3.5, 0.0, peaks);
53+
}
54+
delete[] peaks;
55+
}
56+
catch (const BrainFlowException &err)
57+
{
58+
BoardShim::log_message ((int)LogLevels::LEVEL_ERROR, err.what ());
59+
res = err.exit_code;
60+
if (board->is_prepared ())
61+
{
62+
board->release_session ();
63+
}
64+
}
65+
66+
delete board;
67+
68+
return res;
69+
}

cpp_package/src/data_filter.cpp

+21
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,27 @@ void DataFilter::remove_environmental_noise (
7373
}
7474
}
7575

76+
void DataFilter::restore_data_from_wavelet_detailed_coeffs (double *data, int data_len, int wavelet,
77+
int decomposition_level, int level_to_restore, double *output)
78+
{
79+
int res = ::restore_data_from_wavelet_detailed_coeffs (
80+
data, data_len, wavelet, decomposition_level, level_to_restore, output);
81+
if (res != (int)BrainFlowExitCodes::STATUS_OK)
82+
{
83+
throw BrainFlowException ("failed to restore", res);
84+
}
85+
}
86+
87+
void DataFilter::detect_peaks_z_score (
88+
double *data, int data_len, int lag, double threshold, double influence, double *output)
89+
{
90+
int res = ::detect_peaks_z_score (data, data_len, lag, threshold, influence, output);
91+
if (res != (int)BrainFlowExitCodes::STATUS_OK)
92+
{
93+
throw BrainFlowException ("failed to detect", res);
94+
}
95+
}
96+
7697
void DataFilter::perform_rolling_filter (double *data, int data_len, int period, int agg_operation)
7798
{
7899
int res = ::perform_rolling_filter (data, data_len, period, agg_operation);

cpp_package/src/inc/data_filter.h

+6
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ class DataFilter
8484
int threshold = (int)ThresholdTypes::HARD,
8585
int extenstion_type = (int)WaveletExtensionTypes::SYMMETRIC,
8686
int noise_level = (int)NoiseEstimationLevelTypes::FIRST_LEVEL);
87+
/// restore data from selected detailed coeffs
88+
static void restore_data_from_wavelet_detailed_coeffs (double *data, int data_len, int wavelet,
89+
int decomposition_level, int level_to_restore, double *output);
90+
/// z score peak detection, more info https://stackoverflow.com/a/22640362
91+
static void detect_peaks_z_score (
92+
double *data, int data_len, int lag, double threshold, double influence, double *output);
8793
// clang-format off
8894
/**
8995
* calculate filters and the corresponding eigenvalues using the Common Spatial Patterns

csharp_package/brainflow/brainflow/data_filter.cs

+14
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,20 @@ public static double[] perform_downsampling (double[] data, int period, int oper
250250
return downsampled_data;
251251
}
252252

253+
/// <summary>
254+
/// detect peaks using z score algorithm
255+
/// </summary>
256+
public static double[] detect_peaks_z_score (double[] data, int lag, double threshold, double influence)
257+
{
258+
double[] peaks = new double[data.Length];
259+
int res = DataHandlerLibrary.detect_peaks_z_score (data, data.Length, lag, threshold, influence, peaks);
260+
if (res != (int)BrainFlowExitCodes.STATUS_OK)
261+
{
262+
throw new BrainFlowError (res);
263+
}
264+
return peaks;
265+
}
266+
253267
/// <summary>
254268
/// calc stddev
255269
/// </summary>

csharp_package/brainflow/brainflow/data_handler_library.cs

+50
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,10 @@ public static extern int perform_wavelet_denoising (double[] data, int data_len,
176176
public static extern int get_version_data_handler (byte[] version, int[] len, int max_len);
177177
[DllImport ("DataHandler.dll", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
178178
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);
179+
[DllImport ("DataHandler.dll", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
180+
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);
181+
[DllImport ("DataHandler.dll", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
182+
public static extern int detect_peaks_z_score (double[] data, int data_len, int lag, double threshold, double influence, double[] output);
179183
}
180184

181185
class DataHandlerLibrary32
@@ -242,6 +246,10 @@ public static extern int perform_wavelet_denoising (double[] data, int data_len,
242246
public static extern int get_railed_percentage (double[] data, int len, int gain, double[] output);
243247
[DllImport ("DataHandler32.dll", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
244248
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);
249+
[DllImport ("DataHandler32.dll", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
250+
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);
251+
[DllImport ("DataHandler32.dll", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
252+
public static extern int detect_peaks_z_score (double[] data, int data_len, int lag, double threshold, double influence, double[] output);
245253
}
246254

247255
class DataHandlerLibraryLinux
@@ -308,6 +316,10 @@ public static extern int perform_wavelet_denoising (double[] data, int data_len,
308316
public static extern int get_railed_percentage (double[] data, int len, int gain, double[] output);
309317
[DllImport ("libDataHandler.so", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
310318
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);
319+
[DllImport ("libDataHandler.so", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
320+
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);
321+
[DllImport ("libDataHandler.so", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
322+
public static extern int detect_peaks_z_score (double[] data, int data_len, int lag, double threshold, double influence, double[] output);
311323
}
312324

313325
class DataHandlerLibraryMac
@@ -374,6 +386,10 @@ public static extern int perform_wavelet_denoising (double[] data, int data_len,
374386
public static extern int get_railed_percentage (double[] data, int len, int gain, double[] output);
375387
[DllImport ("libDataHandler.dylib", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
376388
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);
389+
[DllImport ("libDataHandler.dylib", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
390+
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);
391+
[DllImport ("libDataHandler.dylib", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
392+
public static extern int detect_peaks_z_score (double[] data, int data_len, int lag, double threshold, double influence, double[] output);
377393
}
378394

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

534+
public static int restore_data_from_wavelet_detailed_coeffs (double[] data, int len, int wavelet, int decomposition_level, int level_to_restore, double[] output)
535+
{
536+
switch (PlatformHelper.get_library_environment ())
537+
{
538+
case LibraryEnvironment.x64:
539+
return DataHandlerLibrary64.restore_data_from_wavelet_detailed_coeffs (data, len, wavelet, decomposition_level, level_to_restore, output);
540+
case LibraryEnvironment.x86:
541+
return DataHandlerLibrary32.restore_data_from_wavelet_detailed_coeffs (data, len, wavelet, decomposition_level, level_to_restore, output);
542+
case LibraryEnvironment.Linux:
543+
return DataHandlerLibraryLinux.restore_data_from_wavelet_detailed_coeffs (data, len, wavelet, decomposition_level, level_to_restore, output);
544+
case LibraryEnvironment.MacOS:
545+
return DataHandlerLibraryMac.restore_data_from_wavelet_detailed_coeffs (data, len, wavelet, decomposition_level, level_to_restore, output);
546+
}
547+
548+
return (int)BrainFlowExitCodes.GENERAL_ERROR;
549+
}
550+
551+
public static int detect_peaks_z_score (double[] data, int len, int lag, double threshold, double influence, double[] output)
552+
{
553+
switch (PlatformHelper.get_library_environment ())
554+
{
555+
case LibraryEnvironment.x64:
556+
return DataHandlerLibrary64.detect_peaks_z_score (data, len, lag, threshold, influence, output);
557+
case LibraryEnvironment.x86:
558+
return DataHandlerLibrary32.detect_peaks_z_score (data, len, lag, threshold, influence, output);
559+
case LibraryEnvironment.Linux:
560+
return DataHandlerLibraryLinux.detect_peaks_z_score (data, len, lag, threshold, influence, output);
561+
case LibraryEnvironment.MacOS:
562+
return DataHandlerLibraryMac.detect_peaks_z_score (data, len, lag, threshold, influence, output);
563+
}
564+
565+
return (int)BrainFlowExitCodes.GENERAL_ERROR;
566+
}
567+
518568
public static int detrend (double[] data, int len, int operation)
519569
{
520570
switch (PlatformHelper.get_library_environment ())

java_package/brainflow/src/main/java/brainflow/DataFilter.java

+47
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ int perform_bandstop (double[] data, int data_len, int sampling_rate, double sta
4343

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

46+
int restore_data_from_wavelet_detailed_coeffs (double[] data, int data_len, int wavelet,
47+
int decomposition_level, int level_to_restore, double[] output);
48+
49+
int detect_peaks_z_score (double[] data, int data_len, int lag, double threshold, double influence,
50+
double[] output);
51+
4652
int remove_environmental_noise (double[] data, int data_len, int sampling_rate, int noise_type);
4753

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

442+
/**
443+
* restore data from a single wavelet coeff
444+
*/
445+
public static double[] restore_data_from_wavelet_detailed_coeffs (double[] data, int wavelet,
446+
int decomposition_level, int level_to_restore) throws BrainFlowError
447+
{
448+
double[] restored_data = new double[data.length];
449+
int ec = instance.restore_data_from_wavelet_detailed_coeffs (data, data.length, wavelet, decomposition_level,
450+
level_to_restore, restored_data);
451+
if (ec != BrainFlowExitCode.STATUS_OK.get_code ())
452+
{
453+
throw new BrainFlowError ("Failed to perform restore_data_from_wavelet_detailed_coeffs", ec);
454+
}
455+
return restored_data;
456+
}
457+
458+
/**
459+
* restore data from a single wavelet coeff
460+
*/
461+
public static double[] restore_data_from_wavelet_detailed_coeffs (double[] data, WaveletTypes wavelet,
462+
int decomposition_level, int level_to_restore) throws BrainFlowError
463+
{
464+
return restore_data_from_wavelet_detailed_coeffs (data, wavelet.get_code (), decomposition_level,
465+
level_to_restore);
466+
}
467+
468+
/**
469+
* peak detection using z score algorithm
470+
*/
471+
public static double[] detect_peaks_z_score (double[] data, int lag, double threshold, double influence)
472+
throws BrainFlowError
473+
{
474+
double[] peaks = new double[data.length];
475+
int ec = instance.detect_peaks_z_score (data, data.length, lag, threshold, influence, peaks);
476+
if (ec != BrainFlowExitCode.STATUS_OK.get_code ())
477+
{
478+
throw new BrainFlowError ("Failed to perform detect_peaks_z_score", ec);
479+
}
480+
return peaks;
481+
}
482+
436483
/**
437484
* perform data downsampling, it doesnt apply lowpass filter for you, it just
438485
* aggregates several data points

julia_package/brainflow/src/data_filter.jl

+18
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,24 @@ end
185185
return
186186
end
187187

188+
@brainflow_rethrow function restore_data_from_wavelet_detailed_coeffs(data, wavelet::WaveletType,
189+
decomposition_level::Integer,
190+
level_to_restore::Integer)
191+
restored_data = Vector{Float64}(undef, length(data))
192+
ccall((:restore_data_from_wavelet_detailed_coeffs, DATA_HANDLER_INTERFACE), Cint, (Ptr{Float64}, Cint, Cint, Cint, Cint, Ptr{Float64}),
193+
data, length(data), Int32(wavelet), Int32(decomposition_level), Int32(level_to_restore), restored_data)
194+
return restored_data
195+
end
196+
197+
@brainflow_rethrow function detect_peaks_z_score(data, lag::Integer,
198+
threshold::Float64,
199+
influence::Float64)
200+
peaks = Vector{Float64}(undef, length(data))
201+
ccall((:detect_peaks_z_score, DATA_HANDLER_INTERFACE), Cint, (Ptr{Float64}, Cint, Cint, Float64, Float64, Ptr{Float64}),
202+
data, length(data), Int32(lag), threshold, influence, peaks)
203+
return peaks
204+
end
205+
188206
@brainflow_rethrow function perform_wavelet_denoising(data, wavelet::WaveletType, decomposition_level::Integer,
189207
wavelet_denoising::WaveletDenoisingType,
190208
threshold::ThresholdType,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using BrainFlow
2+
3+
BrainFlow.enable_dev_logger(BrainFlow.BOARD_CONTROLLER)
4+
5+
params = BrainFlowInputParams()
6+
board_shim = BrainFlow.BoardShim(BrainFlow.MUSE_S_BOARD, params)
7+
8+
BrainFlow.prepare_session(board_shim)
9+
BrainFlow.config_board("p61", board_shim) # to enable ppg only use p61, p50 enables aux(5th eeg) channel, ppg and smth else
10+
BrainFlow.add_streamer("file://default4.csv:w", board_shim, BrainFlow.DEFAULT_PRESET)
11+
BrainFlow.add_streamer("file://aux4.csv:w", board_shim, BrainFlow.AUXILIARY_PRESET)
12+
BrainFlow.add_streamer("file://anc4.csv:w", board_shim, BrainFlow.ANCILLARY_PRESET) # this preset contains ppg data and not available for Muse 2016
13+
BrainFlow.start_stream(board_shim)
14+
sleep(24000)
15+
BrainFlow.stop_stream(board_shim)
16+
BrainFlow.release_session(board_shim)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using BrainFlow
2+
3+
# enable logs
4+
BrainFlow.enable_dev_logger(BrainFlow.BOARD_CONTROLLER)
5+
BrainFlow.enable_dev_logger(BrainFlow.DATA_HANDLER)
6+
7+
params = BrainFlowInputParams()
8+
board_shim = BrainFlow.BoardShim(BrainFlow.SYNTHETIC_BOARD, params)
9+
sampling_rate = BrainFlow.get_sampling_rate(BrainFlow.SYNTHETIC_BOARD)
10+
11+
BrainFlow.prepare_session(board_shim)
12+
BrainFlow.start_stream(board_shim)
13+
sleep(10)
14+
BrainFlow.stop_stream(board_shim)
15+
data = BrainFlow.get_current_board_data(1024, board_shim)
16+
BrainFlow.release_session(board_shim)
17+
18+
eeg_channels = BrainFlow.get_eeg_channels(BrainFlow.SYNTHETIC_BOARD)
19+
data_first_channel = data[eeg_channels[1], :]
20+
21+
restored_data = BrainFlow.restore_data_from_wavelet_detailed_coeffs(data_first_channel, BrainFlow.DB4, 4, 2)
22+
peaks = BrainFlow.detect_peaks_z_score(restored_data, 20, 3.5, 0.0)

0 commit comments

Comments
 (0)