""" Configuration dialog for the microscopy object detection application. """ from PySide6.QtWidgets import ( QDialog, QVBoxLayout, QHBoxLayout, QFormLayout, QPushButton, QLineEdit, QSpinBox, QDoubleSpinBox, QFileDialog, QTabWidget, QWidget, QLabel, QGroupBox, ) from PySide6.QtCore import Qt from src.utils.config_manager import ConfigManager from src.utils.logger import get_logger logger = get_logger(__name__) class ConfigDialog(QDialog): """Configuration dialog window.""" def __init__(self, config_manager: ConfigManager, parent=None): super().__init__(parent) self.config_manager = config_manager self.setWindowTitle("Settings") self.setMinimumWidth(500) self.setMinimumHeight(400) self._setup_ui() self._load_settings() def _setup_ui(self): """Setup user interface.""" layout = QVBoxLayout() # Create tab widget for different setting categories self.tab_widget = QTabWidget() # General settings tab general_tab = self._create_general_tab() self.tab_widget.addTab(general_tab, "General") # Training settings tab training_tab = self._create_training_tab() self.tab_widget.addTab(training_tab, "Training") # Detection settings tab detection_tab = self._create_detection_tab() self.tab_widget.addTab(detection_tab, "Detection") layout.addWidget(self.tab_widget) # Buttons button_layout = QHBoxLayout() button_layout.addStretch() self.save_button = QPushButton("Save") self.save_button.clicked.connect(self.accept) button_layout.addWidget(self.save_button) self.cancel_button = QPushButton("Cancel") self.cancel_button.clicked.connect(self.reject) button_layout.addWidget(self.cancel_button) layout.addLayout(button_layout) self.setLayout(layout) def _create_general_tab(self) -> QWidget: """Create general settings tab.""" widget = QWidget() layout = QVBoxLayout() # Image repository group repo_group = QGroupBox("Image Repository") repo_layout = QFormLayout() # Repository path path_layout = QHBoxLayout() self.repo_path_edit = QLineEdit() self.repo_path_edit.setPlaceholderText("Path to image repository") path_layout.addWidget(self.repo_path_edit) browse_button = QPushButton("Browse...") browse_button.clicked.connect(self._browse_repository) path_layout.addWidget(browse_button) repo_layout.addRow("Base Path:", path_layout) repo_group.setLayout(repo_layout) layout.addWidget(repo_group) # Database group db_group = QGroupBox("Database") db_layout = QFormLayout() self.db_path_edit = QLineEdit() self.db_path_edit.setPlaceholderText("Path to database file") db_layout.addRow("Database Path:", self.db_path_edit) db_group.setLayout(db_layout) layout.addWidget(db_group) # Models group models_group = QGroupBox("Models") models_layout = QFormLayout() self.models_dir_edit = QLineEdit() self.models_dir_edit.setPlaceholderText("Directory for saved models") models_layout.addRow("Models Directory:", self.models_dir_edit) self.base_model_edit = QLineEdit() self.base_model_edit.setPlaceholderText("yolov8s.pt") models_layout.addRow("Default Base Model:", self.base_model_edit) models_group.setLayout(models_layout) layout.addWidget(models_group) layout.addStretch() widget.setLayout(layout) return widget def _create_training_tab(self) -> QWidget: """Create training settings tab.""" widget = QWidget() layout = QVBoxLayout() form_layout = QFormLayout() # Epochs self.epochs_spin = QSpinBox() self.epochs_spin.setRange(1, 1000) self.epochs_spin.setValue(100) form_layout.addRow("Default Epochs:", self.epochs_spin) # Batch size self.batch_size_spin = QSpinBox() self.batch_size_spin.setRange(1, 128) self.batch_size_spin.setValue(16) form_layout.addRow("Default Batch Size:", self.batch_size_spin) # Image size self.imgsz_spin = QSpinBox() self.imgsz_spin.setRange(320, 1280) self.imgsz_spin.setSingleStep(32) self.imgsz_spin.setValue(640) form_layout.addRow("Default Image Size:", self.imgsz_spin) # Patience self.patience_spin = QSpinBox() self.patience_spin.setRange(1, 200) self.patience_spin.setValue(50) form_layout.addRow("Default Patience:", self.patience_spin) # Learning rate self.lr_spin = QDoubleSpinBox() self.lr_spin.setRange(0.0001, 0.1) self.lr_spin.setSingleStep(0.001) self.lr_spin.setDecimals(4) self.lr_spin.setValue(0.01) form_layout.addRow("Default Learning Rate:", self.lr_spin) layout.addLayout(form_layout) layout.addStretch() widget.setLayout(layout) return widget def _create_detection_tab(self) -> QWidget: """Create detection settings tab.""" widget = QWidget() layout = QVBoxLayout() form_layout = QFormLayout() # Confidence threshold self.conf_spin = QDoubleSpinBox() self.conf_spin.setRange(0.0, 1.0) self.conf_spin.setSingleStep(0.05) self.conf_spin.setDecimals(2) self.conf_spin.setValue(0.25) form_layout.addRow("Default Confidence:", self.conf_spin) # IoU threshold self.iou_spin = QDoubleSpinBox() self.iou_spin.setRange(0.0, 1.0) self.iou_spin.setSingleStep(0.05) self.iou_spin.setDecimals(2) self.iou_spin.setValue(0.45) form_layout.addRow("Default IoU:", self.iou_spin) # Max batch size self.max_batch_spin = QSpinBox() self.max_batch_spin.setRange(1, 1000) self.max_batch_spin.setValue(100) form_layout.addRow("Max Batch Size:", self.max_batch_spin) layout.addLayout(form_layout) layout.addStretch() widget.setLayout(layout) return widget def _browse_repository(self): """Browse for image repository directory.""" directory = QFileDialog.getExistingDirectory( self, "Select Image Repository", self.repo_path_edit.text() ) if directory: self.repo_path_edit.setText(directory) def _load_settings(self): """Load current settings into dialog.""" # General settings self.repo_path_edit.setText( self.config_manager.get("image_repository.base_path", "") ) self.db_path_edit.setText( self.config_manager.get("database.path", "data/detections.db") ) self.models_dir_edit.setText( self.config_manager.get("models.models_directory", "data/models") ) self.base_model_edit.setText( self.config_manager.get("models.default_base_model", "yolov8s.pt") ) # Training settings self.epochs_spin.setValue( self.config_manager.get("training.default_epochs", 100) ) self.batch_size_spin.setValue( self.config_manager.get("training.default_batch_size", 16) ) self.imgsz_spin.setValue(self.config_manager.get("training.default_imgsz", 640)) self.patience_spin.setValue( self.config_manager.get("training.default_patience", 50) ) self.lr_spin.setValue(self.config_manager.get("training.default_lr0", 0.01)) # Detection settings self.conf_spin.setValue( self.config_manager.get("detection.default_confidence", 0.25) ) self.iou_spin.setValue(self.config_manager.get("detection.default_iou", 0.45)) self.max_batch_spin.setValue( self.config_manager.get("detection.max_batch_size", 100) ) def accept(self): """Save settings and close dialog.""" logger.info("Saving configuration") # Save general settings self.config_manager.set( "image_repository.base_path", self.repo_path_edit.text() ) self.config_manager.set("database.path", self.db_path_edit.text()) self.config_manager.set("models.models_directory", self.models_dir_edit.text()) self.config_manager.set( "models.default_base_model", self.base_model_edit.text() ) # Save training settings self.config_manager.set("training.default_epochs", self.epochs_spin.value()) self.config_manager.set( "training.default_batch_size", self.batch_size_spin.value() ) self.config_manager.set("training.default_imgsz", self.imgsz_spin.value()) self.config_manager.set("training.default_patience", self.patience_spin.value()) self.config_manager.set("training.default_lr0", self.lr_spin.value()) # Save detection settings self.config_manager.set("detection.default_confidence", self.conf_spin.value()) self.config_manager.set("detection.default_iou", self.iou_spin.value()) self.config_manager.set("detection.max_batch_size", self.max_batch_spin.value()) # Save to file self.config_manager.save_config() super().accept()