Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -5,6 +5,7 @@ from collections import deque
|
|
| 5 |
import time
|
| 6 |
import matplotlib.pyplot as plt
|
| 7 |
from scipy import signal
|
|
|
|
| 8 |
|
| 9 |
MAX_LEN = 500
|
| 10 |
FPS = 30
|
|
@@ -56,19 +57,6 @@ def calculate_signal_quality(signal_data):
|
|
| 56 |
quality = 0
|
| 57 |
return quality
|
| 58 |
|
| 59 |
-
def find_peaks_simple(signal_data, fps=30, min_distance=0.4):
|
| 60 |
-
if len(signal_data) < int(fps * 1.0):
|
| 61 |
-
return []
|
| 62 |
-
data = np.array(signal_data)
|
| 63 |
-
data_norm = (data - data.mean()) / (data.std() + 1e-9)
|
| 64 |
-
min_dist = int(min_distance * fps)
|
| 65 |
-
peaks = []
|
| 66 |
-
for i in range(min_dist, len(data_norm) - min_dist):
|
| 67 |
-
if data_norm[i] > data_norm[i-1] and data_norm[i] > data_norm[i+1]:
|
| 68 |
-
if not peaks or i - peaks[-1] >= min_dist:
|
| 69 |
-
peaks.append(i)
|
| 70 |
-
return peaks
|
| 71 |
-
|
| 72 |
def process_frame(frame, state):
|
| 73 |
if frame is None:
|
| 74 |
return None, state["red"], state["green"], state["time"]
|
|
@@ -108,14 +96,22 @@ def process_frame(frame, state):
|
|
| 108 |
return img, red_list, green_list, time_list
|
| 109 |
|
| 110 |
def make_plots(time_list, red_list, green_list):
|
| 111 |
-
"""2-subplot
|
|
|
|
|
|
|
|
|
|
| 112 |
if len(time_list) < 10:
|
| 113 |
return None
|
|
|
|
| 114 |
time_array = np.array(time_list)
|
| 115 |
red_array = np.array(red_list)
|
| 116 |
green_array = np.array(green_list)
|
|
|
|
|
|
|
| 117 |
red_norm = (red_array - red_array.mean()) / (red_array.std() + 1e-9)
|
| 118 |
green_norm = (green_array - green_array.mean()) / (green_array.std() + 1e-9)
|
|
|
|
|
|
|
| 119 |
nyquist = FPS / 2
|
| 120 |
low = 0.67 / nyquist
|
| 121 |
high = 3.33 / nyquist
|
|
@@ -126,27 +122,45 @@ def make_plots(time_list, red_list, green_list):
|
|
| 126 |
else:
|
| 127 |
red_filt = red_norm
|
| 128 |
green_filt = green_norm
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
hr_bpm, _ = estimate_heart_rate(green_array, fps=FPS, window_secs=WINDOW_SIZE)
|
| 130 |
-
|
|
|
|
| 131 |
fig = plt.figure(figsize=(12, 4))
|
|
|
|
|
|
|
| 132 |
ax1 = fig.add_subplot(1, 2, 1)
|
| 133 |
-
ax2 = fig.add_subplot(1, 2, 2)
|
| 134 |
ax1.plot(time_array, red_filt, label='Red (filtered)', color='red', linewidth=1.5)
|
| 135 |
ax1.plot(time_array, green_filt, label='Green (filtered)', color='green', linewidth=1.5)
|
| 136 |
ax1.set_xlabel('Time (s)')
|
| 137 |
ax1.set_ylabel('Normalized Intensity')
|
| 138 |
-
ax1.set_title('Filtered PPG
|
| 139 |
ax1.legend(loc='upper right')
|
| 140 |
ax1.grid(True, alpha=0.3)
|
|
|
|
|
|
|
|
|
|
| 141 |
ax2.plot(time_array, green_filt, label='Green (filtered)', color='green', linewidth=1.5)
|
| 142 |
if len(peaks_idx) > 0:
|
| 143 |
ax2.scatter(time_array[peaks_idx], green_filt[peaks_idx], color='red', s=50, label='Peaks', zorder=5)
|
| 144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
ax2.set_xlabel('Time (s)')
|
| 146 |
ax2.set_ylabel('Normalized Intensity')
|
| 147 |
-
ax2.set_title(hr_title)
|
| 148 |
ax2.legend(loc='upper right')
|
| 149 |
ax2.grid(True, alpha=0.3)
|
|
|
|
| 150 |
fig.tight_layout()
|
| 151 |
return fig
|
| 152 |
|
|
@@ -156,12 +170,15 @@ def snap(frame, state):
|
|
| 156 |
red_quality = calculate_signal_quality(state["red"])
|
| 157 |
green_quality = calculate_signal_quality(state["green"])
|
| 158 |
plot_fig = make_plots(time_list, red_list, green_list)
|
|
|
|
| 159 |
hr_text = f'{bpm:.1f} bpm' if bpm is not None else 'Measuring...'
|
| 160 |
red_quality_text = f'Red: {red_quality:.0f}%'
|
| 161 |
green_quality_text = f'Green: {green_quality:.0f}%'
|
| 162 |
stats_text = f'Frames: {state["frame_count"]} | Duration: {state["session_duration"]:.1f}s | Samples: {len(state["red"])}/{MAX_LEN}'
|
|
|
|
| 163 |
return img, plot_fig, hr_text, red_quality_text, green_quality_text, stats_text, state
|
| 164 |
|
|
|
|
| 165 |
with gr.Blocks(title="Finger Vital Sign Monitor") as demo:
|
| 166 |
gr.Markdown("# Real-time Finger Vital Sign Monitoring")
|
| 167 |
gr.Markdown("Place your finger on the camera and keep it steady. The system will continuously monitor PPG signals and estimate heart rate.")
|
|
|
|
| 5 |
import time
|
| 6 |
import matplotlib.pyplot as plt
|
| 7 |
from scipy import signal
|
| 8 |
+
from scipy.signal import find_peaks
|
| 9 |
|
| 10 |
MAX_LEN = 500
|
| 11 |
FPS = 30
|
|
|
|
| 57 |
quality = 0
|
| 58 |
return quality
|
| 59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
def process_frame(frame, state):
|
| 61 |
if frame is None:
|
| 62 |
return None, state["red"], state["green"], state["time"]
|
|
|
|
| 96 |
return img, red_list, green_list, time_list
|
| 97 |
|
| 98 |
def make_plots(time_list, red_list, green_list):
|
| 99 |
+
"""Create 2-subplot plot matching app_old.py layout:
|
| 100 |
+
Left subplot: Red and Green filtered signals overlay
|
| 101 |
+
Right subplot: Green signal with peak detection
|
| 102 |
+
"""
|
| 103 |
if len(time_list) < 10:
|
| 104 |
return None
|
| 105 |
+
|
| 106 |
time_array = np.array(time_list)
|
| 107 |
red_array = np.array(red_list)
|
| 108 |
green_array = np.array(green_list)
|
| 109 |
+
|
| 110 |
+
# Normalize signals
|
| 111 |
red_norm = (red_array - red_array.mean()) / (red_array.std() + 1e-9)
|
| 112 |
green_norm = (green_array - green_array.mean()) / (green_array.std() + 1e-9)
|
| 113 |
+
|
| 114 |
+
# Apply bandpass filter
|
| 115 |
nyquist = FPS / 2
|
| 116 |
low = 0.67 / nyquist
|
| 117 |
high = 3.33 / nyquist
|
|
|
|
| 122 |
else:
|
| 123 |
red_filt = red_norm
|
| 124 |
green_filt = green_norm
|
| 125 |
+
|
| 126 |
+
# Detect peaks on green channel
|
| 127 |
+
min_dist = int(0.35 * FPS)
|
| 128 |
+
prom = 0.3 * np.std(green_filt) if np.std(green_filt) > 0 else 0.1
|
| 129 |
+
peaks_idx, _ = find_peaks(green_filt, distance=min_dist, prominence=prom)
|
| 130 |
+
|
| 131 |
+
# Estimate HR
|
| 132 |
hr_bpm, _ = estimate_heart_rate(green_array, fps=FPS, window_secs=WINDOW_SIZE)
|
| 133 |
+
|
| 134 |
+
# Create figure with 2 subplots (1 row, 2 columns)
|
| 135 |
fig = plt.figure(figsize=(12, 4))
|
| 136 |
+
|
| 137 |
+
# Left subplot: Both red and green signals
|
| 138 |
ax1 = fig.add_subplot(1, 2, 1)
|
|
|
|
| 139 |
ax1.plot(time_array, red_filt, label='Red (filtered)', color='red', linewidth=1.5)
|
| 140 |
ax1.plot(time_array, green_filt, label='Green (filtered)', color='green', linewidth=1.5)
|
| 141 |
ax1.set_xlabel('Time (s)')
|
| 142 |
ax1.set_ylabel('Normalized Intensity')
|
| 143 |
+
ax1.set_title('Filtered PPG (last 5s)')
|
| 144 |
ax1.legend(loc='upper right')
|
| 145 |
ax1.grid(True, alpha=0.3)
|
| 146 |
+
|
| 147 |
+
# Right subplot: Green signal with peaks
|
| 148 |
+
ax2 = fig.add_subplot(1, 2, 2)
|
| 149 |
ax2.plot(time_array, green_filt, label='Green (filtered)', color='green', linewidth=1.5)
|
| 150 |
if len(peaks_idx) > 0:
|
| 151 |
ax2.scatter(time_array[peaks_idx], green_filt[peaks_idx], color='red', s=50, label='Peaks', zorder=5)
|
| 152 |
+
|
| 153 |
+
# Set title with HR estimate
|
| 154 |
+
if hr_bpm is not None:
|
| 155 |
+
ax2.set_title(f'Peaks (HR = {hr_bpm:.1f} bpm)')
|
| 156 |
+
else:
|
| 157 |
+
ax2.set_title('Peaks')
|
| 158 |
+
|
| 159 |
ax2.set_xlabel('Time (s)')
|
| 160 |
ax2.set_ylabel('Normalized Intensity')
|
|
|
|
| 161 |
ax2.legend(loc='upper right')
|
| 162 |
ax2.grid(True, alpha=0.3)
|
| 163 |
+
|
| 164 |
fig.tight_layout()
|
| 165 |
return fig
|
| 166 |
|
|
|
|
| 170 |
red_quality = calculate_signal_quality(state["red"])
|
| 171 |
green_quality = calculate_signal_quality(state["green"])
|
| 172 |
plot_fig = make_plots(time_list, red_list, green_list)
|
| 173 |
+
|
| 174 |
hr_text = f'{bpm:.1f} bpm' if bpm is not None else 'Measuring...'
|
| 175 |
red_quality_text = f'Red: {red_quality:.0f}%'
|
| 176 |
green_quality_text = f'Green: {green_quality:.0f}%'
|
| 177 |
stats_text = f'Frames: {state["frame_count"]} | Duration: {state["session_duration"]:.1f}s | Samples: {len(state["red"])}/{MAX_LEN}'
|
| 178 |
+
|
| 179 |
return img, plot_fig, hr_text, red_quality_text, green_quality_text, stats_text, state
|
| 180 |
|
| 181 |
+
|
| 182 |
with gr.Blocks(title="Finger Vital Sign Monitor") as demo:
|
| 183 |
gr.Markdown("# Real-time Finger Vital Sign Monitoring")
|
| 184 |
gr.Markdown("Place your finger on the camera and keep it steady. The system will continuously monitor PPG signals and estimate heart rate.")
|