justus-tobias commited on
Commit
061389d
·
1 Parent(s): 3b53c15
Files changed (2) hide show
  1. app.py +48 -183
  2. utils.py +141 -0
app.py CHANGED
@@ -1,6 +1,7 @@
1
  from plotly.subplots import make_subplots
2
  from scipy.signal import find_peaks, butter, filtfilt
3
  import plotly.graph_objects as go
 
4
  import gradio as gr
5
  import numpy as np
6
  import itertools
@@ -8,55 +9,14 @@ import librosa
8
  import random
9
  import os
10
 
 
 
11
  example_dir = "Examples"
12
  example_files = [os.path.join(example_dir, f) for f in os.listdir(example_dir) if f.endswith(('.wav', '.mp3', '.ogg'))]
13
  all_pairs = list(itertools.combinations(example_files, 2))
14
  random.shuffle(all_pairs)
15
  example_pairs = [list(pair) for pair in all_pairs[:25]]
16
 
17
- # GENERAL HELPER FUNCTIONS
18
- def getaudiodata(filepath)->tuple[int,np.ndarray]:
19
-
20
- audiodata, sr = librosa.load(filepath, sr=None)
21
-
22
- # Ensure audiodata is a numpy array
23
- if not isinstance(audiodata, np.ndarray):
24
- audiodata = np.array(audiodata)
25
-
26
- # Check if audio is mono or stereo
27
- if len(audiodata.shape) > 1:
28
- # If stereo, convert to mono by averaging channels
29
- audiodata = np.mean(audiodata, axis=1)
30
-
31
- audiodata = np.astype(audiodata, np.float16)
32
-
33
- return sr, audiodata
34
-
35
- def getBeats(audiodata:np.ndarray, sr:int):
36
- # Convert audio data to float32
37
- audiodata = audiodata.astype(np.float32)
38
-
39
- # Normalize the audio data
40
- audiodata = audiodata / np.max(np.abs(audiodata))
41
-
42
- # Set the threshold for peak detection (adjust this value as needed)
43
- threshold = 0.5 # 50% of the maximum amplitude
44
-
45
- # Find peaks above the threshold
46
- peaks, _ = find_peaks(np.abs(audiodata), height=threshold, distance=int(sr * 0.3))
47
-
48
- # Convert peak indices to times
49
- peak_times = (peaks / sr)*2
50
-
51
- # Calculate tempo (beats per minute)
52
- if len(peak_times) > 1:
53
- avg_interval = np.mean(np.diff(peak_times))
54
- tempo = 60 / avg_interval
55
- else:
56
- tempo = 0
57
-
58
- return [tempo], peak_times
59
-
60
  def getHRV(beattimes: np.ndarray) -> np.ndarray:
61
  # Calculate instantaneous heart rate
62
  instantaneous_hr = 60 * np.diff(beattimes)
@@ -166,43 +126,6 @@ def plotCombined(audiodata, sr, filename):
166
 
167
  return fig
168
 
169
- def plotbeatscatter(tempo, beattimes):
170
- # Calculate beat durations
171
- beat_durations = np.diff(beattimes)
172
-
173
- # Calculate cumulative times for x-axis
174
- cumulative_times = np.cumsum(beat_durations)
175
-
176
- # Create scatter plot
177
- fig = go.Figure()
178
-
179
- # Add scatter plot of beat durations
180
- fig.add_trace(go.Scatter(
181
- x=cumulative_times,
182
- y=beat_durations,
183
- mode='markers',
184
- name='Beat Durations',
185
- marker=dict(
186
- size=8
187
- )
188
- ))
189
-
190
- # Add line for average beat duration
191
- avg_duration = 60 / tempo[0] if isinstance(tempo, list) else 60 / tempo # Convert tempo (BPM) to seconds
192
- fig.add_hline(y=avg_duration, line=dict(color='red', dash='dash'),
193
- annotation_text=f"Average: {avg_duration:.2f}s",
194
- annotation_position="top right")
195
-
196
- # Update layout
197
- fig.update_layout(
198
- title_text='Beat Durations Over Time',
199
- xaxis_title_text='Cumulative Time (seconds)',
200
- yaxis_title_text='Beat Duration (seconds)',
201
- showlegend=True
202
- )
203
-
204
- return fig
205
-
206
  def analyze_single(audio:gr.Audio):
