quality_control_panel.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. """
  2. Quality Control Panel
  3. Panel for quality analysis controls including camera selection, parameters, and processing options.
  4. """
  5. from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
  6. QPushButton, QCheckBox, QSlider, QComboBox,
  7. QLineEdit, QSizePolicy, QGroupBox, QFrame, QFileDialog)
  8. from PyQt5.QtCore import Qt, pyqtSignal
  9. from PyQt5.QtGui import QFont
  10. from ui.widgets.panel_header import PanelHeader
  11. from ui.widgets.mode_toggle import ModeToggle
  12. class QualityControlPanel(QWidget):
  13. """
  14. Panel for quality control settings and analysis triggering.
  15. Includes camera selection, detection parameters, and processing options.
  16. """
  17. # Signals
  18. analyze_requested = pyqtSignal()
  19. open_file_requested = pyqtSignal(str) # file_path
  20. parameter_changed = pyqtSignal(str, float) # parameter_name, value
  21. mode_changed = pyqtSignal(str) # 'live' or 'file'
  22. def __init__(self, parent=None):
  23. super().__init__(parent)
  24. self.current_mode = "file" # Default to file mode
  25. self.parameter_values = {
  26. 'shape_sensitivity': 70,
  27. 'defect_threshold': 60,
  28. 'locule_algorithm': 'Deep Learning v3'
  29. }
  30. self.init_ui()
  31. def init_ui(self):
  32. """Initialize the panel UI."""
  33. # Set size policy
  34. self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
  35. layout = QVBoxLayout(self)
  36. layout.setContentsMargins(0, 0, 0, 0)
  37. layout.setSpacing(0)
  38. # Main panel container with card styling
  39. self.setStyleSheet("""
  40. QWidget {
  41. background-color: white;
  42. border: 1px solid #ddd;
  43. }
  44. """)
  45. # Header using the PanelHeader widget
  46. header = PanelHeader(
  47. title="Control Panel",
  48. color="#34495e" # Dark gray for control
  49. )
  50. layout.addWidget(header)
  51. # Content area
  52. content = QWidget()
  53. content.setStyleSheet("""
  54. background-color: #2c3e50;
  55. border: 1px solid #34495e;
  56. border-top: none;
  57. """)
  58. content_layout = QVBoxLayout(content)
  59. content_layout.setContentsMargins(10, 10, 10, 10)
  60. content_layout.setSpacing(12)
  61. # Camera Selection Section
  62. self._create_camera_section(content_layout)
  63. # Separator
  64. self._add_separator(content_layout)
  65. # Detection Parameters Section
  66. self._create_parameters_section(content_layout)
  67. # Separator
  68. self._add_separator(content_layout)
  69. # Processing Options Section
  70. self._create_processing_section(content_layout)
  71. # Separator
  72. self._add_separator(content_layout)
  73. # Test Mode Toggle Section
  74. self._create_mode_toggle_section(content_layout)
  75. # Separator
  76. self._add_separator(content_layout)
  77. # Analyze Button
  78. self._create_analyze_button(content_layout)
  79. layout.addWidget(content, 1)
  80. def _create_camera_section(self, parent_layout):
  81. """Create camera selection section."""
  82. # Section label
  83. cameras_label = QLabel("Active Cameras:")
  84. cameras_label.setFont(QFont("Arial", 11, QFont.Bold))
  85. cameras_label.setStyleSheet("color: #ecf0f1;")
  86. parent_layout.addWidget(cameras_label)
  87. # Camera checkboxes (mark coming soon features)
  88. cameras = [
  89. ("RGB Top View", True, "#27ae60", "Currently showing sample data"),
  90. ("RGB Side View", True, "#27ae60", "Currently showing sample data"),
  91. ("Thermal Camera", False, "#e74c3c", "Coming with thermal camera hardware"),
  92. ("Multispectral", True, "#27ae60", "Coming with multispectral camera integration")
  93. ]
  94. self.camera_checkboxes = {}
  95. for camera_name, checked, color, tooltip in cameras:
  96. checkbox = QCheckBox(camera_name)
  97. checkbox.setChecked(checked)
  98. checkbox.setFont(QFont("Arial", 10))
  99. if tooltip:
  100. checkbox.setToolTip(tooltip)
  101. if checked:
  102. checkbox.setStyleSheet(f"""
  103. QCheckBox {{
  104. color: #ecf0f1;
  105. spacing: 8px;
  106. }}
  107. QCheckBox::indicator:checked {{
  108. background-color: {color};
  109. border: 1px solid {color};
  110. width: 12px;
  111. height: 12px;
  112. border-radius: 2px;
  113. }}
  114. QCheckBox::indicator:unchecked {{
  115. background-color: transparent;
  116. border: 1px solid #7f8c8d;
  117. width: 12px;
  118. height: 12px;
  119. border-radius: 2px;
  120. }}
  121. """)
  122. else:
  123. checkbox.setStyleSheet("""
  124. QCheckBox {
  125. color: #7f8c8d;
  126. spacing: 8px;
  127. }
  128. QCheckBox::indicator:checked {
  129. background-color: #e74c3c;
  130. border: 1px solid #e74c3c;
  131. width: 12px;
  132. height: 12px;
  133. border-radius: 2px;
  134. }
  135. QCheckBox::indicator:unchecked {
  136. background-color: transparent;
  137. border: 1px solid #7f8c8d;
  138. width: 12px;
  139. height: 12px;
  140. border-radius: 2px;
  141. }
  142. """)
  143. self.camera_checkboxes[camera_name] = checkbox
  144. parent_layout.addWidget(checkbox)
  145. def _create_parameters_section(self, parent_layout):
  146. """Create detection parameters section."""
  147. # Section label
  148. params_label = QLabel("Detection Parameters:")
  149. params_label.setFont(QFont("Arial", 11, QFont.Bold))
  150. params_label.setStyleSheet("color: #ecf0f1;")
  151. parent_layout.addWidget(params_label)
  152. # Shape Sensitivity Slider
  153. self._create_parameter_slider(
  154. parent_layout, "Shape Sensitivity:", "shape_sensitivity",
  155. self.parameter_values['shape_sensitivity'], 0, 100, "%"
  156. )
  157. # Defect Threshold Slider
  158. self._create_parameter_slider(
  159. parent_layout, "Defect Threshold:", "defect_threshold",
  160. self.parameter_values['defect_threshold'], 0, 100, "%"
  161. )
  162. # Locule Algorithm Dropdown
  163. locule_label = QLabel("Locule Algorithm:")
  164. locule_label.setFont(QFont("Arial", 10))
  165. locule_label.setStyleSheet("color: #ecf0f1;")
  166. parent_layout.addWidget(locule_label)
  167. self.locule_combo = QComboBox()
  168. self.locule_combo.addItems([
  169. "Watershed Seg. v2",
  170. "Edge Detection v1",
  171. "Deep Learning v3"
  172. ])
  173. self.locule_combo.setCurrentText(self.parameter_values['locule_algorithm'])
  174. self.locule_combo.setFont(QFont("Arial", 9))
  175. self.locule_combo.setStyleSheet("""
  176. QComboBox {
  177. background-color: #34495e;
  178. color: #ecf0f1;
  179. border: 1px solid #4a5f7a;
  180. border-radius: 3px;
  181. padding: 5px;
  182. min-width: 120px;
  183. }
  184. QComboBox::drop-down {
  185. border: none;
  186. width: 20px;
  187. }
  188. QComboBox::down-arrow {
  189. image: url(down_arrow.png);
  190. width: 12px;
  191. height: 12px;
  192. }
  193. """)
  194. parent_layout.addWidget(self.locule_combo)
  195. def _create_parameter_slider(self, parent_layout, label_text, param_name, value, min_val, max_val, suffix=""):
  196. """Create a parameter slider with label and value display."""
  197. # Container for slider row
  198. slider_widget = QWidget()
  199. slider_layout = QHBoxLayout(slider_widget)
  200. slider_layout.setContentsMargins(0, 0, 0, 0)
  201. slider_layout.setSpacing(8)
  202. # Label
  203. label = QLabel(label_text)
  204. label.setFont(QFont("Arial", 10))
  205. label.setStyleSheet("color: #ecf0f1;")
  206. label.setFixedWidth(120)
  207. # Slider
  208. slider = QSlider(Qt.Horizontal)
  209. slider.setValue(value)
  210. slider.setMinimum(min_val)
  211. slider.setMaximum(max_val)
  212. slider.setStyleSheet("""
  213. QSlider::groove:horizontal {
  214. border: 1px solid #4a5f7a;
  215. height: 8px;
  216. background: #34495e;
  217. margin: 0px;
  218. border-radius: 4px;
  219. }
  220. QSlider::handle:horizontal {
  221. background: #3498db;
  222. border: 1px solid #2980b9;
  223. width: 16px;
  224. margin: -4px 0;
  225. border-radius: 8px;
  226. }
  227. QSlider::handle:horizontal:hover {
  228. background: #5dade2;
  229. }
  230. """)
  231. # Value label
  232. value_label = QLabel(f"{value}{suffix}")
  233. value_label.setFont(QFont("Arial", 9))
  234. value_label.setStyleSheet("color: #3498db; font-weight: bold;")
  235. value_label.setFixedWidth(35)
  236. value_label.setAlignment(Qt.AlignCenter)
  237. # Store references for updates
  238. slider_widget.slider = slider
  239. slider_widget.value_label = value_label
  240. slider_widget.param_name = param_name
  241. slider_widget.suffix = suffix
  242. # Connect slider signal
  243. def update_value():
  244. val = slider.value()
  245. value_label.setText(f"{val}{suffix}")
  246. self.parameter_values[param_name] = val
  247. self.parameter_changed.emit(param_name, val)
  248. slider.valueChanged.connect(update_value)
  249. # Add to layout
  250. slider_layout.addWidget(label)
  251. slider_layout.addWidget(slider, 1)
  252. slider_layout.addWidget(value_label)
  253. parent_layout.addWidget(slider_widget)
  254. def _create_processing_section(self, parent_layout):
  255. """Create processing options section."""
  256. # Section label
  257. processing_label = QLabel("Processing Options:")
  258. processing_label.setFont(QFont("Arial", 11, QFont.Bold))
  259. processing_label.setStyleSheet("color: #ecf0f1;")
  260. parent_layout.addWidget(processing_label)
  261. # Processing checkboxes (mark coming soon features)
  262. options = [
  263. ("Auto Contrast", True, "#27ae60", "Currently functional"),
  264. ("Edge Enhancement", True, "#27ae60", "Currently functional"),
  265. ("Color Correction", False, "#7f8c8d", "Coming in future update")
  266. ]
  267. for option_name, checked, color, tooltip in options:
  268. checkbox = QCheckBox(option_name)
  269. checkbox.setChecked(checked)
  270. checkbox.setFont(QFont("Arial", 10))
  271. if tooltip:
  272. checkbox.setToolTip(tooltip)
  273. if checked:
  274. checkbox.setStyleSheet(f"""
  275. QCheckBox {{
  276. color: #ecf0f1;
  277. spacing: 8px;
  278. }}
  279. QCheckBox::indicator:checked {{
  280. background-color: {color};
  281. border: 1px solid {color};
  282. width: 12px;
  283. height: 12px;
  284. border-radius: 2px;
  285. }}
  286. """)
  287. else:
  288. checkbox.setStyleSheet("""
  289. QCheckBox {
  290. color: #7f8c8d;
  291. spacing: 8px;
  292. }
  293. QCheckBox::indicator:checked {
  294. background-color: #e74c3c;
  295. border: 1px solid #e74c3c;
  296. width: 12px;
  297. height: 12px;
  298. border-radius: 2px;
  299. }
  300. QCheckBox::indicator:unchecked {
  301. background-color: transparent;
  302. border: 1px solid #7f8c8d;
  303. width: 12px;
  304. height: 12px;
  305. border-radius: 2px;
  306. }
  307. """)
  308. parent_layout.addWidget(checkbox)
  309. def _create_analyze_button(self, parent_layout):
  310. """Create the analyze button."""
  311. self.analyze_btn = QPushButton("ANALYZE")
  312. self.analyze_btn.setFixedHeight(40)
  313. self.analyze_btn.setFont(QFont("Arial", 14, QFont.Bold))
  314. self.analyze_btn.setStyleSheet("""
  315. QPushButton {
  316. background-color: #27ae60;
  317. color: white;
  318. border: 2px solid #229954;
  319. border-radius: 5px;
  320. font-size: 14px;
  321. font-weight: bold;
  322. }
  323. QPushButton:hover {
  324. background-color: #229954;
  325. border-color: #1e8449;
  326. }
  327. QPushButton:pressed {
  328. background-color: #1e8449;
  329. padding-top: 2px;
  330. }
  331. QPushButton:disabled {
  332. background-color: #7f8c8d;
  333. border-color: #95a5a6;
  334. color: #bdc3c7;
  335. }
  336. """)
  337. # Connect signal
  338. self.analyze_btn.clicked.connect(self._on_analyze_clicked)
  339. parent_layout.addWidget(self.analyze_btn)
  340. def _create_mode_toggle_section(self, parent_layout):
  341. """Create the test mode toggle section."""
  342. # Section label
  343. mode_label = QLabel("Test Mode:")
  344. mode_label.setFont(QFont("Arial", 11, QFont.Bold))
  345. mode_label.setStyleSheet("color: #ecf0f1;")
  346. parent_layout.addWidget(mode_label)
  347. # Mode toggle widget
  348. self.mode_toggle = ModeToggle()
  349. self.mode_toggle.mode_changed.connect(self._on_mode_changed)
  350. # Disable live mode (coming soon)
  351. self.mode_toggle.live_btn.setEnabled(False)
  352. self.mode_toggle.live_btn.setToolTip("Live camera analysis - Coming with camera hardware integration")
  353. parent_layout.addWidget(self.mode_toggle)
  354. def _add_separator(self, parent_layout):
  355. """Add a visual separator line."""
  356. separator = QFrame()
  357. separator.setFrameShape(QFrame.HLine)
  358. separator.setStyleSheet("color: #4a5f7a;")
  359. separator.setFixedHeight(1)
  360. parent_layout.addWidget(separator)
  361. def set_analyzing(self, is_analyzing):
  362. """Set analyzing state for the button."""
  363. self.analyze_btn.setEnabled(not is_analyzing)
  364. if is_analyzing:
  365. self.analyze_btn.setText("ANALYZING...")
  366. self.analyze_btn.setStyleSheet("""
  367. QPushButton {
  368. background-color: #f39c12;
  369. color: white;
  370. border: 2px solid #e67e22;
  371. border-radius: 5px;
  372. font-size: 14px;
  373. font-weight: bold;
  374. }
  375. """)
  376. else:
  377. self._update_analyze_button_label()
  378. self.analyze_btn.setStyleSheet("""
  379. QPushButton {
  380. background-color: #27ae60;
  381. color: white;
  382. border: 2px solid #229954;
  383. border-radius: 5px;
  384. font-size: 14px;
  385. font-weight: bold;
  386. }
  387. QPushButton:hover {
  388. background-color: #229954;
  389. border-color: #1e8449;
  390. }
  391. """)
  392. def get_parameters(self):
  393. """Get current parameter values."""
  394. return {
  395. 'shape_sensitivity': self.parameter_values['shape_sensitivity'],
  396. 'defect_threshold': self.parameter_values['defect_threshold'],
  397. 'locule_algorithm': self.locule_combo.currentText(),
  398. 'cameras': {name: cb.isChecked() for name, cb in self.camera_checkboxes.items()},
  399. 'current_mode': self.current_mode
  400. }
  401. def _on_mode_changed(self, mode: str):
  402. """Handle mode change from the toggle."""
  403. self.current_mode = mode
  404. self.mode_changed.emit(mode)
  405. self._update_analyze_button_label()
  406. def _update_analyze_button_label(self):
  407. """Update analyze button label and tooltip based on mode."""
  408. if self.current_mode == 'file':
  409. self.analyze_btn.setText("OPEN FILE")
  410. self.analyze_btn.setToolTip("Select an image file to analyze quality")
  411. else:
  412. self.analyze_btn.setText("ANALYZE")
  413. self.analyze_btn.setToolTip("Live camera analysis (coming soon)")
  414. def _on_analyze_clicked(self):
  415. """Handle analyze button click based on current mode."""
  416. if self.current_mode == 'file':
  417. self._open_file_dialog()
  418. else:
  419. # Live mode - coming soon
  420. self.analyze_requested.emit()
  421. def _open_file_dialog(self):
  422. """Open file dialog to select image file."""
  423. from utils.config import DEFAULT_DIRS, FILE_FILTERS
  424. file_dialog = QFileDialog()
  425. file_dialog.setNameFilter(FILE_FILTERS.get("image", "Image Files (*.jpg *.jpeg *.png *.bmp *.JPG *.JPEG *.PNG *.BMP)"))
  426. file_dialog.setWindowTitle("Select Image for Quality Analysis")
  427. # Set default directory if available
  428. default_dir = DEFAULT_DIRS.get("image", ".")
  429. file_dialog.setDirectory(default_dir)
  430. if file_dialog.exec_():
  431. file_paths = file_dialog.selectedFiles()
  432. if file_paths and file_paths[0]:
  433. self.open_file_requested.emit(file_paths[0])