manual_input_dialog.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. """
  2. Manual Input Dialog
  3. Dialog for manual camera input from different sources.
  4. """
  5. from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel,
  6. QPushButton, QLineEdit, QFileDialog, QGroupBox,
  7. QFormLayout, QMessageBox, QCheckBox, QWidget)
  8. from PyQt5.QtCore import Qt, pyqtSignal
  9. from PyQt5.QtGui import QPixmap
  10. from pathlib import Path
  11. from resources.styles import GROUP_BOX_STYLE
  12. class ManualInputDialog(QDialog):
  13. """
  14. Dialog for collecting manual camera inputs.
  15. Allows users to select files from different camera sources:
  16. - DSLR (RGB images)
  17. - Multispectral camera
  18. - Thermal camera
  19. Signals:
  20. inputs_confirmed: Emitted when user confirms inputs with paths dict
  21. """
  22. inputs_confirmed = pyqtSignal(dict) # Emits dict of {source: file_path}
  23. def __init__(self, parent=None):
  24. """Initialize the manual input dialog."""
  25. super().__init__(parent)
  26. self.setWindowTitle("Manual Camera Input")
  27. self.setModal(True)
  28. self.setMinimumWidth(900)
  29. self.setMinimumHeight(700)
  30. self.inputs = {
  31. 'dslr_side': '', # Side view for defect model
  32. 'dslr_top': '', # Top view for locule counter
  33. 'multispectral': '',
  34. 'thermal': '',
  35. 'audio': ''
  36. }
  37. # Get project root directory
  38. from pathlib import Path
  39. self.project_root = Path(__file__).parent.parent.parent # Navigate to project root
  40. self.init_ui()
  41. def init_ui(self):
  42. """Initialize the UI components."""
  43. layout = QVBoxLayout(self)
  44. # Instructions
  45. instructions = QLabel(
  46. "Please select data from camera and sensor sources.\n"
  47. "At least one source is required to proceed. All inputs are optional."
  48. )
  49. instructions.setWordWrap(True)
  50. instructions.setStyleSheet("font-size: 16px; color: #555; padding: 10px;")
  51. layout.addWidget(instructions)
  52. # DSLR Input Group
  53. dslr_group = QGroupBox("DSLR Camera (RGB)")
  54. dslr_group.setStyleSheet(GROUP_BOX_STYLE)
  55. dslr_layout = QFormLayout()
  56. # Side View (Plain) - for defect model
  57. self.dslr_side_path_edit = QLineEdit()
  58. self.dslr_side_path_edit.setPlaceholderText("No file selected...")
  59. self.dslr_side_path_edit.setReadOnly(True)
  60. self.dslr_side_path_edit.setMinimumHeight(30)
  61. self.dslr_side_path_edit.setStyleSheet("font-size: 14px; padding: 5px;")
  62. dslr_side_btn = QPushButton("Browse...")
  63. dslr_side_btn.clicked.connect(lambda: self.select_file('dslr_side'))
  64. dslr_side_btn.setMinimumHeight(35)
  65. dslr_side_btn.setStyleSheet("font-size: 14px; font-weight: bold;")
  66. dslr_side_layout = QHBoxLayout()
  67. dslr_side_layout.addWidget(self.dslr_side_path_edit, 1)
  68. dslr_side_layout.addWidget(dslr_side_btn)
  69. side_label = QLabel("Side View (Plain):")
  70. side_label.setStyleSheet("font-size: 15px; font-weight: bold;")
  71. dslr_layout.addRow(side_label, dslr_side_layout)
  72. # Top View (RGB) - for locule counter
  73. self.dslr_top_path_edit = QLineEdit()
  74. self.dslr_top_path_edit.setPlaceholderText("No file selected...")
  75. self.dslr_top_path_edit.setReadOnly(True)
  76. self.dslr_top_path_edit.setMinimumHeight(30)
  77. self.dslr_top_path_edit.setStyleSheet("font-size: 14px; padding: 5px;")
  78. dslr_top_btn = QPushButton("Browse...")
  79. dslr_top_btn.clicked.connect(lambda: self.select_file('dslr_top'))
  80. dslr_top_btn.setMinimumHeight(35)
  81. dslr_top_btn.setStyleSheet("font-size: 14px; font-weight: bold;")
  82. dslr_top_layout = QHBoxLayout()
  83. dslr_top_layout.addWidget(self.dslr_top_path_edit, 1)
  84. dslr_top_layout.addWidget(dslr_top_btn)
  85. top_label = QLabel("Top View (RGB):")
  86. top_label.setStyleSheet("font-size: 15px; font-weight: bold;")
  87. dslr_layout.addRow(top_label, dslr_top_layout)
  88. dslr_group.setLayout(dslr_layout)
  89. layout.addWidget(dslr_group)
  90. # Multispectral Input Group
  91. multi_group = QGroupBox("Multispectral Camera (2nd Look)")
  92. multi_group.setStyleSheet(GROUP_BOX_STYLE)
  93. multi_layout = QFormLayout()
  94. self.multi_path_edit = QLineEdit()
  95. self.multi_path_edit.setPlaceholderText("No file selected...")
  96. self.multi_path_edit.setReadOnly(True)
  97. self.multi_path_edit.setMinimumHeight(30)
  98. self.multi_path_edit.setStyleSheet("font-size: 14px; padding: 5px;")
  99. multi_btn = QPushButton("Browse...")
  100. multi_btn.clicked.connect(lambda: self.select_file('multispectral'))
  101. multi_btn.setMinimumHeight(35)
  102. multi_btn.setStyleSheet("font-size: 14px; font-weight: bold;")
  103. multi_input_layout = QHBoxLayout()
  104. multi_input_layout.addWidget(self.multi_path_edit, 1)
  105. multi_input_layout.addWidget(multi_btn)
  106. multi_label = QLabel("TIFF File:")
  107. multi_label.setStyleSheet("font-size: 15px; font-weight: bold;")
  108. multi_layout.addRow(multi_label, multi_input_layout)
  109. multi_group.setLayout(multi_layout)
  110. layout.addWidget(multi_group)
  111. # Thermal Input Group
  112. thermal_group = QGroupBox("Thermal Camera (AnalyzIR)")
  113. thermal_group.setStyleSheet(GROUP_BOX_STYLE)
  114. thermal_layout = QFormLayout()
  115. self.thermal_path_edit = QLineEdit()
  116. self.thermal_path_edit.setPlaceholderText("No file selected...")
  117. self.thermal_path_edit.setReadOnly(True)
  118. self.thermal_path_edit.setMinimumHeight(30)
  119. self.thermal_path_edit.setStyleSheet("font-size: 14px; padding: 5px;")
  120. thermal_btn = QPushButton("Browse...")
  121. thermal_btn.clicked.connect(lambda: self.select_file('thermal'))
  122. thermal_btn.setMinimumHeight(35)
  123. thermal_btn.setStyleSheet("font-size: 14px; font-weight: bold;")
  124. thermal_input_layout = QHBoxLayout()
  125. thermal_input_layout.addWidget(self.thermal_path_edit, 1)
  126. thermal_input_layout.addWidget(thermal_btn)
  127. thermal_label = QLabel("CSV File:")
  128. thermal_label.setStyleSheet("font-size: 15px; font-weight: bold;")
  129. thermal_layout.addRow(thermal_label, thermal_input_layout)
  130. thermal_group.setLayout(thermal_layout)
  131. layout.addWidget(thermal_group)
  132. # Audio Input Group
  133. audio_group = QGroupBox("Audio/Sound Sensor")
  134. audio_group.setStyleSheet(GROUP_BOX_STYLE)
  135. audio_layout = QFormLayout()
  136. self.audio_path_edit = QLineEdit()
  137. self.audio_path_edit.setPlaceholderText("No file selected...")
  138. self.audio_path_edit.setReadOnly(True)
  139. self.audio_path_edit.setMinimumHeight(30)
  140. self.audio_path_edit.setStyleSheet("font-size: 14px; padding: 5px;")
  141. audio_btn = QPushButton("Browse...")
  142. audio_btn.clicked.connect(lambda: self.select_file('audio'))
  143. audio_btn.setMinimumHeight(35)
  144. audio_btn.setStyleSheet("font-size: 14px; font-weight: bold;")
  145. audio_input_layout = QHBoxLayout()
  146. audio_input_layout.addWidget(self.audio_path_edit, 1)
  147. audio_input_layout.addWidget(audio_btn)
  148. audio_label = QLabel("WAV File:")
  149. audio_label.setStyleSheet("font-size: 15px; font-weight: bold;")
  150. audio_layout.addRow(audio_label, audio_input_layout)
  151. audio_group.setLayout(audio_layout)
  152. layout.addWidget(audio_group)
  153. layout.addStretch()
  154. # Button box
  155. button_layout = QHBoxLayout()
  156. button_layout.addStretch()
  157. cancel_btn = QPushButton("Cancel")
  158. cancel_btn.clicked.connect(self.reject)
  159. cancel_btn.setMinimumWidth(100)
  160. cancel_btn.setMinimumHeight(40)
  161. cancel_btn.setStyleSheet("font-size: 15px; font-weight: bold;")
  162. button_layout.addWidget(cancel_btn)
  163. confirm_btn = QPushButton("Confirm")
  164. confirm_btn.clicked.connect(self.confirm_inputs)
  165. confirm_btn.setStyleSheet("""
  166. QPushButton {
  167. background-color: #27ae60;
  168. color: white;
  169. font-weight: bold;
  170. font-size: 15px;
  171. padding: 10px 16px;
  172. border-radius: 4px;
  173. }
  174. QPushButton:hover {
  175. background-color: #229954;
  176. }
  177. """)
  178. confirm_btn.setMinimumWidth(100)
  179. confirm_btn.setMinimumHeight(40)
  180. button_layout.addWidget(confirm_btn)
  181. layout.addLayout(button_layout)
  182. def select_file(self, source: str):
  183. """
  184. Open file dialog for specific camera source.
  185. Args:
  186. source: Camera source ('dslr_side', 'dslr_top', 'multispectral', 'thermal', 'audio')
  187. """
  188. if source == 'dslr_side':
  189. title = "Select DSLR Side View Image"
  190. filters = "Image Files (*.jpg *.jpeg *.png *.bmp);;All Files (*.*)"
  191. edit_widget = self.dslr_side_path_edit
  192. elif source == 'dslr_top':
  193. title = "Select DSLR Top View Image (RGB)"
  194. filters = "Image Files (*.jpg *.jpeg *.png *.bmp);;All Files (*.*)"
  195. edit_widget = self.dslr_top_path_edit
  196. elif source == 'multispectral':
  197. title = "Select Multispectral TIFF"
  198. filters = "TIFF Files (*.tif *.tiff);;All Files (*.*)"
  199. edit_widget = self.multi_path_edit
  200. elif source == 'thermal':
  201. title = "Select Thermal CSV"
  202. filters = "CSV Files (*.csv);;All Files (*.*)"
  203. edit_widget = self.thermal_path_edit
  204. elif source == 'audio':
  205. title = "Select Audio File"
  206. filters = "Audio Files (*.wav);;All Files (*.*)"
  207. edit_widget = self.audio_path_edit
  208. else:
  209. return
  210. try:
  211. file_path, _ = QFileDialog.getOpenFileName(
  212. self,
  213. title,
  214. str(self.project_root),
  215. filters,
  216. options=QFileDialog.DontUseNativeDialog
  217. )
  218. if file_path and Path(file_path).exists():
  219. self.inputs[source] = file_path
  220. edit_widget.setText(file_path)
  221. except Exception as e:
  222. print(f"Error in file dialog: {e}")
  223. def confirm_inputs(self):
  224. """Validate and confirm inputs."""
  225. # Check if at least one input is provided (side view or multispectral counts)
  226. has_input = (
  227. bool(self.inputs.get('dslr_side')) or
  228. bool(self.inputs.get('dslr_top')) or
  229. bool(self.inputs.get('multispectral')) or
  230. bool(self.inputs.get('thermal')) or
  231. bool(self.inputs.get('audio'))
  232. )
  233. if not has_input:
  234. QMessageBox.warning(
  235. self,
  236. "No Input Selected",
  237. "Please select at least one camera input before confirming."
  238. )
  239. return
  240. # Emit signal with inputs
  241. self.inputs_confirmed.emit(self.inputs)
  242. self.accept()
  243. class CameraAppCheckDialog(QDialog):
  244. """
  245. Dialog to inform user about missing camera applications.
  246. """
  247. def __init__(self, missing_apps: list, parent=None):
  248. """
  249. Initialize the dialog.
  250. Args:
  251. missing_apps: List of missing application names
  252. parent: Parent widget
  253. """
  254. super().__init__(parent)
  255. self.setWindowTitle("Camera Applications Not Found")
  256. self.setModal(True)
  257. self.setMinimumWidth(500)
  258. self.setMinimumHeight(350)
  259. self.missing_apps = missing_apps
  260. self.init_ui()
  261. def init_ui(self):
  262. """Initialize the UI components."""
  263. layout = QVBoxLayout(self)
  264. # Warning icon and message
  265. message = QLabel(
  266. "The following camera applications are not currently running:\n"
  267. )
  268. message.setStyleSheet("font-size: 15px; font-weight: bold; color: #e74c3c;")
  269. layout.addWidget(message)
  270. # List of missing apps
  271. for app in self.missing_apps:
  272. app_label = QLabel(f" • {app}")
  273. app_label.setStyleSheet("font-size: 14px; color: #555; padding-left: 20px;")
  274. layout.addWidget(app_label)
  275. # Instructions
  276. instructions = QLabel(
  277. "\nPlease ensure these applications are opened and running "
  278. "before attempting automated camera capture.\n\n"
  279. "You can either:\n"
  280. "1. Open the required applications and try again\n"
  281. "2. Use Manual Input mode instead"
  282. )
  283. instructions.setWordWrap(True)
  284. instructions.setStyleSheet("font-size: 13px; color: #666; padding: 10px;")
  285. layout.addWidget(instructions)
  286. layout.addStretch()
  287. # OK button
  288. button_layout = QHBoxLayout()
  289. button_layout.addStretch()
  290. ok_btn = QPushButton("OK")
  291. ok_btn.clicked.connect(self.accept)
  292. ok_btn.setMinimumWidth(100)
  293. ok_btn.setStyleSheet("""
  294. QPushButton {
  295. padding: 8px 16px;
  296. background-color: #3498db;
  297. color: white;
  298. font-weight: bold;
  299. font-size: 14px;
  300. border-radius: 4px;
  301. }
  302. QPushButton:hover {
  303. background-color: #2980b9;
  304. }
  305. """)
  306. button_layout.addWidget(ok_btn)
  307. layout.addLayout(button_layout)