Clean push: Removed heavy files & added only latest snapshot
This commit is contained in:
662
qt_app_pyside1/ui/analytics_tab.py
Normal file
662
qt_app_pyside1/ui/analytics_tab.py
Normal file
@@ -0,0 +1,662 @@
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||||
QGroupBox, QPushButton, QScrollArea, QSplitter
|
||||
)
|
||||
from PySide6.QtCore import Qt, Slot
|
||||
from PySide6.QtCharts import QChart, QChartView, QLineSeries, QPieSeries, QBarSeries, QBarSet, QBarCategoryAxis, QScatterSeries, QValueAxis
|
||||
from PySide6.QtGui import QPainter, QColor, QPen, QFont, QBrush, QLinearGradient, QGradient
|
||||
|
||||
class ChartWidget(QWidget):
|
||||
"""Base widget for analytics charts"""
|
||||
def __init__(self, title):
|
||||
super().__init__()
|
||||
self.layout = QVBoxLayout(self)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
# Chart title
|
||||
self.title_label = QLabel(title)
|
||||
self.title_label.setAlignment(Qt.AlignCenter)
|
||||
self.title_label.setStyleSheet("font-weight: bold; font-size: 14px;")
|
||||
self.layout.addWidget(self.title_label)
|
||||
|
||||
# Create chart
|
||||
self.chart = QChart()
|
||||
self.chart.setAnimationOptions(QChart.SeriesAnimations)
|
||||
self.chart.setBackgroundBrush(QBrush(QColor(240, 240, 240)))
|
||||
self.chart.legend().setVisible(True)
|
||||
self.chart.legend().setAlignment(Qt.AlignBottom)
|
||||
|
||||
# Chart view
|
||||
self.chartview = QChartView(self.chart)
|
||||
self.chartview.setRenderHint(QPainter.RenderHint.Antialiasing)
|
||||
self.layout.addWidget(self.chartview)
|
||||
|
||||
self.setMinimumSize(400, 300)
|
||||
|
||||
class TimeSeriesChart(ChartWidget):
|
||||
"""Time series chart for traffic data"""
|
||||
def __init__(self, title="Traffic Over Time"):
|
||||
super().__init__(title)
|
||||
|
||||
# Create series
|
||||
self.vehicle_series = QLineSeries()
|
||||
self.vehicle_series.setName("Vehicles")
|
||||
self.vehicle_series.setPen(QPen(QColor(0, 162, 232), 2))
|
||||
|
||||
self.pedestrian_series = QLineSeries()
|
||||
self.pedestrian_series.setName("Pedestrians")
|
||||
self.pedestrian_series.setPen(QPen(QColor(255, 140, 0), 2))
|
||||
|
||||
self.violation_series = QLineSeries()
|
||||
self.violation_series.setName("Violations")
|
||||
self.violation_series.setPen(QPen(QColor(232, 0, 0), 2))
|
||||
|
||||
self.traffic_light_color_series = QLineSeries()
|
||||
self.traffic_light_color_series.setName("Traffic Light Color")
|
||||
self.traffic_light_color_series.setPen(QPen(QColor(128, 0, 128), 2, Qt.DashLine))
|
||||
|
||||
# Add series to chart
|
||||
self.chart.addSeries(self.vehicle_series)
|
||||
self.chart.addSeries(self.pedestrian_series)
|
||||
self.chart.addSeries(self.violation_series)
|
||||
self.chart.addSeries(self.traffic_light_color_series)
|
||||
|
||||
# Create and configure axes
|
||||
self.chart.createDefaultAxes()
|
||||
x_axis = self.chart.axes(Qt.Horizontal)[0]
|
||||
x_axis.setTitleText("Time")
|
||||
x_axis.setGridLineVisible(True)
|
||||
x_axis.setLabelsAngle(45)
|
||||
|
||||
y_axis = self.chart.axes(Qt.Vertical)[0]
|
||||
y_axis.setTitleText("Count")
|
||||
y_axis.setGridLineVisible(True)
|
||||
|
||||
def update_data(self, time_series):
|
||||
"""Update chart with new time series data"""
|
||||
try:
|
||||
if not time_series or 'timestamps' not in time_series:
|
||||
return
|
||||
|
||||
# Check if chart and series are still valid
|
||||
if not hasattr(self, 'chart') or self.chart is None:
|
||||
return
|
||||
if not hasattr(self, 'vehicle_series') or self.vehicle_series is None:
|
||||
return
|
||||
|
||||
timestamps = time_series.get('timestamps', [])
|
||||
vehicle_counts = time_series.get('vehicle_counts', [])
|
||||
pedestrian_counts = time_series.get('pedestrian_counts', [])
|
||||
violation_counts = time_series.get('violation_counts', [])
|
||||
traffic_light_colors = time_series.get('traffic_light_colors', [])
|
||||
|
||||
# Clear existing series safely
|
||||
try:
|
||||
self.vehicle_series.clear()
|
||||
self.pedestrian_series.clear()
|
||||
self.violation_series.clear()
|
||||
self.traffic_light_color_series.clear()
|
||||
except RuntimeError:
|
||||
# C++ object was already deleted, skip update
|
||||
return
|
||||
|
||||
# Add data points
|
||||
for i in range(len(timestamps)):
|
||||
try:
|
||||
# Add x as index, y as count
|
||||
self.vehicle_series.append(i, vehicle_counts[i] if i < len(vehicle_counts) else 0)
|
||||
self.pedestrian_series.append(i, pedestrian_counts[i] if i < len(pedestrian_counts) else 0)
|
||||
self.violation_series.append(i, violation_counts[i] if i < len(violation_counts) else 0)
|
||||
|
||||
# Add traffic light color as mapped int for charting (0=unknown, 1=red, 2=yellow, 3=green)
|
||||
if i < len(traffic_light_colors):
|
||||
color_map = {'unknown': 0, 'red': 1, 'yellow': 2, 'green': 3}
|
||||
color_val = color_map.get(traffic_light_colors[i], 0)
|
||||
self.traffic_light_color_series.append(i, color_val)
|
||||
except RuntimeError:
|
||||
# C++ object was deleted during update
|
||||
return
|
||||
|
||||
# Update axes safely
|
||||
try:
|
||||
axes = self.chart.axes(Qt.Horizontal)
|
||||
if axes:
|
||||
axes[0].setRange(0, max(len(timestamps)-1, 10))
|
||||
|
||||
max_count = max(
|
||||
max(vehicle_counts) if vehicle_counts else 0,
|
||||
max(pedestrian_counts) if pedestrian_counts else 0,
|
||||
max(violation_counts) if violation_counts else 0
|
||||
)
|
||||
axes = self.chart.axes(Qt.Vertical)
|
||||
if axes:
|
||||
axes[0].setRange(0, max(max_count+1, 5))
|
||||
except (RuntimeError, IndexError):
|
||||
# Chart axes were deleted or not available
|
||||
pass
|
||||
|
||||
# Optionally, set y-axis label for traffic light color
|
||||
axes = self.chart.axes(Qt.Vertical)
|
||||
if axes:
|
||||
axes[0].setTitleText("Count / TL Color (0=U,1=R,2=Y,3=G)")
|
||||
except Exception as e:
|
||||
print(f"[WARNING] Chart update failed: {e}")
|
||||
|
||||
class DetectionPieChart(ChartWidget):
|
||||
"""Pie chart for detected object classes"""
|
||||
def __init__(self, title="Detection Classes"):
|
||||
super().__init__(title)
|
||||
|
||||
self.pie_series = QPieSeries()
|
||||
self.chart.addSeries(self.pie_series)
|
||||
|
||||
def update_data(self, detection_counts):
|
||||
"""Update chart with detection counts"""
|
||||
try:
|
||||
if not detection_counts:
|
||||
return
|
||||
|
||||
# Check if chart and series are still valid
|
||||
if not hasattr(self, 'chart') or self.chart is None:
|
||||
return
|
||||
if not hasattr(self, 'pie_series') or self.pie_series is None:
|
||||
return
|
||||
|
||||
# Clear existing slices safely
|
||||
try:
|
||||
self.pie_series.clear()
|
||||
except RuntimeError:
|
||||
# C++ object was already deleted, skip update
|
||||
return
|
||||
|
||||
# Add new slices
|
||||
for class_name, count in detection_counts.items():
|
||||
# Only add if count > 0
|
||||
if count > 0:
|
||||
try:
|
||||
slice = self.pie_series.append(class_name, count)
|
||||
|
||||
# Set colors based on class
|
||||
if class_name.lower() == 'car':
|
||||
slice.setBrush(QColor(0, 200, 0))
|
||||
elif class_name.lower() == 'person':
|
||||
slice.setBrush(QColor(255, 165, 0))
|
||||
elif class_name.lower() == 'truck':
|
||||
slice.setBrush(QColor(0, 100, 200))
|
||||
elif class_name.lower() == 'bus':
|
||||
slice.setBrush(QColor(200, 0, 100))
|
||||
|
||||
# Highlight important slices
|
||||
if count > 10:
|
||||
slice.setExploded(True)
|
||||
slice.setLabelVisible(True)
|
||||
except RuntimeError:
|
||||
# C++ object was deleted during update
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"[WARNING] Pie chart update failed: {e}")
|
||||
|
||||
class ViolationBarChart(ChartWidget):
|
||||
"""Bar chart for violation types"""
|
||||
def __init__(self, title="Violations by Type"):
|
||||
super().__init__(title)
|
||||
|
||||
# Create series
|
||||
self.bar_series = QBarSeries()
|
||||
self.chart.addSeries(self.bar_series)
|
||||
|
||||
# Create axes
|
||||
self.axis_x = QBarCategoryAxis()
|
||||
self.chart.addAxis(self.axis_x, Qt.AlignBottom)
|
||||
self.bar_series.attachAxis(self.axis_x)
|
||||
|
||||
self.chart.createDefaultAxes()
|
||||
self.chart.axes(Qt.Vertical)[0].setTitleText("Count")
|
||||
|
||||
def update_data(self, violation_counts):
|
||||
"""Update chart with violation counts"""
|
||||
try:
|
||||
if not violation_counts:
|
||||
return
|
||||
|
||||
# Check if chart and series are still valid
|
||||
if not hasattr(self, 'chart') or self.chart is None:
|
||||
return
|
||||
if not hasattr(self, 'bar_series') or self.bar_series is None:
|
||||
return
|
||||
if not hasattr(self, 'axis_x') or self.axis_x is None:
|
||||
return
|
||||
|
||||
# Clear existing data safely
|
||||
try:
|
||||
self.bar_series.clear()
|
||||
except RuntimeError:
|
||||
# C++ object was already deleted, skip update
|
||||
return
|
||||
|
||||
# Create bar set
|
||||
bar_set = QBarSet("Violations")
|
||||
|
||||
# Set colors
|
||||
try:
|
||||
bar_set.setColor(QColor(232, 0, 0))
|
||||
except RuntimeError:
|
||||
return
|
||||
|
||||
# Add values
|
||||
values = []
|
||||
categories = []
|
||||
|
||||
for violation_type, count in violation_counts.items():
|
||||
if count > 0:
|
||||
values.append(count)
|
||||
# Format violation type for display
|
||||
display_name = violation_type.replace('_', ' ').title()
|
||||
categories.append(display_name)
|
||||
|
||||
if values:
|
||||
try:
|
||||
bar_set.append(values)
|
||||
self.bar_series.append(bar_set)
|
||||
|
||||
# Update x-axis categories
|
||||
self.axis_x.setCategories(categories)
|
||||
|
||||
# Update y-axis range
|
||||
y_axes = self.chart.axes(Qt.Vertical)
|
||||
if y_axes:
|
||||
y_axes[0].setRange(0, max(values) * 1.2)
|
||||
except RuntimeError:
|
||||
# C++ object was deleted during update
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"[WARNING] Bar chart update failed: {e}")
|
||||
|
||||
class LatencyChartWidget(ChartWidget):
|
||||
"""Custom latency chart with spikes, device/res changes, and live stats legend."""
|
||||
def __init__(self, title="Inference Latency Over Time"):
|
||||
super().__init__(title)
|
||||
self.chart.setBackgroundBrush(QBrush(QColor(24, 28, 32)))
|
||||
self.title_label.setStyleSheet("font-weight: bold; font-size: 16px; color: #fff;")
|
||||
self.chart.legend().setVisible(False)
|
||||
# Main latency line
|
||||
self.latency_series = QLineSeries()
|
||||
self.latency_series.setName("Latency (ms)")
|
||||
self.latency_series.setPen(QPen(QColor(0, 255, 255), 2))
|
||||
self.chart.addSeries(self.latency_series)
|
||||
# Spikes as red dots
|
||||
self.spike_series = QScatterSeries()
|
||||
self.spike_series.setName("Spikes")
|
||||
self.spike_series.setMarkerSize(8)
|
||||
self.spike_series.setColor(QColor(255, 64, 64))
|
||||
self.chart.addSeries(self.spike_series)
|
||||
# Device/resolution change lines (vertical)
|
||||
self.event_lines = []
|
||||
# Axes
|
||||
self.chart.createDefaultAxes()
|
||||
self.x_axis = self.chart.axes(Qt.Horizontal)[0]
|
||||
self.x_axis.setTitleText("")
|
||||
self.x_axis.setLabelsColor(QColor("#fff"))
|
||||
self.x_axis.setGridLineColor(QColor("#444"))
|
||||
self.y_axis = self.chart.axes(Qt.Vertical)[0]
|
||||
self.y_axis.setTitleText("ms")
|
||||
self.y_axis.setLabelsColor(QColor("#fff"))
|
||||
self.y_axis.setGridLineColor(QColor("#444"))
|
||||
# Stats label
|
||||
self.stats_label = QLabel()
|
||||
self.stats_label.setStyleSheet("color: #00e6ff; font-size: 13px; font-weight: bold; margin: 2px 0 0 8px;")
|
||||
self.layout.addWidget(self.stats_label)
|
||||
|
||||
def update_data(self, latency_data):
|
||||
"""
|
||||
latency_data: dict with keys:
|
||||
'latencies': list of float,
|
||||
'spike_indices': list of int,
|
||||
'device_switches': list of int,
|
||||
'resolution_changes': list of int
|
||||
"""
|
||||
if not latency_data or 'latencies' not in latency_data:
|
||||
return
|
||||
latencies = latency_data.get('latencies', [])
|
||||
spikes = set(latency_data.get('spike_indices', []))
|
||||
device_switches = set(latency_data.get('device_switches', []))
|
||||
res_changes = set(latency_data.get('resolution_changes', []))
|
||||
# Clear series
|
||||
self.latency_series.clear()
|
||||
self.spike_series.clear()
|
||||
# Remove old event lines
|
||||
for line in self.event_lines:
|
||||
self.chart.removeAxis(line)
|
||||
self.event_lines = []
|
||||
# Plot latency and spikes
|
||||
for i, val in enumerate(latencies):
|
||||
self.latency_series.append(i, val)
|
||||
if i in spikes:
|
||||
self.spike_series.append(i, val)
|
||||
# Add device/resolution change lines
|
||||
for idx in device_switches:
|
||||
line = QLineSeries()
|
||||
line.setPen(QPen(QColor(33, 150, 243), 3)) # Blue
|
||||
line.append(idx, min(latencies) if latencies else 0)
|
||||
line.append(idx, max(latencies) if latencies else 1)
|
||||
self.chart.addSeries(line)
|
||||
line.attachAxis(self.x_axis)
|
||||
line.attachAxis(self.y_axis)
|
||||
self.event_lines.append(line)
|
||||
for idx in res_changes:
|
||||
line = QLineSeries()
|
||||
line.setPen(QPen(QColor(255, 167, 38), 3)) # Orange
|
||||
line.append(idx, min(latencies) if latencies else 0)
|
||||
line.append(idx, max(latencies) if latencies else 1)
|
||||
self.chart.addSeries(line)
|
||||
line.attachAxis(self.x_axis)
|
||||
line.attachAxis(self.y_axis)
|
||||
self.event_lines.append(line)
|
||||
# Update axes
|
||||
self.x_axis.setRange(0, max(len(latencies)-1, 10))
|
||||
self.y_axis.setRange(0, max(max(latencies) if latencies else 1, 10))
|
||||
# Stats
|
||||
if latencies:
|
||||
avg = sum(latencies)/len(latencies)
|
||||
mx = max(latencies)
|
||||
self.stats_label.setText(f"Avg: {avg:.1f}ms | Max: {mx:.1f}ms | Spikes: {len(spikes)}")
|
||||
else:
|
||||
self.stats_label.setText("")
|
||||
|
||||
class FPSChartWidget(ChartWidget):
|
||||
"""FPS & Resolution Impact chart with device/resolution change lines and live stats."""
|
||||
def __init__(self, title="FPS & Resolution Impact"):
|
||||
super().__init__(title)
|
||||
self.chart.setBackgroundBrush(QBrush(QColor(24, 28, 32)))
|
||||
self.title_label.setStyleSheet("font-weight: bold; font-size: 16px; color: #fff;")
|
||||
self.chart.legend().setVisible(False)
|
||||
self.fps_series = QLineSeries()
|
||||
self.fps_series.setName("FPS")
|
||||
self.fps_series.setPen(QPen(QColor(0, 255, 255), 2))
|
||||
self.chart.addSeries(self.fps_series)
|
||||
self.event_lines = []
|
||||
self.chart.createDefaultAxes()
|
||||
self.x_axis = self.chart.axes(Qt.Horizontal)[0]
|
||||
self.x_axis.setLabelsColor(QColor("#fff"))
|
||||
self.x_axis.setGridLineColor(QColor("#444"))
|
||||
self.y_axis = self.chart.axes(Qt.Vertical)[0]
|
||||
self.y_axis.setTitleText("FPS")
|
||||
self.y_axis.setLabelsColor(QColor("#fff"))
|
||||
self.y_axis.setGridLineColor(QColor("#444"))
|
||||
self.stats_label = QLabel()
|
||||
self.stats_label.setStyleSheet("color: #00ff82; font-size: 13px; font-weight: bold; margin: 2px 0 0 8px;")
|
||||
self.layout.addWidget(self.stats_label)
|
||||
def update_data(self, fps_data):
|
||||
if not fps_data or 'fps' not in fps_data:
|
||||
return
|
||||
fps = fps_data.get('fps', [])
|
||||
device_switches = set(fps_data.get('device_switches', []))
|
||||
res_changes = set(fps_data.get('resolution_changes', []))
|
||||
device_labels = fps_data.get('device_labels', {})
|
||||
res_labels = fps_data.get('resolution_labels', {})
|
||||
self.fps_series.clear()
|
||||
for line in self.event_lines:
|
||||
self.chart.removeAxis(line)
|
||||
self.event_lines = []
|
||||
for i, val in enumerate(fps):
|
||||
self.fps_series.append(i, val)
|
||||
for idx in device_switches:
|
||||
line = QLineSeries()
|
||||
line.setPen(QPen(QColor(33, 150, 243), 3))
|
||||
line.append(idx, min(fps) if fps else 0)
|
||||
line.append(idx, max(fps) if fps else 1)
|
||||
self.chart.addSeries(line)
|
||||
line.attachAxis(self.x_axis)
|
||||
line.attachAxis(self.y_axis)
|
||||
self.event_lines.append(line)
|
||||
for idx in res_changes:
|
||||
line = QLineSeries()
|
||||
line.setPen(QPen(QColor(255, 167, 38), 3))
|
||||
line.append(idx, min(fps) if fps else 0)
|
||||
line.append(idx, max(fps) if fps else 1)
|
||||
self.chart.addSeries(line)
|
||||
line.attachAxis(self.x_axis)
|
||||
line.attachAxis(self.y_axis)
|
||||
self.event_lines.append(line)
|
||||
self.x_axis.setRange(0, max(len(fps)-1, 10))
|
||||
self.y_axis.setRange(0, max(max(fps) if fps else 1, 10))
|
||||
# Live stats (current FPS, resolution, device)
|
||||
cur_fps = fps[-1] if fps else 0
|
||||
cur_res = res_labels.get(len(fps)-1, "-")
|
||||
cur_dev = device_labels.get(len(fps)-1, "-")
|
||||
self.stats_label.setText(f"Current FPS: {cur_fps:.1f} | Resolution: {cur_res} | Device: {cur_dev}")
|
||||
|
||||
class DeviceSwitchChartWidget(ChartWidget):
|
||||
"""Device Switching & Resolution Changes chart with colored vertical lines and legend."""
|
||||
def __init__(self, title="Device Switching & Resolution Changes"):
|
||||
super().__init__(title)
|
||||
self.chart.setBackgroundBrush(QBrush(QColor(24, 28, 32)))
|
||||
self.title_label.setStyleSheet("font-weight: bold; font-size: 16px; color: #fff;")
|
||||
self.chart.legend().setVisible(False)
|
||||
self.event_lines = []
|
||||
self.chart.createDefaultAxes()
|
||||
self.x_axis = self.chart.axes(Qt.Horizontal)[0]
|
||||
self.x_axis.setLabelsColor(QColor("#fff"))
|
||||
self.x_axis.setGridLineColor(QColor("#444"))
|
||||
self.y_axis = self.chart.axes(Qt.Vertical)[0]
|
||||
self.y_axis.setTitleText("-")
|
||||
self.y_axis.setLabelsColor(QColor("#fff"))
|
||||
self.y_axis.setGridLineColor(QColor("#444"))
|
||||
self.legend_label = QLabel()
|
||||
self.legend_label.setStyleSheet("color: #ffb300; font-size: 13px; font-weight: bold; margin: 2px 0 0 8px;")
|
||||
self.layout.addWidget(self.legend_label)
|
||||
def update_data(self, event_data):
|
||||
if not event_data:
|
||||
return
|
||||
cpu_spikes = set(event_data.get('cpu_spikes', []))
|
||||
gpu_spikes = set(event_data.get('gpu_spikes', []))
|
||||
switches = set(event_data.get('switches', []))
|
||||
res_changes = set(event_data.get('res_changes', []))
|
||||
n = event_data.get('n', 100)
|
||||
for line in self.event_lines:
|
||||
self.chart.removeAxis(line)
|
||||
self.event_lines = []
|
||||
for idx in cpu_spikes:
|
||||
line = QLineSeries()
|
||||
line.setPen(QPen(QColor(255, 64, 64), 2))
|
||||
line.append(idx, 0)
|
||||
line.append(idx, 1)
|
||||
self.chart.addSeries(line)
|
||||
line.attachAxis(self.x_axis)
|
||||
line.attachAxis(self.y_axis)
|
||||
self.event_lines.append(line)
|
||||
for idx in gpu_spikes:
|
||||
line = QLineSeries()
|
||||
line.setPen(QPen(QColor(255, 87, 34), 2))
|
||||
line.append(idx, 0)
|
||||
line.append(idx, 1)
|
||||
self.chart.addSeries(line)
|
||||
line.attachAxis(self.x_axis)
|
||||
line.attachAxis(self.y_axis)
|
||||
self.event_lines.append(line)
|
||||
for idx in switches:
|
||||
line = QLineSeries()
|
||||
line.setPen(QPen(QColor(33, 150, 243), 2))
|
||||
line.append(idx, 0)
|
||||
line.append(idx, 1)
|
||||
self.chart.addSeries(line)
|
||||
line.attachAxis(self.x_axis)
|
||||
line.attachAxis(self.y_axis)
|
||||
self.event_lines.append(line)
|
||||
for idx in res_changes:
|
||||
line = QLineSeries()
|
||||
line.setPen(QPen(QColor(255, 167, 38), 2))
|
||||
line.append(idx, 0)
|
||||
line.append(idx, 1)
|
||||
self.chart.addSeries(line)
|
||||
line.attachAxis(self.x_axis)
|
||||
line.attachAxis(self.y_axis)
|
||||
self.event_lines.append(line)
|
||||
self.x_axis.setRange(0, n)
|
||||
self.y_axis.setRange(0, 1)
|
||||
self.legend_label.setText("<span style='color:#ff4444;'>CPU Spikes</span>: {} | <span style='color:#ff5722;'>GPU Spikes</span>: {} | <span style='color:#2196f3;'>Switches</span>: {} | <span style='color:#ffa726;'>Res Changes</span>: {}".format(len(cpu_spikes), len(gpu_spikes), len(switches), len(res_changes)))
|
||||
|
||||
class AnalyticsTab(QWidget):
|
||||
"""Analytics tab with charts and statistics"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.initUI()
|
||||
|
||||
def initUI(self):
|
||||
"""Initialize UI components"""
|
||||
main_layout = QVBoxLayout(self)
|
||||
|
||||
# Add notice that violations are disabled
|
||||
notice_label = QLabel("⚠️ Violation detection is currently disabled. Only object detection statistics will be shown.")
|
||||
notice_label.setStyleSheet("font-size: 14px; color: #FFA500; font-weight: bold; padding: 10px;")
|
||||
notice_label.setAlignment(Qt.AlignCenter)
|
||||
main_layout.addWidget(notice_label)
|
||||
|
||||
# Charts section
|
||||
charts_splitter = QSplitter(Qt.Horizontal)
|
||||
|
||||
# Latency chart (top, full width)
|
||||
self.latency_chart = LatencyChartWidget("Inference Latency Over Time")
|
||||
main_layout.addWidget(self.latency_chart)
|
||||
|
||||
# Left side - Time series chart
|
||||
self.time_series_chart = TimeSeriesChart("Traffic Over Time")
|
||||
charts_splitter.addWidget(self.time_series_chart)
|
||||
|
||||
# Right side - Detection and violation charts
|
||||
right_charts = QWidget()
|
||||
right_layout = QVBoxLayout(right_charts)
|
||||
|
||||
self.detection_chart = DetectionPieChart("Detection Classes")
|
||||
self.violation_chart = ViolationBarChart("Violations by Type")
|
||||
|
||||
right_layout.addWidget(self.detection_chart)
|
||||
right_layout.addWidget(self.violation_chart)
|
||||
|
||||
charts_splitter.addWidget(right_charts)
|
||||
charts_splitter.setSizes([500, 500]) # Equal initial sizes
|
||||
|
||||
main_layout.addWidget(charts_splitter)
|
||||
|
||||
# Key metrics section
|
||||
metrics_box = QGroupBox("Key Metrics")
|
||||
metrics_layout = QHBoxLayout(metrics_box)
|
||||
|
||||
# Vehicle metrics
|
||||
vehicle_metrics = QGroupBox("Traffic")
|
||||
vehicle_layout = QVBoxLayout(vehicle_metrics)
|
||||
self.total_vehicles_label = QLabel("Total Vehicles: 0")
|
||||
self.total_pedestrians_label = QLabel("Total Pedestrians: 0")
|
||||
vehicle_layout.addWidget(self.total_vehicles_label)
|
||||
vehicle_layout.addWidget(self.total_pedestrians_label)
|
||||
metrics_layout.addWidget(vehicle_metrics)
|
||||
|
||||
# Violation metrics
|
||||
violation_metrics = QGroupBox("Violations")
|
||||
violation_layout = QVBoxLayout(violation_metrics)
|
||||
self.total_violations_label = QLabel("Total Violations: 0")
|
||||
self.peak_violation_label = QLabel("Peak Violation Hour: --")
|
||||
violation_layout.addWidget(self.total_violations_label)
|
||||
violation_layout.addWidget(self.peak_violation_label)
|
||||
metrics_layout.addWidget(violation_metrics)
|
||||
|
||||
# Performance metrics
|
||||
performance_metrics = QGroupBox("Performance")
|
||||
performance_layout = QVBoxLayout(performance_metrics)
|
||||
self.avg_fps_label = QLabel("Avg FPS: 0")
|
||||
self.avg_processing_label = QLabel("Avg Processing Time: 0 ms")
|
||||
performance_layout.addWidget(self.avg_fps_label)
|
||||
performance_layout.addWidget(self.avg_processing_label)
|
||||
metrics_layout.addWidget(performance_metrics)
|
||||
|
||||
main_layout.addWidget(metrics_box)
|
||||
|
||||
# Controls
|
||||
controls = QHBoxLayout()
|
||||
self.reset_btn = QPushButton("Reset Statistics")
|
||||
controls.addWidget(self.reset_btn)
|
||||
controls.addStretch(1) # Push button to left
|
||||
|
||||
main_layout.addLayout(controls)
|
||||
|
||||
@Slot(dict)
|
||||
def update_analytics(self, analytics):
|
||||
"""
|
||||
Update analytics display with new data.
|
||||
|
||||
Args:
|
||||
analytics: Dictionary of analytics data
|
||||
"""
|
||||
try:
|
||||
if not analytics:
|
||||
return
|
||||
|
||||
# Update latency chart
|
||||
try:
|
||||
if hasattr(self, 'latency_chart') and self.latency_chart is not None:
|
||||
self.latency_chart.update_data(analytics.get('latency', {}))
|
||||
except Exception as e:
|
||||
print(f"[WARNING] Latency chart update failed: {e}")
|
||||
|
||||
# Update charts with error handling
|
||||
try:
|
||||
if hasattr(self, 'time_series_chart') and self.time_series_chart is not None:
|
||||
self.time_series_chart.update_data(analytics.get('time_series', {}))
|
||||
except Exception as e:
|
||||
print(f"[WARNING] Time series chart update failed: {e}")
|
||||
|
||||
try:
|
||||
if hasattr(self, 'detection_chart') and self.detection_chart is not None:
|
||||
self.detection_chart.update_data(analytics.get('detection_counts', {}))
|
||||
except Exception as e:
|
||||
print(f"[WARNING] Detection chart update failed: {e}")
|
||||
|
||||
try:
|
||||
if hasattr(self, 'violation_chart') and self.violation_chart is not None:
|
||||
self.violation_chart.update_data(analytics.get('violation_counts', {}))
|
||||
except Exception as e:
|
||||
print(f"[WARNING] Violation chart update failed: {e}")
|
||||
|
||||
# Update metrics
|
||||
try:
|
||||
metrics = analytics.get('metrics', {})
|
||||
|
||||
if hasattr(self, 'total_vehicles_label'):
|
||||
self.total_vehicles_label.setText(f"Total Vehicles: {metrics.get('total_vehicles', 0)}")
|
||||
if hasattr(self, 'total_pedestrians_label'):
|
||||
self.total_pedestrians_label.setText(f"Total Pedestrians: {metrics.get('total_pedestrians', 0)}")
|
||||
|
||||
if hasattr(self, 'total_violations_label'):
|
||||
self.total_violations_label.setText(f"Total Violations: {metrics.get('total_violations', 0)}")
|
||||
|
||||
peak_hour = metrics.get('peak_violation_hour')
|
||||
if peak_hour:
|
||||
peak_text = f"Peak Violation Hour: {peak_hour.get('time', '--')} ({peak_hour.get('violations', 0)})"
|
||||
else:
|
||||
peak_text = "Peak Violation Hour: --"
|
||||
if hasattr(self, 'peak_violation_label'):
|
||||
self.peak_violation_label.setText(peak_text)
|
||||
|
||||
if hasattr(self, 'avg_fps_label'):
|
||||
self.avg_fps_label.setText(f"Avg FPS: {metrics.get('avg_fps', 0):.1f}")
|
||||
if hasattr(self, 'avg_processing_label'):
|
||||
self.avg_processing_label.setText(
|
||||
f"Avg Processing Time: {metrics.get('avg_processing_time', 0):.1f} ms"
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"[WARNING] Metrics update failed: {e}")
|
||||
|
||||
# Update traffic light label with latest color
|
||||
try:
|
||||
tl_series = analytics.get('traffic_light_color_series', [])
|
||||
if tl_series:
|
||||
latest = tl_series[-1][1]
|
||||
self.traffic_light_label.setText(f"Traffic Light: {latest.title()}")
|
||||
else:
|
||||
self.traffic_light_label.setText("Traffic Light: Unknown")
|
||||
except Exception as e:
|
||||
print(f"[WARNING] Traffic light label update failed: {e}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Analytics update failed: {e}")
|
||||
Reference in New Issue
Block a user