notUbaid commited on
Commit
f67144d
·
verified ·
1 Parent(s): 9e475da

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +123 -86
app.py CHANGED
@@ -14,8 +14,10 @@ except ImportError:
14
  HAS_SPACES = False
15
 
16
  import gc
 
17
  import os
18
  import time
 
19
  from pathlib import Path
20
  from typing import Optional, Dict, Any, List, Tuple
21
 
@@ -55,112 +57,147 @@ SENTENCE_PRESETS = {
55
  }
56
 
57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  def _diagnose_speech_core(
59
  audio_path: Optional[str],
60
  target_phrase: str,
61
  healthy_baseline_path: Optional[str] = None,
62
- ) -> Tuple[str, str, str, str, str, str]:
63
  """Run full diagnostic pipeline and return formatted clinical report for Gradio."""
 
 
64
  if not audio_path:
65
  return (
66
- "Please record speech or upload an audio file to evaluate.",
 
 
67
  "N/A",
68
- "0 / 100",
69
- "0.0%",
70
  "",
71
- "{}",
 
72
  )
73
 
74
- if not target_phrase.strip():
75
  return (
76
- "Please enter or select an expected Target Phrase.",
 
 
77
  "N/A",
78
- "0 / 100",
79
- "0.0%",
80
  "",
81
- "{}",
 
82
  )
83
 
84
- engine = get_engine()
 
85
 
86
- # Execute Diagnostic Engine
87
- diag_res = engine.diagnose_audio(
88
- audio_input=audio_path,
89
- target_phrase=target_phrase,
90
- normal_calibration_audio=healthy_baseline_path,
91
- )
 
 
 
 
 
 
 
 
 
 
 
92
 
