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,
QWidget, QLabel, QPushButton, QComboBox, QSpinBox,
QFileDialog, QMessageBox, QMenu, QAction, QMenuBar,
QActionGroup, QSizePolicy, QGridLayout, QGroupBox)
from PyQt5.QtCore import Qt, QTimer, QDir
from PyQt5.QtGui import QImage, QPixmap
QActionGroup, QSizePolicy, QGridLayout, QGroupBox,
QDockWidget, QScrollArea, QToolButton)
from PyQt5.QtCore import Qt, QTimer, QDir, QSize
from PyQt5.QtGui import QImage, QPixmap, QIcon, QColor
class MultiCamYOLODetector:
def __init__(self):
@@ -214,27 +215,123 @@ class MultiCamYOLODetector:
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):
"""Custom QLabel for displaying camera feed"""
"""Custom QLabel for displaying camera feed with fullscreen support"""
def __init__(self, parent=None):
super().__init__(parent)
self.setAlignment(Qt.AlignCenter)
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.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):
def __init__(self):
@@ -242,13 +339,111 @@ class MainWindow(QMainWindow):
self.setWindowTitle("Multi-Camera YOLO Detection")
self.setGeometry(100, 100, 1200, 800)
self.detector = MultiCamYOLODetector()
self.camera_settings = {} # Store camera-specific settings
self.create_menus()
# Set dark theme style
self.setStyleSheet("""
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_timer()
def create_menus(self):
"""Create the menu bar with model and camera menus"""
menubar = self.menuBar()
@@ -310,52 +505,101 @@ class MainWindow(QMainWindow):
QMessageBox.information(self, "Success", "Model loaded successfully!")
else:
QMessageBox.critical(self, "Error", "Failed to load model from selected directory")
def init_ui(self):
"""Initialize the user interface"""
"""Initialize the user interface with collapsible sidebar"""
main_widget = QWidget()
main_layout = QVBoxLayout()
main_layout = QHBoxLayout()
# Control panel
control_panel = QGroupBox("Controls")
control_layout = QHBoxLayout()
# Create collapsible sidebar
self.sidebar = CollapsibleDock("Controls")
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")
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")
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_layout = QHBoxLayout()
fps_layout.addWidget(QLabel("FPS:"))
self.fps_spin = QSpinBox()
self.fps_spin.setRange(1, 60)
self.fps_spin.setValue(10)
control_layout.addWidget(QLabel("FPS:"))
control_layout.addWidget(self.fps_spin)
fps_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.addItems(["1 Camera", "2 Cameras", "3 Cameras", "4 Cameras", "Grid Layout"])
self.layout_combo.currentIndexChanged.connect(self.change_camera_layout)
control_layout.addWidget(QLabel("Layout:"))
control_layout.addWidget(self.layout_combo)
layout_layout.addWidget(self.layout_combo)
settings_layout.addLayout(layout_layout)
# Buttons
self.start_btn = QPushButton("Start Detection")
settings_group.setLayout(settings_layout)
sidebar_layout.addWidget(settings_group)
# Control buttons
btn_layout = QHBoxLayout()
self.start_btn = QPushButton("Start")
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.setEnabled(False)
control_layout.addWidget(self.stop_btn)
btn_layout.addWidget(self.stop_btn)
control_panel.setLayout(control_layout)
main_layout.addWidget(control_panel)
sidebar_layout.addLayout(btn_layout)
# 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_layout = QGridLayout()
self.camera_displays = []
@@ -363,6 +607,7 @@ class MainWindow(QMainWindow):
# Initially create 4 camera displays
for i in range(4):
display = CameraDisplay()
display.set_cam_id(i+1)
self.camera_displays.append(display)
self.display_layout.addWidget(display, i//2, i%2)
@@ -372,6 +617,9 @@ class MainWindow(QMainWindow):
main_widget.setLayout(main_layout)
self.setCentralWidget(main_widget)
# Start with sidebar expanded
self.sidebar.expand()
def change_camera_layout(self, index):
"""Change the camera display layout"""
# Clear the layout
@@ -402,12 +650,12 @@ class MainWindow(QMainWindow):
# Hide unused displays
for i, display in enumerate(self.camera_displays):
display.setVisible(i < num_cameras)
def init_timer(self):
"""Initialize the timer for updating camera feeds"""
self.timer = QTimer()
self.timer.timeout.connect(self.update_feeds)
def start_detection(self):
"""Start the detection process"""
if not self.detector.model_dir:
@@ -441,7 +689,7 @@ class MainWindow(QMainWindow):
# Start timer
self.timer.start(int(1000 / self.detector.target_fps))
def stop_detection(self):
"""Stop the detection process"""
self.timer.stop()
@@ -455,8 +703,15 @@ class MainWindow(QMainWindow):
# Clear displays
for display in self.camera_displays:
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):
"""Update the model and camera selection labels"""
# Update cameras label
@@ -465,7 +720,7 @@ class MainWindow(QMainWindow):
if action.isChecked():
selected_cams.append(action.text())
self.cameras_label.setText(f"Selected Cameras: {', '.join(selected_cams) or 'None'}")
def update_feeds(self):
"""Update the camera feeds in the display"""
frames = self.detector.get_frames()
@@ -493,6 +748,10 @@ class MainWindow(QMainWindow):
if __name__ == "__main__":
app = QApplication(sys.argv)
# Set application style to Fusion for better dark mode support
app.setStyle("Fusion")
window = MainWindow()
window.show()
sys.exit(app.exec_())