ripeness_tab.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. """
  2. Ripeness Classification Tab
  3. This tab handles audio file processing for ripeness classification.
  4. Enhanced with Phase 8: Professional grid layout with multiple data visualization panels.
  5. """
  6. from PyQt5.QtWidgets import QWidget, QGridLayout, QHBoxLayout, QMessageBox
  7. from PyQt5.QtCore import pyqtSignal
  8. from PyQt5.QtGui import QPixmap
  9. import time
  10. from ui.panels.rgb_preview_panel import RGBPreviewPanel
  11. from ui.panels.multispectral_panel import MultispectralPanel
  12. from ui.panels.audio_spectrogram_panel import AudioSpectrogramPanel
  13. from ui.panels.ripeness_results_panel import RipenessResultsPanel
  14. from ui.panels.ripeness_control_panel import RipenessControlPanel
  15. from ui.panels.analysis_timeline_panel import AnalysisTimelinePanel
  16. from utils.session_manager import SessionManager
  17. class RipenessTab(QWidget):
  18. """
  19. Tab for ripeness classification using audio analysis.
  20. Features:
  21. - RGB Preview (Coming Soon)
  22. - Multispectral Analysis (Coming Soon)
  23. - Audio Spectrogram Display
  24. - Ripeness Results with Confidence Bars
  25. - Control Panel
  26. - Analysis Timeline & Statistics
  27. Signals:
  28. load_audio_requested: Emitted when user wants to load an audio file
  29. """
  30. load_audio_requested = pyqtSignal()
  31. def __init__(self, parent=None):
  32. super().__init__(parent)
  33. self.session_manager = SessionManager()
  34. self.current_file_path = None
  35. self.processing_start_time = None
  36. self.init_ui()
  37. def init_ui(self):
  38. """Initialize the UI components with 2x2+2 grid layout matching mockup."""
  39. # Main layout with content area styling to match mockup
  40. main_layout = QHBoxLayout(self)
  41. main_layout.setContentsMargins(20, 20, 20, 0)
  42. main_layout.setSpacing(20)
  43. # Left panels grid (2x2)
  44. left_grid = QGridLayout()
  45. left_grid.setSpacing(20)
  46. left_grid.setContentsMargins(0, 0, 0, 0)
  47. # Create left grid panels
  48. self.rgb_panel = RGBPreviewPanel()
  49. self.multispectral_panel = MultispectralPanel()
  50. self.audio_panel = AudioSpectrogramPanel()
  51. self.results_panel = RipenessResultsPanel()
  52. # Add panels to grid
  53. left_grid.addWidget(self.rgb_panel, 0, 0, 1, 1)
  54. left_grid.addWidget(self.multispectral_panel, 0, 1, 1, 1)
  55. left_grid.addWidget(self.audio_panel, 1, 0, 1, 1)
  56. left_grid.addWidget(self.results_panel, 1, 1, 1, 1)
  57. # Set column and row stretch factors for equal sizing
  58. left_grid.setColumnStretch(0, 1)
  59. left_grid.setColumnStretch(1, 1)
  60. left_grid.setRowStretch(0, 1)
  61. left_grid.setRowStretch(1, 1)
  62. # Right panels
  63. self.control_panel = RipenessControlPanel()
  64. self.timeline_panel = AnalysisTimelinePanel()
  65. # Add panels to main content layout with adjusted proportions
  66. # Increased left grid to make previews more square, reduced timeline
  67. main_layout.addLayout(left_grid, 3) # Stretch factor 3 for 2x2 grid (larger previews)
  68. main_layout.addWidget(self.control_panel, 0) # No stretch (fixed width ~200px)
  69. main_layout.addWidget(self.timeline_panel, 1) # Stretch factor 1 (reduced from 2)
  70. # Connect signals
  71. self.control_panel.run_test_clicked.connect(self._on_run_test)
  72. self.control_panel.open_file_clicked.connect(self._on_run_test)
  73. self.control_panel.stop_clicked.connect(self._on_stop)
  74. self.control_panel.reset_clicked.connect(self._on_reset)
  75. self.timeline_panel.save_audio_clicked.connect(self._on_save_audio)
  76. def _on_run_test(self):
  77. """Handle RUN TEST button click."""
  78. self.processing_start_time = time.time()
  79. self.control_panel.set_processing(True)
  80. self.load_audio_requested.emit()
  81. def _on_stop(self):
  82. """Handle STOP button click."""
  83. self.control_panel.set_processing(False)
  84. # TODO: Implement worker cancellation
  85. def _on_reset(self):
  86. """Handle RESET button click."""
  87. # Clear all displays
  88. self.audio_panel.clear_spectrogram()
  89. self.results_panel.clear_results()
  90. self.current_file_path = None
  91. self.control_panel.set_processing(False)
  92. def _on_save_audio(self):
  93. """Handle save audio button click."""
  94. if self.current_file_path:
  95. QMessageBox.information(
  96. self,
  97. "Save Audio",
  98. "Audio save functionality coming in future update.\n\n"
  99. f"Would save: {self.current_file_path}"
  100. )
  101. def set_loading(self, is_loading: bool):
  102. """
  103. Set loading state.
  104. Args:
  105. is_loading: Whether processing is active
  106. """
  107. self.control_panel.set_processing(is_loading)
  108. def update_results(self, spectrogram: QPixmap, predicted_class: str,
  109. probabilities: dict, file_path: str):
  110. """
  111. Update the tab with processing results.
  112. Args:
  113. spectrogram: QPixmap of the spectrogram
  114. predicted_class: Predicted ripeness class
  115. probabilities: Dictionary of class probabilities (0-1 scale)
  116. file_path: Path to the audio file
  117. """
  118. # Calculate processing time
  119. processing_time = 0
  120. if self.processing_start_time:
  121. processing_time = time.time() - self.processing_start_time
  122. self.processing_start_time = None
  123. # Store current file path
  124. self.current_file_path = file_path
  125. # Update Audio Spectrogram Panel
  126. # TODO: Extract actual sample rate and duration from audio
  127. self.audio_panel.update_spectrogram(
  128. spectrogram,
  129. sample_rate=44100,
  130. duration=3.2,
  131. audio_path=file_path
  132. )
  133. # Update Ripeness Results Panel
  134. self.results_panel.update_results(
  135. predicted_class,
  136. probabilities,
  137. processing_time,
  138. model_version="RipeNet v3.2"
  139. )
  140. # Convert probability to percentage for display (probabilities are now in decimal form)
  141. confidence = probabilities.get(predicted_class, 0) * 100
  142. self.timeline_panel.add_test_result(
  143. predicted_class,
  144. confidence,
  145. processing_time
  146. )
  147. # Add to session manager (probabilities are already in percentage form)
  148. self.session_manager.add_result(
  149. classification=predicted_class,
  150. confidence=confidence,
  151. probabilities=probabilities,
  152. processing_time=processing_time,
  153. file_path=file_path
  154. )
  155. # Update statistics
  156. stats = self.session_manager.get_statistics_summary()
  157. self.timeline_panel.update_statistics(
  158. total_tests=stats["total_tests"],
  159. avg_processing=stats["avg_processing_time"],
  160. ripe_count=stats["ripe_count"],
  161. session_start=stats["session_start"]
  162. )
  163. # Update processing state
  164. self.control_panel.set_processing(False)
  165. def clear_results(self):
  166. """Clear all displayed results and reset session."""
  167. self.audio_panel.clear_spectrogram()
  168. self.results_panel.clear_results()
  169. self.timeline_panel.clear_timeline()
  170. self.session_manager.clear_session()
  171. self.current_file_path = None
  172. self.processing_start_time = None
  173. def get_denoise_enabled(self) -> bool:
  174. """
  175. Check if audio denoising is enabled.
  176. Returns:
  177. bool: True if denoise checkbox is checked
  178. """
  179. return self.control_panel.denoise_checkbox.isChecked()