| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227 |
- """
- Maturity Classification Tab
- This tab handles multispectral TIFF file processing for maturity classification.
- """
- from PyQt5.QtWidgets import QWidget, QGridLayout, QHBoxLayout, QMessageBox
- from PyQt5.QtCore import pyqtSignal
- from PyQt5.QtGui import QPixmap, QImage
- import time
- from ui.panels.rgb_preview_panel import RGBPreviewPanel
- from ui.panels.multispectral_panel import MultispectralPanel
- from ui.panels.maturity_results_panel import MaturityResultsPanel
- from ui.panels.maturity_control_panel import MaturityControlPanel
- from ui.panels.analysis_timeline_panel import AnalysisTimelinePanel
- from utils.session_manager import SessionManager
- class MaturityTab(QWidget):
- """
- Tab for maturity classification using multispectral analysis.
-
- Features:
- - RGB Preview (Coming Soon)
- - Multispectral Analysis with Grad-CAM
- - Maturity Results with Confidence Bars
- - Control Panel
- - Analysis Timeline & Statistics
-
- Signals:
- load_tiff_requested: Emitted when user wants to load a TIFF file
- """
-
- load_tiff_requested = pyqtSignal()
-
- def __init__(self, parent=None):
- super().__init__(parent)
- self.session_manager = SessionManager()
- self.current_file_path = None
- self.processing_start_time = None
- self.init_ui()
-
- def init_ui(self):
- """Initialize the UI components with 2x2+2 grid layout matching mockup."""
- # Main layout with content area styling to match mockup
- main_layout = QHBoxLayout(self)
- main_layout.setContentsMargins(20, 20, 20, 0)
- main_layout.setSpacing(20)
-
- # Left panels grid (2x2)
- left_grid = QGridLayout()
- left_grid.setSpacing(20)
- left_grid.setContentsMargins(0, 0, 0, 0)
-
- # Create left grid panels
- self.rgb_panel = RGBPreviewPanel()
- self.multispectral_panel = MultispectralPanel()
- self.results_panel = MaturityResultsPanel()
-
- # Create a placeholder panel for the bottom-left (can be used for band visualization)
- self.band_panel = QWidget()
- self.band_panel.setStyleSheet("""
- QWidget {
- background-color: white;
- border: 1px solid #ddd;
- }
- """)
-
- # Add panels to grid
- left_grid.addWidget(self.rgb_panel, 0, 0, 1, 1)
- left_grid.addWidget(self.multispectral_panel, 0, 1, 1, 1)
- left_grid.addWidget(self.band_panel, 1, 0, 1, 1)
- left_grid.addWidget(self.results_panel, 1, 1, 1, 1)
-
- # Set column and row stretch factors for equal sizing
- left_grid.setColumnStretch(0, 1)
- left_grid.setColumnStretch(1, 1)
- left_grid.setRowStretch(0, 1)
- left_grid.setRowStretch(1, 1)
-
- # Right panels
- self.control_panel = MaturityControlPanel()
- self.timeline_panel = AnalysisTimelinePanel()
-
- # Add panels to main content layout with adjusted proportions
- # Increased left grid to make previews more square, reduced timeline
- main_layout.addLayout(left_grid, 3) # Stretch factor 3 for 2x2 grid (larger previews)
- main_layout.addWidget(self.control_panel, 0) # No stretch (fixed width ~200px)
- main_layout.addWidget(self.timeline_panel, 1) # Stretch factor 1 (reduced from 2)
-
- # Connect signals
- self.control_panel.run_test_clicked.connect(self._on_run_test)
- self.control_panel.open_file_clicked.connect(self._on_open_file)
- self.control_panel.stop_clicked.connect(self._on_stop)
- self.control_panel.reset_clicked.connect(self._on_reset)
- self.timeline_panel.save_audio_clicked.connect(self._on_save_tiff)
-
- def _on_run_test(self):
- """Handle RUN TEST button click."""
- self.processing_start_time = time.time()
- self.control_panel.set_processing(True)
- # TODO: Implement live mode
- QMessageBox.information(
- self,
- "Live Mode",
- "Live multispectral camera mode coming soon!"
- )
- self.control_panel.set_processing(False)
-
- def _on_open_file(self):
- """Handle OPEN FILE button click."""
- self.processing_start_time = time.time()
- self.control_panel.set_processing(True)
- self.load_tiff_requested.emit()
-
- def _on_stop(self):
- """Handle STOP button click."""
- self.control_panel.set_processing(False)
- # TODO: Implement worker cancellation
-
- def _on_reset(self):
- """Handle RESET button click."""
- # Clear all displays
- self.multispectral_panel.clear() if hasattr(self.multispectral_panel, 'clear') else None
- self.results_panel.clear_results()
- self.current_file_path = None
- self.control_panel.set_processing(False)
-
- def _on_save_tiff(self):
- """Handle save TIFF button click."""
- if self.current_file_path:
- QMessageBox.information(
- self,
- "Save TIFF",
- "TIFF save functionality coming in future update.\n\n"
- f"Would save: {self.current_file_path}"
- )
-
- def set_loading(self, is_loading: bool):
- """
- Set loading state.
-
- Args:
- is_loading: Whether processing is active
- """
- self.control_panel.set_processing(is_loading)
-
- def update_results(self, gradcam_image: QImage, predicted_class: str,
- probabilities: dict, file_path: str):
- """
- Update the tab with processing results.
-
- Args:
- gradcam_image: QImage of the Grad-CAM visualization
- predicted_class: Predicted maturity class
- probabilities: Dictionary of class probabilities (0-1 scale)
- file_path: Path to the TIFF file
- """
- # Calculate processing time
- processing_time = 0
- if self.processing_start_time:
- processing_time = time.time() - self.processing_start_time
- self.processing_start_time = None
-
- # Store current file path
- self.current_file_path = file_path
-
- # Update Multispectral Panel with Grad-CAM
- if gradcam_image:
- pixmap = QPixmap.fromImage(gradcam_image)
- # Update the multispectral panel
- self.multispectral_panel.set_image(pixmap)
-
- # Update Maturity Results Panel
- self.results_panel.update_results(
- predicted_class,
- probabilities,
- processing_time,
- model_version="MaturityNet v1.0"
- )
-
- # Convert probability to percentage for display
- confidence = probabilities.get(predicted_class, 0) * 100
- if not confidence:
- # Try case-insensitive match
- for key, value in probabilities.items():
- if key.lower() == predicted_class.lower():
- confidence = value * 100
- break
-
- self.timeline_panel.add_test_result(
- predicted_class,
- confidence,
- processing_time
- )
-
- # Add to session manager
- self.session_manager.add_result(
- classification=predicted_class,
- confidence=confidence,
- probabilities=probabilities,
- processing_time=processing_time,
- file_path=file_path
- )
-
- # Update statistics
- stats = self.session_manager.get_statistics_summary()
- self.timeline_panel.update_statistics(
- total_tests=stats["total_tests"],
- avg_processing=stats["avg_processing_time"],
- ripe_count=stats["ripe_count"], # Reuse ripe_count for maturity count
- session_start=stats["session_start"]
- )
-
- # Update processing state
- self.control_panel.set_processing(False)
-
- def clear_results(self):
- """Clear all displayed results and reset session."""
- self.multispectral_panel.clear() if hasattr(self.multispectral_panel, 'clear') else None
- self.results_panel.clear_results()
- self.timeline_panel.clear_timeline()
- self.session_manager.clear_session()
- self.current_file_path = None
- self.processing_start_time = None
|