1
- # import numpy as np
2
- # from scipy.signal import butter, filtfilt
3
- # from PyQt5.QtWidgets import QApplication, QVBoxLayout, QLabel, QMainWindow, QWidget
4
- # from PyQt5.QtCore import Qt
5
- # from pyqtgraph import PlotWidget
6
- # import pyqtgraph as pg
7
- # import pylsl
8
- # import neurokit2 as nk
9
- # from collections import deque
10
- # import sys
11
-
12
- # class ECGMonitor(QMainWindow):
13
- # def __init__(self):
14
- # super().__init__()
15
-
16
- # # Set up GUI window
17
- # self.setWindowTitle("Real-Time ECG Monitor")
18
- # self.setGeometry(100, 100, 800, 600)
19
-
20
- # self.plot_widget = PlotWidget(self)
21
- # self.plot_widget.setBackground('w')
22
- # self.plot_widget.showGrid(x=True, y=True)
23
-
24
- # # Add a label to display the heart rate in bold at the bottom center
25
- # self.heart_rate_label = QLabel(self)
26
- # self.heart_rate_label.setStyleSheet("font-size: 20px; font-weight: bold; color: black;")
27
- # self.heart_rate_label.setAlignment(Qt.AlignCenter)
28
-
29
- # layout = QVBoxLayout()
30
- # layout.addWidget(self.plot_widget)
31
- # layout.addWidget(self.heart_rate_label)
32
-
33
- # central_widget = QWidget()
34
- # central_widget.setLayout(layout)
35
- # self.setCentralWidget(central_widget)
36
-
37
- # # Set up LSL stream inlet
38
- # print("Looking for an ECG stream...")
39
- # streams = pylsl.resolve_stream('name', 'BioAmpDataStream')
40
- # if not streams:
41
- # print("No LSL stream found!")
42
- # sys.exit(0)
43
- # self.inlet = pylsl.StreamInlet(streams[0])
44
-
45
- # # Sampling rate
46
- # self.sampling_rate = self.inlet.info().nominal_srate()
47
- # if self.sampling_rate == pylsl.IRREGULAR_RATE:
48
- # print("Irregular sampling rate detected!")
49
- # sys.exit(0)
50
- # print(f"Sampling rate: {self.sampling_rate} Hz")
51
-
52
- # self.sampling_rate = int(self.sampling_rate) #Conversion into int
53
- # # Data and buffers
54
- # self.ecg_data = deque(maxlen=self.sampling_rate * 10) # 10 seconds of data at 250/500 Hz
55
- # self.time_data = deque(maxlen=self.sampling_rate * 10) #Sampling rate - 250/500
56
- # self.r_peaks = []
57
- # self.heart_rate = None
58
-
59
- # # Timer for updating the GUI
60
- # self.timer = pg.QtCore.QTimer()
61
- # self.timer.timeout.connect(self.update_plot)
62
- # self.timer.start(10) # Update every 10 ms
63
-
64
- # # Low-pass filter coefficients
65
- # self.b, self.a = butter(4, 20.0 / (0.5 * self.sampling_rate), btype='low')
66
-
67
- # # Plot configuration
68
- # self.plot_window = 10 # Plot window of 10 seconds
69
- # self.buffer_size = self.plot_window * self.sampling_rate # 10 seconds at 250/500 Hz sampling rate
70
-
71
- # # Set y-axis limits based on sampling rate
72
- # if self.sampling_rate == 250:
73
- # self.plot_widget.setYRange(0, 2**10) #for R3
74
- # elif self.sampling_rate == 500:
75
- # self.plot_widget.setYRange(0, 2**14) #for R4
76
-
77
- # def update_plot(self):
78
- # samples, timestamps = self.inlet.pull_chunk(timeout=0.0, max_samples=32)
79
- # if samples:
80
- # for sample, timestamp in zip(samples, timestamps):
81
- # self.ecg_data.append(sample[0])
82
- # self.time_data.append(timestamp)
83
-
84
- # # Convert deque to numpy array for processing
85
- # ecg_array = np.array(self.ecg_data)
86
- # filtered_ecg = filtfilt(self.b, self.a, ecg_array) # Apply low-pass filter
87
- # self.r_peaks = self.detect_r_peaks(filtered_ecg) # Detect R-peaks using NeuroKit2
88
- # self.calculate_heart_rate() # Calculate heart rate
89
-
90
- # # Update plot immediately with whatever data is available
91
- # self.plot_widget.clear()
92
- # current_time = np.linspace(0, len(ecg_array)/self.sampling_rate, len(ecg_array))
93
- # self.plot_widget.setXRange(0, self.plot_window) # Fixed x-axis range
94
- # self.plot_widget.plot(current_time, filtered_ecg, pen=pg.mkPen('k', width=1))
95
-
96
- # # Mark R-peaks on the plot
97
- # if len(self.r_peaks) > 0:
98
- # self.plot_widget.plot(current_time[self.r_peaks], filtered_ecg[self.r_peaks], pen=None, symbol='o', symbolBrush='r')
99
-
100
- # # Update heart rate display
101
- # if self.heart_rate:
102
- # self.heart_rate_label.setText(f"Heart Rate: {int(self.heart_rate)} BPM")
103
- # else:
104
- # self.heart_rate_label.setText("Heart Rate: Calculating...")
105
- # else:
106
- # self.heart_rate_label.setText("Heart Rate: Collecting data...")
107
-
108
- # def detect_r_peaks(self, ecg_signal):
109
- # # Using NeuroKit2 to detect R-peaks
110
- # r_peaks = nk.ecg_findpeaks(ecg_signal, sampling_rate=self.sampling_rate)
111
-
112
- # if 'ECG_R_Peaks' in r_peaks:
113
- # return r_peaks['ECG_R_Peaks']
114
- # else:
115
- # print("No R-peaks detected. Please check the input ECG signal.")
116
- # return []
117
-
118
- # def calculate_heart_rate(self):
119
- # if len(self.r_peaks) > 1:
120
- # peak_times = np.array([self.time_data[i] for i in self.r_peaks])
121
- # rr_intervals = np.diff(peak_times)
122
- # avg_rr_interval = np.mean(rr_intervals)
123
- # self.heart_rate = 60.0 / avg_rr_interval
124
- # else:
125
- # self.heart_rate = None
126
-
127
- # if __name__ == "__main__":
128
- # app = QApplication(sys.argv)
129
- # window = ECGMonitor()
130
- # window.show()
131
- # sys.exit(app.exec_())
132
-
133
-
134
-
135
- # Updated Holter monitor
136
1
import numpy as np
137
2
from scipy .signal import butter , filtfilt
138
3
from PyQt5 .QtWidgets import QApplication , QVBoxLayout , QLabel , QMainWindow , QWidget
142
7
import pylsl
143
8
import neurokit2 as nk
144
9
import sys
10
+ import argparse
145
11
146
12
class ECGMonitor (QMainWindow ):
147
- def __init__ (self ):
13
+ def __init__ (self , invert = False ): # Add the invert parameter
148
14
super ().__init__ ()
15
+ self .invert = invert # Store the invert flag
149
16
150
17
# Set up GUI window
151
18
self .setWindowTitle ("Real-Time ECG Monitor" )
@@ -197,9 +64,9 @@ def __init__(self):
197
64
198
65
# Set y-axis limits based on sampling rate
199
66
if self .sampling_rate == 250 :
200
- self .plot_widget .setYRange (0 , 2 ** 10 ) # for R3
67
+ self .plot_widget .setYRange (- 2 ** 10 , 2 ** 10 ) # for R3
201
68
elif self .sampling_rate == 500 :
202
- self .plot_widget .setYRange (0 , 2 ** 14 ) # for R4
69
+ self .plot_widget .setYRange (- 2 ** 14 , 2 ** 14 ) # for R4
203
70
204
71
# Set fixed x-axis range
205
72
self .plot_widget .setXRange (0 , 10 ) # 10 seconds
@@ -209,7 +76,7 @@ def __init__(self):
209
76
self .r_peak_curve = self .plot_widget .plot ([], [], pen = None , symbol = 'o' , symbolBrush = 'r' , symbolSize = 10 ) # R-peaks in red
210
77
211
78
def update_plot (self ):
212
- samples , _ = self .inlet .pull_chunk (timeout = 0.0 , max_samples = 32 )
79
+ samples , _ = self .inlet .pull_chunk (timeout = 0.0 , max_samples = 30 )
213
80
if samples :
214
81
for sample in samples :
215
82
# Overwrite the oldest data point in the buffer
@@ -219,6 +86,10 @@ def update_plot(self):
219
86
# Filter the signal
220
87
filtered_ecg = filtfilt (self .b , self .a , self .ecg_data )
221
88
89
+ # Invert the signal if the invert flag is set
90
+ if self .invert :
91
+ filtered_ecg = - filtered_ecg
92
+
222
93
# Update the plot data
223
94
self .ecg_curve .setData (self .time_data , filtered_ecg ) # Use current buffer for plotting
224
95
@@ -232,25 +103,15 @@ def detect_r_peaks(self, ecg_signal):
232
103
return r_peaks ['ECG_R_Peaks' ] if 'ECG_R_Peaks' in r_peaks else []
233
104
234
105
def calculate_heart_rate (self ):
235
- # Ensure there are enough R-peaks and at least 10 seconds of data before calculating heart rate
236
- if len (self .r_peaks ) > 1 and self .current_index == 0 : # Buffer is full (10 seconds)
237
- rr_intervals = np .diff ([self .time_data [i ] for i in self .r_peaks ])
238
- if len (rr_intervals ) > 0 :
239
- avg_rr = np .mean (rr_intervals )
240
- self .heart_rate = 60.0 / avg_rr
241
- self .heart_rate_label .setText (f"Heart Rate: { int (self .heart_rate )} BPM" )
242
- else :
243
- self .heart_rate_label .setText ("Heart Rate: Calculating..." )
244
- elif len (self .r_peaks ) > 1 : # After initial 10 seconds, keep updating with new R-peaks
245
- rr_intervals = np .diff ([self .time_data [i ] for i in self .r_peaks [- 2 :]]) # Last two R-peaks for instant HR
106
+ if len (self .r_peaks ) >= 10 : # Check if we have 10 or more R-peaks
107
+ recent_r_peaks = self .r_peaks [- 10 :] # Use the last 10 R-peaks for heart rate calculation
108
+ rr_intervals = np .diff ([self .time_data [i ] for i in recent_r_peaks ]) # Calculate RR intervals (time differences between consecutive R-peaks)
246
109
if len (rr_intervals ) > 0 :
247
- avg_rr = np .mean (rr_intervals )
248
- self .heart_rate = 60.0 / avg_rr
110
+ avg_rr = np .mean (rr_intervals ) # Average RR interval
111
+ self .heart_rate = 60.0 / avg_rr # Convert to heart rate (BPM)
249
112
self .heart_rate_label .setText (f"Heart Rate: { int (self .heart_rate )} BPM" )
250
- else :
251
- self .heart_rate_label .setText ("Heart Rate: Calculating..." )
252
113
else :
253
- self .heart_rate_label .setText ("Heart Rate: Calculating..." )
114
+ self .heart_rate_label .setText ("Heart Rate: Calculating..." ) # If fewer than 10 R-peaks are detected, show "Calculating"
254
115
255
116
def plot_r_peaks (self , filtered_ecg ):
256
117
# Extract the time of detected R-peaks
@@ -259,136 +120,13 @@ def plot_r_peaks(self, filtered_ecg):
259
120
self .r_peak_curve .setData (r_peak_times , r_peak_values ) # Plot R-peaks as red dots
260
121
261
122
if __name__ == "__main__" :
123
+ # Set up argument parser
124
+ parser = argparse .ArgumentParser (description = "Real-Time ECG Monitor" )
125
+ parser .add_argument ("--invert" , action = "store_true" , help = "Invert the ECG signal plot" )
126
+
127
+ args = parser .parse_args ()
128
+
262
129
app = QApplication (sys .argv )
263
- window = ECGMonitor ()
130
+ window = ECGMonitor (invert = args . invert ) # Pass the invert flag to ECGMonitor
264
131
window .show ()
265
- sys .exit (app .exec_ ())
266
-
267
-
268
-
269
-
270
- #final one -- need to be tested
271
- # import numpy as np
272
- # from scipy.signal import butter, filtfilt
273
- # from PyQt5.QtWidgets import QApplication, QVBoxLayout, QLabel, QMainWindow, QWidget
274
- # from PyQt5.QtCore import Qt
275
- # from pyqtgraph import PlotWidget
276
- # import pyqtgraph as pg
277
- # import pylsl
278
- # import neurokit2 as nk
279
- # import sys
280
-
281
- # class ECGMonitor(QMainWindow):
282
- # def __init__(self):
283
- # super().__init__()
284
-
285
- # # Set up GUI window
286
- # self.setWindowTitle("Real-Time ECG Monitor")
287
- # self.setGeometry(100, 100, 800, 600)
288
-
289
- # self.plot_widget = PlotWidget(self)
290
- # self.plot_widget.setBackground('w')
291
- # self.plot_widget.showGrid(x=True, y=True)
292
-
293
- # # Heart rate label at the bottom
294
- # self.heart_rate_label = QLabel(self)
295
- # self.heart_rate_label.setStyleSheet("font-size: 20px; font-weight: bold; color: black;")
296
- # self.heart_rate_label.setAlignment(Qt.AlignCenter)
297
-
298
- # layout = QVBoxLayout()
299
- # layout.addWidget(self.plot_widget)
300
- # layout.addWidget(self.heart_rate_label)
301
-
302
- # central_widget = QWidget()
303
- # central_widget.setLayout(layout)
304
- # self.setCentralWidget(central_widget)
305
-
306
- # # Set up LSL stream inlet
307
- # streams = pylsl.resolve_stream('name', 'BioAmpDataStream')
308
- # if not streams:
309
- # print("No LSL stream found!")
310
- # sys.exit(0)
311
- # self.inlet = pylsl.StreamInlet(streams[0])
312
-
313
- # # Sampling rate
314
- # self.sampling_rate = int(self.inlet.info().nominal_srate())
315
- # print(f"Sampling rate: {self.sampling_rate} Hz")
316
-
317
- # # Data and buffers
318
- # self.buffer_size = self.sampling_rate * 10 # Fixed-size buffer for 10 seconds
319
- # self.ecg_data = np.zeros(self.buffer_size) # Fixed-size array for circular buffer
320
- # self.time_data = np.linspace(0, 10, self.buffer_size) # Fixed time array for plotting
321
- # self.r_peaks = [] # Store the indices of R-peaks
322
- # self.heart_rate = None
323
- # self.current_index = 0 # Index for overwriting data
324
-
325
- # # Low-pass filter coefficients
326
- # self.b, self.a = butter(4, 20.0 / (0.5 * self.sampling_rate), btype='low')
327
-
328
- # # Timer for updating the plot
329
- # self.timer = pg.QtCore.QTimer()
330
- # self.timer.timeout.connect(self.update_plot)
331
- # self.timer.start(10)
332
-
333
- # # Set y-axis limits based on sampling rate
334
- # if self.sampling_rate == 250:
335
- # self.plot_widget.setYRange(0, 2**10) # for R3
336
- # elif self.sampling_rate == 500:
337
- # self.plot_widget.setYRange(0, 2**14) # for R4
338
-
339
- # # Set fixed x-axis range
340
- # self.plot_widget.setXRange(0, 10) # 10 seconds
341
-
342
- # # Initialize the plot curves
343
- # self.ecg_curve = self.plot_widget.plot(self.time_data, self.ecg_data, pen=pg.mkPen('k', width=1))
344
- # self.r_peak_curve = self.plot_widget.plot([], [], pen=None, symbol='o', symbolBrush='r', symbolSize=10) # R-peaks in red
345
-
346
- # def update_plot(self):
347
- # samples, _ = self.inlet.pull_chunk(timeout=0.0, max_samples=32)
348
- # if samples:
349
- # for sample in samples:
350
- # # Overwrite the oldest data point in the buffer
351
- # self.ecg_data[self.current_index] = sample[0]
352
- # self.current_index = (self.current_index + 1) % self.buffer_size # Circular increment
353
-
354
- # # Filter the signal
355
- # filtered_ecg = filtfilt(self.b, self.a, self.ecg_data)
356
-
357
- # # Update the plot data
358
- # self.ecg_curve.setData(self.time_data, filtered_ecg) # Use current buffer for plotting
359
-
360
- # # Detect R-peaks and update heart rate
361
- # self.r_peaks = self.detect_r_peaks(filtered_ecg)
362
-
363
- # # Only calculate heart rate after the first 10 seconds
364
- # if self.current_index >= self.buffer_size - self.sampling_rate * 10:
365
- # self.calculate_heart_rate()
366
-
367
- # # Plot R-peaks
368
- # self.plot_r_peaks(filtered_ecg)
369
-
370
- # def detect_r_peaks(self, ecg_signal):
371
- # r_peaks = nk.ecg_findpeaks(ecg_signal, sampling_rate=self.sampling_rate)
372
- # return r_peaks['ECG_R_Peaks'] if 'ECG_R_Peaks' in r_peaks else []
373
-
374
- # def calculate_heart_rate(self):
375
- # # Calculate heart rate only if there are enough R-peaks
376
- # if len(self.r_peaks) > 1:
377
- # rr_intervals = np.diff([self.time_data[i] for i in self.r_peaks])
378
- # avg_rr = np.mean(rr_intervals)
379
- # self.heart_rate = 60.0 / avg_rr
380
- # self.heart_rate_label.setText(f"Heart Rate: {int(self.heart_rate)} BPM")
381
- # else:
382
- # self.heart_rate_label.setText("Heart Rate: Calculating...")
383
-
384
- # def plot_r_peaks(self, filtered_ecg):
385
- # # Extract the time of detected R-peaks
386
- # r_peak_times = self.time_data[self.r_peaks]
387
- # r_peak_values = filtered_ecg[self.r_peaks]
388
- # self.r_peak_curve.setData(r_peak_times, r_peak_values) # Plot R-peaks as red dots
389
-
390
- # if __name__ == "__main__":
391
- # app = QApplication(sys.argv)
392
- # window = ECGMonitor()
393
- # window.show()
394
- # sys.exit(app.exec_())
132
+ sys .exit (app .exec_ ())
0 commit comments