Skip to content

Commit ef20ef3

Browse files
committed
adding invert flag to invert the signal
1 parent 31b9c8f commit ef20ef3

File tree

1 file changed

+24
-286
lines changed

1 file changed

+24
-286
lines changed

applications/heartbeat_ecg.py

Lines changed: 24 additions & 286 deletions
Original file line numberDiff line numberDiff line change
@@ -1,138 +1,3 @@
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
1361
import numpy as np
1372
from scipy.signal import butter, filtfilt
1383
from PyQt5.QtWidgets import QApplication, QVBoxLayout, QLabel, QMainWindow, QWidget
@@ -142,10 +7,12 @@
1427
import pylsl
1438
import neurokit2 as nk
1449
import sys
10+
import argparse
14511

14612
class ECGMonitor(QMainWindow):
147-
def __init__(self):
13+
def __init__(self, invert=False): # Add the invert parameter
14814
super().__init__()
15+
self.invert = invert # Store the invert flag
14916

15017
# Set up GUI window
15118
self.setWindowTitle("Real-Time ECG Monitor")
@@ -197,9 +64,9 @@ def __init__(self):
19764

19865
# Set y-axis limits based on sampling rate
19966
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
20168
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
20370

20471
# Set fixed x-axis range
20572
self.plot_widget.setXRange(0, 10) # 10 seconds
@@ -209,7 +76,7 @@ def __init__(self):
20976
self.r_peak_curve = self.plot_widget.plot([], [], pen=None, symbol='o', symbolBrush='r', symbolSize=10) # R-peaks in red
21077

21178
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)
21380
if samples:
21481
for sample in samples:
21582
# Overwrite the oldest data point in the buffer
@@ -219,6 +86,10 @@ def update_plot(self):
21986
# Filter the signal
22087
filtered_ecg = filtfilt(self.b, self.a, self.ecg_data)
22188

89+
# Invert the signal if the invert flag is set
90+
if self.invert:
91+
filtered_ecg = -filtered_ecg
92+
22293
# Update the plot data
22394
self.ecg_curve.setData(self.time_data, filtered_ecg) # Use current buffer for plotting
22495

@@ -232,25 +103,15 @@ def detect_r_peaks(self, ecg_signal):
232103
return r_peaks['ECG_R_Peaks'] if 'ECG_R_Peaks' in r_peaks else []
233104

234105
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)
246109
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)
249112
self.heart_rate_label.setText(f"Heart Rate: {int(self.heart_rate)} BPM")
250-
else:
251-
self.heart_rate_label.setText("Heart Rate: Calculating...")
252113
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"
254115

255116
def plot_r_peaks(self, filtered_ecg):
256117
# Extract the time of detected R-peaks
@@ -259,136 +120,13 @@ def plot_r_peaks(self, filtered_ecg):
259120
self.r_peak_curve.setData(r_peak_times, r_peak_values) # Plot R-peaks as red dots
260121

261122
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+
262129
app = QApplication(sys.argv)
263-
window = ECGMonitor()
130+
window = ECGMonitor(invert=args.invert) # Pass the invert flag to ECGMonitor
264131
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

Comments
 (0)