207
  # Extract audio data and sample rate
208
  filepath = audio
@@ -230,7 +153,7 @@ def analyze_single(audio:gr.Audio):
230
 
231
  tempo, beattimes = getBeats(audiodata, sr)
232
  spectogram_wave = plotCombined(audiodata, sr, filename)
233
- beats_histogram = plotbeatscatter(tempo[0], beattimes)
234
 
235
  # Add the new average heartbeat analysis
236
  avg_beat_plot, avg_beat = create_average_heartbeat(audiodata, sr)
@@ -253,83 +176,28 @@ def analyze_single(audio:gr.Audio):
253
  - Beat durations: {np.diff(beattimes)}
254
  - Mean Beat Duration: {np.mean(np.diff(beattimes)):.4f}
255
  """
256
- return results, spectogram_wave, avg_beat_plot, beats_histogram
257
  #-----------------------------------------------
258
  #-----------------------------------------------
259
 
260
- # HELPER FUNCTIONS FOR DUAL AUDIO ANALYSIS
261
- def analyze_double(audio1:gr.Audio, audio2:gr.Audio):
262
-
263
- sr1, audiodata1 = getaudiodata(audio1)
264
- sr2, audiodata2 = getaudiodata(audio2)
265
 
 
266
 
267
- combinedfig = plotCombineddouble(audiodata1, sr1, audiodata2, sr2)
268
- return combinedfig
269
 
270
- def plotCombineddouble(audiodata1, sr1, audiodata2, sr2):
271
- # Create subplots
272
- fig = make_subplots(rows=2, cols=2, shared_xaxes=True, vertical_spacing=0.1, shared_yaxes=True,
273
- subplot_titles=['Audio 1 Waveform','Audio 2 Audio Waveform', 'Audio 1 Spectrogram', 'Audio 2 Spectrogram'])
274
 
275
- # Waveform plot
276
- time = (np.arange(0, len(audiodata1)) / sr1)*2
277
- fig.add_trace(
278
- go.Scatter(x=time, y=audiodata1, mode='lines', line=dict(color='blue', width=1), showlegend=False),
279
- row=1, col=1
280
- )
281
 
282
- # Spectrogram plot
283
- D = librosa.stft(audiodata1)
284
- S_db = librosa.amplitude_to_db(np.abs(D), ref=np.max)
285
- times = librosa.times_like(S_db)
286
- freqs = librosa.fft_frequencies(sr=sr1)
287
 
288
- fig.add_trace(
289
- go.Heatmap(z=S_db, x=times, y=freqs, colorscale='Viridis',
290
- zmin=S_db.min(), zmax=S_db.max(), showlegend=False),#, colorbar=dict(title='Magnitude (dB)')),
291
- row=2, col=1
292
- )
293
 
294
- # Waveform plot
295
- time = (np.arange(0, len(audiodata2)) / sr2)*2
296
- fig.add_trace(
297
- go.Scatter(x=time, y=audiodata2, mode='lines', line=dict(color='blue', width=1), showlegend=False),
298
- row=1, col=2
299
- )
300
 
301
- # Spectrogram plot
302
- D = librosa.stft(audiodata2)
303
- S_db = librosa.amplitude_to_db(np.abs(D), ref=np.max)
304
- times = librosa.times_like(S_db)
305
- freqs = librosa.fft_frequencies(sr=sr2)
306
-
307
- fig.add_trace(
308
- go.Heatmap(z=S_db, x=times, y=freqs, colorscale='Viridis',
309
- zmin=S_db.min(), zmax=S_db.max(), showlegend=False),#, colorbar=dict(title='Magnitude (dB)')),
310
- row=2, col=2
311
- )
312
-
313
-
314
-
315
-
316
-
317
- # Update layout
318
- fig.update_layout(
319
- height=800, width=1200,
320
- title_text="Audio Analysis",
321
- showlegend=False
322
- )
323
-
324
- fig.update_xaxes(title_text="Time (s)", row=2, col=1)
325
- fig.update_yaxes(title_text="Amplitude", row=1, col=1)
326
- fig.update_yaxes(title_text="Frequency (Hz)", type="log", row=2, col=1)
327
-
328
- fig.update_xaxes(title_text="Time (s)", row=2, col=2)
329
- fig.update_yaxes(title_text="Amplitude", row=1, col=2)
330
- fig.update_yaxes(title_text="Frequency (Hz)", type="log", row=2, col=2)
331
-
332
- return fig
333
 
334
 
335
 
@@ -337,7 +205,7 @@ def plotCombineddouble(audiodata1, sr1, audiodata2, sr2):
337
  with gr.Blocks() as app:
338
 
339
  gr.Markdown("# Heartbeat")
340
- gr.Markdown("This App helps to analyze and extract Information from Heartbeats")
341
  gr.Markdown("""