93
- if diag_res["is_silent"] or diag_res["decision"].get("is_silent"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  return (
95
- "No Speech Detected: The audio is silent or below acoustic energy thresholds. Please speak clearly.",
96
- "SILENT",
97
  "0 / 100",
98
  "0.0%",
99
  "",
100
- str(diag_res),
 
101
  )
102
 
103
- result = diag_res["decision"]
104
- pron = diag_res["pronunciation"]
105
- flaws = diag_res["flaws"]
106
- artic = diag_res["articulation"]
107
- p_stut = float(diag_res["stutter_probs"][1]) if (diag_res["stutter_probs"] and len(diag_res["stutter_probs"]) > 1) else 0.0
108
-
109
- overall_bucket = result["buckets"]["overall"].upper()
110
- fluency_score = f"{int(result.get('fluency_100', 100))} / 100"
111
- pron_acc = f"{max(0.0, min(100.0, (1.0 - pron.get('wer', 0.0)) * 100.0)):.1f}%"
112
-
113
- # Build Word Alignment Chips HTML
114
- alignment = pron.get("alignment", [])
115
- chips_html = "<div style='display:flex; flex-wrap:wrap; gap:8px; padding:12px; background:rgba(15,23,42,0.6); border-radius:8px; margin:10px 0;'>"
116
- for item in alignment:
117
- status = item["status"]
118
- exp = item["expected"]
119
- spk = item["spoken"]
120
- if status == "correct":
121
- chips_html += f"<span style='padding:6px 12px; background:rgba(16,185,129,0.15); color:#34D399; border:1px solid rgba(16,185,129,0.3); border-radius:6px; font-weight:600;'>[MATCH] {exp}</span>"
122
- elif status == "substitution":
123
- chips_html += f"<span style='padding:6px 12px; background:rgba(239,68,68,0.15); color:#F87171; border:1px solid rgba(239,68,68,0.3); border-radius:6px; font-weight:600;'>[DIFF] {exp} (heard: \"{spk}\")</span>"
124
- elif status == "omission":
125
- chips_html += f"<span style='padding:6px 12px; background:rgba(245,158,11,0.15); color:#FBBF24; border:1px solid rgba(245,158,11,0.3); border-radius:6px; font-weight:600;'>[UNSPOKEN] {exp}</span>"
126
- elif status == "insertion":
127
- chips_html += f"<span style='padding:6px 12px; background:rgba(168,85,247,0.15); color:#C084FC; border:1px solid rgba(168,85,247,0.3); border-radius:6px; font-weight:600;'>[EXTRA] {spk}</span>"
128
- chips_html += "</div>"
129
-
130
- # Build Flaw Report Summary Markdown
131
- flaws_md = "### Specific Speech Pathology Findings:\n\n"
132
- if flaws["has_r_flaw"]:
133
- for r_err in flaws["r_sound_issues"]:
134
- flaws_md += f"- **Rhotacism Flaw**: {r_err['message']}\n"
135
- else:
136
- flaws_md += "- **'R' Sound Articulation**: Accurate (No R->W/L substitution detected).\n"
137
-
138
- if flaws["has_s_flaw"]:
139
- for s_err in flaws["s_sound_issues"]:
140
- flaws_md += f"- **Sigmatism Flaw**: {s_err['message']}\n"
141
- else:
142
- flaws_md += "- **'S' Sound Articulation**: Accurate (No sibilant lisp detected).\n"
143
-
144
- if p_stut >= 0.78:
145
- flaws_md += f"- **Disfluency Detected**: Elevated probability of repetition/block ({p_stut*100:.1f}%)\n"
146
- elif p_stut >= 0.60:
147
- flaws_md += f"- **Mild Hesitation**: Minor syllable repetition observed ({p_stut*100:.1f}%)\n"
148
- else:
149
- flaws_md += "- **Fluency Flow**: Continuous cadence (No disfluent events detected).\n"
150
-
151
- flaws_md += f"- **Voice Quality**: Pitch F0={artic.get('pitch_f0_mean_hz',0):.1f}Hz, HNR={artic.get('hnr_db',0):.1f}dB, Jitter={artic.get('jitter',0)*100:.2f}%\n"
152
-
153
- heard_summary = f"**Decoded Transcription**: *\"{pron.get('asr_hypothesis','')}\"*\n\n**Inference Latency**: `{diag_res['latency_ms']} ms`"
154
-
155
- return (
156
- heard_summary,
157
- overall_bucket,
158
- fluency_score,
159
- pron_acc,
160
- chips_html + "\n\n" + flaws_md,
161
- str(diag_res),
162
- )
163
-
164
 
165
  # Apply ZeroGPU acceleration decorator if running on Hugging Face ZeroGPU
166
  if HAS_SPACES:
@@ -175,8 +212,8 @@ else:
175
  # Construct Gradio Modern Interface
176
  with gr.Blocks(title="Anvaya | Speech Pathology Diagnostics") as demo:
177
  gr.Markdown("""
178
- # ANVAYA · Clinical Speech Pathology & Articulation Diagnostics
179
- ### Multi-Modal Diagnostics: Neural Disfluency · Rhotacism ('r') · Sigmatism ('s' Lisp) · Praat Vocal Phonation
180
  """)
181
 
182
  with gr.Row():
@@ -243,6 +280,7 @@ with gr.Blocks(title="Anvaya | Speech Pathology Diagnostics") as demo:
243
 
244
  summary_box = gr.Markdown("### Clinical Assessment Summary\n*Results will appear here after analysis.*")
245
  alignment_html = gr.HTML(label="Word-Level Alignment")
 
246
 
247
  with gr.Accordion("Auditable Telemetry & Acoustic Evidence Trace", open=False):
248
  raw_json = gr.JSON()
@@ -250,9 +288,8 @@ with gr.Blocks(title="Anvaya | Speech Pathology Diagnostics") as demo:
250
  diagnose_btn.click(
251
  fn=diagnose_speech_hf,
252
  inputs=[audio_input, target_text, healthy_baseline],
253
- outputs=[summary_box, kpi_strat, kpi_fluency, kpi_acc, alignment_html, raw_json],
254
  )
255
 
256
  if __name__ == "__main__":
257
  demo.launch()
258
-
 
14
  HAS_SPACES = False
15
 
16
  import gc
17
+ import json
18
  import os
19
  import time
20
+ import traceback
21
  from pathlib import Path
22
  from typing import Optional, Dict, Any, List, Tuple
23
 
 
57
  }
58
 
59
 
