ab646 commited on
Commit
a06c4af
·
1 Parent(s): a2b0d57

Add DAM voice biomarker inference app

Browse files
Files changed (4) hide show
  1. README.md +8 -7
  2. app.py +110 -0
  3. packages.txt +1 -0
  4. requirements.txt +6 -0
README.md CHANGED
@@ -1,13 +1,14 @@
1
  ---
2
- title: Dam Inference
3
- emoji: 🏢
4
- colorFrom: pink
5
- colorTo: pink
6
  sdk: gradio
7
- sdk_version: 6.12.0
8
  app_file: app.py
9
  pinned: false
10
- short_description: Voice biomarker inference using KintsugiHealth/DAM
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
  ---
2
+ title: Loop Mind DAM Inference
3
+ emoji: 🧬
4
+ colorFrom: indigo
5
+ colorTo: purple
6
  sdk: gradio
7
+ sdk_version: 5.23.0
8
  app_file: app.py
9
  pinned: false
10
+ license: apache-2.0
11
  ---
12
 
13
+ Voice biomarker inference for Loop Mind using KintsugiHealth/DAM.
14
+ Returns depression/anxiety severity scores from acoustic voice features.
app.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Loop Mind — DAM Voice Biomarker Inference Space
3
+ ================================================
4
+ Wraps KintsugiHealth/DAM for voice biomarker analysis.
5
+ Accepts audio, returns depression/anxiety severity scores.
6
+
7
+ Deployed as a Hugging Face Space (Gradio).
8
+ Called by the Loop Mind /voice-biomarker Supabase Edge Function.
9
+ """
10
+
11
+ import sys
12
+ import os
13
+ import subprocess
14
+ import warnings
15
+ import json
16
+
17
+ import gradio as gr
18
+ import torch
19
+ import torchaudio
20
+
21
+ warnings.filterwarnings("ignore")
22
+
23
+ # Download the DAM model on first run
24
+ if not os.path.exists("dam"):
25
+ print("Downloading KintsugiHealth/DAM model (~1GB)...")
26
+ subprocess.run(["git", "clone", "https://huggingface.co/KintsugiHealth/dam"])
27
+
28
+ sys.path.append(os.path.abspath("dam"))
29
+
30
+ print("Loading DAM pipeline...")
31
+ try:
32
+ from pipeline import Pipeline
33
+ dam_pipeline = Pipeline()
34
+ print("DAM model loaded successfully.")
35
+ except Exception as e:
36
+ print(f"Failed to load DAM model: {e}")
37
+ dam_pipeline = None
38
+
39
+
40
+ DEP_LABELS = {0: "none", 1: "mild-moderate", 2: "severe"}
41
+ ANX_LABELS = {0: "none", 1: "mild", 2: "moderate", 3: "severe"}
42
+
43
+
44
+ def analyze_audio(audio_filepath: str) -> str:
45
+ """
46
+ Accepts an audio file path, runs DAM inference, returns JSON string
47
+ with depression/anxiety scores (both quantized and raw).
48
+ """
49
+ if audio_filepath is None:
50
+ return json.dumps({"error": "No audio provided"})
51
+
52
+ if dam_pipeline is None:
53
+ return json.dumps({"error": "Model not loaded"})
54
+
55
+ try:
56
+ # Pre-process: convert to mono if needed
57
+ waveform, sample_rate = torchaudio.load(audio_filepath)
58
+ if waveform.shape[0] > 1:
59
+ waveform = torch.mean(waveform, dim=0, keepdim=True)
60
+ audio_filepath = "temp_mono.wav"
61
+ torchaudio.save(audio_filepath, waveform, sample_rate)
62
+
63
+ # Run inference (both quantized and raw)
64
+ res_q = dam_pipeline.run_on_file(audio_filepath, quantize=True)
65
+ res_r = dam_pipeline.run_on_file(audio_filepath, quantize=False)
66
+
67
+ # Extract and normalize scores
68
+ dep_q = int(res_q.get("depression", 0).item() if hasattr(res_q.get("depression", 0), "item") else res_q.get("depression", 0))
69
+ anx_q = int(res_q.get("anxiety", 0).item() if hasattr(res_q.get("anxiety", 0), "item") else res_q.get("anxiety", 0))
70
+ dep_r = float(res_r.get("depression", 0.0).item() if hasattr(res_r.get("depression", 0.0), "item") else res_r.get("depression", 0.0))
71
+ anx_r = float(res_r.get("anxiety", 0.0).item() if hasattr(res_r.get("anxiety", 0.0), "item") else res_r.get("anxiety", 0.0))
72
+
73
+ result = {
74
+ "depression": dep_q,
75
+ "depression_label": DEP_LABELS.get(dep_q, "unknown"),
76
+ "anxiety": anx_q,
77
+ "anxiety_label": ANX_LABELS.get(anx_q, "unknown"),
78
+ "raw_depression": round(dep_r, 4),
79
+ "raw_anxiety": round(anx_r, 4),
80
+ "model": "KintsugiHealth/dam",
81
+ }
82
+
83
+ return json.dumps(result)
84
+
85
+ except Exception as e:
86
+ return json.dumps({"error": str(e)})
87
+
88
+
89
+ # Clean up temp file after processing
90
+ def analyze_and_cleanup(audio_filepath: str) -> str:
91
+ result = analyze_audio(audio_filepath)
92
+ # Delete temp mono file if created
93
+ if os.path.exists("temp_mono.wav"):
94
+ try:
95
+ os.unlink("temp_mono.wav")
96
+ except OSError:
97
+ pass
98
+ return result
99
+
100
+
101
+ demo = gr.Interface(
102
+ fn=analyze_and_cleanup,
103
+ inputs=gr.Audio(type="filepath", label="Upload audio or record (30+ seconds recommended)"),
104
+ outputs=gr.Textbox(label="Analysis Result (JSON)", lines=10),
105
+ title="Loop Mind — Voice Biomarker Analysis",
106
+ description="Powered by KintsugiHealth/DAM. Returns depression and anxiety severity scores from voice acoustic features. For research and wellness tracking only — not a clinical diagnosis.",
107
+ theme="soft",
108
+ )
109
+
110
+ demo.launch()
packages.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ ffmpeg
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ torch
2
+ torchaudio
3
+ transformers
4
+ soundfile
5
+ peft
6
+ torchcodec