342
  - Beat (mean) (average heartbeat duration)
343
  - S1, S2 (mean) (average S1,S2 duration)
@@ -346,21 +214,46 @@ with gr.Blocks() as app:
346
  - Plot of Wave & Spectogram (Beats annotated)
347
  """)
348
 
349
- with gr.Tab("Single Audio"):
 
 
 
350
 
351
- audiofile = gr.Audio(
352
- type="filepath",
353
- label="Audio of a Heartbeat",
354
- sources="upload")
 
 
 
 
 
 
355
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
356
  analyzebtn = gr.Button("analyze")
357
 
358
  results = gr.Markdown()
359
  spectogram_wave = gr.Plot()
360
  avg_beat_plot = gr.Plot()
361
- beats_histogram = gr.Plot()
362
 
363
- analyzebtn.click(analyze_single, audiofile, [results, spectogram_wave, avg_beat_plot, beats_histogram])
364
 
365
  gr.Examples(
366
  examples=example_files,
@@ -371,34 +264,6 @@ with gr.Blocks() as app:
371
  )
372
 
373
 
374
- # with gr.Tab("Two Audios"):
375
-
376
- # with gr.Row():
377
-
378
- # audioone = gr.Audio(
379
- # type="filepath",
380
- # label="Audio of a Heartbeat",
381
- # sources="upload")
382
- # audiotwo = gr.Audio(
383
- # type="filepath",
384
- # label="Audio of a Heartbeat",
385
- # sources="upload")
386
-
387
- # analyzebtn2 = gr.Button("analyze & compare")
388
-
389
- # with gr.Accordion("Results",open=False):
390
- # results2 = gr.Markdown()
391
- # spectogram_wave2 = gr.Plot()
392
-
393
- # analyzebtn2.click(analyze_double, inputs=[audioone,audiotwo], outputs=spectogram_wave2)
394
-
395
- # gr.Examples(
396
- # examples=example_pairs,
397
- # inputs=[audioone, audiotwo],
398
- # outputs=spectogram_wave2,
399
- # fn=analyze_double,
400
- # cache_examples=False
401
- # )
402
 
403
 
404
  app.launch()
 
1
  from plotly.subplots import make_subplots
2
  from scipy.signal import find_peaks, butter, filtfilt
3
  import plotly.graph_objects as go
4
+ import pandas as pd
5
  import gradio as gr
6
  import numpy as np
7
  import itertools
 
9
  import random
10
  import os
11
 
12
+ from utils import getaudiodata, getBeats, plotBeattimes
13
+
14
  example_dir = "Examples"
15
  example_files = [os.path.join(example_dir, f) for f in os.listdir(example_dir) if f.endswith(('.wav', '.mp3', '.ogg'))]
16
  all_pairs = list(itertools.combinations(example_files, 2))
17
  random.shuffle(all_pairs)
18
  example_pairs = [list(pair) for pair in all_pairs[:25]]
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  def getHRV(beattimes: np.ndarray) -> np.ndarray:
21
  # Calculate instantaneous heart rate
22
  instantaneous_hr = 60 * np.diff(beattimes)
 
126
 
127
  return fig
128
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  def analyze_single(audio:gr.Audio):
130
  # Extract audio data and sample rate
131
  filepath = audio
 
153
 
154
  tempo, beattimes = getBeats(audiodata, sr)
155
  spectogram_wave = plotCombined(audiodata, sr, filename)
156
+ #beats_histogram = plotbeatscatter(tempo[0], beattimes)
157
 
158
  # Add the new average heartbeat analysis
159
  avg_beat_plot, avg_beat = create_average_heartbeat(audiodata, sr)
 
176
  - Beat durations: {np.diff(beattimes)}
177
  - Mean Beat Duration: {np.mean(np.diff(beattimes)):.4f}
178
  """
179
+ return results, spectogram_wave, avg_beat_plot
180
  #-----------------------------------------------