60
+ def _to_json_safe(obj: Any) -> Any:
61
+ """Recursively convert numpy/torch structures to JSON-serializable primitives."""
62
+ if isinstance(obj, dict):
63
+ return {str(k): _to_json_safe(v) for k, v in obj.items()}
64
+ elif isinstance(obj, (list, tuple)):
65
+ return [_to_json_safe(x) for x in obj]
66
+ elif isinstance(obj, (np.floating, float)):
67
+ return float(obj)
68
+ elif isinstance(obj, (np.integer, int)):
69
+ return int(obj)
70
+ elif isinstance(obj, np.ndarray):
71
+ return _to_json_safe(obj.tolist())
72
+ else:
73
+ return obj
74
+
75
+
76
  def _diagnose_speech_core(
77
  audio_path: Optional[str],
78
  target_phrase: str,
79
  healthy_baseline_path: Optional[str] = None,
80
+ ) -> Tuple[str, str, str, str, str, str, dict]:
81
  """Run full diagnostic pipeline and return formatted clinical report for Gradio."""
82
+ empty_dict = {"status": "waiting_for_input"}
83
+
84
  if not audio_path:
85
  return (
86
+ "**Notice**: Please record speech or upload an audio file to evaluate.",
87
+ "NO AUDIO",
88
+ "N/A",
89
  "N/A",
 
 
90
  "",
91
+ "",
92
+ empty_dict,
93
  )
94
 
95
+ if not target_phrase or not target_phrase.strip():
96
  return (
97
+ "**Notice**: Please enter or select an expected Target Phrase.",
98
+ "NO TARGET",
99
+ "N/A",
100
  "N/A",
 
 
101
  "",
102
+ "",
103
+ empty_dict,
104
  )
105
 
106
+ try:
107
+ engine = get_engine()
108
 
109
+ # Execute Diagnostic Engine
110
+ diag_res = engine.diagnose_audio(
111
+ audio_input=audio_path,
112
+ target_phrase=target_phrase,
113
+ normal_calibration_audio=healthy_baseline_path,
114
+ )
115
+
116
+ if diag_res.get("is_silent") or diag_res["decision"].get("is_silent"):
117
+ return (
118
+ "**No Speech Detected**: The audio is silent or below acoustic energy thresholds. Please speak clearly into your microphone.",
119
+ "SILENT",
120
+ "0 / 100",
121
+ "0.0%",
122
+ "",
123
+ "- **Silence Guard Active**: No vocal signal detected in audio stream.",
124
+ _to_json_safe(diag_res),
125
+ )
126
 
