aether-raider
removed back button
02c7c7b
Raw
History Blame Contribute Delete
12.6 kB
"""
ATC TTS MOS Evaluation App
Main entrypoint wiring backend + frontend pages.
"""
import time
import gradio as gr
import os
import sys
# Add parent directory (mos) to path so we can import backend
mos_dir = os.path.dirname(os.path.dirname(__file__))
sys.path.insert(0, mos_dir)
# Backend pieces
from backend.data_manager import DataManager
from backend.session_manager import SessionManager
from backend.hf_logging import push_session_to_hub
# Frontend pieces
from frontend.css import CSS
from frontend.pages.intro import build_intro_page
from frontend.pages.samples import build_sample_page
from frontend.pages.mos import build_mos_page, render_mos_html
from frontend.pages.ab_model import build_ab_model_page, render_ab_model_html
from frontend.pages.ab_gender import build_ab_gender_page, render_ab_gender_html
from frontend.pages.conclusion import build_conclusion_page
from frontend.pages.thank_you import build_thank_you_page
from frontend.utils.validation import (
validate_mos_completion,
validate_ab_completion,
validate_final_submission,
)
from frontend.utils.js_collectors import (
COLLECT_MOS_DATA_JS,
COLLECT_AB_DATA_JS,
COLLECT_CONCLUSION_DATA_JS,
SCROLL_TO_TOP_JS,
)
def create_app(data_manager, session_manager):
with gr.Blocks(title="ATC TTS Evaluation", css=CSS) as app:
# Global state
session_state = gr.State()
# Persistent data state - stores user inputs across page transitions
mos_data_state = gr.State(value="{}")
ab_data_state = gr.State(value="{}")
# Hidden textboxes used by the JS collectors on MOS / AB pages
mos_data_textbox = gr.Textbox(
value="", visible=False, elem_id="mos_data_store"
)
ab_data_textbox = gr.Textbox(
value="", visible=False, elem_id="ab_data_store"
)
# Build all pages
intro_page, start_btn = build_intro_page()
(
sample_page,
back_to_intro_btn,
continue_samples_btn,
sample_audio_container,
) = build_sample_page()
(
mos_page,
continue_mos_btn,
mos_dynamic_content
) = build_mos_page()
(
ab_model_page,
continue_ab_model_btn,
ab_model_dynamic_content,
) = build_ab_model_page()
(
ab_gender_page,
continue_ab_gender_btn,
ab_gender_dynamic_content,
) = build_ab_gender_page()
(
conclusion_page,
export_btn,
overall_feedback,
final_comments,
) = build_conclusion_page()
thank_you_page = build_thank_you_page()
# =========================
# Event handlers
# =========================
def start_evaluation():
"""Create a new session and populate sample page with sample clips."""
try:
session = session_manager.create_session()
print(f"[INFO] Session created: {session['session_id']}")
print(f"[INFO] Sample clips: {len(session.get('sample_clips', []))}")
except Exception as e:
print(f"[ERROR] Error creating session: {e}")
session = {
"session_id": "fallback",
"sample_clips": [],
"mos_clips": [],
"ab_model_pairs": [],
"ab_gender_pairs": [],
"created_at": time.time(),
"completed": False,
}
return (
gr.update(visible=False), # hide intro
gr.update(visible=True), # show sample page
session, # update session_state
)
def show_mos_page(session):
"""Navigate from sample page to MOS page and render MOS HTML."""
if not session:
return gr.update(), gr.update(), ""
mos_html = render_mos_html(session)
return (
gr.update(visible=False), # hide sample page
gr.update(visible=True), # show MOS page
mos_html, # set MOS HTML content
)
def continue_from_mos_to_ab_model(session, mos_data_json):
"""Process MOS data and move to Model-vs-Model page."""
print(f"[DEBUG] continue_from_mos_to_ab_model called")
print(f"[DEBUG] MOS data JSON received: '{mos_data_json}'")
# Validate MOS completion
expected_clips = len(session.get("mos_clips", []))
is_valid, error_msg = validate_mos_completion(mos_data_json, expected_clips)
if not is_valid:
gr.Warning(error_msg)
return gr.update(), gr.update(), ""
# Process and navigate
session_manager.process_mos_data(session, mos_data_json)
ab_model_html = render_ab_model_html(session)
return (
gr.update(visible=False), # hide MOS page
gr.update(visible=True), # show AB model page
ab_model_html, # set AB model HTML content
)
def continue_from_ab_model_to_ab_gender(session, ab_data_json):
"""Process A/B model comparison data and move to Gender A/B page."""
print(f"[DEBUG] continue_from_ab_model_to_ab_gender called")
print(f"[DEBUG] AB data JSON received: '{ab_data_json}'")
# Validate AB model completion
expected_comparisons = len(session.get("ab_model_pairs", []))
is_valid, error_msg = validate_ab_completion(ab_data_json, expected_comparisons, "model comparisons")
if not is_valid:
gr.Warning(error_msg)
return gr.update(), gr.update(), ""
# Process and navigate
session_manager.process_ab_data(session, ab_data_json)
ab_gender_html = render_ab_gender_html(session)
return (
gr.update(visible=False), # hide AB model page
gr.update(visible=True), # show AB gender page
ab_gender_html, # set AB gender HTML content
)
def complete_evaluation_with_ab_gender_data(session, ab_data_json):
"""Process A/B gender comparison data and move to conclusion page."""
print(f"[DEBUG] complete_evaluation_with_ab_gender_data called")
print(f"[DEBUG] AB gender data JSON received: '{ab_data_json}'")
# Validate AB gender completion
expected_comparisons = len(session.get("ab_gender_pairs", []))
is_valid, error_msg = validate_ab_completion(ab_data_json, expected_comparisons, "gender comparisons")
if not is_valid:
gr.Warning(error_msg)
return gr.update(), gr.update()
# Process and navigate
session_manager.process_ab_data(session, ab_data_json)
if session:
session["completed"] = True
return (
gr.update(visible=False), # hide AB gender page
gr.update(visible=True), # show conclusion page
)
def export_results_with_data_processing(
session,
overall_preference,
comments,
mos_data_json,
ab_data_json,
):
"""Final submit: process data, validate overall preference, save, and push to HF."""
print(f"[DEBUG] export_results_with_data_processing called")
print(f"[DEBUG] overall_preference: '{overall_preference}'")
print(f"[DEBUG] comments: '{comments}'")
if not session:
return gr.update(), gr.update()
# Validate that overall preference is selected
is_valid, error_msg = validate_final_submission(overall_preference)
if not is_valid:
print(f"[ERROR] {error_msg}")
gr.Warning(error_msg)
return gr.update(), gr.update()
# Process all MOS / AB ratings
session_manager.process_mos_data(session, mos_data_json)
session_manager.process_ab_data(session, ab_data_json)
# Save overall feedback
feedback_response = {
"session_id": session["session_id"],
"overall_preference": overall_preference,
"final_comments": comments or "",
"timestamp": time.time(),
}
session_manager.save_response("feedback", feedback_response)
print(f"[INFO] Saved feedback response: {feedback_response}")
# Export and push to Hub
export_data = session_manager.export_session(session["session_id"])
try:
push_session_to_hub(session["session_id"], export_data)
print(f"[INFO] Successfully submitted session {session['session_id']} to Hub")
except Exception as e:
print(f"[WARN] Failed to push session {session['session_id']} to Hub: {e}")
return (
gr.update(visible=False), # hide conclusion page
gr.update(visible=True), # show thank you page
)
# =========================
# Wire buttons to handlers
# =========================
# Intro → Samples
start_btn.click(
start_evaluation,
inputs=[],
outputs=[intro_page, sample_page, session_state],
js=SCROLL_TO_TOP_JS
)
# Samples → MOS
continue_samples_btn.click(
show_mos_page,
inputs=[session_state],
outputs=[sample_page, mos_page, mos_dynamic_content],
js=SCROLL_TO_TOP_JS
)
# MOS → AB Model (process MOS first)
continue_mos_btn.click(
fn=continue_from_mos_to_ab_model,
inputs=[session_state, mos_data_textbox],
outputs=[mos_page, ab_model_page, ab_model_dynamic_content],
js=COLLECT_MOS_DATA_JS
)
# AB Model → AB Gender (process AB model first)
continue_ab_model_btn.click(
continue_from_ab_model_to_ab_gender,
inputs=[session_state, ab_data_textbox],
outputs=[ab_model_page, ab_gender_page, ab_gender_dynamic_content],
js=COLLECT_AB_DATA_JS
)
# AB Gender → Conclusion (process AB gender data)
continue_ab_gender_btn.click(
complete_evaluation_with_ab_gender_data,
inputs=[session_state, ab_data_textbox],
outputs=[ab_gender_page, conclusion_page],
js=COLLECT_AB_DATA_JS
)
# Conclusion → Thank You (process any remaining data + export)
export_btn.click(
export_results_with_data_processing,
inputs=[
session_state,
overall_feedback,
final_comments,
mos_data_textbox,
ab_data_textbox,
],
outputs=[conclusion_page, thank_you_page],
js=COLLECT_CONCLUSION_DATA_JS
)
# Back navigation
back_to_intro_btn.click(
lambda: (gr.update(visible=True), gr.update(visible=False)),
inputs=[],
outputs=[intro_page, sample_page],
js=SCROLL_TO_TOP_JS
)
return app
if __name__ == "__main__":
print("Starting ATC TTS MOS Evaluation...")
try:
data_manager = DataManager()
session_manager = SessionManager(data_manager)
clips = data_manager.load_clips()
print(f"Dataset ready with {len(clips)} clips")
models = {clip.model for clip in clips}
speakers = {clip.speaker for clip in clips}
exercises = {clip.exercise for clip in clips}
print(f"Models: {', '.join(models)}")
print(f"Speakers: {', '.join(speakers)}")
print(f"Exercises: {len(exercises)} unique exercises")
except Exception as e:
print(f"[ERROR] Error loading data: {e}")
print("Please check your dataset access and try again.")
raise SystemExit(1)
app = create_app(data_manager, session_manager)
app.launch(
server_name="0.0.0.0",
server_port=7860,
share=False,
show_error=True,
)