justus-tobias commited on
Commit
5fb3911
·
1 Parent(s): 376f45a

added analysis

Browse files
Files changed (3) hide show
  1. .DS_Store +0 -0
  2. app.py +214 -15
  3. utils.py +90 -32
.DS_Store ADDED
Binary file (6.15 kB). View file
 
app.py CHANGED
@@ -12,7 +12,8 @@ import random
12
  import mdpd
13
  import os
14
 
15
- from utils import getaudiodata, getBeats, plotBeattimes, find_s1s2
 
16
 
17
  example_dir = "Examples"
18
  example_files = [os.path.join(example_dir, f) for f in os.listdir(example_dir) if f.endswith(('.wav', '.mp3', '.ogg'))]
@@ -20,6 +21,10 @@ all_pairs = list(itertools.combinations(example_files, 2))
20
  random.shuffle(all_pairs)
21
  example_pairs = [list(pair) for pair in all_pairs[:25]]
22
 
 
 
 
 
23
  def getHRV(beattimes: np.ndarray) -> np.ndarray:
24
  # Calculate instantaneous heart rate
25
  instantaneous_hr = 60 * np.diff(beattimes)
@@ -67,7 +72,7 @@ def create_average_heartbeat(audiodata, sr):
67
  # HELPER FUNCTIONS FOR SINGLE AUDIO ANALYSIS
68
  def plotCombined(audiodata, sr, filename):
69
  # Get beat times
70
- tempo, beattimes = getBeats(audiodata, sr)
71
 
72
  # Create subplots
