| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266 |
- """
- Analysis Timeline Panel
- Panel showing history of analysis results and session statistics.
- """
- from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
- QPushButton, QScrollArea, QFrame)
- from PyQt5.QtCore import Qt, pyqtSignal
- from PyQt5.QtGui import QFont
- from datetime import datetime
- from ui.widgets.panel_header import PanelHeader
- from ui.widgets.timeline_entry import TimelineEntry
- class AnalysisTimelinePanel(QWidget):
- """
- Panel for displaying analysis timeline and statistics.
-
- Signals:
- save_audio_clicked: Emitted when save audio button is clicked
- save_complete_clicked: Emitted when save complete package is clicked
- """
-
- save_audio_clicked = pyqtSignal()
- save_complete_clicked = pyqtSignal()
-
- def __init__(self, parent=None):
- super().__init__(parent)
- self.test_counter = 0
- self.init_ui()
-
- def init_ui(self):
- """Initialize the timeline panel UI."""
- layout = QVBoxLayout(self)
- layout.setContentsMargins(0, 0, 0, 0)
- layout.setSpacing(0)
-
- # Main panel container with card styling
- self.setStyleSheet("""
- QWidget {
- background-color: white;
- border: 1px solid #ddd;
- }
- """)
-
- # Header
- header = QWidget()
- header.setFixedHeight(25)
- header.setStyleSheet("background-color: #34495e;")
- header_layout = QHBoxLayout(header)
- header_layout.setContentsMargins(10, 0, 10, 0)
- header_layout.setSpacing(0)
-
- title = QLabel("Analysis Timeline")
- title.setStyleSheet("color: white; font-weight: bold; font-size: 16px;")
-
- header_layout.addWidget(title)
-
- # Content area
- content = QWidget()
- content.setStyleSheet("""
- background-color: white;
- border: none;
- """)
-
- content_layout = QVBoxLayout(content)
- content_layout.setSpacing(10)
- content_layout.setContentsMargins(10, 10, 10, 10)
-
- # Timeline entries (scrollable)
- timeline_scroll = QScrollArea()
- timeline_scroll.setWidgetResizable(True)
- timeline_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
- timeline_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
- timeline_scroll.setMinimumHeight(220)
- timeline_scroll.setStyleSheet("""
- QScrollArea {
- border: none;
- background-color: transparent;
- }
- """)
-
- timeline_widget = QWidget()
- self.timeline_layout = QVBoxLayout(timeline_widget)
- self.timeline_layout.setSpacing(8)
- self.timeline_layout.setContentsMargins(0, 0, 5, 0)
- self.timeline_layout.addStretch()
-
- timeline_scroll.setWidget(timeline_widget)
- content_layout.addWidget(timeline_scroll)
-
- # Separator
- separator = QFrame()
- separator.setFrameShape(QFrame.HLine)
- separator.setStyleSheet("background-color: #ecf0f1;")
- content_layout.addWidget(separator)
-
- # Snapshot options section
- snapshot_label = QLabel("Current Snapshot Options:")
- snapshot_label.setFont(QFont("Arial", 10, QFont.Bold))
- snapshot_label.setStyleSheet("color: #2c3e50; margin-top: 8px;")
- content_layout.addWidget(snapshot_label)
-
- # Snapshot buttons (first row)
- snapshot_row1 = QHBoxLayout()
- snapshot_row1.setSpacing(8)
-
- save_images_btn = self._create_snapshot_btn("Save Images", "#3498db")
- save_images_btn.setEnabled(False)
- save_images_btn.setToolTip("Save camera images - Coming soon")
- snapshot_row1.addWidget(save_images_btn)
-
- self.save_audio_btn = self._create_snapshot_btn("Save Audio", "#16a085")
- self.save_audio_btn.clicked.connect(self.save_audio_clicked.emit)
- self.save_audio_btn.setEnabled(False) # Enabled after processing
- snapshot_row1.addWidget(self.save_audio_btn)
-
- save_spectral_btn = self._create_snapshot_btn("Save Spectral", "#8e44ad")
- save_spectral_btn.setEnabled(False)
- save_spectral_btn.setToolTip("Save spectral data - Coming soon")
- snapshot_row1.addWidget(save_spectral_btn)
-
- content_layout.addLayout(snapshot_row1)
-
- # Complete package button (second row)
- self.save_complete_btn = QPushButton("Save Complete Analysis Package")
- self.save_complete_btn.setFont(QFont("Arial", 9, QFont.Bold))
- self.save_complete_btn.setFixedHeight(28)
- self.save_complete_btn.setStyleSheet("""
- QPushButton {
- background-color: #27ae60;
- border: 1px solid #229954;
- color: white;
- }
- QPushButton:hover {
- background-color: #229954;
- }
- QPushButton:disabled {
- background-color: #95a5a6;
- border-color: #7f8c8d;
- }
- """)
- self.save_complete_btn.clicked.connect(self.save_complete_clicked.emit)
- self.save_complete_btn.setEnabled(False)
- self.save_complete_btn.setToolTip("Save complete analysis - Coming soon")
- content_layout.addWidget(self.save_complete_btn)
-
- # Session statistics
- stats_label = QLabel("Session Statistics:")
- stats_label.setFont(QFont("Arial", 10, QFont.Bold))
- stats_label.setStyleSheet("color: #2c3e50; margin-top: 10px;")
- content_layout.addWidget(stats_label)
-
- # Statistics labels
- self.stats_labels = {}
- stats = [
- ("tests", "• Tests Completed: 0"),
- ("avg_time", "• Average Processing: 0.00s"),
- ("accuracy", "• Classification Accuracy: --"),
- ("ripe_count", "• Ripe Fruits Detected: 0 (0.0%)"),
- ("duration", "• Session Duration: 0h 0m")
- ]
-
- for key, text in stats:
- label = QLabel(text)
- label.setFont(QFont("Arial", 10))
- label.setStyleSheet("color: #2c3e50; line-height: 1.4;")
- self.stats_labels[key] = label
- content_layout.addWidget(label)
-
- content_layout.addStretch()
- layout.addWidget(header)
- layout.addWidget(content)
-
- def _create_snapshot_btn(self, text: str, color: str) -> QPushButton:
- """Create a snapshot button with specific color."""
- btn = QPushButton(text)
- btn.setFont(QFont("Arial", 8, QFont.Bold))
- btn.setFixedHeight(26)
- btn.setStyleSheet(f"""
- QPushButton {{
- background-color: {color};
- border: 1px solid {color};
- color: white;
- }}
- QPushButton:hover {{
- opacity: 0.9;
- }}
- QPushButton:disabled {{
- background-color: #95a5a6;
- border-color: #7f8c8d;
- }}
- """)
- return btn
-
- def add_test_result(self, classification: str, confidence: float,
- processing_time: float):
- """
- Add a new test result to the timeline.
-
- Args:
- classification: Classification result
- confidence: Confidence percentage (0-100)
- processing_time: Processing time in seconds
- """
- self.test_counter += 1
- timestamp = datetime.now().strftime("%H:%M:%S")
-
- entry = TimelineEntry(
- self.test_counter,
- timestamp,
- classification,
- confidence,
- processing_time
- )
-
- # Insert at the top (most recent first)
- self.timeline_layout.insertWidget(0, entry)
-
- # Keep only last 10 entries
- while self.timeline_layout.count() > 11: # 10 + stretch
- item = self.timeline_layout.takeAt(10)
- if item.widget():
- item.widget().deleteLater()
-
- # Enable save audio button
- self.save_audio_btn.setEnabled(True)
-
- def update_statistics(self, total_tests: int, avg_processing: float,
- ripe_count: int, session_start: datetime = None):
- """
- Update session statistics.
-
- Args:
- total_tests: Total number of tests
- avg_processing: Average processing time
- ripe_count: Number of ripe classifications
- session_start: Session start datetime
- """
- self.stats_labels["tests"].setText(f"• Tests Completed: {total_tests}")
- self.stats_labels["avg_time"].setText(f"• Average Processing: {avg_processing:.2f}s")
-
- ripe_percentage = (ripe_count / total_tests * 100) if total_tests > 0 else 0
- self.stats_labels["ripe_count"].setText(
- f"• Ripe Fruits Detected: {ripe_count} ({ripe_percentage:.1f}%)"
- )
-
- if session_start:
- duration = datetime.now() - session_start
- hours = duration.seconds // 3600
- minutes = (duration.seconds % 3600) // 60
- self.stats_labels["duration"].setText(f"• Session Duration: {hours}h {minutes}m")
-
- def clear_timeline(self):
- """Clear all timeline entries."""
- while self.timeline_layout.count() > 1: # Keep stretch
- item = self.timeline_layout.takeAt(0)
- if item.widget():
- item.widget().deleteLater()
-
- self.test_counter = 0
- self.save_audio_btn.setEnabled(False)
|