127
+ result = diag_res["decision"]
128
+ pron = diag_res["pronunciation"]
129
+ flaws = diag_res["flaws"]
130
+ artic = diag_res["articulation"]
131
+ p_stut = float(diag_res["stutter_probs"][1]) if (diag_res.get("stutter_probs") and len(diag_res["stutter_probs"]) > 1) else 0.0
132
+
133
+ overall_bucket = result["buckets"]["overall"].upper()
134
+ fluency_score = f"{int(result.get('fluency_100', 100))} / 100"
135
+ pron_acc = f"{max(0.0, min(100.0, (1.0 - pron.get('wer', 0.0)) * 100.0)):.1f}%"
136
+
137
+ # Build Word Alignment Chips HTML
138
+ alignment = pron.get("alignment", [])
139
+ chips_html = "<div style='display:flex; flex-wrap:wrap; gap:8px; padding:12px; background:rgba(15,23,42,0.6); border-radius:8px; margin:10px 0;'>"
140
+ for item in alignment:
141
+ status = item["status"]
142
+ exp = item["expected"]
143
+ spk = item["spoken"]
144
+ if status == "correct":
145
+ chips_html += f"<span style='padding:6px 12px; background:rgba(16,185,129,0.15); color:#34D399; border:1px solid rgba(16,185,129,0.3); border-radius:6px; font-weight:600;'>[MATCH] {exp}</span>"
146
+ elif status == "substitution":
147
+ chips_html += f"<span style='padding:6px 12px; background:rgba(239,68,68,0.15); color:#F87171; border:1px solid rgba(239,68,68,0.3); border-radius:6px; font-weight:600;'>[DIFF] {exp} (heard: \"{spk}\")</span>"
148
+ elif status == "omission":
149
+ chips_html += f"<span style='padding:6px 12px; background:rgba(245,158,11,0.15); color:#FBBF24; border:1px solid rgba(245,158,11,0.3); border-radius:6px; font-weight:600;'>[UNSPOKEN] {exp}</span>"
150
+ elif status == "insertion":
151
+ chips_html += f"<span style='padding:6px 12px; background:rgba(168,85,247,0.15); color:#C084FC; border:1px solid rgba(168,85,247,0.3); border-radius:6px; font-weight:600;'>[EXTRA] {spk}</span>"
152
+ chips_html += "</div>"
153
+
154
+ # Build Flaw Report Summary Markdown
155
+ flaws_md = "### Specific Speech Pathology Findings:\n\n"
156
+ if flaws["has_r_flaw"]:
157
+ for r_err in flaws["r_sound_issues"]:
158
+ flaws_md += f"- **Rhotacism Flaw**: {r_err['message']}\n"
159
+ else:
160
+ flaws_md += "- **'R' Sound Articulation**: Accurate (No R->W/L substitution detected).\n"
161
+
162
+ if flaws["has_s_flaw"]:
163
+ for s_err in flaws["s_sound_issues"]:
164
+ flaws_md += f"- **Sigmatism Flaw**: {s_err['message']}\n"
165
+ else:
166
+ flaws_md += "- **'S' Sound Articulation**: Accurate (No sibilant lisp detected).\n"
167
+
168
+ if p_stut >= 0.78:
169
+ flaws_md += f"- **Disfluency Detected**: Elevated probability of repetition/block ({p_stut*100:.1f}%)\n"
170
+ elif p_stut >= 0.60:
171
+ flaws_md += f"- **Mild Hesitation**: Minor syllable repetition observed ({p_stut*100:.1f}%)\n"
172
+ else:
173
+ flaws_md += "- **Fluency Flow**: Continuous cadence (No disfluent events detected).\n"
174
+
175
+ flaws_md += f"- **Voice Phonation Correlates**: Pitch F0={artic.get('pitch_f0_mean_hz',0):.1f}Hz, HNR={artic.get('hnr_db',0):.1f}dB, Jitter={artic.get('jitter',0)*100:.2f}%\n"
176
+
177
+ confidence_val = result.get("confidence", "high")
178
+ heard_summary = f"**Decoded Transcription**: *\"{pron.get('asr_hypothesis','')}\"*\n\n**Confidence Rating**: `{confidence_val}` | **Latency**: `{diag_res['latency_ms']} ms`"
179
+
180
+ return (
181
+ heard_summary,
182
+ overall_bucket,
183
+ fluency_score,
184
+ pron_acc,
185
+ chips_html,
186
+ flaws_md,
187
+ _to_json_safe(diag_res),
188
+ )
189
+ except Exception as ex:
190
+ err_msg = f"**Execution Error**: {ex}\n\n```\n{traceback.format_exc()}\n```"
191
  return (
192
+ err_msg,
193
+ "ERROR",
194
  "0 / 100",
195
  "0.0%",
196
  "",
197
+ f"- **Internal Error**: {ex}",
198
+ {"error": str(ex), "traceback": traceback.format_exc()},
199
  )
200
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
 
202
  # Apply ZeroGPU acceleration decorator if running on Hugging Face ZeroGPU
203
  if HAS_SPACES:
 
212
  # Construct Gradio Modern Interface
213
  with gr.Blocks(title="Anvaya | Speech Pathology Diagnostics") as demo:
214
  gr.Markdown("""
215
+ # ANVAYA · Speech Disfluency & Articulation Screening
216
+ ### Multi-Modal Screening: Neural Disfluency · Rhotacism ('r') · Sigmatism ('s' Lisp) · Phonation Acoustics
217
  """)
218
 
219
  with gr.Row():
 
280
 
281
  summary_box = gr.Markdown("### Clinical Assessment Summary\n*Results will appear here after analysis.*")
282
  alignment_html = gr.HTML(label="Word-Level Alignment")
283
+ flaws_box = gr.Markdown("### Specific Speech Pathology Findings\n*Sound checks will appear here.*")
284
 
285
  with gr.Accordion("Auditable Telemetry & Acoustic Evidence Trace", open=False):
286
  raw_json = gr.JSON()
 
288
  diagnose_btn.click(
289
  fn=diagnose_speech_hf,
290
  inputs=[audio_input, target_text, healthy_baseline],
291
+ outputs=[summary_box, kpi_strat, kpi_fluency, kpi_acc, alignment_html, flaws_box, raw_json],
292
  )
293
 
294
  if __name__ == "__main__":
295
  demo.launch()