Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
16d98e7
updates navigations
Oscarkyamuwendo Jan 20, 2025
7fc1b55
Update about.html
Oscarkyamuwendo Jan 20, 2025
0a58ce2
changed signup.html to signup
Oscarkyamuwendo Jan 20, 2025
f2871c0
Update routes.py
Oscarkyamuwendo Jan 20, 2025
f2e0b89
Update signup.html
Oscarkyamuwendo Jan 20, 2025
e187e28
Add files via upload
Oscarkyamuwendo Jan 20, 2025
78eee99
added csv file
Oscarkyamuwendo Jan 20, 2025
d9d057f
Create bita.py
Rhyan2 Jan 20, 2025
2b9966f
Create bitabuffered.py
Rhyan2 Jan 20, 2025
c12a644
added csv link
Oscarkyamuwendo Jan 20, 2025
53daeb1
added web socket
Oscarkyamuwendo Jan 20, 2025
a2af778
updated web socket
Oscarkyamuwendo Jan 20, 2025
9b3e8ba
Update data.py
Oscarkyamuwendo Jan 20, 2025
7ad060d
Update data.py
Oscarkyamuwendo Jan 20, 2025
4ad600e
returned to former web socket
Oscarkyamuwendo Jan 20, 2025
2bdc2a2
Update data.py
Oscarkyamuwendo Jan 20, 2025
784eb25
Update README.md
GEORGEMUTALE Jan 21, 2025
df6b9c0
Add files via upload
GEORGEMUTALE Jan 21, 2025
4841d3c
Delete photo_2025-01-20_08-47-32.jpg
GEORGEMUTALE Jan 21, 2025
64b42de
Delete photo_2025-01-18_13-10-55.jpg
GEORGEMUTALE Jan 21, 2025
590756d
Delete photo_2025-01-17_14-48-08.jpg
GEORGEMUTALE Jan 21, 2025
b163d1e
Delete photo_2025-01-17_14-46-44.jpg
GEORGEMUTALE Jan 21, 2025
63b9a70
Add files via upload
GEORGEMUTALE Jan 21, 2025
a7de09c
Delete photo_2025-01-18_13-10-55.jpg
GEORGEMUTALE Jan 21, 2025
c35dac1
Update README.md
GEORGEMUTALE Jan 21, 2025
1bb84c5
Update README.md
GEORGEMUTALE Jan 21, 2025
2f3ff8f
Update README.md
GEORGEMUTALE Jan 21, 2025
d57a277
Update docker-compose.yml
Rhyan2 Jan 21, 2025
219c6bd
Update docker-compose.yml
Rhyan2 Jan 21, 2025
c3fcc42
Update docker-compose.yml
Rhyan2 Jan 21, 2025
425f1fc
Update README.md
GEORGEMUTALE Jan 21, 2025
f6f77c2
Update README.md
GEORGEMUTALE Jan 21, 2025
c1d1ef5
Update README.md
GEORGEMUTALE Jan 21, 2025
6aa672a
Update README.md
GEORGEMUTALE Jan 21, 2025
f9c2a01
Update README.md
GEORGEMUTALE Jan 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 77 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,77 @@
INNOVATION AND COMPLEXITY PROJECT
## HEALTH SIGNALS
## Team Members

| Name | Role |
|-----------------------|-----------------------------------|
| **George Mutale** | Data Analysis, Database, AI |
| **Rhyan Lubega** | Backend |
| **Oscar Kyamuwendo** | Data Collection and Hardware |
| **Ebenezer Amakye** | Frontend Development |
| **Emmanuel Boaz** | Security and Backend Development |

---

## Our project usecase

Our project, called **"PulseFlowBridge"**, is a web-based application designed to help monitor the heart rates of elderly people in real-time without requiring a nurse to constantly watch them. This system is aimed at making healthcare for the elderly more efficient and safer. The hospital doctors can create accounts on the platform and add patients as needed.

We use a **Bitalino sensor**, which can be placed either on a shirt near the chest or in a wristwatch, to measure heartbeats in real life. The sensor collects ECG signal data, and after cleaning and processing it, the system checks whether the heart rate is within the normal range. If the heart rate falls below **60 BPM** or exceeds **100 BPM**, the system automatically sends an alert to the hospital’s email. This alert includes the patient’s ID so that doctors can respond immediately and know who needs help.

This system reduces the need for constant manual monitoring by nurses, making it a **smart and efficient way to ensure the safety of elderly patients**.

---

