Skip to content

Commit 16d8563

Browse files
committed
Working Code (R Peaks & 15 seconds)
1 parent ae92be5 commit 16d8563

File tree

1 file changed

+45
-225
lines changed

1 file changed

+45
-225
lines changed

applications/ecgg.py

Lines changed: 45 additions & 225 deletions
Original file line numberDiff line numberDiff line change
@@ -1,172 +1,3 @@
1-
# import sys
2-
# import numpy as np
3-
# import time
4-
# from pylsl import StreamInlet, resolve_stream
5-
# from scipy.signal import butter, lfilter, find_peaks
6-
# import pyqtgraph as pg # For real-time plotting
7-
# from pyqtgraph.Qt import QtWidgets, QtCore # PyQt components for GUI
8-
# import signal # For handling Ctrl+C
9-
# from collections import deque # For creating a ring buffer
10-
11-
# # Initialize global variables
12-
# inlet = None
13-
# data_buffer = np.zeros(2000) # Buffer to hold the last 2000 samples for ECG data
14-
15-
# # Function to design a Butterworth filter
16-
# def butter_filter(cutoff, fs, order=4, btype='low'):
17-
# nyquist = 0.5 * fs # Nyquist frequency
18-
# normal_cutoff = cutoff / nyquist
19-
# b, a = butter(order, normal_cutoff, btype=btype, analog=False)
20-
# return b, a
21-
22-
# # Apply the Butterworth filter to the data
23-
# def apply_filter(data, b, a):
24-
# return lfilter(b, a, data)
25-
26-
# # Function to detect heartbeats using peak detection
27-
# def detect_heartbeats(ecg_data, sampling_rate):
28-
# peaks, _ = find_peaks(ecg_data, distance=sampling_rate * 0.6, prominence=0.5) # Adjust as necessary
29-
# return peaks
30-
31-
# class DataCollector(QtCore.QThread):
32-
# data_ready = QtCore.pyqtSignal(np.ndarray)
33-
34-
# def __init__(self):
35-
# super().__init__()
36-
# self.running = True
37-
# self.sampling_rate = None
38-
39-
# def run(self):
40-
# global inlet
41-
# print("Looking for LSL stream...")
42-
# streams = resolve_stream('name', 'BioAmpDataStream')
43-
44-
# if not streams:
45-
# print("No LSL Stream found! Exiting...")
46-
# sys.exit(0)
47-
48-
# inlet = StreamInlet(streams[0])
49-
# self.sampling_rate = inlet.info().nominal_srate()
50-
# print(f"Detected sampling rate: {self.sampling_rate} Hz")
51-
52-
# # Create and design filters
53-
# low_cutoff = 20.0 # 20 Hz low-pass filter
54-
# self.low_b, self.low_a = butter_filter(low_cutoff, self.sampling_rate, order=4, btype='low')
55-
56-
# while self.running:
57-
# # Pull multiple samples at once
58-
# samples, _ = inlet.pull_chunk(timeout=0.0, max_samples=10) # Pull up to 10 samples
59-
# if samples:
60-
# global data_buffer
61-
# data_buffer = np.roll(data_buffer, -len(samples)) # Shift data left
62-
# data_buffer[-len(samples):] = [sample[0] for sample in samples] # Add new samples to the end
63-
64-
# filtered_data = apply_filter(data_buffer, self.low_b, self.low_a) # Low-pass Filter
65-
# self.data_ready.emit(filtered_data) # Emit the filtered data for plotting
66-
67-
# time.sleep(0.01)
68-
69-
# def stop(self):
70-
# self.running = False
71-
72-
# class ECGApp(QtWidgets.QMainWindow):
73-
# def __init__(self):
74-
# super().__init__()
75-
76-
# # Create a plot widget
77-
# self.plot_widget = pg.PlotWidget(title="ECG Signal")
78-
# self.setCentralWidget(self.plot_widget)
79-
# self.plot_widget.setBackground('w')
80-
81-
# # Create a label to display heart rate
82-
# self.heart_rate_label = QtWidgets.QLabel("Heart rate: - bpm", self)
83-
# self.heart_rate_label.setStyleSheet("font-size: 20px; font-weight: bold;")
84-
# self.heart_rate_label.setAlignment(QtCore.Qt.AlignCenter)
85-
86-
# layout = QtWidgets.QVBoxLayout()
87-
# layout.addWidget(self.plot_widget)
88-
# layout.addWidget(self.heart_rate_label)
89-
90-
# container = QtWidgets.QWidget()
91-
# container.setLayout(layout)
92-
# self.setCentralWidget(container)
93-
94-
# self.ecg_buffer = [] # Buffer to hold the ECG data
95-
# self.r_peak_times = deque(maxlen=10) # Store up to 20 R-peaks
96-
# self.last_update_time = time.time()
97-
98-
# self.data_collector = DataCollector() # Data collector thread
99-
# self.data_collector.data_ready.connect(self.update_plot)
100-
# self.data_collector.start()
101-
102-
# self.time_axis = np.linspace(0, 2000 / 200, 2000) # Store the x-axis time window
103-
# self.plot_widget.setYRange(0, 700) # Set fixed y-axis limits
104-
105-
# def update_plot(self, ecg_data):
106-
# self.ecg_buffer = ecg_data # Update buffer
107-
# self.plot_widget.clear()
108-
# # Set a fixed window (time axis) to ensure stable plotting
109-
# self.plot_widget.plot(self.time_axis, self.ecg_buffer, pen='#000000') # Plot ECG data with black line
110-
# self.plot_widget.setXRange(self.time_axis[0], self.time_axis[-1], padding=0)
111-
112-
# # Detect heartbeats in real time
113-
# heartbeats = detect_heartbeats(np.array(self.ecg_buffer), self.data_collector.sampling_rate)
114-
115-
# # Mark detected R-peaks
116-
# for index in heartbeats:
117-
# time_point = index / self.data_collector.sampling_rate # Convert index to time
118-
# self.r_peak_times.append(time_point) # Store the time of the detected R-peak
119-
120-
# # Plot a red circle at the R-peak position
121-
# self.plot_widget.plot([self.time_axis[index]], [self.ecg_buffer[index]], pen=None, symbol='o', symbolBrush='r', symbolSize=8)
122-
123-
# self.calculate_heart_rate() # Calculate heart rate after detecting R-peaks
124-
125-
# def calculate_heart_rate(self):
126-
# current_time = time.time()
127-
# if current_time - self.last_update_time >= 10: # Update every 10 seconds
128-
# if len(self.r_peak_times) > 1:
129-
# # Calculate the time intervals between successive R-peaks
130-
# intervals = np.diff(self.r_peak_times)
131-
# valid_intervals = intervals[intervals > 0] # Filter positive intervals
132-
133-
# if len(valid_intervals) > 0:
134-
# bpm = 60 / np.mean(valid_intervals) # Mean interval is in seconds, converting to bpm
135-
# self.heart_rate_label.setText(f"Heart rate: {bpm:.2f} bpm")
136-
# else:
137-
# self.heart_rate_label.setText("Heart rate: 0 bpm")
138-
# else:
139-
# self.heart_rate_label.setText("Heart rate: 0 bpm")
140-
141-
# self.r_peak_times.clear() # Clear the deque after calculation
142-
# self.last_update_time = current_time # Update last update time
143-
144-
# def closeEvent(self, event):
145-
# self.data_collector.stop() # Stop the data collector thread on close
146-
# self.data_collector.wait() # Wait for the thread to finish
147-
# event.accept() # Accept the close event
148-
149-
# def signal_handler(sig, frame):
150-
# print("Exiting...")
151-
# QtWidgets.QApplication.quit()
152-
153-
# if __name__ == "__main__":
154-
# signal.signal(signal.SIGINT, signal_handler) # Handle Ctrl+C
155-
156-
# streams = resolve_stream('name', 'BioAmpDataStream')
157-
# if not streams:
158-
# print("No LSL Stream found! Exiting...")
159-
# sys.exit(0)
160-
161-
# app = QtWidgets.QApplication(sys.argv)
162-
163-
# window = ECGApp()
164-
# window.setWindowTitle("Real-Time ECG Monitoring")
165-
# window.resize(800, 600)
166-
# window.show()
167-
168-
# sys.exit(app.exec_())
169-
1701
import sys
1712
import numpy as np
1723
import time
@@ -219,7 +50,7 @@ def run(self):
21950
print(f"Detected sampling rate: {self.sampling_rate} Hz")
22051

