Clean push: Removed heavy files & added only latest snapshot

This commit is contained in:
2025-07-26 05:16:12 +05:30
commit acf84e8767
250 changed files with 58564 additions and 0 deletions

View 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}")