|
|
""" |
|
|
Medical Transcription GUI Application |
|
|
Полнофункциональное приложение для транскрибирования медицинских диктовок |
|
|
с автоматической генерацией отчётов |
|
|
""" |
|
|
|
|
|
import sys |
|
|
import logging |
|
|
from pathlib import Path |
|
|
from typing import Optional, Dict, Any |
|
|
import threading |
|
|
import traceback |
|
|
from datetime import datetime |
|
|
import os |
|
|
|
|
|
from PyQt6.QtWidgets import ( |
|
|
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, |
|
|
QLabel, QPushButton, QLineEdit, QTextEdit, QFileDialog, |
|
|
QComboBox, QSpinBox, QCheckBox, QProgressBar, QMessageBox, |
|
|
QTabWidget, QFormLayout, QGroupBox, QDialog, QScrollArea |
|
|
) |
|
|
from PyQt6.QtCore import Qt, pyqtSignal, QObject, QThread |
|
|
from PyQt6.QtGui import QFont, QIcon, QColor |
|
|
from PyQt6.QtCore import QTimer |
|
|
|
|
|
|
|
|
from common import ( |
|
|
get_logger, |
|
|
UIColors, |
|
|
UIDimensions, |
|
|
Messages, |
|
|
Placeholders, |
|
|
AudioFileException, |
|
|
TranscriptionException, |
|
|
ValidationException |
|
|
) |
|
|
|
|
|
|
|
|
from pipeline import PipelineConfig |
|
|
|
|
|
logger = get_logger(__name__) |
|
|
|
|
|
|
|
|
class WorkerSignals(QObject): |
|
|
"""Сигналы для воркера обработки""" |
|
|
progress = pyqtSignal(str) |
|
|
finished = pyqtSignal(dict) |
|
|
error = pyqtSignal(str) |
|
|
|
|
|
|
|
|
class TranscriptionWorker(QThread): |
|
|
"""Воркер для обработки аудио в отдельном потоке""" |
|
|
|
|
|
signals = WorkerSignals() |
|
|
|
|
|
def __init__( |
|
|
self, |
|
|
audio_path: str, |
|
|
config: PipelineConfig, |
|
|
patient_data: Dict[str, Any] |
|
|
) -> None: |
|
|
super().__init__() |
|
|
self.audio_path: str = audio_path |
|
|
self.config: PipelineConfig = config |
|
|
self.patient_data: Dict[str, Any] = patient_data |
|
|
|
|
|
def run(self) -> None: |
|
|
try: |
|
|
|
|
|
from pipeline.medical_pipeline import MedicalTranscriptionPipeline |
|
|
|
|
|
self.signals.progress.emit("Инициализация пайплайна...") |
|
|
pipeline = MedicalTranscriptionPipeline(self.config) |
|
|
|
|
|
self.signals.progress.emit("Запуск транскрибирования...") |
|
|
result = pipeline.process( |
|
|
audio_path=self.audio_path, |
|
|
patient_name=self.patient_data.get("patient_name"), |
|
|
patient_dob=self.patient_data.get("patient_dob"), |
|
|
study_area=self.patient_data.get("study_area"), |
|
|
study_number=self.patient_data.get("study_number"), |
|
|
study_date=self.patient_data.get("study_date"), |
|
|
doctor_name=self.patient_data.get("doctor_name"), |
|
|
generate_report=self.config.generate_report |
|
|
) |
|
|
|
|
|
self.signals.progress.emit("Обработка завершена!") |
|
|
self.signals.finished.emit(result) |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"Error in transcription worker: {e}\n{traceback.format_exc()}") |
|
|
self.signals.error.emit(str(e)) |
|
|
|
|
|
|
|
|
class PatientDataDialog(QDialog): |
|
|
"""Диалог для ввода данных пациента""" |
|
|
|
|
|
def __init__(self, parent=None): |
|
|
super().__init__(parent) |
|
|
self.setWindowTitle("Данные пациента") |
|
|
self.setGeometry(100, 100, 500, 400) |
|
|
self.init_ui() |
|
|
self.result = None |
|
|
|
|
|
def init_ui(self): |
|
|
layout = QFormLayout() |
|
|
|
|
|
self.patient_name = QLineEdit() |
|
|
self.patient_name.setPlaceholderText("Фамилия Имя Отчество") |
|
|
|
|
|
self.patient_dob = QLineEdit() |
|
|
self.patient_dob.setPlaceholderText("ДД.MM.YYYY") |
|
|
|
|
|
self.study_area = QLineEdit() |
|
|
self.study_area.setPlaceholderText("Область исследования (напр. МРТ головы)") |
|
|
|
|
|
self.study_number = QLineEdit() |
|
|
self.study_number.setPlaceholderText("Номер исследования") |
|
|
|
|
|
self.study_date = QLineEdit() |
|
|
self.study_date.setPlaceholderText("ДД.MM.YYYY") |
|
|
self.study_date.setText(datetime.now().strftime("%d.%m.%Y")) |
|
|
|
|
|
self.doctor_name = QLineEdit() |
|
|
self.doctor_name.setPlaceholderText("ФИО врача") |
|
|
|
|
|
layout.addRow("ФИО пациента:", self.patient_name) |
|
|
layout.addRow("Дата рождения:", self.patient_dob) |
|
|
layout.addRow("Область исследования:", self.study_area) |
|
|
layout.addRow("Номер исследования:", self.study_number) |
|
|
layout.addRow("Дата исследования:", self.study_date) |
|
|
layout.addRow("ФИО врача:", self.doctor_name) |
|
|
|
|
|
|
|
|
button_layout = QHBoxLayout() |
|
|
ok_btn = QPushButton("OK") |
|
|
cancel_btn = QPushButton("Отмена") |
|
|
|
|
|
ok_btn.clicked.connect(self.accept) |
|
|
cancel_btn.clicked.connect(self.reject) |
|
|
|
|
|
button_layout.addWidget(ok_btn) |
|
|
button_layout.addWidget(cancel_btn) |
|
|
|
|
|
layout.addRow(button_layout) |
|
|
self.setLayout(layout) |
|
|
|
|
|
def get_data(self): |
|
|
"""Получить введённые данные""" |
|
|
return { |
|
|
"patient_name": self.patient_name.text(), |
|
|
"patient_dob": self.patient_dob.text(), |
|
|
"study_area": self.study_area.text(), |
|
|
"study_number": self.study_number.text(), |
|
|
"study_date": self.study_date.text(), |
|
|
"doctor_name": self.doctor_name.text() |
|
|
} |
|
|
|
|
|
|
|
|
class MedicalTranscriptionApp(QMainWindow): |
|
|
"""Главное окно приложения""" |
|
|
|
|
|
def __init__(self): |
|
|
super().__init__() |
|
|
self.setWindowTitle(Messages.APP_TITLE) |
|
|
self.setGeometry(100, 100, UIDimensions.WINDOW_WIDTH, UIDimensions.WINDOW_HEIGHT) |
|
|
|
|
|
|
|
|
self.audio_path = None |
|
|
self.model_path = Path(__file__).parent.parent |
|
|
self.worker = None |
|
|
self.patient_data = {} |
|
|
|
|
|
self.init_ui() |
|
|
self.setup_logging() |
|
|
|
|
|
|
|
|
self.apply_styles() |
|
|
|
|
|
def setup_logging(self): |
|
|
"""Настройка логирования""" |
|
|
from common import configure_logging |
|
|
configure_logging() |
|
|
|
|
|
def init_ui(self): |
|
|
"""Инициализация интерфейса""" |
|
|
main_widget = QWidget() |
|
|
self.setCentralWidget(main_widget) |
|
|
|
|
|
|
|
|
tabs = QTabWidget() |
|
|
|
|
|
|
|
|
transcription_tab = self.create_transcription_tab() |
|
|
tabs.addTab(transcription_tab, "Транскрибирование") |
|
|
|
|
|
|
|
|
settings_tab = self.create_settings_tab() |
|
|
tabs.addTab(settings_tab, "Настройки") |
|
|
|
|
|
|
|
|
main_layout = QVBoxLayout() |
|
|
main_layout.addWidget(tabs) |
|
|
main_widget.setLayout(main_layout) |
|
|
|
|
|
def create_transcription_tab(self): |
|
|
"""Создание вкладки транскрибирования""" |
|
|
widget = QWidget() |
|
|
layout = QVBoxLayout() |
|
|
|
|
|
|
|
|
file_group = QGroupBox("1. Выбор аудиофайла") |
|
|
file_layout = QHBoxLayout() |
|
|
|
|
|
self.file_path_label = QLineEdit() |
|
|
self.file_path_label.setReadOnly(True) |
|
|
self.file_path_label.setPlaceholderText("Аудиофайл не выбран") |
|
|
|
|
|
browse_btn = QPushButton("Обзор...") |
|
|
browse_btn.clicked.connect(self.browse_audio_file) |
|
|
|
|
|
file_layout.addWidget(QLabel("Файл:")) |
|
|
file_layout.addWidget(self.file_path_label, 1) |
|
|
file_layout.addWidget(browse_btn) |
|
|
|
|
|
file_group.setLayout(file_layout) |
|
|
layout.addWidget(file_group) |
|
|
|
|
|
|
|
|
patient_group = QGroupBox("2. Данные пациента") |
|
|
patient_layout = QVBoxLayout() |
|
|
|
|
|
self.patient_info_label = QLabel("Данные пациента не заполнены") |
|
|
patient_info_font = QFont() |
|
|
patient_info_font.setItalic(True) |
|
|
self.patient_info_label.setFont(patient_info_font) |
|
|
|
|
|
patient_btn = QPushButton("Заполнить данные пациента...") |
|
|
patient_btn.clicked.connect(self.open_patient_dialog) |
|
|
|
|
|
patient_layout.addWidget(self.patient_info_label) |
|
|
patient_layout.addWidget(patient_btn) |
|
|
patient_group.setLayout(patient_layout) |
|
|
layout.addWidget(patient_group) |
|
|
|
|
|
|
|
|
options_group = QGroupBox("3. Опции обработки") |
|
|
options_layout = QFormLayout() |
|
|
|
|
|
self.llm_checkbox = QCheckBox("Использовать LLM-коррекцию") |
|
|
self.llm_checkbox.setChecked(True) |
|
|
|
|
|
self.report_checkbox = QCheckBox("Автоматически создать отчёт") |
|
|
self.report_checkbox.setChecked(True) |
|
|
|
|
|
self.save_original_checkbox = QCheckBox("Сохранить оригинальную транскрипцию") |
|
|
self.save_original_checkbox.setChecked(True) |
|
|
|
|
|
options_layout.addRow(self.llm_checkbox) |
|
|
options_layout.addRow(self.report_checkbox) |
|
|
options_layout.addRow(self.save_original_checkbox) |
|
|
|
|
|
options_group.setLayout(options_layout) |
|
|
layout.addWidget(options_group) |
|
|
|
|
|
|
|
|
progress_group = QGroupBox("4. Статус обработки") |
|
|
progress_layout = QVBoxLayout() |
|
|
|
|
|
self.progress_label = QLabel("Готов к обработке") |
|
|
self.progress_bar = QProgressBar() |
|
|
self.progress_bar.setValue(0) |
|
|
self.progress_bar.setVisible(False) |
|
|
|
|
|
progress_layout.addWidget(self.progress_label) |
|
|
progress_layout.addWidget(self.progress_bar) |
|
|
|
|
|
progress_group.setLayout(progress_layout) |
|
|
layout.addWidget(progress_group) |
|
|
|
|
|
|
|
|
results_group = QGroupBox("5. Результаты") |
|
|
results_layout = QVBoxLayout() |
|
|
|
|
|
self.results_text = QTextEdit() |
|
|
self.results_text.setReadOnly(True) |
|
|
self.results_text.setPlaceholderText("Результаты обработки появятся здесь") |
|
|
self.results_text.setMinimumHeight(200) |
|
|
|
|
|
results_layout.addWidget(self.results_text) |
|
|
results_group.setLayout(results_layout) |
|
|
layout.addWidget(results_group) |
|
|
|
|
|
|
|
|
button_layout = QHBoxLayout() |
|
|
|
|
|
self.start_btn = QPushButton("▶ Начать транскрибирование") |
|
|
self.start_btn.setStyleSheet(""" |
|
|
QPushButton { |
|
|
background-color: #4CAF50; |
|
|
color: white; |
|
|
font-weight: bold; |
|
|
padding: 10px; |
|
|
border-radius: 5px; |
|
|
} |
|
|
QPushButton:hover { |
|
|
background-color: #45a049; |
|
|
} |
|
|
QPushButton:disabled { |
|
|
background-color: #cccccc; |
|
|
} |
|
|
""") |
|
|
self.start_btn.clicked.connect(self.start_transcription) |
|
|
|
|
|
clear_btn = QPushButton("🗑 Очистить результаты") |
|
|
clear_btn.clicked.connect(lambda: self.results_text.clear()) |
|
|
|
|
|
button_layout.addWidget(self.start_btn, 1) |
|
|
button_layout.addWidget(clear_btn) |
|
|
|
|
|
layout.addLayout(button_layout) |
|
|
|
|
|
widget.setLayout(layout) |
|
|
return widget |
|
|
|
|
|
def create_settings_tab(self): |
|
|
"""Создание вкладки настроек""" |
|
|
widget = QWidget() |
|
|
layout = QVBoxLayout() |
|
|
|
|
|
|
|
|
model_group = QGroupBox("Модель Whisper") |
|
|
model_layout = QFormLayout() |
|
|
|
|
|
self.model_path_input = QLineEdit() |
|
|
self.model_path_input.setText(str(self.model_path)) |
|
|
|
|
|
browse_model_btn = QPushButton("Обзор...") |
|
|
browse_model_btn.clicked.connect(self.browse_model_path) |
|
|
|
|
|
model_path_layout = QHBoxLayout() |
|
|
model_path_layout.addWidget(self.model_path_input, 1) |
|
|
model_path_layout.addWidget(browse_model_btn) |
|
|
|
|
|
model_layout.addRow("Путь к модели:", model_path_layout) |
|
|
|
|
|
self.device_combo = QComboBox() |
|
|
self.device_combo.addItems(["auto", "cuda", "cpu"]) |
|
|
model_layout.addRow("Устройство:", self.device_combo) |
|
|
|
|
|
self.dtype_combo = QComboBox() |
|
|
self.dtype_combo.addItems(["float32", "float16", "bfloat16"]) |
|
|
model_layout.addRow("Тип данных:", self.dtype_combo) |
|
|
|
|
|
model_group.setLayout(model_layout) |
|
|
layout.addWidget(model_group) |
|
|
|
|
|
|
|
|
api_group = QGroupBox("OpenRouter API (для LLM-коррекции)") |
|
|
api_layout = QFormLayout() |
|
|
|
|
|
self.api_key_input = QLineEdit() |
|
|
self.api_key_input.setEchoMode(QLineEdit.EchoMode.Password) |
|
|
self.api_key_input.setPlaceholderText("Введите ваш API ключ OpenRouter") |
|
|
api_layout.addRow("API Ключ:", self.api_key_input) |
|
|
|
|
|
self.model_combo = QComboBox() |
|
|
self.model_combo.addItems([ |
|
|
"gpt-4o", |
|
|
"claude-3-opus", |
|
|
"gemini-pro", |
|
|
"gpt-4-turbo" |
|
|
]) |
|
|
api_layout.addRow("Модель LLM:", self.model_combo) |
|
|
|
|
|
api_group.setLayout(api_layout) |
|
|
layout.addWidget(api_group) |
|
|
|
|
|
|
|
|
terms_group = QGroupBox("База медицинских терминов") |
|
|
terms_layout = QFormLayout() |
|
|
|
|
|
self.terms_path_input = QLineEdit() |
|
|
self.terms_path_input.setText(str(Path(__file__).parent.parent / "medical_terms.txt")) |
|
|
|
|
|
browse_terms_btn = QPushButton("Обзор...") |
|
|
browse_terms_btn.clicked.connect(self.browse_terms_path) |
|
|
|
|
|
terms_path_layout = QHBoxLayout() |
|
|
terms_path_layout.addWidget(self.terms_path_input, 1) |
|
|
terms_path_layout.addWidget(browse_terms_btn) |
|
|
|
|
|
terms_layout.addRow("Путь к файлу терминов:", terms_path_layout) |
|
|
|
|
|
terms_group.setLayout(terms_layout) |
|
|
layout.addWidget(terms_group) |
|
|
|
|
|
layout.addStretch() |
|
|
|
|
|
|
|
|
save_settings_btn = QPushButton("💾 Сохранить настройки") |
|
|
save_settings_btn.clicked.connect(self.save_settings) |
|
|
layout.addWidget(save_settings_btn) |
|
|
|
|
|
widget.setLayout(layout) |
|
|
return widget |
|
|
|
|
|
def apply_styles(self): |
|
|
"""Применение стилей к приложению""" |
|
|
style = f""" |
|
|
QMainWindow {{ |
|
|
background-color: {UIColors.BACKGROUND}; |
|
|
}} |
|
|
QPushButton {{ |
|
|
background-color: {UIColors.PRIMARY}; |
|
|
color: white; |
|
|
border: none; |
|
|
padding: 8px 16px; |
|
|
border-radius: 4px; |
|
|
font-size: 14px; |
|
|
}} |
|
|
QPushButton:hover {{ |
|
|
background-color: {UIColors.PRIMARY_HOVER}; |
|
|
}} |
|
|
QPushButton:disabled {{ |
|
|
background-color: {UIColors.DISABLED}; |
|
|
color: {UIColors.TEXT_DISABLED}; |
|
|
}} |
|
|
QProgressBar {{ |
|
|
border: 1px solid {UIColors.BORDER}; |
|
|
border-radius: 5px; |
|
|
text-align: center; |
|
|
}} |
|
|
QProgressBar::chunk {{ |
|
|
background-color: {UIColors.SUCCESS}; |
|
|
}} |
|
|
QGroupBox {{ |
|
|
font-weight: bold; |
|
|
border: 1px solid {UIColors.BORDER}; |
|
|
border-radius: 5px; |
|
|
margin-top: 10px; |
|
|
padding-top: 10px; |
|
|
}} |
|
|
QGroupBox::title {{ |
|
|
subcontrol-origin: margin; |
|
|
left: 10px; |
|
|
padding: 0 3px 0 3px; |
|
|
}} |
|
|
QLineEdit, QTextEdit, QComboBox, QSpinBox {{ |
|
|
border: 1px solid {UIColors.BORDER}; |
|
|
border-radius: 4px; |
|
|
padding: 5px; |
|
|
background-color: white; |
|
|
}} |
|
|
QLabel {{ |
|
|
color: {UIColors.TEXT}; |
|
|
}} |
|
|
""" |
|
|
self.setStyleSheet(style) |
|
|
|
|
|
def browse_audio_file(self): |
|
|
"""Выбор аудиофайла""" |
|
|
file_path, _ = QFileDialog.getOpenFileName( |
|
|
self, |
|
|
"Выберите аудиофайл", |
|
|
"", |
|
|
"Audio Files (*.wav *.mp3 *.m4a);;All Files (*)" |
|
|
) |
|
|
if file_path: |
|
|
self.audio_path = file_path |
|
|
self.file_path_label.setText(file_path) |
|
|
|
|
|
def browse_model_path(self): |
|
|
"""Выбор пути к модели""" |
|
|
path = QFileDialog.getExistingDirectory( |
|
|
self, |
|
|
"Выберите папку с моделью Whisper" |
|
|
) |
|
|
if path: |
|
|
self.model_path_input.setText(path) |
|
|
|
|
|
def browse_terms_path(self): |
|
|
"""Выбор пути к файлу терминов""" |
|
|
file_path, _ = QFileDialog.getOpenFileName( |
|
|
self, |
|
|
"Выберите файл с медицинскими терминами", |
|
|
"", |
|
|
"Text Files (*.txt);;All Files (*)" |
|
|
) |
|
|
if file_path: |
|
|
self.terms_path_input.setText(file_path) |
|
|
|
|
|
def open_patient_dialog(self): |
|
|
"""Открытие диалога ввода данных пациента""" |
|
|
dialog = PatientDataDialog(self) |
|
|
if dialog.exec() == QDialog.DialogCode.Accepted: |
|
|
self.patient_data = dialog.get_data() |
|
|
self.update_patient_info_label() |
|
|
|
|
|
def update_patient_info_label(self): |
|
|
"""Обновление метки с информацией о пациенте""" |
|
|
if self.patient_data: |
|
|
text = f"Пациент: {self.patient_data.get('patient_name', 'Не указано')}" |
|
|
self.patient_info_label.setText(text) |
|
|
self.patient_info_label.setStyleSheet("color: #4CAF50; font-weight: bold;") |
|
|
else: |
|
|
self.patient_info_label.setText("Данные пациента не заполнены") |
|
|
self.patient_info_label.setStyleSheet("color: #ff9800; font-style: italic;") |
|
|
|
|
|
def save_settings(self): |
|
|
"""Сохранение настроек""" |
|
|
try: |
|
|
|
|
|
QMessageBox.information( |
|
|
self, |
|
|
"Успешно", |
|
|
"Настройки сохранены!" |
|
|
) |
|
|
except Exception as e: |
|
|
QMessageBox.critical( |
|
|
self, |
|
|
"Ошибка", |
|
|
f"Ошибка при сохранении настроек: {e}" |
|
|
) |
|
|
|
|
|
def start_transcription(self): |
|
|
"""Запуск транскрибирования""" |
|
|
|
|
|
if not self.audio_path: |
|
|
QMessageBox.warning( |
|
|
self, |
|
|
"Ошибка", |
|
|
"Пожалуйста, выберите аудиофайл!" |
|
|
) |
|
|
return |
|
|
|
|
|
|
|
|
if not Path(self.audio_path).exists(): |
|
|
QMessageBox.critical( |
|
|
self, |
|
|
"Ошибка", |
|
|
f"Файл не найден: {self.audio_path}" |
|
|
) |
|
|
return |
|
|
|
|
|
|
|
|
if self.report_checkbox.isChecked() and not self.patient_data: |
|
|
QMessageBox.warning( |
|
|
self, |
|
|
"Ошибка", |
|
|
"Для создания отчёта необходимо заполнить данные пациента!" |
|
|
) |
|
|
return |
|
|
|
|
|
|
|
|
self.start_btn.setEnabled(False) |
|
|
self.progress_bar.setVisible(True) |
|
|
self.progress_bar.setValue(0) |
|
|
|
|
|
|
|
|
try: |
|
|
from pipeline.pipeline_config import PipelineConfig |
|
|
|
|
|
config = PipelineConfig( |
|
|
model_path=Path(self.model_path_input.text()), |
|
|
device=self.device_combo.currentText(), |
|
|
dtype=self.dtype_combo.currentText(), |
|
|
medical_terms_file=Path(self.terms_path_input.text()), |
|
|
openai_api_key=self.api_key_input.text() or None, |
|
|
openai_model=self.model_combo.currentText(), |
|
|
correction_enabled=self.llm_checkbox.isChecked(), |
|
|
save_original=self.save_original_checkbox.isChecked(), |
|
|
save_corrected=True, |
|
|
generate_report=self.report_checkbox.isChecked() |
|
|
) |
|
|
except Exception as e: |
|
|
QMessageBox.critical( |
|
|
self, |
|
|
"Ошибка конфигурации", |
|
|
f"Ошибка при создании конфига: {e}" |
|
|
) |
|
|
self.start_btn.setEnabled(True) |
|
|
self.progress_bar.setVisible(False) |
|
|
return |
|
|
|
|
|
|
|
|
self.worker = TranscriptionWorker( |
|
|
self.audio_path, |
|
|
config, |
|
|
self.patient_data |
|
|
) |
|
|
|
|
|
self.worker.signals.progress.connect(self.on_progress) |
|
|
self.worker.signals.finished.connect(self.on_finished) |
|
|
self.worker.signals.error.connect(self.on_error) |
|
|
|
|
|
self.worker.start() |
|
|
|
|
|
def on_progress(self, message: str): |
|
|
"""Обновление прогресса""" |
|
|
self.progress_label.setText(message) |
|
|
self.progress_bar.setValue(min(self.progress_bar.value() + 20, 90)) |
|
|
|
|
|
def on_finished(self, result: dict): |
|
|
"""Завершение обработки""" |
|
|
self.progress_bar.setValue(100) |
|
|
self.start_btn.setEnabled(True) |
|
|
|
|
|
|
|
|
output = "=" * 60 + "\n" |
|
|
output += "РЕЗУЛЬТАТЫ ОБРАБОТКИ\n" |
|
|
output += "=" * 60 + "\n\n" |
|
|
|
|
|
if "transcription_original" in result: |
|
|
output += "ОРИГИНАЛЬНАЯ ТРАНСКРИПЦИЯ:\n" |
|
|
output += "-" * 40 + "\n" |
|
|
output += result["transcription_original"] + "\n\n" |
|
|
|
|
|
if "transcription_corrected" in result: |
|
|
output += "СКОРРЕКТИРОВАННАЯ ТРАНСКРИПЦИЯ:\n" |
|
|
output += "-" * 40 + "\n" |
|
|
output += result["transcription_corrected"] + "\n\n" |
|
|
|
|
|
if "report_path" in result: |
|
|
output += "✓ Отчёт успешно создан:\n" |
|
|
output += f" {result['report_path']}\n\n" |
|
|
|
|
|
output += "=" * 60 + "\n" |
|
|
output += "Обработка завершена успешно!" |
|
|
|
|
|
self.results_text.setText(output) |
|
|
|
|
|
QMessageBox.information( |
|
|
self, |
|
|
"Успешно", |
|
|
"Транскрибирование завершено!" |
|
|
) |
|
|
|
|
|
def on_error(self, error_message: str): |
|
|
"""Обработка ошибки""" |
|
|
self.progress_bar.setVisible(False) |
|
|
self.start_btn.setEnabled(True) |
|
|
|
|
|
self.results_text.setText(f"ОШИБКА:\n{error_message}") |
|
|
|
|
|
QMessageBox.critical( |
|
|
self, |
|
|
"Ошибка обработки", |
|
|
f"Произошла ошибка:\n{error_message}" |
|
|
) |
|
|
|
|
|
|
|
|
def main(): |
|
|
"""Запуск приложения""" |
|
|
from PyQt6.QtWidgets import QApplication |
|
|
|
|
|
app = QApplication(sys.argv) |
|
|
window = MedicalTranscriptionApp() |
|
|
window.show() |
|
|
sys.exit(app.exec()) |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
|
|
logging.basicConfig( |
|
|
level=logging.INFO, |
|
|
format='%(asctime)s - %(levelname)s - %(message)s' |
|
|
) |
|
|
main() |
|
|
|