22152
# Create and design filters
222-
low_cutoff = 20.0 # 20 Hz low-pass filter
53+
low_cutoff = 20.0 # 40 Hz low-pass filter
22354
self.low_b, self.low_a = butter_filter(low_cutoff, self.sampling_rate, order=4, btype='low')
22455

22556
while self.running:
@@ -238,43 +69,6 @@ def run(self):
23869
def stop(self):
23970
self.running = False
24071

241-
class HeartRateCalculator(QtCore.QThread):
242-
heart_rate_ready = QtCore.pyqtSignal(float)
243-
244-
def __init__(self):
245-
super().__init__()
246-
self.running = True
247-
self.r_peak_times = deque(maxlen=20) # Store R-peaks times
248-
self.last_update_time = time.time()
249-
250-
def run(self):
251-
while self.running:
252-
time.sleep(1) # Check every second
253-
current_time = time.time()
254-
255-
if current_time - self.last_update_time >= 10: # Calculate heart rate every 10 seconds
256-
if len(self.r_peak_times) > 1:
257-
# Calculate the time intervals between successive R-peaks
258-
intervals = np.diff(self.r_peak_times)
259-
valid_intervals = intervals[intervals > 0] # Filter positive intervals
260-
261-
if len(valid_intervals) > 0:
262-
bpm = 60 / np.mean(valid_intervals) # Mean interval is in seconds, converting to bpm
263-
self.heart_rate_ready.emit(bpm)
264-
else:
265-
self.heart_rate_ready.emit(0) # No valid intervals, heart rate is 0
266-
else:
267-
self.heart_rate_ready.emit(0) # Not enough R-peaks to calculate heart rate
268-
269-
self.r_peak_times.clear() # Clear R-peaks after calculation
270-
self.last_update_time = current_time # Update last update time
271-
272-
def add_r_peak_time(self, time):
273-
self.r_peak_times.append(time) # Add R-peak time to the deque
274-
275-
def stop(self):
276-
self.running = False
277-
27872
class ECGApp(QtWidgets.QMainWindow):
27973
def __init__(self):
28074
super().__init__()
@@ -298,47 +92,73 @@ def __init__(self):
29892
self.setCentralWidget(container)
29993

