""" Manual Input Dialog Dialog for manual camera input from different sources. """ from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QLineEdit, QFileDialog, QGroupBox, QFormLayout, QMessageBox, QCheckBox, QWidget) from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtGui import QPixmap from pathlib import Path from resources.styles import GROUP_BOX_STYLE class ManualInputDialog(QDialog): """ Dialog for collecting manual camera inputs. Allows users to select files from different camera sources: - DSLR (RGB images) - Multispectral camera - Thermal camera Signals: inputs_confirmed: Emitted when user confirms inputs with paths dict """ inputs_confirmed = pyqtSignal(dict) # Emits dict of {source: file_path} def __init__(self, parent=None): """Initialize the manual input dialog.""" super().__init__(parent) self.setWindowTitle("Manual Camera Input") self.setModal(True) self.setMinimumWidth(900) self.setMinimumHeight(700) self.inputs = { 'dslr_side': '', # Side view for defect model 'dslr_top': '', # Top view for locule counter 'multispectral': '', 'thermal': '', 'audio': '' } # Get project root directory from pathlib import Path self.project_root = Path(__file__).parent.parent.parent # Navigate to project root self.init_ui() def init_ui(self): """Initialize the UI components.""" layout = QVBoxLayout(self) # Instructions instructions = QLabel( "Please select data from camera and sensor sources.\n" "At least one source is required to proceed. All inputs are optional." ) instructions.setWordWrap(True) instructions.setStyleSheet("font-size: 16px; color: #555; padding: 10px;") layout.addWidget(instructions) # DSLR Input Group dslr_group = QGroupBox("DSLR Camera (RGB)") dslr_group.setStyleSheet(GROUP_BOX_STYLE) dslr_layout = QFormLayout() # Side View (Plain) - for defect model self.dslr_side_path_edit = QLineEdit() self.dslr_side_path_edit.setPlaceholderText("No file selected...") self.dslr_side_path_edit.setReadOnly(True) self.dslr_side_path_edit.setMinimumHeight(30) self.dslr_side_path_edit.setStyleSheet("font-size: 14px; padding: 5px;") dslr_side_btn = QPushButton("Browse...") dslr_side_btn.clicked.connect(lambda: self.select_file('dslr_side')) dslr_side_btn.setMinimumHeight(35) dslr_side_btn.setStyleSheet("font-size: 14px; font-weight: bold;") dslr_side_layout = QHBoxLayout() dslr_side_layout.addWidget(self.dslr_side_path_edit, 1) dslr_side_layout.addWidget(dslr_side_btn) side_label = QLabel("Side View (Plain):") side_label.setStyleSheet("font-size: 15px; font-weight: bold;") dslr_layout.addRow(side_label, dslr_side_layout) # Top View (RGB) - for locule counter self.dslr_top_path_edit = QLineEdit() self.dslr_top_path_edit.setPlaceholderText("No file selected...") self.dslr_top_path_edit.setReadOnly(True) self.dslr_top_path_edit.setMinimumHeight(30) self.dslr_top_path_edit.setStyleSheet("font-size: 14px; padding: 5px;") dslr_top_btn = QPushButton("Browse...") dslr_top_btn.clicked.connect(lambda: self.select_file('dslr_top')) dslr_top_btn.setMinimumHeight(35) dslr_top_btn.setStyleSheet("font-size: 14px; font-weight: bold;") dslr_top_layout = QHBoxLayout() dslr_top_layout.addWidget(self.dslr_top_path_edit, 1) dslr_top_layout.addWidget(dslr_top_btn) top_label = QLabel("Top View (RGB):") top_label.setStyleSheet("font-size: 15px; font-weight: bold;") dslr_layout.addRow(top_label, dslr_top_layout) dslr_group.setLayout(dslr_layout) layout.addWidget(dslr_group) # Multispectral Input Group multi_group = QGroupBox("Multispectral Camera (2nd Look)") multi_group.setStyleSheet(GROUP_BOX_STYLE) multi_layout = QFormLayout() self.multi_path_edit = QLineEdit() self.multi_path_edit.setPlaceholderText("No file selected...") self.multi_path_edit.setReadOnly(True) self.multi_path_edit.setMinimumHeight(30) self.multi_path_edit.setStyleSheet("font-size: 14px; padding: 5px;") multi_btn = QPushButton("Browse...") multi_btn.clicked.connect(lambda: self.select_file('multispectral')) multi_btn.setMinimumHeight(35) multi_btn.setStyleSheet("font-size: 14px; font-weight: bold;") multi_input_layout = QHBoxLayout() multi_input_layout.addWidget(self.multi_path_edit, 1) multi_input_layout.addWidget(multi_btn) multi_label = QLabel("TIFF File:") multi_label.setStyleSheet("font-size: 15px; font-weight: bold;") multi_layout.addRow(multi_label, multi_input_layout) multi_group.setLayout(multi_layout) layout.addWidget(multi_group) # Thermal Input Group thermal_group = QGroupBox("Thermal Camera (AnalyzIR)") thermal_group.setStyleSheet(GROUP_BOX_STYLE) thermal_layout = QFormLayout() self.thermal_path_edit = QLineEdit() self.thermal_path_edit.setPlaceholderText("No file selected...") self.thermal_path_edit.setReadOnly(True) self.thermal_path_edit.setMinimumHeight(30) self.thermal_path_edit.setStyleSheet("font-size: 14px; padding: 5px;") thermal_btn = QPushButton("Browse...") thermal_btn.clicked.connect(lambda: self.select_file('thermal')) thermal_btn.setMinimumHeight(35) thermal_btn.setStyleSheet("font-size: 14px; font-weight: bold;") thermal_input_layout = QHBoxLayout() thermal_input_layout.addWidget(self.thermal_path_edit, 1) thermal_input_layout.addWidget(thermal_btn) thermal_label = QLabel("CSV File:") thermal_label.setStyleSheet("font-size: 15px; font-weight: bold;") thermal_layout.addRow(thermal_label, thermal_input_layout) thermal_group.setLayout(thermal_layout) layout.addWidget(thermal_group) # Audio Input Group audio_group = QGroupBox("Audio/Sound Sensor") audio_group.setStyleSheet(GROUP_BOX_STYLE) audio_layout = QFormLayout() self.audio_path_edit = QLineEdit() self.audio_path_edit.setPlaceholderText("No file selected...") self.audio_path_edit.setReadOnly(True) self.audio_path_edit.setMinimumHeight(30) self.audio_path_edit.setStyleSheet("font-size: 14px; padding: 5px;") audio_btn = QPushButton("Browse...") audio_btn.clicked.connect(lambda: self.select_file('audio')) audio_btn.setMinimumHeight(35) audio_btn.setStyleSheet("font-size: 14px; font-weight: bold;") audio_input_layout = QHBoxLayout() audio_input_layout.addWidget(self.audio_path_edit, 1) audio_input_layout.addWidget(audio_btn) audio_label = QLabel("WAV File:") audio_label.setStyleSheet("font-size: 15px; font-weight: bold;") audio_layout.addRow(audio_label, audio_input_layout) audio_group.setLayout(audio_layout) layout.addWidget(audio_group) layout.addStretch() # Button box button_layout = QHBoxLayout() button_layout.addStretch() cancel_btn = QPushButton("Cancel") cancel_btn.clicked.connect(self.reject) cancel_btn.setMinimumWidth(100) cancel_btn.setMinimumHeight(40) cancel_btn.setStyleSheet("font-size: 15px; font-weight: bold;") button_layout.addWidget(cancel_btn) confirm_btn = QPushButton("Confirm") confirm_btn.clicked.connect(self.confirm_inputs) confirm_btn.setStyleSheet(""" QPushButton { background-color: #27ae60; color: white; font-weight: bold; font-size: 15px; padding: 10px 16px; border-radius: 4px; } QPushButton:hover { background-color: #229954; } """) confirm_btn.setMinimumWidth(100) confirm_btn.setMinimumHeight(40) button_layout.addWidget(confirm_btn) layout.addLayout(button_layout) def select_file(self, source: str): """ Open file dialog for specific camera source. Args: source: Camera source ('dslr_side', 'dslr_top', 'multispectral', 'thermal', 'audio') """ if source == 'dslr_side': title = "Select DSLR Side View Image" filters = "Image Files (*.jpg *.jpeg *.png *.bmp);;All Files (*.*)" edit_widget = self.dslr_side_path_edit elif source == 'dslr_top': title = "Select DSLR Top View Image (RGB)" filters = "Image Files (*.jpg *.jpeg *.png *.bmp);;All Files (*.*)" edit_widget = self.dslr_top_path_edit elif source == 'multispectral': title = "Select Multispectral TIFF" filters = "TIFF Files (*.tif *.tiff);;All Files (*.*)" edit_widget = self.multi_path_edit elif source == 'thermal': title = "Select Thermal CSV" filters = "CSV Files (*.csv);;All Files (*.*)" edit_widget = self.thermal_path_edit elif source == 'audio': title = "Select Audio File" filters = "Audio Files (*.wav);;All Files (*.*)" edit_widget = self.audio_path_edit else: return try: file_path, _ = QFileDialog.getOpenFileName( self, title, str(self.project_root), filters, options=QFileDialog.DontUseNativeDialog ) if file_path and Path(file_path).exists(): self.inputs[source] = file_path edit_widget.setText(file_path) except Exception as e: print(f"Error in file dialog: {e}") def confirm_inputs(self): """Validate and confirm inputs.""" # Check if at least one input is provided (side view or multispectral counts) has_input = ( bool(self.inputs.get('dslr_side')) or bool(self.inputs.get('dslr_top')) or bool(self.inputs.get('multispectral')) or bool(self.inputs.get('thermal')) or bool(self.inputs.get('audio')) ) if not has_input: QMessageBox.warning( self, "No Input Selected", "Please select at least one camera input before confirming." ) return # Emit signal with inputs self.inputs_confirmed.emit(self.inputs) self.accept() class CameraAppCheckDialog(QDialog): """ Dialog to inform user about missing camera applications. """ def __init__(self, missing_apps: list, parent=None): """ Initialize the dialog. Args: missing_apps: List of missing application names parent: Parent widget """ super().__init__(parent) self.setWindowTitle("Camera Applications Not Found") self.setModal(True) self.setMinimumWidth(500) self.setMinimumHeight(350) self.missing_apps = missing_apps self.init_ui() def init_ui(self): """Initialize the UI components.""" layout = QVBoxLayout(self) # Warning icon and message message = QLabel( "The following camera applications are not currently running:\n" ) message.setStyleSheet("font-size: 15px; font-weight: bold; color: #e74c3c;") layout.addWidget(message) # List of missing apps for app in self.missing_apps: app_label = QLabel(f" • {app}") app_label.setStyleSheet("font-size: 14px; color: #555; padding-left: 20px;") layout.addWidget(app_label) # Instructions instructions = QLabel( "\nPlease ensure these applications are opened and running " "before attempting automated camera capture.\n\n" "You can either:\n" "1. Open the required applications and try again\n" "2. Use Manual Input mode instead" ) instructions.setWordWrap(True) instructions.setStyleSheet("font-size: 13px; color: #666; padding: 10px;") layout.addWidget(instructions) layout.addStretch() # OK button button_layout = QHBoxLayout() button_layout.addStretch() ok_btn = QPushButton("OK") ok_btn.clicked.connect(self.accept) ok_btn.setMinimumWidth(100) ok_btn.setStyleSheet(""" QPushButton { padding: 8px 16px; background-color: #3498db; color: white; font-weight: bold; font-size: 14px; border-radius: 4px; } QPushButton:hover { background-color: #2980b9; } """) button_layout.addWidget(ok_btn) layout.addLayout(button_layout)