363 lines
13 KiB
Python
363 lines
13 KiB
Python
"""
|
|
Camera Control Panel Widget - Individual camera controls
|
|
"""
|
|
|
|
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
|
QPushButton, QSlider, QSpinBox, QComboBox,
|
|
QCheckBox, QGroupBox, QGridLayout)
|
|
from PySide6.QtCore import Qt, Signal
|
|
from PySide6.QtGui import QFont
|
|
|
|
class CameraControlPanel(QWidget):
|
|
"""
|
|
Individual camera control panel with adjustment capabilities
|
|
|
|
Features:
|
|
- Brightness/Contrast/Saturation controls
|
|
- Zoom and pan controls
|
|
- Recording controls
|
|
- Detection sensitivity
|
|
- ROI (Region of Interest) settings
|
|
"""
|
|
|
|
# Signals
|
|
setting_changed = Signal(str, str, object) # camera_id, setting_name, value
|
|
recording_toggled = Signal(str, bool) # camera_id, recording_state
|
|
snapshot_requested = Signal(str) # camera_id
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
|
|
self.active_camera = None
|
|
self.camera_settings = {}
|
|
|
|
self._setup_ui()
|
|
|
|
print("🎛️ Camera Control Panel initialized")
|
|
|
|
def _setup_ui(self):
|
|
"""Setup the camera control panel UI"""
|
|
layout = QVBoxLayout(self)
|
|
layout.setContentsMargins(5, 5, 5, 5)
|
|
layout.setSpacing(8)
|
|
|
|
# Camera selection info
|
|
info_section = self._create_info_section()
|
|
layout.addWidget(info_section)
|
|
|
|
# Image adjustment controls
|
|
image_section = self._create_image_section()
|
|
layout.addWidget(image_section)
|
|
|
|
# Position controls
|
|
position_section = self._create_position_section()
|
|
layout.addWidget(position_section)
|
|
|
|
# Recording controls
|
|
recording_section = self._create_recording_section()
|
|
layout.addWidget(recording_section)
|
|
|
|
layout.addStretch()
|
|
|
|
def _create_info_section(self):
|
|
"""Create camera info section"""
|
|
section = QGroupBox("Camera Info")
|
|
layout = QVBoxLayout(section)
|
|
|
|
# Active camera display
|
|
self.camera_info_label = QLabel("No camera selected")
|
|
self.camera_info_label.setFont(QFont("Segoe UI", 9, QFont.Bold))
|
|
layout.addWidget(self.camera_info_label)
|
|
|
|
# Status
|
|
self.status_info_label = QLabel("Status: Offline")
|
|
layout.addWidget(self.status_info_label)
|
|
|
|
# Resolution
|
|
self.resolution_label = QLabel("Resolution: --")
|
|
layout.addWidget(self.resolution_label)
|
|
|
|
return section
|
|
|
|
def _create_image_section(self):
|
|
"""Create image adjustment section"""
|
|
section = QGroupBox("Image Adjustment")
|
|
layout = QGridLayout(section)
|
|
|
|
# Brightness
|
|
layout.addWidget(QLabel("Brightness:"), 0, 0)
|
|
self.brightness_slider = QSlider(Qt.Horizontal)
|
|
self.brightness_slider.setRange(-100, 100)
|
|
self.brightness_slider.setValue(0)
|
|
self.brightness_slider.valueChanged.connect(
|
|
lambda v: self._setting_changed("brightness", v)
|
|
)
|
|
layout.addWidget(self.brightness_slider, 0, 1)
|
|
|
|
self.brightness_label = QLabel("0")
|
|
layout.addWidget(self.brightness_label, 0, 2)
|
|
|
|
# Contrast
|
|
layout.addWidget(QLabel("Contrast:"), 1, 0)
|
|
self.contrast_slider = QSlider(Qt.Horizontal)
|
|
self.contrast_slider.setRange(-100, 100)
|
|
self.contrast_slider.setValue(0)
|
|
self.contrast_slider.valueChanged.connect(
|
|
lambda v: self._setting_changed("contrast", v)
|
|
)
|
|
layout.addWidget(self.contrast_slider, 1, 1)
|
|
|
|
self.contrast_label = QLabel("0")
|
|
layout.addWidget(self.contrast_label, 1, 2)
|
|
|
|
# Saturation
|
|
layout.addWidget(QLabel("Saturation:"), 2, 0)
|
|
self.saturation_slider = QSlider(Qt.Horizontal)
|
|
self.saturation_slider.setRange(-100, 100)
|
|
self.saturation_slider.setValue(0)
|
|
self.saturation_slider.valueChanged.connect(
|
|
lambda v: self._setting_changed("saturation", v)
|
|
)
|
|
layout.addWidget(self.saturation_slider, 2, 1)
|
|
|
|
self.saturation_label = QLabel("0")
|
|
layout.addWidget(self.saturation_label, 2, 2)
|
|
|
|
# Auto adjust button
|
|
auto_btn = QPushButton("Auto Adjust")
|
|
auto_btn.clicked.connect(self._auto_adjust)
|
|
layout.addWidget(auto_btn, 3, 0, 1, 3)
|
|
|
|
# Connect slider value updates to labels
|
|
self.brightness_slider.valueChanged.connect(
|
|
lambda v: self.brightness_label.setText(str(v))
|
|
)
|
|
self.contrast_slider.valueChanged.connect(
|
|
lambda v: self.contrast_label.setText(str(v))
|
|
)
|
|
self.saturation_slider.valueChanged.connect(
|
|
lambda v: self.saturation_label.setText(str(v))
|
|
)
|
|
|
|
return section
|
|
|
|
def _create_position_section(self):
|
|
"""Create position and zoom controls"""
|
|
section = QGroupBox("Position & Zoom")
|
|
layout = QGridLayout(section)
|
|
|
|
# Zoom control
|
|
layout.addWidget(QLabel("Zoom:"), 0, 0)
|
|
self.zoom_slider = QSlider(Qt.Horizontal)
|
|
self.zoom_slider.setRange(100, 500) # 100% to 500%
|
|
self.zoom_slider.setValue(100)
|
|
self.zoom_slider.valueChanged.connect(
|
|
lambda v: self._setting_changed("zoom", v)
|
|
)
|
|
layout.addWidget(self.zoom_slider, 0, 1)
|
|
|
|
self.zoom_label = QLabel("100%")
|
|
layout.addWidget(self.zoom_label, 0, 2)
|
|
|
|
# Pan controls
|
|
pan_layout = QHBoxLayout()
|
|
|
|
# Pan buttons
|
|
pan_up_btn = QPushButton("↑")
|
|
pan_up_btn.setFixedSize(30, 30)
|
|
pan_up_btn.clicked.connect(lambda: self._pan_camera("up"))
|
|
|
|
pan_down_btn = QPushButton("↓")
|
|
pan_down_btn.setFixedSize(30, 30)
|
|
pan_down_btn.clicked.connect(lambda: self._pan_camera("down"))
|
|
|
|
pan_left_btn = QPushButton("←")
|
|
pan_left_btn.setFixedSize(30, 30)
|
|
pan_left_btn.clicked.connect(lambda: self._pan_camera("left"))
|
|
|
|
pan_right_btn = QPushButton("→")
|
|
pan_right_btn.setFixedSize(30, 30)
|
|
pan_right_btn.clicked.connect(lambda: self._pan_camera("right"))
|
|
|
|
# Arrange pan buttons in cross pattern
|
|
pan_grid = QGridLayout()
|
|
pan_grid.addWidget(pan_up_btn, 0, 1)
|
|
pan_grid.addWidget(pan_left_btn, 1, 0)
|
|
pan_grid.addWidget(pan_right_btn, 1, 2)
|
|
pan_grid.addWidget(pan_down_btn, 2, 1)
|
|
|
|
# Reset button in center
|
|
reset_btn = QPushButton("⌂")
|
|
reset_btn.setFixedSize(30, 30)
|
|
reset_btn.setToolTip("Reset to center")
|
|
reset_btn.clicked.connect(self._reset_position)
|
|
pan_grid.addWidget(reset_btn, 1, 1)
|
|
|
|
layout.addLayout(pan_grid, 1, 0, 1, 3)
|
|
|
|
# Connect zoom slider to label
|
|
self.zoom_slider.valueChanged.connect(
|
|
lambda v: self.zoom_label.setText(f"{v}%")
|
|
)
|
|
|
|
return section
|
|
|
|
def _create_recording_section(self):
|
|
"""Create recording controls"""
|
|
section = QGroupBox("Recording Controls")
|
|
layout = QVBoxLayout(section)
|
|
|
|
# Recording toggle
|
|
self.record_btn = QPushButton("🔴 Start Recording")
|
|
self.record_btn.clicked.connect(self._toggle_recording)
|
|
layout.addWidget(self.record_btn)
|
|
|
|
# Snapshot button
|
|
snapshot_btn = QPushButton("📸 Take Snapshot")
|
|
snapshot_btn.clicked.connect(self._take_snapshot)
|
|
layout.addWidget(snapshot_btn)
|
|
|
|
# Recording settings
|
|
settings_layout = QGridLayout()
|
|
|
|
# Quality setting
|
|
settings_layout.addWidget(QLabel("Quality:"), 0, 0)
|
|
self.quality_combo = QComboBox()
|
|
self.quality_combo.addItems(["Low", "Medium", "High", "Ultra"])
|
|
self.quality_combo.setCurrentText("High")
|
|
self.quality_combo.currentTextChanged.connect(
|
|
lambda v: self._setting_changed("quality", v)
|
|
)
|
|
settings_layout.addWidget(self.quality_combo, 0, 1)
|
|
|
|
# Frame rate
|
|
settings_layout.addWidget(QLabel("FPS:"), 1, 0)
|
|
self.fps_spinbox = QSpinBox()
|
|
self.fps_spinbox.setRange(1, 60)
|
|
self.fps_spinbox.setValue(30)
|
|
self.fps_spinbox.valueChanged.connect(
|
|
lambda v: self._setting_changed("fps", v)
|
|
)
|
|
settings_layout.addWidget(self.fps_spinbox, 1, 1)
|
|
|
|
layout.addLayout(settings_layout)
|
|
|
|
# Recording status
|
|
self.recording_status_label = QLabel("Not recording")
|
|
self.recording_status_label.setStyleSheet("color: gray; font-style: italic;")
|
|
layout.addWidget(self.recording_status_label)
|
|
|
|
return section
|
|
|
|
def set_active_camera(self, camera_id):
|
|
"""Set the active camera for controls"""
|
|
self.active_camera = camera_id
|
|
|
|
# Update display
|
|
display_name = camera_id.replace('_', ' ').title()
|
|
self.camera_info_label.setText(f"Camera: {display_name}")
|
|
|
|
# Load camera settings if available
|
|
if camera_id in self.camera_settings:
|
|
self._load_camera_settings(camera_id)
|
|
else:
|
|
self._reset_all_settings()
|
|
|
|
print(f"🎛️ Active camera set to: {camera_id}")
|
|
|
|
def _setting_changed(self, setting_name, value):
|
|
"""Handle setting change"""
|
|
if self.active_camera:
|
|
# Store setting
|
|
if self.active_camera not in self.camera_settings:
|
|
self.camera_settings[self.active_camera] = {}
|
|
|
|
self.camera_settings[self.active_camera][setting_name] = value
|
|
|
|
# Emit signal
|
|
self.setting_changed.emit(self.active_camera, setting_name, value)
|
|
|
|
def _toggle_recording(self):
|
|
"""Toggle recording for active camera"""
|
|
if not self.active_camera:
|
|
return
|
|
|
|
is_recording = self.record_btn.text().startswith("🔴")
|
|
|
|
if is_recording:
|
|
# Start recording
|
|
self.record_btn.setText("⏹️ Stop Recording")
|
|
self.recording_status_label.setText("Recording...")
|
|
self.recording_status_label.setStyleSheet("color: red; font-weight: bold;")
|
|
self.recording_toggled.emit(self.active_camera, True)
|
|
else:
|
|
# Stop recording
|
|
self.record_btn.setText("🔴 Start Recording")
|
|
self.recording_status_label.setText("Not recording")
|
|
self.recording_status_label.setStyleSheet("color: gray; font-style: italic;")
|
|
self.recording_toggled.emit(self.active_camera, False)
|
|
|
|
def _take_snapshot(self):
|
|
"""Take snapshot of active camera"""
|
|
if self.active_camera:
|
|
self.snapshot_requested.emit(self.active_camera)
|
|
|
|
def _auto_adjust(self):
|
|
"""Auto-adjust image settings"""
|
|
if self.active_camera:
|
|
# Reset to defaults with slight improvements
|
|
self.brightness_slider.setValue(10)
|
|
self.contrast_slider.setValue(15)
|
|
self.saturation_slider.setValue(5)
|
|
print(f"🎛️ Auto-adjusted settings for {self.active_camera}")
|
|
|
|
def _pan_camera(self, direction):
|
|
"""Pan camera in specified direction"""
|
|
if self.active_camera:
|
|
self._setting_changed(f"pan_{direction}", True)
|
|
print(f"🎛️ Panning camera {direction}")
|
|
|
|
def _reset_position(self):
|
|
"""Reset camera position to center"""
|
|
if self.active_camera:
|
|
self.zoom_slider.setValue(100)
|
|
self._setting_changed("reset_position", True)
|
|
print(f"🎛️ Reset position for {self.active_camera}")
|
|
|
|
def _load_camera_settings(self, camera_id):
|
|
"""Load stored settings for camera"""
|
|
settings = self.camera_settings.get(camera_id, {})
|
|
|
|
# Apply settings to controls
|
|
self.brightness_slider.setValue(settings.get("brightness", 0))
|
|
self.contrast_slider.setValue(settings.get("contrast", 0))
|
|
self.saturation_slider.setValue(settings.get("saturation", 0))
|
|
self.zoom_slider.setValue(settings.get("zoom", 100))
|
|
self.quality_combo.setCurrentText(settings.get("quality", "High"))
|
|
self.fps_spinbox.setValue(settings.get("fps", 30))
|
|
|
|
def _reset_all_settings(self):
|
|
"""Reset all controls to default values"""
|
|
self.brightness_slider.setValue(0)
|
|
self.contrast_slider.setValue(0)
|
|
self.saturation_slider.setValue(0)
|
|
self.zoom_slider.setValue(100)
|
|
self.quality_combo.setCurrentText("High")
|
|
self.fps_spinbox.setValue(30)
|
|
|
|
def update_camera_status(self, camera_id, status, resolution=None):
|
|
"""Update camera status information"""
|
|
if camera_id == self.active_camera:
|
|
self.status_info_label.setText(f"Status: {status.title()}")
|
|
|
|
if resolution:
|
|
self.resolution_label.setText(f"Resolution: {resolution}")
|
|
|
|
# Update status color
|
|
if status == "online":
|
|
self.status_info_label.setStyleSheet("color: green;")
|
|
elif status == "offline":
|
|
self.status_info_label.setStyleSheet("color: red;")
|
|
else:
|
|
self.status_info_label.setStyleSheet("color: orange;")
|