""" Quality Control Panel Panel for quality analysis controls including camera selection, parameters, and processing options. """ from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QCheckBox, QSlider, QComboBox, QLineEdit, QSizePolicy, QGroupBox, QFrame, QFileDialog) from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtGui import QFont from ui.widgets.panel_header import PanelHeader from ui.widgets.mode_toggle import ModeToggle class QualityControlPanel(QWidget): """ Panel for quality control settings and analysis triggering. Includes camera selection, detection parameters, and processing options. """ # Signals analyze_requested = pyqtSignal() open_file_requested = pyqtSignal(str) # file_path parameter_changed = pyqtSignal(str, float) # parameter_name, value mode_changed = pyqtSignal(str) # 'live' or 'file' def __init__(self, parent=None): super().__init__(parent) self.current_mode = "file" # Default to file mode self.parameter_values = { 'shape_sensitivity': 70, 'defect_threshold': 60, 'locule_algorithm': 'Deep Learning v3' } self.init_ui() def init_ui(self): """Initialize the panel UI.""" # Set size policy self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) # Main panel container with card styling self.setStyleSheet(""" QWidget { background-color: white; border: 1px solid #ddd; } """) # Header using the PanelHeader widget header = PanelHeader( title="Control Panel", color="#34495e" # Dark gray for control ) layout.addWidget(header) # Content area content = QWidget() content.setStyleSheet(""" background-color: #2c3e50; border: 1px solid #34495e; border-top: none; """) content_layout = QVBoxLayout(content) content_layout.setContentsMargins(10, 10, 10, 10) content_layout.setSpacing(12) # Camera Selection Section self._create_camera_section(content_layout) # Separator self._add_separator(content_layout) # Detection Parameters Section self._create_parameters_section(content_layout) # Separator self._add_separator(content_layout) # Processing Options Section self._create_processing_section(content_layout) # Separator self._add_separator(content_layout) # Test Mode Toggle Section self._create_mode_toggle_section(content_layout) # Separator self._add_separator(content_layout) # Analyze Button self._create_analyze_button(content_layout) layout.addWidget(content, 1) def _create_camera_section(self, parent_layout): """Create camera selection section.""" # Section label cameras_label = QLabel("Active Cameras:") cameras_label.setFont(QFont("Arial", 11, QFont.Bold)) cameras_label.setStyleSheet("color: #ecf0f1;") parent_layout.addWidget(cameras_label) # Camera checkboxes (mark coming soon features) cameras = [ ("RGB Top View", True, "#27ae60", "Currently showing sample data"), ("RGB Side View", True, "#27ae60", "Currently showing sample data"), ("Thermal Camera", False, "#e74c3c", "Coming with thermal camera hardware"), ("Multispectral", True, "#27ae60", "Coming with multispectral camera integration") ] self.camera_checkboxes = {} for camera_name, checked, color, tooltip in cameras: checkbox = QCheckBox(camera_name) checkbox.setChecked(checked) checkbox.setFont(QFont("Arial", 10)) if tooltip: checkbox.setToolTip(tooltip) if checked: checkbox.setStyleSheet(f""" QCheckBox {{ color: #ecf0f1; spacing: 8px; }} QCheckBox::indicator:checked {{ background-color: {color}; border: 1px solid {color}; width: 12px; height: 12px; border-radius: 2px; }} QCheckBox::indicator:unchecked {{ background-color: transparent; border: 1px solid #7f8c8d; width: 12px; height: 12px; border-radius: 2px; }} """) else: checkbox.setStyleSheet(""" QCheckBox { color: #7f8c8d; spacing: 8px; } QCheckBox::indicator:checked { background-color: #e74c3c; border: 1px solid #e74c3c; width: 12px; height: 12px; border-radius: 2px; } QCheckBox::indicator:unchecked { background-color: transparent; border: 1px solid #7f8c8d; width: 12px; height: 12px; border-radius: 2px; } """) self.camera_checkboxes[camera_name] = checkbox parent_layout.addWidget(checkbox) def _create_parameters_section(self, parent_layout): """Create detection parameters section.""" # Section label params_label = QLabel("Detection Parameters:") params_label.setFont(QFont("Arial", 11, QFont.Bold)) params_label.setStyleSheet("color: #ecf0f1;") parent_layout.addWidget(params_label) # Shape Sensitivity Slider self._create_parameter_slider( parent_layout, "Shape Sensitivity:", "shape_sensitivity", self.parameter_values['shape_sensitivity'], 0, 100, "%" ) # Defect Threshold Slider self._create_parameter_slider( parent_layout, "Defect Threshold:", "defect_threshold", self.parameter_values['defect_threshold'], 0, 100, "%" ) # Locule Algorithm Dropdown locule_label = QLabel("Locule Algorithm:") locule_label.setFont(QFont("Arial", 10)) locule_label.setStyleSheet("color: #ecf0f1;") parent_layout.addWidget(locule_label) self.locule_combo = QComboBox() self.locule_combo.addItems([ "Watershed Seg. v2", "Edge Detection v1", "Deep Learning v3" ]) self.locule_combo.setCurrentText(self.parameter_values['locule_algorithm']) self.locule_combo.setFont(QFont("Arial", 9)) self.locule_combo.setStyleSheet(""" QComboBox { background-color: #34495e; color: #ecf0f1; border: 1px solid #4a5f7a; border-radius: 3px; padding: 5px; min-width: 120px; } QComboBox::drop-down { border: none; width: 20px; } QComboBox::down-arrow { image: url(down_arrow.png); width: 12px; height: 12px; } """) parent_layout.addWidget(self.locule_combo) def _create_parameter_slider(self, parent_layout, label_text, param_name, value, min_val, max_val, suffix=""): """Create a parameter slider with label and value display.""" # Container for slider row slider_widget = QWidget() slider_layout = QHBoxLayout(slider_widget) slider_layout.setContentsMargins(0, 0, 0, 0) slider_layout.setSpacing(8) # Label label = QLabel(label_text) label.setFont(QFont("Arial", 10)) label.setStyleSheet("color: #ecf0f1;") label.setFixedWidth(120) # Slider slider = QSlider(Qt.Horizontal) slider.setValue(value) slider.setMinimum(min_val) slider.setMaximum(max_val) slider.setStyleSheet(""" QSlider::groove:horizontal { border: 1px solid #4a5f7a; height: 8px; background: #34495e; margin: 0px; border-radius: 4px; } QSlider::handle:horizontal { background: #3498db; border: 1px solid #2980b9; width: 16px; margin: -4px 0; border-radius: 8px; } QSlider::handle:horizontal:hover { background: #5dade2; } """) # Value label value_label = QLabel(f"{value}{suffix}") value_label.setFont(QFont("Arial", 9)) value_label.setStyleSheet("color: #3498db; font-weight: bold;") value_label.setFixedWidth(35) value_label.setAlignment(Qt.AlignCenter) # Store references for updates slider_widget.slider = slider slider_widget.value_label = value_label slider_widget.param_name = param_name slider_widget.suffix = suffix # Connect slider signal def update_value(): val = slider.value() value_label.setText(f"{val}{suffix}") self.parameter_values[param_name] = val self.parameter_changed.emit(param_name, val) slider.valueChanged.connect(update_value) # Add to layout slider_layout.addWidget(label) slider_layout.addWidget(slider, 1) slider_layout.addWidget(value_label) parent_layout.addWidget(slider_widget) def _create_processing_section(self, parent_layout): """Create processing options section.""" # Section label processing_label = QLabel("Processing Options:") processing_label.setFont(QFont("Arial", 11, QFont.Bold)) processing_label.setStyleSheet("color: #ecf0f1;") parent_layout.addWidget(processing_label) # Processing checkboxes (mark coming soon features) options = [ ("Auto Contrast", True, "#27ae60", "Currently functional"), ("Edge Enhancement", True, "#27ae60", "Currently functional"), ("Color Correction", False, "#7f8c8d", "Coming in future update") ] for option_name, checked, color, tooltip in options: checkbox = QCheckBox(option_name) checkbox.setChecked(checked) checkbox.setFont(QFont("Arial", 10)) if tooltip: checkbox.setToolTip(tooltip) if checked: checkbox.setStyleSheet(f""" QCheckBox {{ color: #ecf0f1; spacing: 8px; }} QCheckBox::indicator:checked {{ background-color: {color}; border: 1px solid {color}; width: 12px; height: 12px; border-radius: 2px; }} """) else: checkbox.setStyleSheet(""" QCheckBox { color: #7f8c8d; spacing: 8px; } QCheckBox::indicator:checked { background-color: #e74c3c; border: 1px solid #e74c3c; width: 12px; height: 12px; border-radius: 2px; } QCheckBox::indicator:unchecked { background-color: transparent; border: 1px solid #7f8c8d; width: 12px; height: 12px; border-radius: 2px; } """) parent_layout.addWidget(checkbox) def _create_analyze_button(self, parent_layout): """Create the analyze button.""" self.analyze_btn = QPushButton("ANALYZE") self.analyze_btn.setFixedHeight(40) self.analyze_btn.setFont(QFont("Arial", 14, QFont.Bold)) self.analyze_btn.setStyleSheet(""" QPushButton { background-color: #27ae60; color: white; border: 2px solid #229954; border-radius: 5px; font-size: 14px; font-weight: bold; } QPushButton:hover { background-color: #229954; border-color: #1e8449; } QPushButton:pressed { background-color: #1e8449; padding-top: 2px; } QPushButton:disabled { background-color: #7f8c8d; border-color: #95a5a6; color: #bdc3c7; } """) # Connect signal self.analyze_btn.clicked.connect(self._on_analyze_clicked) parent_layout.addWidget(self.analyze_btn) def _create_mode_toggle_section(self, parent_layout): """Create the test mode toggle section.""" # Section label mode_label = QLabel("Test Mode:") mode_label.setFont(QFont("Arial", 11, QFont.Bold)) mode_label.setStyleSheet("color: #ecf0f1;") parent_layout.addWidget(mode_label) # Mode toggle widget self.mode_toggle = ModeToggle() self.mode_toggle.mode_changed.connect(self._on_mode_changed) # Disable live mode (coming soon) self.mode_toggle.live_btn.setEnabled(False) self.mode_toggle.live_btn.setToolTip("Live camera analysis - Coming with camera hardware integration") parent_layout.addWidget(self.mode_toggle) def _add_separator(self, parent_layout): """Add a visual separator line.""" separator = QFrame() separator.setFrameShape(QFrame.HLine) separator.setStyleSheet("color: #4a5f7a;") separator.setFixedHeight(1) parent_layout.addWidget(separator) def set_analyzing(self, is_analyzing): """Set analyzing state for the button.""" self.analyze_btn.setEnabled(not is_analyzing) if is_analyzing: self.analyze_btn.setText("ANALYZING...") self.analyze_btn.setStyleSheet(""" QPushButton { background-color: #f39c12; color: white; border: 2px solid #e67e22; border-radius: 5px; font-size: 14px; font-weight: bold; } """) else: self._update_analyze_button_label() self.analyze_btn.setStyleSheet(""" QPushButton { background-color: #27ae60; color: white; border: 2px solid #229954; border-radius: 5px; font-size: 14px; font-weight: bold; } QPushButton:hover { background-color: #229954; border-color: #1e8449; } """) def get_parameters(self): """Get current parameter values.""" return { 'shape_sensitivity': self.parameter_values['shape_sensitivity'], 'defect_threshold': self.parameter_values['defect_threshold'], 'locule_algorithm': self.locule_combo.currentText(), 'cameras': {name: cb.isChecked() for name, cb in self.camera_checkboxes.items()}, 'current_mode': self.current_mode } def _on_mode_changed(self, mode: str): """Handle mode change from the toggle.""" self.current_mode = mode self.mode_changed.emit(mode) self._update_analyze_button_label() def _update_analyze_button_label(self): """Update analyze button label and tooltip based on mode.""" if self.current_mode == 'file': self.analyze_btn.setText("OPEN FILE") self.analyze_btn.setToolTip("Select an image file to analyze quality") else: self.analyze_btn.setText("ANALYZE") self.analyze_btn.setToolTip("Live camera analysis (coming soon)") def _on_analyze_clicked(self): """Handle analyze button click based on current mode.""" if self.current_mode == 'file': self._open_file_dialog() else: # Live mode - coming soon self.analyze_requested.emit() def _open_file_dialog(self): """Open file dialog to select image file.""" from utils.config import DEFAULT_DIRS, FILE_FILTERS file_dialog = QFileDialog() file_dialog.setNameFilter(FILE_FILTERS.get("image", "Image Files (*.jpg *.jpeg *.png *.bmp *.JPG *.JPEG *.PNG *.BMP)")) file_dialog.setWindowTitle("Select Image for Quality Analysis") # Set default directory if available default_dir = DEFAULT_DIRS.get("image", ".") file_dialog.setDirectory(default_dir) if file_dialog.exec_(): file_paths = file_dialog.selectedFiles() if file_paths and file_paths[0]: self.open_file_requested.emit(file_paths[0])