73
  fig = make_subplots(rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.1,
@@ -135,7 +140,7 @@ def analyze_single(audio:gr.Audio):
135
  filename = filepath.split("/")[-1]
136
 
137
 
138
- sr, audiodata = getaudiodata(filepath)
139
 
140
 
141
  # Now you have:
@@ -154,7 +159,7 @@ def analyze_single(audio:gr.Audio):
154
  rms = librosa.feature.rms(y=audiodata)[0]
155
  # print(f"Mean RMS Energy: {np.mean(rms):.4f}")
156
 
157
- tempo, beattimes = getBeats(audiodata, sr)
158
  spectogram_wave = plotCombined(audiodata, sr, filename)
159
  #beats_histogram = plotbeatscatter(tempo[0], beattimes)
160
 
@@ -180,19 +185,207 @@ def analyze_single(audio:gr.Audio):
180
  - Mean Beat Duration: {np.mean(np.diff(beattimes)):.4f}
181
  """
182
  return results, spectogram_wave, avg_beat_plot
 
183
  #-----------------------------------------------
 
184
  #-----------------------------------------------
185
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  # HELPER FUNCTIONS FOR SINGLE AUDIO ANALYSIS V2
187
 
188
  def getBeatsv2(audio:gr.Audio):
189
 
190
- sr, audiodata = getaudiodata(audio)
191
- _, beattimes, audiodata = getBeats(audiodata, sr)
192
 
193
  beattimes_table = pd.DataFrame(data={"Beattimes":beattimes})
194
 
195
- feature_array = find_s1s2(beattimes_table)
196
 
197
  featuredf = pd.DataFrame(
198
  data=feature_array,
@@ -200,7 +393,7 @@ def getBeatsv2(audio:gr.Audio):
200
  "Beattimes",
201
  "S1 to S2",
202
  "S2 to S1",
203
- "Label (S1=0/S2=1)"]
204
  )
205
 
206
  # Create boolean masks for each label
@@ -211,7 +404,7 @@ def getBeatsv2(audio:gr.Audio):
211
  times_label_one = feature_array[mask_ones, 0]
212
  times_label_zero = feature_array[mask_zeros, 0]
213
 
214
- fig = plotBeattimes(times_label_one, audiodata, sr, times_label_zero)
215
 
216
 
217
  featuredf = featuredf.drop(columns=["S1 to S2", "S2 to S1"])
@@ -221,7 +414,7 @@ def getBeatsv2(audio:gr.Audio):
221
 
222
  def updateBeatsv2(audio:gr.Audio, uploadeddf:gr.File=None)-> go.Figure:
223
 
224
- sr, audiodata = getaudiodata(audio)
225
 
226
 
227
  if uploadeddf != None:
@@ -233,10 +426,10 @@ def updateBeatsv2(audio:gr.Audio, uploadeddf:gr.File=None)-> go.Figure:
233
  else:
234
  raise FileNotFoundError("No file uploaded")
235
 
236
- s1_times = beattimes_table[beattimes_table["Label (S1=0/S2=1)"] == 0]["Beattimes"].to_numpy()
237
- s2_times = beattimes_table[beattimes_table["Label (S1=0/S2=1)"] == 1]["Beattimes"].to_numpy()
238
 
239
- fig = plotBeattimes(s1_times, audiodata, sr, s2_times)
240
 
241
  return fig, beattimes_table.to_markdown()
242
 
@@ -244,7 +437,7 @@ def download_df (beattimes_table: str):
244
 
245
  df = mdpd.from_md(beattimes_table)
246
  df['Beattimes'] = df['Beattimes'].astype(float)
247
- df['Label (S1=0/S2=1)'] = df['Label (S1=0/S2=1)'].astype(int)
248
 
249
 
250
  temp_dir = tempfile.gettempdir()
@@ -253,7 +446,7 @@ def download_df (beattimes_table: str):
253
 
254
  df.to_csv(
255
  index=False,
256
- columns=["Beattimes", "Label (S1=0/S2=1)"],
257
  path_or_buf=temp_path,
258
  sep=";",
259
  decimal=",",
@@ -316,5 +509,11 @@ with gr.Blocks() as app:
316
 
317
  gr.Markdown("🚨 Please make sure to first run the 'Preprocessing'")
318
 
 
 
 
 
 
 
319
 
320
  app.launch()
 
12
  import mdpd
13
  import os
14
 
15
+ #from utils import getaudiodata, getBeats, plotBeattimes, find_s1s2
16
+ import utils as u
17
 
18
  example_dir = "Examples"
19
  example_files = [os.path.join(example_dir, f) for f in os.listdir(example_dir) if f.endswith(('.wav', '.mp3', '.ogg'))]
 
21
  random.shuffle(all_pairs)
22
  example_pairs = [list(pair) for pair in all_pairs[:25]]
23
 
24
+
25
+ #-----------------------------------------------
26
+ # PROCESSING FUNCTIONS
27
+ #-----------------------------------------------
28
  def getHRV(beattimes: np.ndarray) -> np.ndarray:
29
  # Calculate instantaneous heart rate
30
  instantaneous_hr = 60 * np.diff(beattimes)
 
72
  # HELPER FUNCTIONS FOR SINGLE AUDIO ANALYSIS
73
  def plotCombined(audiodata, sr, filename):
74
  # Get beat times
75
+ tempo, beattimes = u.getBeats(audiodata, sr)
76
 
77
  # Create subplots
78
  fig = make_subplots(rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.1,
 
140
  filename = filepath.split("/")[-1]
141
 
142
 
143
+ sr, audiodata = u.getaudiodata(filepath)
144
 
145
 
146
  # Now you have:
 
159
  rms = librosa.feature.rms(y=audiodata)[0]
160
  # print(f"Mean RMS Energy: {np.mean(rms):.4f}")
161
 
162
+ tempo, beattimes = u.getBeats(audiodata, sr)
163
  spectogram_wave = plotCombined(audiodata, sr, filename)
164
  #beats_histogram = plotbeatscatter(tempo[0], beattimes)
165
 
 
185
  - Mean Beat Duration: {np.mean(np.diff(beattimes)):.4f}
186
  """
187
  return results, spectogram_wave, avg_beat_plot
188
+
189
  #-----------------------------------------------
190
+ # ANALYSIS FUNCTIONS
191
  #-----------------------------------------------
192
 
193
+ # - [ ] Berechnungen Pro Segement:
194
+ # - [ ] RMS Energy
195
+ # - [ ] Frequenzen
196
+ # - [ ] Dauer
197
+ # - [ ] S2 - wenn möglich
198
+ # - [ ] Dauer S1 bis S2 (S1)
199
+ # - [ ] Dauer S2 bis S1 (S2)
200
+ # - [ ] Visualisierungen pro Datei:
201
+ # - [ ] Waveform
202
+ # - [ ] Spectogram
203
+ # - [ ] HRV
204
+ # - [ ] Avg. Heartbeat Waveform (fixe y-achse)
205
+ # - [ ] Alle Segmente als Waveform übereinanderlegen (fixe y-achse +-0.05)
206
+ # - [ ] Daten Exportierbar machen
207
+ # - [ ] Einheiten für (RMS Energy, Energy)
208
+ # - [ ] wichtige Einheiten (Energy, RMS Energy, Sample Rate, Audio length, Beats, Beats durations)
209
+
210
+
211
+ def get_visualizations(beattimes_table: str, cleanedaudio: gr.Audio):
212
+
213
+ df = mdpd.from_md(beattimes_table)
214
+ df['Beattimes'] = df['Beattimes'].astype(float)
215
+ df['Label (S1=1/S2=0)'] = df['Label (S1=1/S2=0)'].astype(int)
216
+
217
+ sr, audiodata = cleanedaudio
218
+
219
+ segment_metrics = u.compute_segment_metrics(df, sr, audiodata)
220
+
221
+
222
+
223
+ # Normalize audio data from int16 to float in range [-1, 1]
224
+ audiodata = audiodata.astype(np.float32) / 32768.0
225
+
226
+ # Create figure with secondary y-axes
227
+ fig = make_subplots(
228
+ rows=5, cols=1,
229
+ subplot_titles=('Waveform', 'Spectrogram', 'Heart Rate Variability',
230
+ 'Average Heartbeat Waveform', 'Overlaid Segments'),
231
+ vertical_spacing=0.1,
232
+ row_heights=[0.2, 0.2, 0.2, 0.2, 0.2]
233
+ )
234
+
235
+ # 1. Waveform
236
+ time = np.arange(len(audiodata)) / sr
237
+ fig.add_trace(
238
+ go.Scatter(x=time, y=audiodata, name='Waveform', line=dict(color='blue', width=1)),
239
+ row=1, col=1
240
+ )
241
+
242
+
243
+ # 2. Spectrogram
244
+ D = librosa.stft(audiodata)
245
+ frequencies = librosa.fft_frequencies(sr=sr) # Get frequency values for y-axis
246
+ S_db = librosa.amplitude_to_db(np.abs(D), ref=np.max)
247
+ times = librosa.times_like(S_db, sr=sr) # Get time values for x-axis
248
+
249
+ # Find index corresponding to 200 Hz
250
+ freq_mask = frequencies <= 1000
251
+ S_db_cropped = S_db[freq_mask]
252
+ frequencies_cropped = frequencies[freq_mask]
253
+
254
+ fig.add_trace(
255
+ go.Heatmap(
256
+ z=S_db_cropped,
257
+ x=times,
258
+ y=frequencies_cropped, # Add frequencies to y-axis
259
+ colorscale='Viridis',
260
+ name='Spectrogram'
261
+ ),
262
+ row=2, col=1
263
+ )
264
+
265
+ # 3. HRV (Heart Rate Variability)
266
+ s1_durations = []
267
+ s2_durations = []
268
+ for segment in segment_metrics:
269
+ if segment['s1_to_s2_duration']: # Check if list is not empty
270
+ s1_durations.extend(segment['s1_to_s2_duration'])
271
+ if segment['s2_to_s1_duration']: # Check if list is not empty
272
+ s2_durations.extend(segment['s2_to_s1_duration'])
273
+
274
+ t_interp, sdnn_interp, rmssd_interp, hr_interp = u.compute_and_plot_hrv(s1_durations, s2_durations, sr)
275
+
276
+ # Add each HRV metric as a separate trace
277
+ fig.add_trace(
278
+ go.Scatter(
279
+ x=t_interp,
280
+ y=sdnn_interp,
281
+ name='SDNN',
282
+ line=dict(color='red', width=1)
283
+ ),
284
+ row=3, col=1
285
+ )
286
+
287
+ fig.add_trace(
288
+ go.Scatter(
289
+ x=t_interp,
290
+ y=rmssd_interp,
291
+ name='RMSSD',
292
+ line=dict(color='blue', width=1)
293
+ ),
294
+ row=3, col=1
295
+ )
296
+
297
+ fig.add_trace(
298
+ go.Scatter(
299
+ x=t_interp,
300
+ y=hr_interp,
301
+ name='Heart Rate',
302
+ line=dict(color='green', width=1),
303
+ yaxis='y2' # Use secondary y-axis for heart rate
304
+ ),
305
+ row=3, col=1
306
+ )
307
+
308
+
309
+ # 4. Average Heartbeat Waveform
310
+ max_len = max(len(metric['segment']) for metric in segment_metrics)
311
+ aligned_segments = []
312
+
313
+ for metric in segment_metrics:
314
+ segment = metric['segment']
315
+ segment = segment.astype(np.float32) / 32768.0
316
+ padded = np.pad(segment, (0, max_len - len(segment)))
317
+ aligned_segments.append(padded)
318
+
319
+ avg_waveform = np.mean(aligned_segments, axis=0)
320
+ time_avg = np.arange(len(avg_waveform)) / sr
321
+
322
+ fig.add_trace(
323
+ go.Scatter(x=time_avg, y=avg_waveform, name='Average Heartbeat',
324
+ line=dict(color='green', width=1)),
325
+ row=4, col=1
326
+ )
327
+
328
+ # 5. Overlaid Segments
329
+ colors = [
330
+ '#8dd3c7', '#ffffb3', '#bebada', '#fb8072', '#80b1d3',
331
+ '#fdb462', '#b3de69', '#fccde5', '#d9d9d9', '#bc80bd'
332
+ ]
333
+
334
+ # Then in the loop for overlaid segments:
335
+ for i, metric in enumerate(segment_metrics):
336
+ segment = metric['segment']
337
+ segment = segment.astype(np.float32) / 32768.0
338
+ time_segment = np.arange(len(segment)) / sr
339
+
340
+ fig.add_trace(
341
+ go.Scatter(
342
+ x=time_segment,
343
+ y=segment,
344
+ name=f'Segment {i+1}',
345
+ opacity=0.3,
346
+ line=dict(color=colors[i % len(colors)], width=1)
347
+ ),
348
+ row=5, col=1
349
+ )
350
+
351
+ # Update layout
352
+ fig.update_layout(
353
+ height=1500,
354
+ showlegend=False,
355
+ title_text="",
356
+ plot_bgcolor='white',
357
+ paper_bgcolor='white'
358
+ )
359
+
360
+
361
+ # Update y-axes for fixed scales where needed
362
+ # fig.update_yaxes(range=[-0.05, 0.05], row=5, col=1) # Fixed y-axis for overlaid segments
363
+ fig.update_yaxes(title_text="Amplitude", row=1, col=1, gridcolor='lightgray')
364
+ fig.update_yaxes(title_text="Frequency (Hz)", row=2, col=1)
365
+ fig.update_yaxes(title_text="Duration (s)", row=3, col=1, gridcolor='lightgray')
366
+ fig.update_yaxes(title_text="Amplitude", row=4, col=1, gridcolor='lightgray')
367
+ fig.update_yaxes(title_text="Amplitude", row=5, col=1, gridcolor='lightgray')
368
+
369
+ # Update x-axes
370
+ fig.update_xaxes(title_text="Time (s)", row=1, col=1, gridcolor='lightgray')
371
+ fig.update_xaxes(title_text="Time (s)", row=2, col=1)
372
+ fig.update_xaxes(title_text="Time (s)", row=3, col=1, gridcolor='lightgray')
373
+ fig.update_xaxes(title_text="Time (s)", row=4, col=1, gridcolor='lightgray')
374
+ fig.update_xaxes(title_text="Time (s)", row=5, col=1, gridcolor='lightgray')
375
+
376
+ return fig
377
+ #-----------------------------------------------
378
+ #-----------------------------------------------
379
  # HELPER FUNCTIONS FOR SINGLE AUDIO ANALYSIS V2
380
 
381
  def getBeatsv2(audio:gr.Audio):
382
 
383
+ sr, audiodata = u.getaudiodata(audio)
384
+ _, beattimes, audiodata = u.getBeats(audiodata, sr)
385
 
386
  beattimes_table = pd.DataFrame(data={"Beattimes":beattimes})
387
 
388
+ feature_array = u.find_s1s2(beattimes_table)
389
 
390
  featuredf = pd.DataFrame(
391
  data=feature_array,
 
393
  "Beattimes",
394
  "S1 to S2",
395
  "S2 to S1",
396
+ "Label (S1=1/S2=0)"]
397
  )
