🔧 Lab Procedure
✅ Checkpoint 1: Environment Setup
Start by installing and importing required libraries in your first code cell:
# Install and import required libraries
!pip install scipy matplotlib numpy pandas
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import signal
from scipy.fft import fft, fftfreq
import requests
import io
print("✅ Libraries imported successfully!")
Action: Run this cell and wait for installation to complete.
✅ Checkpoint 2: Load PPG Data
Create a new code cell and choose ONE of these options to load your data:
from google.colab import files
uploaded = files.upload()
# Get the filename
filename = list(uploaded.keys())[0]
ppg_data = pd.read_csv(filename, header=None)
ppg_data.columns = ['red', 'ir']
print(f"✅ File '{filename}' loaded successfully!")
print("Data shape:", ppg_data.shape)
Expected Output: You should see the data shape and first few rows printed.
✅ Checkpoint 3: Generate Time Vector
Create a new code cell and add the following:
# Define sampling rate (adjust according to your data)
sampling_rate = 100 # Hz - typical for PPG
# Generate time vector
time = np.arange(len(ppg_data)) / sampling_rate
# Add time to dataframe
ppg_data['time'] = time
print(f"📊 Data Information:")
print(f"Sampling rate: {sampling_rate} Hz")
print(f"Total duration: {time[-1]:.2f} seconds")
print(f"Number of samples: {len(ppg_data)}")
print(f"Time range: {time[0]:.2f} to {time[-1]:.2f} seconds")
# ✅ Checkpoint verification
if len(time) == len(ppg_data):
print("✅ Time vector generated correctly!")
else:
print("❌ Error in time vector generation")
💡 Note: If you know your actual sampling rate, replace 100 with the correct value.
✅ Checkpoint 4: Visualize Raw Signals
Create a new code cell for signal visualization:
# Create subplots
plt.figure(figsize=(12, 8))
# Plot Red signal
plt.subplot(2, 1, 1)
plt.plot(ppg_data['time'], ppg_data['red'], 'r-', alpha=0.7, linewidth=1)
plt.title('Raw Red PPG Signal', fontsize=14, fontweight='bold')
plt.xlabel('Time (seconds)')
plt.ylabel('Amplitude')
plt.grid(True, alpha=0.3)
# Plot IR signal
plt.subplot(2, 1, 2)
plt.plot(ppg_data['time'], ppg_data['ir'], color='darkred', alpha=0.7, linewidth=1)
plt.title('Raw IR PPG Signal', fontsize=14, fontweight='bold')
plt.xlabel('Time (seconds)')
plt.ylabel('Amplitude')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print("✅ Raw signals plotted successfully!")
print("📝 Observation: Note the periodic nature of PPG waveforms")
Expected Output: Two plots showing your raw PPG signals.
✅ Checkpoint 5: Signal Filtering
Create a new code cell for signal preprocessing:
def bandpass_filter(signal_data, lowcut, highcut, fs, order=4):
"""
Apply bandpass filter to remove noise from PPG signal
"""
nyquist = 0.5 * fs
low = lowcut / nyquist
high = highcut / nyquist
b, a = signal.butter(order, [low, high], btype='band')
filtered_signal = signal.filtfilt(b, a, signal_data)
return filtered_signal
# Apply bandpass filter (0.5 Hz to 5 Hz for heart rate)
lowcut = 0.5
highcut = 5.0
print("🔄 Applying bandpass filter...")
ppg_data['red_filtered'] = bandpass_filter(ppg_data['red'], lowcut, highcut, sampling_rate)
ppg_data['ir_filtered'] = bandpass_filter(ppg_data['ir'], lowcut, highcut, sampling_rate)
print(f"✅ Bandpass filtering completed!")
print(f"Filter range: {lowcut}-{highcut} Hz")
✅ Checkpoint 6: Compare Raw vs Filtered
Create a new code cell to visualize the filtering results:
plt.figure(figsize=(12, 8))
# Plot filtered Red signal
plt.subplot(2, 1, 1)
plt.plot(ppg_data['time'], ppg_data['red'], 'r-', alpha=0.3, label='Raw', linewidth=1)
plt.plot(ppg_data['time'], ppg_data['red_filtered'], 'r-', linewidth=1.5, label='Filtered')
plt.title('Red PPG Signal: Raw vs Filtered', fontsize=14, fontweight='bold')
plt.xlabel('Time (seconds)')
plt.ylabel('Amplitude')
plt.legend()
plt.grid(True, alpha=0.3)
# Plot filtered IR signal
plt.subplot(2, 1, 2)
plt.plot(ppg_data['time'], ppg_data['ir'], color='darkred', alpha=0.3, label='Raw', linewidth=1)
plt.plot(ppg_data['time'], ppg_data['ir_filtered'], color='darkred', linewidth=1.5, label='Filtered')
plt.title('IR PPG Signal: Raw vs Filtered', fontsize=14, fontweight='bold')
plt.xlabel('Time (seconds)')
plt.ylabel('Amplitude')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print("✅ Filtered signals visualized!")
✅ Checkpoint 7: Peak Detection
Create a new code cell for peak detection:
def find_ppg_peaks(signal_data, fs, min_distance=0.4):
"""
Find peaks in PPG signal for heart rate calculation
"""
min_samples = int(min_distance * fs) # Minimum distance between peaks in samples
peaks, properties = signal.find_peaks(signal_data,
distance=min_samples,
prominence=np.std(signal_data))
return peaks
# Find peaks in filtered signals
red_peaks = find_ppg_peaks(ppg_data['red_filtered'], sampling_rate)
ir_peaks = find_ppg_peaks(ppg_data['ir_filtered'], sampling_rate)
print(f"Red signal peaks found: {len(red_peaks)}")
print(f"IR signal peaks found: {len(ir_peaks)}")
✅ Checkpoint 8: Visualize Detected Peaks
Create a new code cell to plot peaks:
plt.figure(figsize=(12, 8))
# Plot Red signal with peaks
plt.subplot(2, 1, 1)
plt.plot(ppg_data['time'], ppg_data['red_filtered'], 'r-', label='Filtered Signal')
plt.plot(ppg_data['time'][red_peaks], ppg_data['red_filtered'][red_peaks], 'ro',
markersize=8, label='Detected Peaks')
plt.title('Red PPG Signal with Detected Peaks', fontsize=14, fontweight='bold')
plt.xlabel('Time (seconds)')
plt.ylabel('Amplitude')
plt.legend()
plt.grid(True, alpha=0.3)
# Plot IR signal with peaks
plt.subplot(2, 1, 2)
plt.plot(ppg_data['time'], ppg_data['ir_filtered'], color='darkred', label='Filtered Signal')
plt.plot(ppg_data['time'][ir_peaks], ppg_data['ir_filtered'][ir_peaks], 'o',
color='darkred', markersize=8, label='Detected Peaks')
plt.title('IR PPG Signal with Detected Peaks', fontsize=14, fontweight='bold')
plt.xlabel('Time (seconds)')
plt.ylabel('Amplitude')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print("✅ Peaks visualized!")
✅ Checkpoint 9: Heart Rate Calculation
Create a new code cell for heart rate analysis:
def calculate_heart_rate(peaks, time_vector):
"""
Calculate heart rate from peak intervals
"""
if len(peaks) < 2:
return 0, []
# Calculate time differences between consecutive peaks
peak_times = time_vector[peaks]
rr_intervals = np.diff(peak_times) # R-R intervals in seconds
# Calculate heart rate in BPM
heart_rates = 60 / rr_intervals # Convert to beats per minute
return np.mean(heart_rates), heart_rates
# Calculate heart rates for both signals
red_hr, red_rr = calculate_heart_rate(red_peaks, ppg_data['time'])
ir_hr, ir_rr = calculate_heart_rate(ir_peaks, ppg_data['time'])
print("Red Signal Heart Rate Analysis:")
print(f" Average HR: {red_hr:.2f} BPM")
print(f" Number of beats: {len(red_peaks)}")
print("\nIR Signal Heart Rate Analysis:")
print(f" Average HR: {ir_hr:.2f} BPM")
print(f" Number of beats: {len(ir_peaks)}")
print(f"\nHeart Rate Difference: {abs(red_hr - ir_hr):.2f} BPM")