30094
self.ecg_buffer = [] # Buffer to hold the ECG data
95+
self.r_peak_times = deque(maxlen=15) # Ring buffer for R-peak timestamps within 15 seconds
96+
self.peak_indices = deque() # Stores R-peak indices for the current 15-second window
30197

302-
self.data_collector = DataCollector() # Data collector thread
98+
self.data_collector = DataCollector() # Data collector thread
30399
self.data_collector.data_ready.connect(self.update_plot)
304100
self.data_collector.start()
305101

306-
self.heart_rate_calculator = HeartRateCalculator() # Heart rate calculation thread
307-
self.heart_rate_calculator.start()
308-
self.heart_rate_calculator.heart_rate_ready.connect(self.update_heart_rate)
102+
self.time_axis = np.linspace(0, 2000/200, 2000) # Store the x-axis time window
103+
self.plot_widget.setYRange(0,800) # Set fixed y-axis limits
309104

310-
self.time_axis = np.linspace(0, 2000 / 200, 2000) # Store the x-axis time window
311-
self.plot_widget.setYRange(0, 700) # Set fixed y-axis limits
105+
# Timer to update heart rate every 15 seconds
106+
self.timer = QtCore.QTimer(self)
107+
self.timer.timeout.connect(self.calculate_and_reset_heart_rate)
108+
self.timer.start(15000) # 15 seconds
312109

