File size: 9,592 Bytes
2f84a1a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c6714a7
2f84a1a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import gradio as gr
import pandas as pd
import json

# --- 1. Загрузка и подготовка данных ---

def load_data(json_path='data.json'):
    """Загружает данные из JSON и преобразует их в pandas DataFrame."""
    with open(json_path, 'r', encoding='utf-8') as f:
        data = json.load(f)
    
    df = pd.DataFrame(data)

    # --- ИСПРАВЛЕНИЕ: Преобразуем колонку с датой в формат datetime ---
    # dayfirst=True указывает pandas, что в формате ДД/ММ/ГГГГ день идет первым.
    df['test_date'] = pd.to_datetime(df['test_date'], dayfirst=True)
    
    return df

# --- 2. Логика фильтрации и обновления таблицы ---

def update_leaderboard(data_df, filter_type, search_query, show_outdated):
    """Фильтрует DataFrame на основе выбранного типа, поискового запроса и флага устаревших данных."""
    
    # Шаг 1: Фильтрация по устаревшим данным
    if not show_outdated:
        # Скрываем модели с пометкой об устаревших данных
        filtered_df = data_df[~data_df['notes'].str.contains('устаревших данных', na=False, case=False)].copy()
    else:
        filtered_df = data_df.copy()
    
    # Шаг 2: Фильтрация по типу
    if filter_type == "Только войсклонинг":
        filtered_df = filtered_df[filtered_df['type'] == 'voice_cloning'].copy()
    elif filter_type == "Без войсклонинга":
        filtered_df = filtered_df[filtered_df['type'] == 'single_speaker'].copy()
    # else: "Все модели" - оставляем как есть

    # Шаг 3: Фильтрация по поисковому запросу
    if search_query:
        query = search_query.lower()
        filtered_df = filtered_df[
            filtered_df['engine'].str.lower().str.contains(query) |
            filtered_df['voice'].str.lower().str.contains(query)
        ]

    # Шаг 4: Подготовка DataFrame для отображения
    
    # --- ИСПРАВЛЕНИЕ: Форматируем дату в 'ГГГГ-ММ-ДД' для корректной сортировки в UI ---
    # Создаем копию, чтобы не изменять оригинальный DataFrame в gr.State
    display_df = filtered_df.copy()
    display_df['test_date'] = display_df['test_date'].dt.strftime('%Y-%m-%d')
    
    column_mapping = {
        "engine": "Движок", "voice": "Голос", "test_date": "Дата",
        "hardware": "Железо", "utmos": "UTMOS (↑)", "cer": "CER (↓)", 
        "encodec_fad": "FAD (↓)", "similarity_avg": "Похожесть Avg (↑)", 
        "xrt_gpu": "xRT GPU (↓)", "xrt_cpu": "xRT CPU (↓)", "notes": "Примечания"
    }
    
    display_df = display_df[column_mapping.keys()].rename(columns=column_mapping)
    
    return display_df


# --- 3. Создание интерфейса Gradio ---

# Загружаем данные один раз при старте приложения
original_df = load_data()

# Сортируем по-умолчанию по дате (самые новые вверху), скрывая устаревшие
initial_display_df = update_leaderboard(original_df, "Все модели", "", show_outdated=False)
initial_display_df = initial_display_df.sort_values(by="Дата", ascending=False)