398
 
399
  # Create boolean masks for each label
 
404
  times_label_one = feature_array[mask_ones, 0]
405
  times_label_zero = feature_array[mask_zeros, 0]
406
 
407
+ fig = u.plotBeattimes(times_label_one, audiodata, sr, times_label_zero)
408
 
409
 
410
  featuredf = featuredf.drop(columns=["S1 to S2", "S2 to S1"])
 
414
 
415
  def updateBeatsv2(audio:gr.Audio, uploadeddf:gr.File=None)-> go.Figure:
416
 
417
+ sr, audiodata = u.getaudiodata(audio)
418
 
419
 
420
  if uploadeddf != None:
 
426
  else:
427
  raise FileNotFoundError("No file uploaded")
428
 
429
+ s1_times = beattimes_table[beattimes_table["Label (S1=1/S2=0)"] == 0]["Beattimes"].to_numpy()
430
+ s2_times = beattimes_table[beattimes_table["Label (S1=1/S2=0)"] == 1]["Beattimes"].to_numpy()
431
 
432
+ fig = u.plotBeattimes(s1_times, audiodata, sr, s2_times)
433
 
434
  return fig, beattimes_table.to_markdown()
435
 
 
437
 
438
  df = mdpd.from_md(beattimes_table)
439
  df['Beattimes'] = df['Beattimes'].astype(float)
