Spaces:
Running on Zero
Running on Zero
Upload folder using huggingface_hub
Browse files- app.py +4 -2
- frontend/layout.py +34 -11
- frontend/live.py +5 -1
app.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
| 1 |
import traceback
|
| 2 |
import numpy as np
|
|
|
|
| 3 |
from backend.orchestrator import run_pipeline
|
| 4 |
from backend.presets import list_presets
|
| 5 |
from frontend.layout import build_app
|
| 6 |
|
|
|
|
| 7 |
def _warmup() -> None:
|
| 8 |
sr = 16000
|
| 9 |
dummy = np.zeros(sr, dtype=np.float32)
|
|
@@ -12,9 +14,9 @@ def _warmup() -> None:
|
|
| 12 |
continue
|
| 13 |
try:
|
| 14 |
run_pipeline(preset['config'], dummy, sample_rate=sr, preset_label=preset['label'])
|
| 15 |
-
print(f
|
| 16 |
except Exception as exc:
|
| 17 |
-
print(f
|
| 18 |
traceback.print_exc()
|
| 19 |
print('[warmup] done')
|
| 20 |
if __name__ == '__main__':
|
|
|
|
| 1 |
import traceback
|
| 2 |
import numpy as np
|
| 3 |
+
import spaces
|
| 4 |
from backend.orchestrator import run_pipeline
|
| 5 |
from backend.presets import list_presets
|
| 6 |
from frontend.layout import build_app
|
| 7 |
|
| 8 |
+
@spaces.GPU
|
| 9 |
def _warmup() -> None:
|
| 10 |
sr = 16000
|
| 11 |
dummy = np.zeros(sr, dtype=np.float32)
|
|
|
|
| 14 |
continue
|
| 15 |
try:
|
| 16 |
run_pipeline(preset['config'], dummy, sample_rate=sr, preset_label=preset['label'])
|
| 17 |
+
print(f'[warmup] ready: {preset['label']}')
|
| 18 |
except Exception as exc:
|
| 19 |
+
print(f'[warmup] skipped {preset['label']}: {exc}')
|
| 20 |
traceback.print_exc()
|
| 21 |
print('[warmup] done')
|
| 22 |
if __name__ == '__main__':
|
frontend/layout.py
CHANGED
|
@@ -24,21 +24,32 @@ def _sample_clip_choices() -> list[tuple[str, str]]:
|
|
| 24 |
choices = []
|
| 25 |
for bucket in BUCKET_ORDER:
|
| 26 |
for i, entry in enumerate(by_bucket.get(bucket, []), start=1):
|
| 27 |
-
label = f
|
| 28 |
choices.append((label, entry['filename']))
|
| 29 |
for bucket, entries in by_bucket.items():
|
| 30 |
if bucket in BUCKET_ORDER:
|
| 31 |
continue
|
| 32 |
for i, entry in enumerate(entries, start=1):
|
| 33 |
-
choices.append((f
|
| 34 |
return choices
|
| 35 |
|
| 36 |
-
def
|
| 37 |
-
return
|
| 38 |
|
| 39 |
-
def
|
| 40 |
-
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
def _param_relevance(active_display_names: list[str]) -> tuple[bool, bool]:
|
| 44 |
display_to_internal = {v: k for k, v in live.DISPLAY_NAME.items()}
|
|
@@ -51,8 +62,19 @@ def build_app() -> gr.Blocks:
|
|
| 51 |
with gr.Blocks(title='Turn Detection - Live Dashboard') as demo:
|
| 52 |
gr.Markdown('# Turn Detection - Live Dashboard\nSpeak, or replay a clip, and watch how different models judge whether the speaker is **done talking** vs. **still going** - plotted directly against the waveform as audio arrives.')
|
| 53 |
session_state = gr.State(live.new_session_state)
|
|
|
|
| 54 |
with gr.Row():
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
with gr.Row():
|
| 57 |
threshold_slider = gr.Slider(0.0, 1.0, value=live.DEFAULT_DECISION_THRESHOLD, step=0.01, label='Decision threshold - probability above this = "complete"')
|
| 58 |
cadence_slider = gr.Slider(0, 5000, value=live.DEFAULT_CADENCE_MS, step=100, label='Update cadence (ms) - how often each model re-checks (slower models may still lag behind this)')
|
|
@@ -75,7 +97,8 @@ def build_app() -> gr.Blocks:
|
|
| 75 |
chart = gr.Plot(value=live.render_chart(live.new_session_state(), [], live.DEFAULT_DECISION_THRESHOLD), label=None)
|
| 76 |
with gr.Accordion('History (this session)', open=False):
|
| 77 |
log_table = gr.Dataframe(headers=LOG_HEADERS, value=[], label=None)
|
| 78 |
-
|
|
|
|
| 79 |
|
| 80 |
def replay_clip(clip_filename, uploaded_path, active_display_names, acoustic_weight, temperature, threshold, cadence_ms, smoothing):
|
| 81 |
path = uploaded_path or (str(config.SAMPLE_CLIPS_DIR / clip_filename) if clip_filename else None)
|
|
@@ -91,7 +114,7 @@ def build_app() -> gr.Blocks:
|
|
| 91 |
state, fig, log_rows = live.process_chunk(state, (sr, chunk), active_display_names, acoustic_weight, temperature, threshold, cadence_ms, smoothing)
|
| 92 |
yield (state, fig, log_rows)
|
| 93 |
time.sleep(chunk_duration_s)
|
| 94 |
-
replay_btn.click(replay_clip, inputs=[replay_clip_dropdown, replay_upload,
|
| 95 |
-
mic.stream(fn=live.process_chunk, inputs=[session_state, mic,
|
| 96 |
clear_btn.click(live.clear_session, inputs=session_state, outputs=[session_state, chart, log_table])
|
| 97 |
return demo
|
|
|
|
| 24 |
choices = []
|
| 25 |
for bucket in BUCKET_ORDER:
|
| 26 |
for i, entry in enumerate(by_bucket.get(bucket, []), start=1):
|
| 27 |
+
label = f'{BUCKET_LABEL.get(bucket, bucket.title())} {i} - {entry['duration_seconds']}s'
|
| 28 |
choices.append((label, entry['filename']))
|
| 29 |
for bucket, entries in by_bucket.items():
|
| 30 |
if bucket in BUCKET_ORDER:
|
| 31 |
continue
|
| 32 |
for i, entry in enumerate(entries, start=1):
|
| 33 |
+
choices.append((f'{bucket.title()} {i} - {entry['duration_seconds']}s', entry['filename']))
|
| 34 |
return choices
|
| 35 |
|
| 36 |
+
def _all_public_presets() -> list[dict]:
|
| 37 |
+
return live.all_public_presets()
|
| 38 |
|
| 39 |
+
def _preset_display_order() -> list[str]:
|
| 40 |
+
return [live.DISPLAY_NAME.get(p['label'], p['label']) for p in _all_public_presets()]
|
| 41 |
+
|
| 42 |
+
def _default_active_display_names() -> list[str]:
|
| 43 |
+
target = 'Whisper-Tiny + Mean-Pool + Linear (trained)'
|
| 44 |
+
name = live.DISPLAY_NAME.get(target, target)
|
| 45 |
+
names = _preset_display_order()
|
| 46 |
+
return [name] if name in names else [names[0]] if names else []
|
| 47 |
+
|
| 48 |
+
def _on_preset_change(*values: bool) -> tuple[list[str], dict, dict]:
|
| 49 |
+
names = _preset_display_order()
|
| 50 |
+
active = [n for n, v in zip(names, values) if v]
|
| 51 |
+
ac, se = _param_relevance(active)
|
| 52 |
+
return (active, gr.update(interactive=ac), gr.update(interactive=se))
|
| 53 |
|
| 54 |
def _param_relevance(active_display_names: list[str]) -> tuple[bool, bool]:
|
| 55 |
display_to_internal = {v: k for k, v in live.DISPLAY_NAME.items()}
|
|
|
|
| 62 |
with gr.Blocks(title='Turn Detection - Live Dashboard') as demo:
|
| 63 |
gr.Markdown('# Turn Detection - Live Dashboard\nSpeak, or replay a clip, and watch how different models judge whether the speaker is **done talking** vs. **still going** - plotted directly against the waveform as audio arrives.')
|
| 64 |
session_state = gr.State(live.new_session_state)
|
| 65 |
+
active_state = gr.State(_default_active_display_names())
|
| 66 |
with gr.Row():
|
| 67 |
+
with gr.Column(scale=1, min_width=280):
|
| 68 |
+
gr.Markdown('### Models to compare')
|
| 69 |
+
gr.Markdown('*Greyed models are not built yet (future work).*')
|
| 70 |
+
default_active = _default_active_display_names()
|
| 71 |
+
preset_checkboxes = []
|
| 72 |
+
for preset in _all_public_presets():
|
| 73 |
+
label = preset['label']
|
| 74 |
+
disp = live.DISPLAY_NAME.get(label, label)
|
| 75 |
+
available = bool(preset.get('available'))
|
| 76 |
+
cb_label = disp if available else f'{disp} (future)'
|
| 77 |
+
preset_checkboxes.append(gr.Checkbox(label=cb_label, value=disp in default_active, interactive=available))
|
| 78 |
with gr.Row():
|
| 79 |
threshold_slider = gr.Slider(0.0, 1.0, value=live.DEFAULT_DECISION_THRESHOLD, step=0.01, label='Decision threshold - probability above this = "complete"')
|
| 80 |
cadence_slider = gr.Slider(0, 5000, value=live.DEFAULT_CADENCE_MS, step=100, label='Update cadence (ms) - how often each model re-checks (slower models may still lag behind this)')
|
|
|
|
| 97 |
chart = gr.Plot(value=live.render_chart(live.new_session_state(), [], live.DEFAULT_DECISION_THRESHOLD), label=None)
|
| 98 |
with gr.Accordion('History (this session)', open=False):
|
| 99 |
log_table = gr.Dataframe(headers=LOG_HEADERS, value=[], label=None)
|
| 100 |
+
for cb in preset_checkboxes:
|
| 101 |
+
cb.change(_on_preset_change, inputs=preset_checkboxes, outputs=[active_state, acoustic_weight_slider, temperature_slider])
|
| 102 |
|
| 103 |
def replay_clip(clip_filename, uploaded_path, active_display_names, acoustic_weight, temperature, threshold, cadence_ms, smoothing):
|
| 104 |
path = uploaded_path or (str(config.SAMPLE_CLIPS_DIR / clip_filename) if clip_filename else None)
|
|
|
|
| 114 |
state, fig, log_rows = live.process_chunk(state, (sr, chunk), active_display_names, acoustic_weight, temperature, threshold, cadence_ms, smoothing)
|
| 115 |
yield (state, fig, log_rows)
|
| 116 |
time.sleep(chunk_duration_s)
|
| 117 |
+
replay_btn.click(replay_clip, inputs=[replay_clip_dropdown, replay_upload, active_state, acoustic_weight_slider, temperature_slider, threshold_slider, cadence_slider, smoothing_slider], outputs=[session_state, chart, log_table])
|
| 118 |
+
mic.stream(fn=live.process_chunk, inputs=[session_state, mic, active_state, acoustic_weight_slider, temperature_slider, threshold_slider, cadence_slider, smoothing_slider], outputs=[session_state, chart, log_table], stream_every=1.0, time_limit=None)
|
| 119 |
clear_btn.click(live.clear_session, inputs=session_state, outputs=[session_state, chart, log_table])
|
| 120 |
return demo
|
frontend/live.py
CHANGED
|
@@ -25,6 +25,10 @@ def public_presets() -> list[dict]:
|
|
| 25 |
by_label = {p['label']: p for p in list_presets()}
|
| 26 |
return [by_label[label] for label in PUBLIC_PRESET_ORDER if label in by_label and by_label[label]['available']]
|
| 27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
def uses_acoustic_weight(label: str) -> bool:
|
| 29 |
by_label = {p['label']: p for p in list_presets()}
|
| 30 |
preset = by_label.get(label)
|
|
@@ -123,7 +127,7 @@ def render_chart(state: dict[str, Any], active_labels: list[str], threshold: flo
|
|
| 123 |
has_probability = any((e['probability'] is not None for e in history))
|
| 124 |
if not has_probability:
|
| 125 |
last = history[-1]
|
| 126 |
-
verdict_only_lines.append(f
|
| 127 |
continue
|
| 128 |
xs = [e['buffer_seconds'] for e in history if e['probability'] is not None]
|
| 129 |
ys_raw = [e['probability'] for e in history if e['probability'] is not None]
|
|
|
|
| 25 |
by_label = {p['label']: p for p in list_presets()}
|
| 26 |
return [by_label[label] for label in PUBLIC_PRESET_ORDER if label in by_label and by_label[label]['available']]
|
| 27 |
|
| 28 |
+
def all_public_presets() -> list[dict]:
|
| 29 |
+
by_label = {p['label']: p for p in list_presets()}
|
| 30 |
+
return [by_label[label] for label in PUBLIC_PRESET_ORDER if label in by_label]
|
| 31 |
+
|
| 32 |
def uses_acoustic_weight(label: str) -> bool:
|
| 33 |
by_label = {p['label']: p for p in list_presets()}
|
| 34 |
preset = by_label.get(label)
|
|
|
|
| 127 |
has_probability = any((e['probability'] is not None for e in history))
|
| 128 |
if not has_probability:
|
| 129 |
last = history[-1]
|
| 130 |
+
verdict_only_lines.append(f'{display_name}: {(last['decision'] or '-').upper()}')
|
| 131 |
continue
|
| 132 |
xs = [e['buffer_seconds'] for e in history if e['probability'] is not None]
|
| 133 |
ys_raw = [e['probability'] for e in history if e['probability'] is not None]
|