with gr.Blocks(theme=gr.themes.Soft(), css="footer {visibility: hidden}") as demo:
    gr.Markdown("# 🏆 Лидерборд TTS моделей для русского языка")
    gr.Markdown(
        """
        Этот лидерборд предназначен для сравнения различных Text-to-Speech моделей.
        
        ### Описание метрик:
        - **UTMOS (↑)**: Оценка качества речи, основанная на мнении слушателей (Mean Opinion Score). **Больше — лучше.**
        - **CER (↓)**: Character Error Rate (коэффициент ошибок по символам). Показывает, насколько часто синтез делает ошибки в произношении. **Меньше — лучше.**
        - **FAD (↓)**: Fréchet Audio Distance. Объективная метрика, измеряющая расстояние между распределениями реального и синтезированного аудио. **Меньше — лучше.**
        - **Похожесть Avg (↑)**: Средняя оценка схожести голоса с оригиналом при клонировании. **Больше — лучше.**
        - **xRT GPU/CPU (↓)**: Real-Time Factor. Во сколько раз синтез быстрее (если < 1) или медленнее (если > 1) реального времени на GPU/CPU. **Меньше — лучше.**
        - **Железо**: Тип оборудования, на котором производился тест (Cloud - облачный сервис, Local GPU/CPU - локальное железо, RTX 4090 - конкретная видеокарта).
        
        *Кликните на заголовок колонки для сортировки. По умолчанию отсортировано по дате (сначала новые).*
        """
    )
    gr.Markdown("✉️ Чтобы добавить свою модель, а также вопросы и предложения пишите в Telegram [@bceloss](https://t.me/bceloss)")
    gr.Markdown('✉️ Добавляйтесь в чат "Распознавание и синтез речи" [@speech_recognition_ru](https://t.me/speech_recognition_ru)')
    gr.Markdown('👥 Авторы: Nikolay Shmyrev [@nshmyrev](https://t.me/nshmyrev), Denis Petrov [@bceloss](https://t.me/bceloss)')



    with gr.Row():
        with gr.Column(scale=3):
            filter_radio = gr.Radio(
                ["Все модели", "Только войсклонинг", "Без войсклонинга"],
                label="Тип модели",
                value="Все модели"
            )
        with gr.Column(scale=3):
            search_box = gr.Textbox(
                label="Поиск по названию движка или голоса",
                placeholder="Например, Silero, Vosk, Multi..."
            )
        with gr.Column(scale=2):
            show_outdated_checkbox = gr.Checkbox(
                label="Показать модели с устаревшими данными",
                value=False,
                info="⚠️ Данные этих моделей могут быть неточными"
            )

    # Информационное сообщение о скрытых моделях
    outdated_info = gr.Markdown(visible=False)
    
    leaderboard_df = gr.DataFrame(
        value=initial_display_df,
        interactive=True,
    )

    # Используем gr.State для передачи полного DataFrame с правильными типами данных
    df_state = gr.State(original_df)

    def on_change(filter_type, search_query, show_outdated, data_df):
        """Обновляет таблицу и показывает информацию о скрытых моделях."""
        updated_df = update_leaderboard(data_df, filter_type, search_query, show_outdated)
        
        # Подсчитываем количество скрытых моделей с устаревшими данными
        if not show_outdated:
            outdated_count = data_df[data_df['notes'].str.contains('устаревших данных', na=False, case=False)].shape[0]
            if outdated_count > 0:
                info_text = f"ℹ️ **Скрыто моделей с устаревшими данными: {outdated_count}**. Включите опцию выше, чтобы показать их."
                return updated_df, gr.update(value=info_text, visible=True)
        
        return updated_df, gr.update(visible=False)

    # Связываем все элементы управления с функцией обновления
    filter_radio.change(
        fn=on_change,
        inputs=[filter_radio, search_box, show_outdated_checkbox, df_state],
        outputs=[leaderboard_df, outdated_info]
    )
    search_box.change(
        fn=on_change,
        inputs=[filter_radio, search_box, show_outdated_checkbox, df_state],
        outputs=[leaderboard_df, outdated_info]
    )
    show_outdated_checkbox.change(
        fn=on_change,
        inputs=[filter_radio, search_box, show_outdated_checkbox, df_state],
        outputs=[leaderboard_df, outdated_info]
    )
    
    # Показываем информацию о скрытых моделях при загрузке
    demo.load(
        fn=lambda: f"ℹ️ **Скрыто моделей с устаревшими данными: {original_df[original_df['notes'].str.contains('устаревших данных', na=False, case=False)].shape[0]}**. Включите опцию выше, чтобы показать их.",
        outputs=outdated_info
    )

# --- 4. Запуск приложения ---
if __name__ == "__main__":
    demo.launch()