maturity_tab.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. """
  2. Maturity Classification Tab
  3. This tab handles multispectral TIFF file processing for maturity classification.
  4. """
  5. from PyQt5.QtWidgets import QWidget, QGridLayout, QHBoxLayout, QMessageBox
  6. from PyQt5.QtCore import pyqtSignal
  7. from PyQt5.QtGui import QPixmap, QImage
  8. import time
  9. from ui.panels.rgb_preview_panel import RGBPreviewPanel
  10. from ui.panels.multispectral_panel import MultispectralPanel
  11. from ui.panels.maturity_results_panel import MaturityResultsPanel
  12. from ui.panels.maturity_control_panel import MaturityControlPanel
  13. from ui.panels.analysis_timeline_panel import AnalysisTimelinePanel
  14. from utils.session_manager import SessionManager
  15. class MaturityTab(QWidget):
  16. """
  17. Tab for maturity classification using multispectral analysis.
  18. Features:
  19. - RGB Preview (Coming Soon)
  20. - Multispectral Analysis with Grad-CAM
  21. - Maturity Results with Confidence Bars
  22. - Control Panel
  23. - Analysis Timeline & Statistics
  24. Signals:
  25. load_tiff_requested: Emitted when user wants to load a TIFF file
  26. """
  27. load_tiff_requested = pyqtSignal()
  28. def __init__(self, parent=None):
  29. super().__init__(parent)
  30. self.session_manager = SessionManager()
  31. self.current_file_path = None
  32. self.processing_start_time = None
  33. self.init_ui()
  34. def init_ui(self):
  35. """Initialize the UI components with 2x2+2 grid layout matching mockup."""
  36. # Main layout with content area styling to match mockup
  37. main_layout = QHBoxLayout(self)
  38. main_layout.setContentsMargins(20, 20, 20, 0)
  39. main_layout.setSpacing(20)
  40. # Left panels grid (2x2)
  41. left_grid = QGridLayout()
  42. left_grid.setSpacing(20)
  43. left_grid.setContentsMargins(0, 0, 0, 0)
  44. # Create left grid panels
  45. self.rgb_panel = RGBPreviewPanel()
  46. self.multispectral_panel = MultispectralPanel()
  47. self.results_panel = MaturityResultsPanel()
  48. # Create a placeholder panel for the bottom-left (can be used for band visualization)
  49. self.band_panel = QWidget()
  50. self.band_panel.setStyleSheet("""
  51. QWidget {
  52. background-color: white;
  53. border: 1px solid #ddd;
  54. }
  55. """)
  56. # Add panels to grid
  57. left_grid.addWidget(self.rgb_panel, 0, 0, 1, 1)
  58. left_grid.addWidget(self.multispectral_panel, 0, 1, 1, 1)
  59. left_grid.addWidget(self.band_panel, 1, 0, 1, 1)
  60. left_grid.addWidget(self.results_panel, 1, 1, 1, 1)
  61. # Set column and row stretch factors for equal sizing
  62. left_grid.setColumnStretch(0, 1)
  63. left_grid.setColumnStretch(1, 1)
  64. left_grid.setRowStretch(0, 1)
  65. left_grid.setRowStretch(1, 1)
  66. # Right panels
  67. self.control_panel = MaturityControlPanel()
  68. self.timeline_panel = AnalysisTimelinePanel()
  69. # Add panels to main content layout with adjusted proportions
  70. # Increased left grid to make previews more square, reduced timeline
  71. main_layout.addLayout(left_grid, 3) # Stretch factor 3 for 2x2 grid (larger previews)
  72. main_layout.addWidget(self.control_panel, 0) # No stretch (fixed width ~200px)
  73. main_layout.addWidget(self.timeline_panel, 1) # Stretch factor 1 (reduced from 2)
  74. # Connect signals
  75. self.control_panel.run_test_clicked.connect(self._on_run_test)
  76. self.control_panel.open_file_clicked.connect(self._on_open_file)
  77. self.control_panel.stop_clicked.connect(self._on_stop)
  78. self.control_panel.reset_clicked.connect(self._on_reset)
  79. self.timeline_panel.save_audio_clicked.connect(self._on_save_tiff)
  80. def _on_run_test(self):
  81. """Handle RUN TEST button click."""
  82. self.processing_start_time = time.time()
  83. self.control_panel.set_processing(True)
  84. # TODO: Implement live mode
  85. QMessageBox.information(
  86. self,
  87. "Live Mode",
  88. "Live multispectral camera mode coming soon!"
  89. )
  90. self.control_panel.set_processing(False)
  91. def _on_open_file(self):
  92. """Handle OPEN FILE button click."""
  93. self.processing_start_time = time.time()
  94. self.control_panel.set_processing(True)
  95. self.load_tiff_requested.emit()
  96. def _on_stop(self):
  97. """Handle STOP button click."""
  98. self.control_panel.set_processing(False)
  99. # TODO: Implement worker cancellation
  100. def _on_reset(self):
  101. """Handle RESET button click."""
  102. # Clear all displays
  103. self.multispectral_panel.clear() if hasattr(self.multispectral_panel, 'clear') else None
  104. self.results_panel.clear_results()
  105. self.current_file_path = None
  106. self.control_panel.set_processing(False)
  107. def _on_save_tiff(self):
  108. """Handle save TIFF button click."""
  109. if self.current_file_path:
  110. QMessageBox.information(
  111. self,
  112. "Save TIFF",
  113. "TIFF save functionality coming in future update.\n\n"
  114. f"Would save: {self.current_file_path}"
  115. )
  116. def set_loading(self, is_loading: bool):
  117. """
  118. Set loading state.
  119. Args:
  120. is_loading: Whether processing is active
  121. """
  122. self.control_panel.set_processing(is_loading)
  123. def update_results(self, gradcam_image: QImage, predicted_class: str,
  124. probabilities: dict, file_path: str):
  125. """
  126. Update the tab with processing results.
  127. Args:
  128. gradcam_image: QImage of the Grad-CAM visualization
  129. predicted_class: Predicted maturity class
  130. probabilities: Dictionary of class probabilities (0-1 scale)
  131. file_path: Path to the TIFF file
  132. """
  133. # Calculate processing time
  134. processing_time = 0
  135. if self.processing_start_time:
  136. processing_time = time.time() - self.processing_start_time
  137. self.processing_start_time = None
  138. # Store current file path
  139. self.current_file_path = file_path
  140. # Update Multispectral Panel with Grad-CAM
  141. if gradcam_image:
  142. pixmap = QPixmap.fromImage(gradcam_image)
  143. # Update the multispectral panel
  144. self.multispectral_panel.set_image(pixmap)
  145. # Update Maturity Results Panel
  146. self.results_panel.update_results(
  147. predicted_class,
  148. probabilities,
  149. processing_time,
  150. model_version="MaturityNet v1.0"
  151. )
  152. # Convert probability to percentage for display
  153. confidence = probabilities.get(predicted_class, 0) * 100
  154. if not confidence:
  155. # Try case-insensitive match
  156. for key, value in probabilities.items():
  157. if key.lower() == predicted_class.lower():
  158. confidence = value * 100
  159. break
  160. self.timeline_panel.add_test_result(
  161. predicted_class,
  162. confidence,
  163. processing_time
  164. )
  165. # Add to session manager
  166. self.session_manager.add_result(
  167. classification=predicted_class,
  168. confidence=confidence,
  169. probabilities=probabilities,
  170. processing_time=processing_time,
  171. file_path=file_path
  172. )
  173. # Update statistics
  174. stats = self.session_manager.get_statistics_summary()
  175. self.timeline_panel.update_statistics(
  176. total_tests=stats["total_tests"],
  177. avg_processing=stats["avg_processing_time"],
  178. ripe_count=stats["ripe_count"], # Reuse ripe_count for maturity count
  179. session_start=stats["session_start"]
  180. )
  181. # Update processing state
  182. self.control_panel.set_processing(False)
  183. def clear_results(self):
  184. """Clear all displayed results and reset session."""
  185. self.multispectral_panel.clear() if hasattr(self.multispectral_panel, 'clear') else None
  186. self.results_panel.clear_results()
  187. self.timeline_panel.clear_timeline()
  188. self.session_manager.clear_session()
  189. self.current_file_path = None
  190. self.processing_start_time = None