181
  #-----------------------------------------------
182
 
183
+ # HELPER FUNCTIONS FOR SINGLE AUDIO ANALYSIS V2
 
 
 
 
184
 
185
+ def getBeatsv2(audio:gr.Audio):
186
 
187
+ sr, audiodata = getaudiodata(audio)
188
+ _, beattimes = getBeats(audiodata, sr)
189
 
190
+ fig = plotBeattimes(beattimes, audiodata, sr)
191
+ beattimes_table = pd.DataFrame(data={"Beattimes":beattimes})
 
 
192
 
193
+ return fig, beattimes_table
 
 
 
 
 
194
 
195
+ def updateBeatsv2(beattimes_table:gr.Dataframe, audio:gr.Audio)-> go.Figure:
196
+ sr, audiodata = getaudiodata(audio)
197
+ return plotBeattimes(beattimes_table["Beattimes"], audiodata, sr)
 
 
198
 
 
 
 
 
 
199
 
 
 
 
 
 
 
200
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
 
202
 
203
 
 
205
  with gr.Blocks() as app:
206
 
207
  gr.Markdown("# Heartbeat")
208
+ gr.Markdown("This App helps to analyze and extract Information from Heartbeat Audios")
209
  gr.Markdown("""
210
  - Beat (mean) (average heartbeat duration)
211
  - S1, S2 (mean) (average S1,S2 duration)
 
214
  - Plot of Wave & Spectogram (Beats annotated)
215
  """)
216
 
217
+ audiofile = gr.Audio(
218
+ type="filepath",
219
+ label="Upload the Audio of a Heartbeat",
220
+ sources="upload")
221
 
222
+
223
+ with gr.Tab("Single Audio V2"):
224
+
225
+ getBeatsbtn = gr.Button("get Beats")
226
+
227
+ beats_wave_plot = gr.Plot()
228
+ beattimes_table = gr.Dataframe(
229
+ col_count=1,
230
+ type='pandas',
231
+ interactive=True)
232
 
233
+ updateBeatsbtn = gr.Button("update Beats")
234
+
235
+
236
+
237
+ getBeatsbtn.click(getBeatsv2, inputs=audiofile, outputs=[beats_wave_plot, beattimes_table])
238
+ updateBeatsbtn.click(updateBeatsv2, inputs=[beattimes_table, audiofile], outputs=[beats_wave_plot])
239
+
240
+ gr.Examples(
241
+ examples=example_files,
242
+ inputs=audiofile,
243
+ fn=getBeatsv2,
244
+ cache_examples=False
245
+ )
246
+
247
+
248
+ with gr.Tab("Single Audio V1"):
249
+
250
  analyzebtn = gr.Button("analyze")
251
 
252
  results = gr.Markdown()
253
  spectogram_wave = gr.Plot()
254
  avg_beat_plot = gr.Plot()
 
255
 
256
+ analyzebtn.click(analyze_single, audiofile, [results, spectogram_wave, avg_beat_plot])
257
 
258
  gr.Examples(
259
  examples=example_files,
 
264
  )
265
 
266
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
 
268
 
269
  app.launch()
