1
+ import numpy as np
2
+ from scipy .signal import butter , filtfilt
3
+ from PyQt5 .QtWidgets import QApplication , QVBoxLayout , QMainWindow , QWidget
4
+ from pyqtgraph import PlotWidget
5
+ import pyqtgraph as pg
6
+ import pylsl
7
+ import sys
8
+
9
+ class EMGMonitor (QMainWindow ):
10
+ def __init__ (self ):
11
+ super ().__init__ ()
12
+
13
+ self .setWindowTitle ("Real-Time EMG Monitor with EMG Envelope" )
14
+ self .setGeometry (100 , 100 , 800 , 600 )
15
+
16
+ self .plot_widget = PlotWidget (self )
17
+ self .plot_widget .setBackground ('w' )
18
+ self .plot_widget .showGrid (x = True , y = True )
19
+
20
+ layout = QVBoxLayout ()
21
+ layout .addWidget (self .plot_widget )
22
+
23
+ central_widget = QWidget ()
24
+ central_widget .setLayout (layout )
25
+ self .setCentralWidget (central_widget )
26
+
27
+ # Set up LSL stream inlet
28
+ streams = pylsl .resolve_stream ('name' , 'BioAmpDataStream' )
29
+ if not streams :
30
+ print ("No LSL stream found!" )
31
+ sys .exit (0 )
32
+ self .inlet = pylsl .StreamInlet (streams [0 ])
33
+
34
+ # Sampling rate
35
+ self .sampling_rate = int (self .inlet .info ().nominal_srate ())
36
+ print (f"Sampling rate: { self .sampling_rate } Hz" )
37
+
38
+ # Data and buffers
39
+ self .buffer_size = self .sampling_rate * 10 # Fixed-size buffer for 10 seconds
40
+ self .emg_data = np .zeros (self .buffer_size ) # Fixed-size array for circular buffer
41
+ self .time_data = np .linspace (0 , 10 , self .buffer_size ) # Fixed time array for plotting
42
+ self .current_index = 0 # Index for overwriting data
43
+
44
+ self .b , self .a = butter (4 , 70.0 / (0.5 * self .sampling_rate ), btype = 'high' )
45
+
46
+ # Moving RMS window size (50 for 250 sampling rate and 100 for 500 sampling rate)
47
+ self .rms_window_size = int (0.1 * self .sampling_rate )
48
+
49
+ # Set fixed axis ranges
50
+ self .plot_widget .setXRange (0 , 10 , padding = 0 )
51
+
52
+ # Set y-axis limits based on sampling rate
53
+ if self .sampling_rate == 250 :
54
+ self .plot_widget .setYRange (400 ,600 ,padding = 0 ) # for R3 & ensuring no extra spaces at end
55
+ elif self .sampling_rate == 500 :
56
+ self .plot_widget .setYRange (400 , 10000 ,padding = 0 ) # for R4 & ensuring no extra spaces at end
57
+
58
+ # Plot curves for EMG data and envelope
59
+ self .emg_curve = self .plot_widget .plot (self .time_data , self .emg_data , pen = pg .mkPen ('b' , width = 1 ))
60
+ self .envelope_curve = self .plot_widget .plot (self .time_data , self .emg_data , pen = pg .mkPen ('r' , width = 2 ))
61
+
62
+ # Timer for plot update
63
+ self .timer = pg .QtCore .QTimer ()
64
+ self .timer .timeout .connect (self .update_plot )
65
+ self .timer .start (15 )
66
+
67
+ def calculate_moving_rms (self , signal , window_size ):
68
+ # Calculate RMS using a moving window
69
+ rms = np .sqrt (np .convolve (signal ** 2 , np .ones (window_size ) / window_size , mode = 'valid' ))
70
+ return np .pad (rms , (len (signal ) - len (rms ), 0 ), 'constant' )
71
+
72
+ def update_plot (self ):
73
+ samples , _ = self .inlet .pull_chunk (timeout = 0.0 , max_samples = 30 )
74
+ if samples :
75
+ for sample in samples :
76
+ # Overwrite the oldest data point in the buffer
77
+ self .emg_data [self .current_index ] = sample [0 ]
78
+ self .current_index = (self .current_index + 1 ) % self .buffer_size # Circular increment
79
+
80
+ # Filter the EMG data
81
+ filtered_emg = filtfilt (self .b , self .a , self .emg_data )
82
+ # print(filtered_emg)
83
+
84
+ # Take absolute value before calculating RMS envelope
85
+ abs_filtered_emg = np .abs (filtered_emg )
86
+ # print(abs_filtered_emg)
87
+
88
+ # Calculate the RMS envelope
89
+ rms_envelope = self .calculate_moving_rms (abs_filtered_emg , self .rms_window_size )
90
+
91
+ # Update curves
92
+ self .emg_curve .setData (self .time_data , filtered_emg ) # Plot filtered EMG in blue
93
+ self .envelope_curve .setData (self .time_data , rms_envelope ) # Plot EMG envelope in red
94
+
95
+ if __name__ == "__main__" :
96
+ app = QApplication (sys .argv )
97
+ window = EMGMonitor ()
98
+ window .show ()
99
+ sys .exit (app .exec_ ())
0 commit comments