| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498 |
- """
- 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])
|