shit works in darkmode!

This commit is contained in:
rattatwinko
2025-05-25 21:03:29 +02:00
parent 8e9b2568b2
commit b704c8623d

View File

@@ -5,9 +5,10 @@ import numpy as np
from PyQt5.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QHBoxLayout, from PyQt5.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QHBoxLayout,
QWidget, QLabel, QPushButton, QComboBox, QSpinBox, QWidget, QLabel, QPushButton, QComboBox, QSpinBox,
QFileDialog, QMessageBox, QMenu, QAction, QMenuBar, QFileDialog, QMessageBox, QMenu, QAction, QMenuBar,
QActionGroup, QSizePolicy, QGridLayout, QGroupBox) QActionGroup, QSizePolicy, QGridLayout, QGroupBox,
from PyQt5.QtCore import Qt, QTimer, QDir QDockWidget, QScrollArea, QToolButton)
from PyQt5.QtGui import QImage, QPixmap from PyQt5.QtCore import Qt, QTimer, QDir, QSize
from PyQt5.QtGui import QImage, QPixmap, QIcon, QColor
class MultiCamYOLODetector: class MultiCamYOLODetector:
def __init__(self): def __init__(self):
@@ -214,27 +215,123 @@ class MultiCamYOLODetector:
return frames return frames
def get_frames(self):
"""Get frames from all cameras with detections"""
frames = []
for i, (cam_path, cam) in enumerate(self.cameras):
ret, frame = cam.read()
if not ret:
print(f"Warning: Could not read frame from camera {cam_path}")
frame = np.zeros((720, 1280, 3), dtype=np.uint8)
else:
frame = self.get_detections(frame)
frames.append(frame)
return frames
class CameraDisplay(QLabel): class CameraDisplay(QLabel):
"""Custom QLabel for displaying camera feed""" """Custom QLabel for displaying camera feed with fullscreen support"""
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self.setAlignment(Qt.AlignCenter) self.setAlignment(Qt.AlignCenter)
self.setText("No camera feed") self.setText("No camera feed")
self.setStyleSheet("background-color: black; color: white;") self.setStyleSheet("""
QLabel {
background-color: #1E1E1E;
color: #DDD;
border: 2px solid #444;
border-radius: 4px;
}
""")
self.setMinimumSize(320, 240) self.setMinimumSize(320, 240)
self.fullscreen_window = None
self.cam_id = None
self.fullscreen_timer = None
def set_cam_id(self, cam_id):
"""Set camera identifier for this display"""
self.cam_id = cam_id
def mouseDoubleClickEvent(self, event):
"""Handle double click to toggle fullscreen"""
if self.pixmap() and not self.fullscreen_window:
self.show_fullscreen()
elif self.fullscreen_window:
self.close_fullscreen()
def show_fullscreen(self):
"""Show this camera in fullscreen mode"""
self.fullscreen_window = QMainWindow()
self.fullscreen_window.setWindowTitle(f"Camera {self.cam_id} - Fullscreen")
screen = QApplication.primaryScreen().availableGeometry()
self.fullscreen_window.resize(int(screen.width() * 0.9), int(screen.height() * 0.9))
label = QLabel()
label.setAlignment(Qt.AlignCenter)
if self.pixmap():
label.setPixmap(self.pixmap().scaled(
self.fullscreen_window.size(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
))
self.fullscreen_window.setCentralWidget(label)
self.fullscreen_window.showFullScreen()
# Update fullscreen image when main window updates
self.fullscreen_timer = QTimer()
self.fullscreen_timer.timeout.connect(
lambda: self.update_fullscreen(label)
)
self.fullscreen_timer.start(30)
def update_fullscreen(self, label):
"""Update the fullscreen display"""
if self.pixmap():
label.setPixmap(self.pixmap().scaled(
label.size(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
))
def close_fullscreen(self):
"""Close the fullscreen window"""
if self.fullscreen_window:
if self.fullscreen_timer:
self.fullscreen_timer.stop()
self.fullscreen_window.close()
self.fullscreen_window = None
self.fullscreen_timer = None
class CollapsibleDock(QDockWidget):
"""Custom dock widget with collapse/expand functionality"""
def __init__(self, title, parent=None):
super().__init__(title, parent)
self.setFeatures(QDockWidget.DockWidgetClosable |
QDockWidget.DockWidgetMovable |
QDockWidget.DockWidgetFloatable)
self.toggle_button = QToolButton(self)
self.toggle_button.setIcon(QIcon.fromTheme("arrow-left"))
self.toggle_button.setIconSize(QSize(16, 16))
self.toggle_button.setStyleSheet("border: none;")
self.toggle_button.clicked.connect(self.toggle_collapse)
self.setTitleBarWidget(self.toggle_button)
self.collapsed = False
self.original_size = None
def toggle_collapse(self):
"""Toggle between collapsed and expanded states"""
if self.collapsed:
self.expand()
else:
self.collapse()
def collapse(self):
"""Collapse the dock widget"""
if not self.collapsed:
self.original_size = self.size()
self.setFixedWidth(40)
self.toggle_button.setIcon(QIcon.fromTheme("arrow-right"))
self.collapsed = True
def expand(self):
"""Expand the dock widget"""
if self.collapsed:
self.setMinimumWidth(250)
self.setMaximumWidth(16777215) # Qt default maximum
if self.original_size:
self.resize(self.original_size)
self.toggle_button.setIcon(QIcon.fromTheme("arrow-left"))
self.collapsed = False
class MainWindow(QMainWindow): class MainWindow(QMainWindow):
def __init__(self): def __init__(self):
@@ -242,13 +339,111 @@ class MainWindow(QMainWindow):
self.setWindowTitle("Multi-Camera YOLO Detection") self.setWindowTitle("Multi-Camera YOLO Detection")
self.setGeometry(100, 100, 1200, 800) self.setGeometry(100, 100, 1200, 800)
self.detector = MultiCamYOLODetector() # Set dark theme style
self.camera_settings = {} # Store camera-specific settings self.setStyleSheet("""
self.create_menus() QMainWindow, QWidget {
background-color: #2D2D2D;
color: #DDD;
}
QLabel {
color: #DDD;
}
QPushButton {
background-color: #3A3A3A;
color: #DDD;
border: 1px solid #555;
border-radius: 4px;
padding: 5px;
}
QPushButton:hover {
background-color: #4A4A4A;
}
QPushButton:pressed {
background-color: #2A2A2A;
}
QPushButton:disabled {
background-color: #2A2A2A;
color: #777;
}
QComboBox, QSpinBox {
background-color: #3A3A3A;
color: #DDD;
border: 1px solid #555;
border-radius: 4px;
padding: 3px;
}
QGroupBox {
border: 1px solid #555;
border-radius: 4px;
margin-top: 10px;
padding-top: 15px;
background-color: #252525;
}
QGroupBox::title {
subcontrol-origin: margin;
left: 10px;
padding: 0 3px;
color: #DDD;
}
QMenuBar {
background-color: #252525;
color: #DDD;
}
QMenuBar::item {
background-color: transparent;
padding: 5px 10px;
}
QMenuBar::item:selected {
background-color: #3A3A3A;
}
QMenu {
background-color: #252525;
border: 1px solid #444;
color: #DDD;
}
QMenu::item:selected {
background-color: #3A3A3A;
}
QScrollArea {
border: none;
}
QDockWidget {
titlebar-close-icon: url(none);
titlebar-normal-icon: url(none);
}
QDockWidget::title {
background: #252525;
padding-left: 5px;
}
QToolButton {
background-color: transparent;
border: none;
}
""")
# Set palette for better dark mode support
palette = self.palette()
palette.setColor(palette.Window, QColor(45, 45, 45))
palette.setColor(palette.WindowText, QColor(221, 221, 221))
palette.setColor(palette.Base, QColor(35, 35, 35))
palette.setColor(palette.AlternateBase, QColor(45, 45, 45))
palette.setColor(palette.ToolTipBase, QColor(221, 221, 221))
palette.setColor(palette.ToolTipText, QColor(221, 221, 221))
palette.setColor(palette.Text, QColor(221, 221, 221))
palette.setColor(palette.Button, QColor(58, 58, 58))
palette.setColor(palette.ButtonText, QColor(221, 221, 221))
palette.setColor(palette.BrightText, Qt.red)
palette.setColor(palette.Link, QColor(42, 130, 218))
palette.setColor(palette.Highlight, QColor(42, 130, 218))
palette.setColor(palette.HighlightedText, Qt.black)
self.setPalette(palette)
self.detector = MultiCamYOLODetector()
self.camera_settings = {}
self.create_menus()
self.init_ui() self.init_ui()
self.init_timer() self.init_timer()
def create_menus(self): def create_menus(self):
"""Create the menu bar with model and camera menus""" """Create the menu bar with model and camera menus"""
menubar = self.menuBar() menubar = self.menuBar()
@@ -310,52 +505,101 @@ class MainWindow(QMainWindow):
QMessageBox.information(self, "Success", "Model loaded successfully!") QMessageBox.information(self, "Success", "Model loaded successfully!")
else: else:
QMessageBox.critical(self, "Error", "Failed to load model from selected directory") QMessageBox.critical(self, "Error", "Failed to load model from selected directory")
def init_ui(self): def init_ui(self):
"""Initialize the user interface""" """Initialize the user interface with collapsible sidebar"""
main_widget = QWidget() main_widget = QWidget()
main_layout = QVBoxLayout() main_layout = QHBoxLayout()
# Control panel # Create collapsible sidebar
control_panel = QGroupBox("Controls") self.sidebar = CollapsibleDock("Controls")
control_layout = QHBoxLayout() self.sidebar.setMinimumWidth(250)
# Sidebar content
sidebar_content = QWidget()
sidebar_layout = QVBoxLayout()
# Model section
model_group = QGroupBox("Model")
model_layout = QVBoxLayout()
# Model info
self.model_label = QLabel("Model: Not loaded") self.model_label = QLabel("Model: Not loaded")
control_layout.addWidget(self.model_label) model_layout.addWidget(self.model_label)
load_model_btn = QPushButton("Load Model Directory...")
load_model_btn.clicked.connect(self.load_model_directory)
model_layout.addWidget(load_model_btn)
model_group.setLayout(model_layout)
sidebar_layout.addWidget(model_group)
# Camera section
camera_group = QGroupBox("Cameras")
camera_layout = QVBoxLayout()
# Selected cameras label
self.cameras_label = QLabel("Selected Cameras: None") self.cameras_label = QLabel("Selected Cameras: None")
control_layout.addWidget(self.cameras_label) camera_layout.addWidget(self.cameras_label)
refresh_cams_btn = QPushButton("Refresh Camera List")
refresh_cams_btn.clicked.connect(self.populate_camera_menu)
camera_layout.addWidget(refresh_cams_btn)
camera_group.setLayout(camera_layout)
sidebar_layout.addWidget(camera_group)
# Settings section
settings_group = QGroupBox("Settings")
settings_layout = QVBoxLayout()
# FPS control # FPS control
fps_layout = QHBoxLayout()
fps_layout.addWidget(QLabel("FPS:"))
self.fps_spin = QSpinBox() self.fps_spin = QSpinBox()
self.fps_spin.setRange(1, 60) self.fps_spin.setRange(1, 60)
self.fps_spin.setValue(10) self.fps_spin.setValue(10)
control_layout.addWidget(QLabel("FPS:")) fps_layout.addWidget(self.fps_spin)
control_layout.addWidget(self.fps_spin) settings_layout.addLayout(fps_layout)
# Camera layout selection # Layout selection
layout_layout = QHBoxLayout()
layout_layout.addWidget(QLabel("Layout:"))
self.layout_combo = QComboBox() self.layout_combo = QComboBox()
self.layout_combo.addItems(["1 Camera", "2 Cameras", "3 Cameras", "4 Cameras", "Grid Layout"]) self.layout_combo.addItems(["1 Camera", "2 Cameras", "3 Cameras", "4 Cameras", "Grid Layout"])
self.layout_combo.currentIndexChanged.connect(self.change_camera_layout) self.layout_combo.currentIndexChanged.connect(self.change_camera_layout)
control_layout.addWidget(QLabel("Layout:")) layout_layout.addWidget(self.layout_combo)
control_layout.addWidget(self.layout_combo) settings_layout.addLayout(layout_layout)
# Buttons settings_group.setLayout(settings_layout)
self.start_btn = QPushButton("Start Detection") sidebar_layout.addWidget(settings_group)
# Control buttons
btn_layout = QHBoxLayout()
self.start_btn = QPushButton("Start")
self.start_btn.clicked.connect(self.start_detection) self.start_btn.clicked.connect(self.start_detection)
control_layout.addWidget(self.start_btn) btn_layout.addWidget(self.start_btn)
self.stop_btn = QPushButton("Stop Detection") self.stop_btn = QPushButton("Stop")
self.stop_btn.clicked.connect(self.stop_detection) self.stop_btn.clicked.connect(self.stop_detection)
self.stop_btn.setEnabled(False) self.stop_btn.setEnabled(False)
control_layout.addWidget(self.stop_btn) btn_layout.addWidget(self.stop_btn)
control_panel.setLayout(control_layout) sidebar_layout.addLayout(btn_layout)
main_layout.addWidget(control_panel)
# Camera display area # Add stretch to push everything up
sidebar_layout.addStretch()
sidebar_content.setLayout(sidebar_layout)
# Add scroll area to sidebar
scroll = QScrollArea()
scroll.setWidget(sidebar_content)
scroll.setWidgetResizable(True)
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.sidebar.setWidget(scroll)
self.addDockWidget(Qt.LeftDockWidgetArea, self.sidebar)
# Main display area
self.display_area = QWidget() self.display_area = QWidget()
self.display_layout = QGridLayout() self.display_layout = QGridLayout()
self.camera_displays = [] self.camera_displays = []
@@ -363,6 +607,7 @@ class MainWindow(QMainWindow):
# Initially create 4 camera displays # Initially create 4 camera displays
for i in range(4): for i in range(4):
display = CameraDisplay() display = CameraDisplay()
display.set_cam_id(i+1)
self.camera_displays.append(display) self.camera_displays.append(display)
self.display_layout.addWidget(display, i//2, i%2) self.display_layout.addWidget(display, i//2, i%2)
@@ -372,6 +617,9 @@ class MainWindow(QMainWindow):
main_widget.setLayout(main_layout) main_widget.setLayout(main_layout)
self.setCentralWidget(main_widget) self.setCentralWidget(main_widget)
# Start with sidebar expanded
self.sidebar.expand()
def change_camera_layout(self, index): def change_camera_layout(self, index):
"""Change the camera display layout""" """Change the camera display layout"""
# Clear the layout # Clear the layout
@@ -402,12 +650,12 @@ class MainWindow(QMainWindow):
# Hide unused displays # Hide unused displays
for i, display in enumerate(self.camera_displays): for i, display in enumerate(self.camera_displays):
display.setVisible(i < num_cameras) display.setVisible(i < num_cameras)
def init_timer(self): def init_timer(self):
"""Initialize the timer for updating camera feeds""" """Initialize the timer for updating camera feeds"""
self.timer = QTimer() self.timer = QTimer()
self.timer.timeout.connect(self.update_feeds) self.timer.timeout.connect(self.update_feeds)
def start_detection(self): def start_detection(self):
"""Start the detection process""" """Start the detection process"""
if not self.detector.model_dir: if not self.detector.model_dir:
@@ -441,7 +689,7 @@ class MainWindow(QMainWindow):
# Start timer # Start timer
self.timer.start(int(1000 / self.detector.target_fps)) self.timer.start(int(1000 / self.detector.target_fps))
def stop_detection(self): def stop_detection(self):
"""Stop the detection process""" """Stop the detection process"""
self.timer.stop() self.timer.stop()
@@ -455,8 +703,15 @@ class MainWindow(QMainWindow):
# Clear displays # Clear displays
for display in self.camera_displays: for display in self.camera_displays:
display.setText("No camera feed") display.setText("No camera feed")
display.setStyleSheet("background-color: black; color: white;") display.setStyleSheet("""
QLabel {
background-color: #1E1E1E;
color: #DDD;
border: 2px solid #444;
border-radius: 4px;
}
""")
def update_selection_labels(self): def update_selection_labels(self):
"""Update the model and camera selection labels""" """Update the model and camera selection labels"""
# Update cameras label # Update cameras label
@@ -465,7 +720,7 @@ class MainWindow(QMainWindow):
if action.isChecked(): if action.isChecked():
selected_cams.append(action.text()) selected_cams.append(action.text())
self.cameras_label.setText(f"Selected Cameras: {', '.join(selected_cams) or 'None'}") self.cameras_label.setText(f"Selected Cameras: {', '.join(selected_cams) or 'None'}")
def update_feeds(self): def update_feeds(self):
"""Update the camera feeds in the display""" """Update the camera feeds in the display"""
frames = self.detector.get_frames() frames = self.detector.get_frames()
@@ -493,6 +748,10 @@ class MainWindow(QMainWindow):
if __name__ == "__main__": if __name__ == "__main__":
app = QApplication(sys.argv) app = QApplication(sys.argv)
# Set application style to Fusion for better dark mode support
app.setStyle("Fusion")
window = MainWindow() window = MainWindow()
window.show() window.show()
sys.exit(app.exec_()) sys.exit(app.exec_())