## Evolution of our graph using images
![Image1](https://github.com/Health-signals/Sensor/blob/main/photo_2025-01-17_14-46-44.jpg)

*Figure 1: Graph with noise or unfiltered signal*

![Image1](https://github.com/Health-signals/Sensor/blob/main/photo_2025-01-17_14-48-08.jpg)

*Figure 2: Before defining the perfect peak detect*

![Image1](https://github.com/Health-signals/Sensor/blob/main/photo_2025-01-20_08-47-32.jpg)

*Figure 3: After getting the perfect threshold for peak detection.*
## Challenges Faced

1. **Sensor Selection:**
Initially, we planned to use the **Bitalino sensor** as the professors had planned, but due to Bluetooth connectivity failures of the sensor, we switched to an **AN ECG Arduino sensor**. However, this sensor came with limitations:
- The electrode pads were one-time use, which reduced sensitivity over time.
- Manual wire connections were prone to disconnection, leading to inaccurate results.

2. **Switching Back to Bitalino:**
- The professor gave us a new the Bitalino sensor and downgraded Python to ensure compatibility.
- We successfully connected the sensor directly to the frontend, enabling real-time visualization. However, this method produced inconsistent BPM values and incomplete peaks despite hardware buffering.

3. **Improving Data Accuracy:**
- To address these issues, we collected data from the sensor and stored it in a **CSV file**. This file was then saved in a database.
- By processing the stored data, we achieved accurate BPM readings and visualized graphs with well-defined peaks.

4. **Use of websocket:**
- We are still debating whether the websocket is the perfect method to send real time data because we could get lags as we are visualising the graph

5. **Making our project work on codespace:**
- Since our project was fully developed locally from the start, we had to change many things to see that it works well on codespaces and we just had a few hours to do this .We managed to do a docker yml file that can setup a demo for the doctor sign in but since we are out of time we did not manage to get over the issue of serving data to an online websocket so that the graph can be visualise as real time but we have provide images of a well working graph on from our local machine.
---

## Future Goals

- Acquire better sensors to improve real-time data acquisition and display.
- Enhance buffering and filtering techniques for more reliable data processing.
- Further refine our system for seamless real-time visualization.
- Making more research about how to send real time data without lags
- Creating an AI that defines scales for more visualisation and having a peak detection for different ecg signals
- Adding functionalities that can support to view live data of morethan 3 patients

---
## Conclusion

**PulseFlowBridge** offers a smart and efficient way to monitor the heart health of elderly patients. By leveraging advanced sensors and real-time data processing, this system ensures timely interventions during emergencies while reducing the workload for healthcare providers. As we continue to refine and improve our solution, we aim to make healthcare more accessible and reliable for elderly populations.

## Development environment setup
Codespaces setup
Open codespaces and use docker-compose up

When register you can use an email and password of your own and any fake data for input. You can just imagine of any data for you the doctor and the one of the patient

78 changes: 78 additions & 0 deletions bita.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import os
import csv
import time

from bitalino import BITalino

# Configuration
MAC_ADDRESS = "98:D3:C1:FE:03:7F" # Replace with your BITalino's MAC address
SAVE_FOLDER = "ecg_data"
FILENAME = "ecg_log.csv"
SAMPLING_RATE = 1000 # Changed to 1000 Hz to match sample data
CHANNELS = [1] # Changed to channel 0 (A1 on BITalino)
DURATION = 10 # Duration to record in seconds

# Ensure the save folder exists
if not os.path.exists(SAVE_FOLDER):
os.makedirs(SAVE_FOLDER)

# Full path for the output file
file_path = os.path.join(SAVE_FOLDER, FILENAME)

def save_data_to_csv(data):
"""Save the data to a CSV file."""
mode = "w" if not os.path.exists(file_path) else "a" # Append if file existsa
with open(file_path, mode=mode, newline="") as file:
writer = csv.writer(file)
if mode == "w": # Write header only when creating a new file
writer.writerow(["timestamp", "ecg_value"])
writer.writerows(data)

try:
# Connect to BITalino
print(f"Connecting to BITalino at {MAC_ADDRESS}...")
device = BITalino(MAC_ADDRESS)

# Start acquisition
print("Starting data acquisition...")
device.start(SAMPLING_RATE, CHANNELS)

start_time = time.time() # Capture the start time
#start_time = int(time.time() * 1000)

ecg_data = []

while time.time() - start_time < DURATION:
try:
# Read samples (block size of 10)
samples = device.read(10)

for i, sample in enumerate(samples):
timestamp = round((time.time() - start_time) + (i / SAMPLING_RATE), 3)
ecg_value = sample[-1] # ECG value is the last in the sample

# Scale ECG value to match sample data range (approximately 470-540)
scaled_ecg = int(ecg_value * (540 - 470) / 1023 + 470)

ecg_data.append([timestamp, scaled_ecg])

except Exception as read_error:
print(f"Error reading data: {read_error}")
break

# Stop acquisition
print("Stopping data acquisition...")
device.stop()

# Save data to CSV
save_data_to_csv(ecg_data)
print(f"Data saved to {file_path}")

except Exception as e:
print(f"An error occurred: {e}")

finally:
# Ensure the device is released
if 'device' in locals():
device.close()
print("Device disconnected.")
122 changes: 122 additions & 0 deletions bitabuffered.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import os
import asyncio
import websockets
import csv
import json
import numpy as np
from bitalino import BITalino
import scipy.signal as signal
import time

# Configuration
MAC_ADDRESS = "98:D3:C1:FE:03:7F" # Replace with your BITalino's MAC address
WEBSOCKET_URI = "ws://localhost:8000/ws/3" # Replace with your WebSocket server URI
SAMPLING_RATE = 1000 # Adjust sampling rate
CHANNELS = [1] # ECG channel on BITalino
DURATION = 1000 # Duration to record in seconds

# Low-pass filter function to remove high-frequency noise
def low_pass_filter(ecg_signal, fs=100, cutoff=1.0):
nyquist = 0.5 * fs
normal_cutoff = cutoff / nyquist
b, a = signal.butter(1, normal_cutoff, btype='low', analog=False)
filtered_ecg = signal.filtfilt(b, a, ecg_signal)
return filtered_ecg

# Bandpass filter and peak detection
def detect_r_peaks(ecg_signal, fs=100):
lowcut = 0.5 # Lower bound of heart rate (0.5 Hz = 30 BPM)
highcut = 5.0 # Upper bound of heart rate (5 Hz = 300 BPM)

nyquist = 0.5 * fs
low = lowcut / nyquist
high = highcut / nyquist
b, a = signal.butter(1, [low, high], btype='band')
filtered_ecg = signal.filtfilt(b, a, ecg_signal)

# Find R-peaks (local maxima) in the filtered ECG signal
peaks, _ = signal.find_peaks(filtered_ecg, distance=fs / 2) # Adjust distance for heart rate
return peaks

# Function to calculate BPM
def calculate_bpm(peaks, sample_rate=100):
rr_intervals = np.diff(peaks) / sample_rate # RR intervals in seconds
bpm = 60 / np.mean(rr_intervals) if len(rr_intervals) > 0 else 0
return bpm

# Convert raw ADC values to voltage
def convert_to_voltage(ecg_signal, adc_resolution=1024, reference_voltage=3.3):
return (ecg_signal / adc_resolution) * reference_voltage

# Process and send data over WebSocket
async def acquire_and_send_data():
try:
print(f"Connecting to BITalino at {MAC_ADDRESS}...")
device = BITalino(MAC_ADDRESS)

# Start acquisition
print("Starting data acquisition...")
device.start(SAMPLING_RATE, CHANNELS)

ecg_data = []
#start_time = int(time.time() * 1000)

start_time = time.time() # Capture the start time

async with websockets.connect(WEBSOCKET_URI, ping_interval=20, ping_timeout=10) as websocket:
print("Connected to WebSocket server.")
while time.time() - start_time < DURATION:
try:
# Read samples (block size of 10)
samples = device.read(10)

for i, sample in enumerate(samples):
timestamp = round((time.time() - start_time) + (i / SAMPLING_RATE), 3)
ecg_value = sample[-1] # ECG value is the last in the sample

# Scale ECG value
scaled_ecg = int(ecg_value * (540 - 470) / 1023 + 470)
ecg_data.append(scaled_ecg)

# Filter and process ECG data in real-time
if len(ecg_data) > SAMPLING_RATE: # Only process if we have 1 second of data
windowed_data = ecg_data[-SAMPLING_RATE:] # Last second of data
filtered_ecg = low_pass_filter(windowed_data, SAMPLING_RATE)
peaks = detect_r_peaks(filtered_ecg, SAMPLING_RATE)
bpm = calculate_bpm(peaks, SAMPLING_RATE)

# Convert to voltage
voltage = convert_to_voltage(scaled_ecg)

# Prepare and send the message
message = json.dumps({
"timestamp": timestamp,
"ecg": [voltage],
"bpm": bpm
})
await websocket.send(message)
print(f"Sent: {message}")

await asyncio.sleep(0.01) # Control data rate

except Exception as read_error:
print(f"Error reading data: {read_error}")
break

print("Stopping data acquisition...")
device.stop()

except Exception as e:
print(f"An error occurred: {e}")

finally:
# Ensure the device is released
if 'device' in locals():
device.close()
print("Device disconnected.")

if __name__ == "__main__":
try:
asyncio.run(acquire_and_send_data())
except KeyboardInterrupt:
print("Shutting down gracefully...")
Loading