pre uc480 integration

This commit is contained in:
Thomas Ales [M S E]
2026-05-22 09:38:39 -05:00
parent 43e7b99512
commit a0e0151b5d
30 changed files with 12557 additions and 41 deletions
+252
View File
@@ -0,0 +1,252 @@
#!/usr/bin/env python3
"""
UC480 Camera Test Application
Simple PyQt6 GUI for testing and viewing the uC480/uEye camera.
"""
import sys
import logging
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QGroupBox, QLabel, QPushButton, QDoubleSpinBox, QSpinBox,
QStatusBar, QSizePolicy
)
from PyQt6.QtCore import Qt, QTimer
from PyQt6.QtGui import QPixmap, QImage
from hardware.uc480_camera import UC480Camera, CameraStreamThread
logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
logger = logging.getLogger(__name__)
class CameraTestWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("UC480 Camera Test")
self.resize(900, 700)
self.camera: UC480Camera | None = None
self.stream_thread: CameraStreamThread | None = None
self._build_ui()
self._update_controls_enabled()
# ------------------------------------------------------------------
# UI construction
# ------------------------------------------------------------------
def _build_ui(self):
central = QWidget()
self.setCentralWidget(central)
root = QHBoxLayout(central)
root.setContentsMargins(8, 8, 8, 8)
# Left: video display
self.lbl_image = QLabel("No camera connected")
self.lbl_image.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.lbl_image.setMinimumSize(640, 480)
self.lbl_image.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
self.lbl_image.setStyleSheet("background: #111; color: #888; border: 1px solid #444;")
root.addWidget(self.lbl_image, stretch=1)
# Right: controls panel
panel = QWidget()
panel.setFixedWidth(230)
panel_layout = QVBoxLayout(panel)
panel_layout.setContentsMargins(0, 0, 0, 0)
root.addWidget(panel)
# Connection group
grp_conn = QGroupBox("Connection")
conn_layout = QVBoxLayout(grp_conn)
self.btn_connect = QPushButton("Connect")
self.btn_connect.clicked.connect(self._on_connect)
self.btn_disconnect = QPushButton("Disconnect")
self.btn_disconnect.clicked.connect(self._on_disconnect)
conn_layout.addWidget(self.btn_connect)
conn_layout.addWidget(self.btn_disconnect)
panel_layout.addWidget(grp_conn)
# Sensor info group
grp_info = QGroupBox("Sensor Info")
info_layout = QVBoxLayout(grp_info)
self.lbl_sensor_name = QLabel("Name: —")
self.lbl_resolution = QLabel("Resolution: —")
self.lbl_pixel_size = QLabel("Pixel size: —")
for lbl in (self.lbl_sensor_name, self.lbl_resolution, self.lbl_pixel_size):
lbl.setWordWrap(True)
info_layout.addWidget(lbl)
panel_layout.addWidget(grp_info)
# Exposure group
grp_exp = QGroupBox("Exposure (ms)")
exp_layout = QHBoxLayout(grp_exp)
self.spin_exposure = QDoubleSpinBox()
self.spin_exposure.setRange(0.01, 10000.0)
self.spin_exposure.setDecimals(2)
self.spin_exposure.setSingleStep(1.0)
self.spin_exposure.setValue(10.0)
self.btn_set_exposure = QPushButton("Set")
self.btn_set_exposure.setFixedWidth(40)
self.btn_set_exposure.clicked.connect(self._on_set_exposure)
exp_layout.addWidget(self.spin_exposure)
exp_layout.addWidget(self.btn_set_exposure)
panel_layout.addWidget(grp_exp)
# Gain group
grp_gain = QGroupBox("Master Gain (0–100)")
gain_layout = QHBoxLayout(grp_gain)
self.spin_gain = QSpinBox()
self.spin_gain.setRange(0, 100)
self.spin_gain.setValue(0)
self.btn_set_gain = QPushButton("Set")
self.btn_set_gain.setFixedWidth(40)
self.btn_set_gain.clicked.connect(self._on_set_gain)
gain_layout.addWidget(self.spin_gain)
gain_layout.addWidget(self.btn_set_gain)
panel_layout.addWidget(grp_gain)
# Stream control group
grp_stream = QGroupBox("Stream")
stream_layout = QVBoxLayout(grp_stream)
self.btn_start_stream = QPushButton("Start Stream")
self.btn_start_stream.clicked.connect(self._on_start_stream)
self.btn_stop_stream = QPushButton("Stop Stream")
self.btn_stop_stream.clicked.connect(self._on_stop_stream)
stream_layout.addWidget(self.btn_start_stream)
stream_layout.addWidget(self.btn_stop_stream)
panel_layout.addWidget(grp_stream)
panel_layout.addStretch()
# Status bar
self.statusBar().showMessage("Not connected")
# ------------------------------------------------------------------
# Button handlers
# ------------------------------------------------------------------
def _on_connect(self):
if self.camera is not None:
self.statusBar().showMessage("Already connected")
return
self.camera = UC480Camera(camera_id=1)
self.camera.error_occurred.connect(self._on_camera_error)
if not self.camera.initialize():
self.statusBar().showMessage("Failed to initialize camera")
self.camera = None
return
info = self.camera.get_sensor_info()
self.lbl_sensor_name.setText(f"Name: {info.get('sensor_name', '?')}")
self.lbl_resolution.setText(
f"Resolution: {info.get('max_width', '?')}×{info.get('max_height', '?')}"
)
self.lbl_pixel_size.setText(f"Pixel size: {info.get('pixel_size', '?')} µm")
self.statusBar().showMessage("Camera connected")
self._update_controls_enabled()
def _on_disconnect(self):
self._on_stop_stream()
if self.camera is not None:
self.camera.cleanup()
self.camera = None
self.lbl_image.setText("No camera connected")
self.lbl_sensor_name.setText("Name: —")
self.lbl_resolution.setText("Resolution: —")
self.lbl_pixel_size.setText("Pixel size: —")
self.statusBar().showMessage("Disconnected")
self._update_controls_enabled()
def _on_set_exposure(self):
if self.camera is None:
return
val = self.spin_exposure.value()
if self.camera.set_exposure(val):
actual = self.camera.get_exposure()
shown = f"{actual:.2f}" if actual is not None else f"{val:.2f}"
self.statusBar().showMessage(f"Exposure set to {shown} ms")
else:
self.statusBar().showMessage("Failed to set exposure")
def _on_set_gain(self):
if self.camera is None:
return
val = self.spin_gain.value()
if self.camera.set_gain(val):
self.statusBar().showMessage(f"Gain set to {val}")
else:
self.statusBar().showMessage("Failed to set gain")
def _on_start_stream(self):
if self.camera is None or self.stream_thread is not None:
return
self.stream_thread = CameraStreamThread(self.camera)
self.stream_thread.frame_ready.connect(self._on_frame)
self.stream_thread.error_occurred.connect(self._on_camera_error)
self.stream_thread.start()
self.statusBar().showMessage("Streaming…")
self._update_controls_enabled()
def _on_stop_stream(self):
if self.stream_thread is not None:
self.stream_thread.stop()
self.stream_thread = None
self.statusBar().showMessage("Stream stopped")
self._update_controls_enabled()
# ------------------------------------------------------------------
# Slots
# ------------------------------------------------------------------
def _on_frame(self, image: QImage):
scaled = image.scaled(
self.lbl_image.width(),
self.lbl_image.height(),
Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.FastTransformation,
)
self.lbl_image.setPixmap(QPixmap.fromImage(scaled))
def _on_camera_error(self, msg: str):
self.statusBar().showMessage(f"Error: {msg}")
logger.error(msg)
# ------------------------------------------------------------------
# Helpers
# ------------------------------------------------------------------
def _update_controls_enabled(self):
connected = self.camera is not None
streaming = self.stream_thread is not None
self.btn_connect.setEnabled(not connected)
self.btn_disconnect.setEnabled(connected)
self.btn_set_exposure.setEnabled(connected)
self.spin_exposure.setEnabled(connected)
self.btn_set_gain.setEnabled(connected)
self.spin_gain.setEnabled(connected)
self.btn_start_stream.setEnabled(connected and not streaming)
self.btn_stop_stream.setEnabled(streaming)
def closeEvent(self, event):
self._on_disconnect()
super().closeEvent(event)
# ---------------------------------------------------------------------------
def main():
app = QApplication(sys.argv)
window = CameraTestWindow()
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()