440
+ df['Label (S1=1/S2=0)'] = df['Label (S1=1/S2=0)'].astype(int)
441
 
442
 
443
  temp_dir = tempfile.gettempdir()
 
446
 
447
  df.to_csv(
448
  index=False,
449
+ columns=["Beattimes", "Label (S1=1/S2=0)"],
450
  path_or_buf=temp_path,
451
  sep=";",
452
  decimal=",",
 
509
 
510
  gr.Markdown("🚨 Please make sure to first run the 'Preprocessing'")
511
 
512
+ analyzebtn = gr.Button("Analyze Audio")
513
+
514
+ plot = gr.Plot()
515
+
516
+ analyzebtn.click(get_visualizations, inputs=[beattimes_table, cleanedaudio], outputs=[plot])
517
+
518
 
519
  app.launch()
utils.py CHANGED
@@ -7,6 +7,8 @@ from sklearn.cluster import KMeans
7
  from sklearn.preprocessing import StandardScaler
8
  import pywt
9
  import pandas as pd
 
 
10
 
11
 
12
  # GENERAL HELPER FUNCTIONS
@@ -71,7 +73,7 @@ def denoise_audio(audiodata: np.ndarray, sr: int) -> tuple[np.ndarray, int]:
71
 
72
  # Ensure consistent length
73
  if len(denoised) != len(audio):
74
- denoised = librosa.util.fix_length(denoised, len(audio))
75
 
76
  # 3. Improved Spectral Subtraction
77
  def spectral_subtract(sig):
@@ -442,30 +444,47 @@ def plotBeattimes(beattimes: np.ndarray,
442
 
443
  def iterate_beat_segments(beat_times, sr, audio):
444
  """
445
- Iterate over audio segments between beats.
446
 
447
  Parameters:
448
- - beat_times: np.ndarray of beat times in seconds
449
  - sr: Sample rate of the audio
450
  - audio: np.ndarray of audio data
451
 
452
  Yields:
453
- - Tuple of (start_sample, end_sample, audio_segment)
454
  """
455
- # Convert beat times to sample indices
456
- beat_samples = librosa.time_to_samples(beat_times, sr=sr)
457
 
458
- # Add start and end points
459
- beat_samples = np.concatenate(([0], beat_samples, [len(audio)]))
 
 
460
 
461
- # Iterate over pairs of beat samples
462
- for start, end in zip(beat_samples[:-1], beat_samples[1:]):
463
- # Extract the audio segment
464
- segment = audio[start:end]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
465
 
466
- segment_metrics = segment_analysis(segment, sr)
467
 
468
- def segment_analysis(segment, sr):
469
  """
470
  Analyze an audio segment and compute various metrics.
471
 
@@ -487,25 +506,28 @@ def segment_analysis(segment, sr):
487
  fft_magnitudes = np.abs(np.fft.rfft(segment))
488
  mean_frequency = np.mean(fft_magnitudes)
489
 
490
- # Attempt to detect S1 and S2
491
- # This is a simplified approach and may not be accurate for all cases
492
- peaks, _ = find_peaks(np.abs(segment), distance=int(0.2*sr)) # Assume at least 0.2s between peaks
 
 
 
 
 
 
 
 
 
 
493
 
494
- if len(peaks) >= 2:
495
- s1_index, s2_index = peaks[:2]
496
- s1_to_s2_duration = (s2_index - s1_index) / sr
497
- s2_to_s1_duration = (len(segment) - s2_index + peaks[0]) / sr if len(peaks) > 2 else None
498
- else:
499
- s1_to_s2_duration = None
500
- s2_to_s1_duration = None
501
-
502
- return [
503
- rms_energy,
504
- mean_frequency,
505
- duration,
506
- s1_to_s2_duration,
507
- s2_to_s1_duration
508
- ]
509
 
510
  def find_s1s2(df:pd.DataFrame):
511
 
@@ -543,3 +565,39 @@ def find_s1s2(df:pd.DataFrame):
543
 
544
  return feature_array
545
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  from sklearn.preprocessing import StandardScaler
8
  import pywt
9
  import pandas as pd
10
+ from scipy.interpolate import interp1d
11
+
12
 
13
 
14
  # GENERAL HELPER FUNCTIONS
 
73
 
74
  # Ensure consistent length
75
  if len(denoised) != len(audio):
76
+ denoised = librosa.util.fix_length(denoised, size=len(audio))
77
 
78
  # 3. Improved Spectral Subtraction
79
  def spectral_subtract(sig):
 
444
 
445
  def iterate_beat_segments(beat_times, sr, audio):
446
  """
447
+ Iterate over audio segments between beats marked with label 1.
448
 
449
  Parameters:
450
+ - beat_times: df of beattimes and labels as DataFrame
451
  - sr: Sample rate of the audio
452
  - audio: np.ndarray of audio data
453
 
454
  Yields:
455
+ - List of segment metrics with associated beat information
456
  """
 
 
457
 
458
+ # Get indices where label is 1
459
+ label_ones = beat_times[beat_times['Label (S1=1/S2=0)'] == 1].index.tolist()
460
+
461
+ segment_metrics = []
462
 
463
+ # Iterate through pairs of label 1 indices
464
+ for i in range(len(label_ones) - 1):
465
+ start_idx = label_ones[i]
466
+ end_idx = label_ones[i + 1]
467
+
468
+ # Get all beats between two label 1 beats (inclusive)
469
+ segment_beats = beat_times.iloc[start_idx:end_idx + 1]
470
+
471
+ # Create list of tuples (label, beattime)
472
+ beat_info = list(zip(segment_beats['Label (S1=1/S2=0)'],
473
+ segment_beats['Beattimes']))
474
+
475
+ # Get start and end samples
476
+ start_sample = librosa.time_to_samples(segment_beats.iloc[0]['Beattimes'], sr=sr)
477
+ end_sample = librosa.time_to_samples(segment_beats.iloc[-1]['Beattimes'], sr=sr)
478
+
479
+ # Extract audio segment
480
+ segment = audio[start_sample:end_sample]
481
+
482
+ # Analyze segment with beat information
483
+ segment_metrics.append(segment_analysis(segment, sr, beat_info))
484
 
485
+ return segment_metrics
486
 
487
+ def segment_analysis(segment, sr, s1s2:list):
488
  """
489
  Analyze an audio segment and compute various metrics.
490
 
 
506
  fft_magnitudes = np.abs(np.fft.rfft(segment))
507
  mean_frequency = np.mean(fft_magnitudes)
508
 
509
+ s1_to_s2_duration = []
510
+ s2_to_s1_duration = []
511
+
512
+ prev = s1s2[0]
513
+ for i in range(1, len(s1s2)):
514
+ if prev[0] == 0 and s1s2[i][0] == 1:
515
+ s2_to_s1_duration.append(s1s2[i][1] - prev[1])
516
+ elif prev[0] == 1 and s1s2[i][0] == 0:
517
+ s1_to_s2_duration.append(s1s2[i][1] - prev[1])
518
+ prev = s1s2[i]
519
+
520
+
521
+
522
 
523
+ return {
524
+ "rms_energy": rms_energy,
525
+ "mean_frequency": mean_frequency,
526
+ "duration": duration,
527
+ "s1_to_s2_duration": s1_to_s2_duration,
528
+ "s2_to_s1_duration": s2_to_s1_duration,
529
+ "segment": segment
530
+ }
 
 
 
 
 
 
 
531
 
532
  def find_s1s2(df:pd.DataFrame):
533
 
 
565
 
566
  return feature_array
567
 
568
+ # ANALYZE
569
+
570
+ def compute_segment_metrics(beattimes: pd.DataFrame, sr: int, audio: np.ndarray):
571
+
572
+ beattimes[beattimes['Label (S1=1/S2=0)'] == 1]
573
+
574
+ segment_metrics = iterate_beat_segments(beattimes, sr, audio)
575
+
576
+ return segment_metrics
577
+
578
+ def compute_and_plot_hrv(s1_to_s2, s2_to_s1, sampling_rate=1000):
579
+ # Combine s1_to_s2 and s2_to_s1 to get RR intervals
580
+ rr_intervals = np.array(s1_to_s2) + np.array(s2_to_s1)
581
+
582
+ # Calculate cumulative time for each heartbeat
583
+ time = np.cumsum(rr_intervals) / sampling_rate # Convert to seconds
584
+
585
+ # Calculate instantaneous heart rate
586
+ hr = 60 / rr_intervals # beats per minute
587
+
588
+ # Compute rolling window HRV metrics
589
+ window_size = 30 # 30-second window
590
+ sdnn = np.array([np.std(rr_intervals[max(0, i-window_size):i+1])
591
+ for i in range(len(rr_intervals))])
592
+ rmssd = np.array([np.sqrt(np.mean(np.diff(rr_intervals[max(0, i-window_size):i+1])**2))
593
+ for i in range(len(rr_intervals))])
594
+
595
+ # Create evenly spaced time array for plotting
596
+ t_interp = np.linspace(time.min(), time.max(), num=1000)
597
+
598
+ # Interpolate HRV metrics for smooth plotting
599
+ sdnn_interp = interp1d(time, sdnn, kind='cubic')(t_interp)
600
+ rmssd_interp = interp1d(time, rmssd, kind='cubic')(t_interp)
601
+ hr_interp = interp1d(time, hr, kind='cubic')(t_interp)
602
+
603
+ return t_interp, sdnn_interp, rmssd_interp, hr_interp