313110
def update_plot(self, ecg_data):
314111
self.ecg_buffer = ecg_data # Update buffer
315112
self.plot_widget.clear()
113+
316114
# Set a fixed window (time axis) to ensure stable plotting
317115
self.plot_widget.plot(self.time_axis, self.ecg_buffer, pen='#000000') # Plot ECG data with black line
318116
self.plot_widget.setXRange(self.time_axis[0], self.time_axis[-1], padding=0)
319117

320118
# Detect heartbeats in real time
321119
heartbeats = detect_heartbeats(np.array(self.ecg_buffer), self.data_collector.sampling_rate)
120+
print(f"Detected R-peaks at indices: {heartbeats}")
322121

323-
# Mark detected R-peaks and store in HeartRateCalculator
122+
# Append the actual time of detection for each detected peak
324123
for index in heartbeats:
325-
time_point = index / self.data_collector.sampling_rate # Convert index to time
326-
self.heart_rate_calculator.add_r_peak_time(time_point) # Store the time of the detected R-peak
327-
328-
# Plot a red circle at the R-peak position
329-
self.plot_widget.plot([self.time_axis[index]], [self.ecg_buffer[index]], pen=None, symbol='o', symbolBrush='r', symbolSize=8)
330-
331-
def update_heart_rate(self, bpm):
332-
if bpm > 0:
333-
self.heart_rate_label.setText(f"Heart rate: {bpm:.2f} bpm")
124+
# Append the time based on the index and the sampling rate
125+
self.r_peak_times.append(index / self.data_collector.sampling_rate)
126+
self.peak_indices.append(index) # Store peak indices for this 10-second window
127+
128+
# Calculate the x-coordinates (time axis) for the R-peaks
129+
r_peak_times = self.time_axis[heartbeats]
130+
131+
# Plot the R-peaks as red circles
132+
r_peak_scatter = pg.ScatterPlotItem(x=r_peak_times, y=self.ecg_buffer[heartbeats],
133+
symbol='o', size=10, pen='r', brush='r')
134+
self.plot_widget.addItem(r_peak_scatter) # Add scatter plot to the ECG plot
135+
136+
def calculate_and_reset_heart_rate(self):
137+
if len(self.r_peak_times) > 1:
138+
# Calculate the time intervals between successive R-peaks
139+
intervals = np.diff(self.r_peak_times)
140+
141+
print(f"R-peak times: {self.r_peak_times}") # Log R-peak times
142+
print(f"Intervals between peaks: {intervals}") # Log intervals
143+
144+
# Filter intervals that are positive and non-zero to avoid issues
145+
valid_intervals = intervals[intervals > 0]
146+
147+
if len(valid_intervals) > 0:
148+
bpm = 60 / np.mean(valid_intervals) # Mean interval is in seconds, converting to bpm
149+
self.heart_rate_label.setText(f"Heart rate: {bpm:.2f} bpm")
150+
else:
151+
self.heart_rate_label.setText("Heart rate: 0 bpm")
334152
else:
335153
self.heart_rate_label.setText("Heart rate: 0 bpm")
336154

155+
# Clear the deque for the next 10-second window
156+
self.r_peak_times.clear()
157+
self.peak_indices.clear()
158+
337159
def closeEvent(self, event):
338160
self.data_collector.stop() # Stop the data collector thread on close
339161
self.data_collector.wait() # Wait for the thread to finish
340-
self.heart_rate_calculator.stop() # Stop the heart rate calculator
341-
self.heart_rate_calculator.wait() # Wait for the thread to finish
342162
event.accept() # Accept the close event
343163

344164
def signal_handler(sig, frame):

0 commit comments

Comments
 (0)