analysis_timeline_panel.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. """
  2. Analysis Timeline Panel
  3. Panel showing history of analysis results and session statistics.
  4. """
  5. from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
  6. QPushButton, QScrollArea, QFrame)
  7. from PyQt5.QtCore import Qt, pyqtSignal
  8. from PyQt5.QtGui import QFont
  9. from datetime import datetime
  10. from ui.widgets.panel_header import PanelHeader
  11. from ui.widgets.timeline_entry import TimelineEntry
  12. class AnalysisTimelinePanel(QWidget):
  13. """
  14. Panel for displaying analysis timeline and statistics.
  15. Signals:
  16. save_audio_clicked: Emitted when save audio button is clicked
  17. save_complete_clicked: Emitted when save complete package is clicked
  18. """
  19. save_audio_clicked = pyqtSignal()
  20. save_complete_clicked = pyqtSignal()
  21. def __init__(self, parent=None):
  22. super().__init__(parent)
  23. self.test_counter = 0
  24. self.init_ui()
  25. def init_ui(self):
  26. """Initialize the timeline panel UI."""
  27. layout = QVBoxLayout(self)
  28. layout.setContentsMargins(0, 0, 0, 0)
  29. layout.setSpacing(0)
  30. # Main panel container with card styling
  31. self.setStyleSheet("""
  32. QWidget {
  33. background-color: white;
  34. border: 1px solid #ddd;
  35. }
  36. """)
  37. # Header
  38. header = QWidget()
  39. header.setFixedHeight(25)
  40. header.setStyleSheet("background-color: #34495e;")
  41. header_layout = QHBoxLayout(header)
  42. header_layout.setContentsMargins(10, 0, 10, 0)
  43. header_layout.setSpacing(0)
  44. title = QLabel("Analysis Timeline")
  45. title.setStyleSheet("color: white; font-weight: bold; font-size: 16px;")
  46. header_layout.addWidget(title)
  47. # Content area
  48. content = QWidget()
  49. content.setStyleSheet("""
  50. background-color: white;
  51. border: none;
  52. """)
  53. content_layout = QVBoxLayout(content)
  54. content_layout.setSpacing(10)
  55. content_layout.setContentsMargins(10, 10, 10, 10)
  56. # Timeline entries (scrollable)
  57. timeline_scroll = QScrollArea()
  58. timeline_scroll.setWidgetResizable(True)
  59. timeline_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
  60. timeline_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
  61. timeline_scroll.setMinimumHeight(220)
  62. timeline_scroll.setStyleSheet("""
  63. QScrollArea {
  64. border: none;
  65. background-color: transparent;
  66. }
  67. """)
  68. timeline_widget = QWidget()
  69. self.timeline_layout = QVBoxLayout(timeline_widget)
  70. self.timeline_layout.setSpacing(8)
  71. self.timeline_layout.setContentsMargins(0, 0, 5, 0)
  72. self.timeline_layout.addStretch()
  73. timeline_scroll.setWidget(timeline_widget)
  74. content_layout.addWidget(timeline_scroll)
  75. # Separator
  76. separator = QFrame()
  77. separator.setFrameShape(QFrame.HLine)
  78. separator.setStyleSheet("background-color: #ecf0f1;")
  79. content_layout.addWidget(separator)
  80. # Snapshot options section
  81. snapshot_label = QLabel("Current Snapshot Options:")
  82. snapshot_label.setFont(QFont("Arial", 10, QFont.Bold))
  83. snapshot_label.setStyleSheet("color: #2c3e50; margin-top: 8px;")
  84. content_layout.addWidget(snapshot_label)
  85. # Snapshot buttons (first row)
  86. snapshot_row1 = QHBoxLayout()
  87. snapshot_row1.setSpacing(8)
  88. save_images_btn = self._create_snapshot_btn("Save Images", "#3498db")
  89. save_images_btn.setEnabled(False)
  90. save_images_btn.setToolTip("Save camera images - Coming soon")
  91. snapshot_row1.addWidget(save_images_btn)
  92. self.save_audio_btn = self._create_snapshot_btn("Save Audio", "#16a085")
  93. self.save_audio_btn.clicked.connect(self.save_audio_clicked.emit)
  94. self.save_audio_btn.setEnabled(False) # Enabled after processing
  95. snapshot_row1.addWidget(self.save_audio_btn)
  96. save_spectral_btn = self._create_snapshot_btn("Save Spectral", "#8e44ad")
  97. save_spectral_btn.setEnabled(False)
  98. save_spectral_btn.setToolTip("Save spectral data - Coming soon")
  99. snapshot_row1.addWidget(save_spectral_btn)
  100. content_layout.addLayout(snapshot_row1)
  101. # Complete package button (second row)
  102. self.save_complete_btn = QPushButton("Save Complete Analysis Package")
  103. self.save_complete_btn.setFont(QFont("Arial", 9, QFont.Bold))
  104. self.save_complete_btn.setFixedHeight(28)
  105. self.save_complete_btn.setStyleSheet("""
  106. QPushButton {
  107. background-color: #27ae60;
  108. border: 1px solid #229954;
  109. color: white;
  110. }
  111. QPushButton:hover {
  112. background-color: #229954;
  113. }
  114. QPushButton:disabled {
  115. background-color: #95a5a6;
  116. border-color: #7f8c8d;
  117. }
  118. """)
  119. self.save_complete_btn.clicked.connect(self.save_complete_clicked.emit)
  120. self.save_complete_btn.setEnabled(False)
  121. self.save_complete_btn.setToolTip("Save complete analysis - Coming soon")
  122. content_layout.addWidget(self.save_complete_btn)
  123. # Session statistics
  124. stats_label = QLabel("Session Statistics:")
  125. stats_label.setFont(QFont("Arial", 10, QFont.Bold))
  126. stats_label.setStyleSheet("color: #2c3e50; margin-top: 10px;")
  127. content_layout.addWidget(stats_label)
  128. # Statistics labels
  129. self.stats_labels = {}
  130. stats = [
  131. ("tests", "• Tests Completed: 0"),
  132. ("avg_time", "• Average Processing: 0.00s"),
  133. ("accuracy", "• Classification Accuracy: --"),
  134. ("ripe_count", "• Ripe Fruits Detected: 0 (0.0%)"),
  135. ("duration", "• Session Duration: 0h 0m")
  136. ]
  137. for key, text in stats:
  138. label = QLabel(text)
  139. label.setFont(QFont("Arial", 10))
  140. label.setStyleSheet("color: #2c3e50; line-height: 1.4;")
  141. self.stats_labels[key] = label
  142. content_layout.addWidget(label)
  143. content_layout.addStretch()
  144. layout.addWidget(header)
  145. layout.addWidget(content)
  146. def _create_snapshot_btn(self, text: str, color: str) -> QPushButton:
  147. """Create a snapshot button with specific color."""
  148. btn = QPushButton(text)
  149. btn.setFont(QFont("Arial", 8, QFont.Bold))
  150. btn.setFixedHeight(26)
  151. btn.setStyleSheet(f"""
  152. QPushButton {{
  153. background-color: {color};
  154. border: 1px solid {color};
  155. color: white;
  156. }}
  157. QPushButton:hover {{
  158. opacity: 0.9;
  159. }}
  160. QPushButton:disabled {{
  161. background-color: #95a5a6;
  162. border-color: #7f8c8d;
  163. }}
  164. """)
  165. return btn
  166. def add_test_result(self, classification: str, confidence: float,
  167. processing_time: float):
  168. """
  169. Add a new test result to the timeline.
  170. Args:
  171. classification: Classification result
  172. confidence: Confidence percentage (0-100)
  173. processing_time: Processing time in seconds
  174. """
  175. self.test_counter += 1
  176. timestamp = datetime.now().strftime("%H:%M:%S")
  177. entry = TimelineEntry(
  178. self.test_counter,
  179. timestamp,
  180. classification,
  181. confidence,
  182. processing_time
  183. )
  184. # Insert at the top (most recent first)
  185. self.timeline_layout.insertWidget(0, entry)
  186. # Keep only last 10 entries
  187. while self.timeline_layout.count() > 11: # 10 + stretch
  188. item = self.timeline_layout.takeAt(10)
  189. if item.widget():
  190. item.widget().deleteLater()
  191. # Enable save audio button
  192. self.save_audio_btn.setEnabled(True)
  193. def update_statistics(self, total_tests: int, avg_processing: float,
  194. ripe_count: int, session_start: datetime = None):
  195. """
  196. Update session statistics.
  197. Args:
  198. total_tests: Total number of tests
  199. avg_processing: Average processing time
  200. ripe_count: Number of ripe classifications
  201. session_start: Session start datetime
  202. """
  203. self.stats_labels["tests"].setText(f"• Tests Completed: {total_tests}")
  204. self.stats_labels["avg_time"].setText(f"• Average Processing: {avg_processing:.2f}s")
  205. ripe_percentage = (ripe_count / total_tests * 100) if total_tests > 0 else 0
  206. self.stats_labels["ripe_count"].setText(
  207. f"• Ripe Fruits Detected: {ripe_count} ({ripe_percentage:.1f}%)"
  208. )
  209. if session_start:
  210. duration = datetime.now() - session_start
  211. hours = duration.seconds // 3600
  212. minutes = (duration.seconds % 3600) // 60
  213. self.stats_labels["duration"].setText(f"• Session Duration: {hours}h {minutes}m")
  214. def clear_timeline(self):
  215. """Clear all timeline entries."""
  216. while self.timeline_layout.count() > 1: # Keep stretch
  217. item = self.timeline_layout.takeAt(0)
  218. if item.widget():
  219. item.widget().deleteLater()
  220. self.test_counter = 0
  221. self.save_audio_btn.setEnabled(False)