utils.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import librosa
2
+ import numpy as np
3
+ import plotly.graph_objects as go
4
+ from scipy.signal import find_peaks
5
+
6
+
7
+ # GENERAL HELPER FUNCTIONS
8
+ def getaudiodata(filepath)->tuple[int,np.ndarray]:
9
+
10
+ audiodata, sr = librosa.load(filepath, sr=None)
11
+
12
+ # Ensure audiodata is a numpy array
13
+ if not isinstance(audiodata, np.ndarray):
14
+ audiodata = np.array(audiodata)
15
+
16
+ # Check if audio is mono or stereo
17
+ if len(audiodata.shape) > 1:
18
+ # If stereo, convert to mono by averaging channels
19
+ audiodata = np.mean(audiodata, axis=1)
20
+
21
+ audiodata = np.astype(audiodata, np.float16)
22
+
23
+ return sr, audiodata
24
+
25
+ def getBeats(audiodata:np.ndarray, sr:int):
26
+ # Convert audio data to float32
27
+ audiodata = audiodata.astype(np.float32)
28
+
29
+ # Normalize the audio data
30
+ audiodata = audiodata / np.max(np.abs(audiodata))
31
+
32
+ # Set the threshold for peak detection (adjust this value as needed)
33
+ threshold = 0.5 # 50% of the maximum amplitude
34
+
35
+ # Find peaks above the threshold
36
+ peaks, _ = find_peaks(np.abs(audiodata), height=threshold, distance=int(sr * 0.3))
37
+
38
+ # Convert peak indices to times
39
+ peak_times = (peaks / sr)*2
40
+
41
+ # Calculate tempo (beats per minute)
42
+ if len(peak_times) > 1:
43
+ avg_interval = np.mean(np.diff(peak_times))
44
+ tempo = 60 / avg_interval
45
+ else:
46
+ tempo = 0
47
+
48
+ return [tempo], peak_times
49
+
50
+ def plotBeattimes(beattimes:np.ndarray, audiodata:np.ndarray, sr:int)->go.Figure:
51
+
52
+ # Time array for the full audio
53
+ time = (np.arange(0, len(audiodata)) / sr) * 2
54
+
55
+ # CREATE BEATTIMES PLOT
56
+ # Waveform plot
57
+ fig = go.Figure(
58
+ go.Scatter(x=time, y=audiodata, mode='lines', name='Waveform', line=dict(color='blue', width=1))
59
+ )
60
+ # Add beat markers
61
+ beat_amplitudes = np.interp(beattimes, time, audiodata)
62
+ fig.add_trace(
63
+ go.Scatter(x=beattimes, y=beat_amplitudes, mode='markers', name='Beats',
64
+ marker=dict(color='red', size=8, symbol='circle'))
65
+ )
66
+
67
+ fig.update_layout(
68
+ showlegend=False
69
+ )
70
+
71
+ return fig
72
+
73
+
74
+ def iterate_beat_segments(beat_times, sr, audio):
75
+ """
76
+ Iterate over audio segments between beats.
77
+
78
+ Parameters:
79
+ - beat_times: np.ndarray of beat times in seconds
80
+ - sr: Sample rate of the audio
81
+ - audio: np.ndarray of audio data
82
+
83
+ Yields:
84
+ - Tuple of (start_sample, end_sample, audio_segment)
85
+ """
86
+ # Convert beat times to sample indices
87
+ beat_samples = librosa.time_to_samples(beat_times, sr=sr)
88
+
89
+ # Add start and end points
90
+ beat_samples = np.concatenate(([0], beat_samples, [len(audio)]))
91
+
92
+ # Iterate over pairs of beat samples
93
+ for start, end in zip(beat_samples[:-1], beat_samples[1:]):
94
+ # Extract the audio segment
95
+ segment = audio[start:end]
96
+
97
+ segment_metrics = segment_analysis(segment, sr)
98
+
99
+
100
+
101
+ def segment_analysis(segment, sr):
102
+ """
103
+ Analyze an audio segment and compute various metrics.
104
+
105
+ Parameters:
106
+ - segment: np.ndarray of audio segment data
107
+ - sr: Sample rate of the audio
108
+
109
+ Returns:
110
+ - List of computed metrics
111
+ """
112
+ # Duration
113
+ duration = len(segment) / sr
114
+
115
+ # RMS Energy
116
+ rms_energy = np.sqrt(np.mean(segment**2))
117
+
118
+ # Frequencies
119
+ # We'll use the mean of the magnitudes of the Fourier transform
120
+ fft_magnitudes = np.abs(np.fft.rfft(segment))
121
+ mean_frequency = np.mean(fft_magnitudes)
122
+
123
+ # Attempt to detect S1 and S2
124
+ # This is a simplified approach and may not be accurate for all cases
125
+ peaks, _ = find_peaks(np.abs(segment), distance=int(0.2*sr)) # Assume at least 0.2s between peaks
126
+
127
+ if len(peaks) >= 2:
128
+ s1_index, s2_index = peaks[:2]
129
+ s1_to_s2_duration = (s2_index - s1_index) / sr
130
+ s2_to_s1_duration = (len(segment) - s2_index + peaks[0]) / sr if len(peaks) > 2 else None
131
+ else:
132
+ s1_to_s2_duration = None
133
+ s2_to_s1_duration = None
134
+
135
+ return [
136
+ rms_energy,
137
+ mean_frequency,
138
+ duration,
139
+ s1_to_s2_duration,
140
+ s2_to_s1_duration
141
+ ]