#!/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, QSizePolicy ) from PyQt6.QtCore import Qt 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()