when'd i last commit this pos?

This commit is contained in:
Thomas Ales [M S E]
2026-02-09 14:40:34 -06:00
parent fc43fbe4b0
commit 23f6331ba2
94 changed files with 14427 additions and 12178 deletions
+475
View File
@@ -0,0 +1,475 @@
"""
uC480 Camera Driver
Driver for IDS/Thorlabs uEye uC480 cameras using pyueye library.
Provides camera control, live streaming, and image capture capabilities.
"""
import numpy as np
from pyueye import ueye
from PyQt6.QtCore import QThread, pyqtSignal, QObject
from PyQt6.QtGui import QImage
import logging
from typing import Optional, Tuple
logger = logging.getLogger(__name__)
class UC480Camera(QObject):
"""
Driver class for uC480 camera.
Handles initialization, configuration, and image acquisition.
"""
# Signals
frame_ready = pyqtSignal(QImage) # Emitted when a new frame is captured
error_occurred = pyqtSignal(str) # Emitted when an error occurs
def __init__(self, camera_id: int = 0):
"""
Initialize the uC480 camera driver.
Args:
camera_id: Camera ID (0 for first available camera)
"""
super().__init__()
self.camera_id = camera_id
self.h_cam = ueye.HIDS(camera_id)
self.is_initialized = False
self.is_capturing = False
# Memory and image info
self.mem_ptr = ueye.c_mem_p()
self.mem_id = ueye.int()
self.pitch = ueye.INT()
# Camera info
self.sensor_info = ueye.SENSORINFO()
self.cam_info = ueye.CAMINFO()
self.rect_aoi = ueye.IS_RECT()
# Image dimensions
self.width = 0
self.height = 0
self.bits_per_pixel = 24 # Default to 24-bit color
self.bytes_per_pixel = 3
self.color_mode = ueye.IS_CM_BGR8_PACKED
def initialize(self) -> bool:
"""
Initialize the camera and allocate memory.
Returns:
True if successful, False otherwise
"""
try:
# Initialize camera
ret = ueye.is_InitCamera(self.h_cam, None)
if ret != ueye.IS_SUCCESS:
logger.error(f"Failed to initialize camera: {ret}")
self.error_occurred.emit(f"Failed to initialize camera: {ret}")
return False
# Get sensor info
ret = ueye.is_GetSensorInfo(self.h_cam, self.sensor_info)
if ret != ueye.IS_SUCCESS:
logger.error(f"Failed to get sensor info: {ret}")
self.cleanup()
return False
# Get camera info
ret = ueye.is_GetCameraInfo(self.h_cam, self.cam_info)
if ret != ueye.IS_SUCCESS:
logger.error(f"Failed to get camera info: {ret}")
self.cleanup()
return False
# Set color mode
ret = ueye.is_SetColorMode(self.h_cam, self.color_mode)
if ret != ueye.IS_SUCCESS:
logger.error(f"Failed to set color mode: {ret}")
self.cleanup()
return False
# Get maximum image size
self.width = self.sensor_info.nMaxWidth.value
self.height = self.sensor_info.nMaxHeight.value
# Set Area of Interest (AOI) to maximum size
self.rect_aoi.s32X = ueye.int(0)
self.rect_aoi.s32Y = ueye.int(0)
self.rect_aoi.s32Width = ueye.int(self.width)
self.rect_aoi.s32Height = ueye.int(self.height)
ret = ueye.is_AOI(self.h_cam, ueye.IS_AOI_IMAGE_SET_AOI, self.rect_aoi, ueye.sizeof(self.rect_aoi))
if ret != ueye.IS_SUCCESS:
logger.error(f"Failed to set AOI: {ret}")
self.cleanup()
return False
# Allocate image memory
ret = ueye.is_AllocImageMem(
self.h_cam,
self.width,
self.height,
self.bits_per_pixel,
self.mem_ptr,
self.mem_id
)
if ret != ueye.IS_SUCCESS:
logger.error(f"Failed to allocate image memory: {ret}")
self.cleanup()
return False
# Set active memory
ret = ueye.is_SetImageMem(self.h_cam, self.mem_ptr, self.mem_id)
if ret != ueye.IS_SUCCESS:
logger.error(f"Failed to set active memory: {ret}")
self.cleanup()
return False
# Get pitch (bytes per line)
ret = ueye.is_GetImageMemPitch(self.h_cam, self.pitch)
if ret != ueye.IS_SUCCESS:
logger.error(f"Failed to get pitch: {ret}")
self.cleanup()
return False
self.is_initialized = True
logger.info(f"Camera initialized: {self.width}x{self.height}, {self.bits_per_pixel}bpp")
# Set default settings
self.set_exposure(10.0) # 10ms default exposure
self.set_pixel_clock(30) # 30MHz default pixel clock
self.set_framerate(30.0) # 30fps default
return True
except Exception as e:
logger.error(f"Exception during camera initialization: {e}")
self.error_occurred.emit(f"Exception during initialization: {e}")
self.cleanup()
return False
def cleanup(self):
"""Release camera resources."""
if self.is_capturing:
self.stop_capture()
if self.mem_ptr:
ueye.is_FreeImageMem(self.h_cam, self.mem_ptr, self.mem_id)
self.mem_ptr = None
if self.is_initialized:
ueye.is_ExitCamera(self.h_cam)
self.is_initialized = False
logger.info("Camera resources released")
def start_capture(self) -> bool:
"""
Start continuous video capture.
Returns:
True if successful, False otherwise
"""
if not self.is_initialized:
logger.error("Camera not initialized")
return False
if self.is_capturing:
logger.warning("Camera already capturing")
return True
ret = ueye.is_CaptureVideo(self.h_cam, ueye.IS_DONT_WAIT)
if ret != ueye.IS_SUCCESS:
logger.error(f"Failed to start capture: {ret}")
self.error_occurred.emit(f"Failed to start capture: {ret}")
return False
self.is_capturing = True
logger.info("Video capture started")
return True
def stop_capture(self) -> bool:
"""
Stop continuous video capture.
Returns:
True if successful, False otherwise
"""
if not self.is_capturing:
return True
ret = ueye.is_StopLiveVideo(self.h_cam, ueye.IS_WAIT)
if ret != ueye.IS_SUCCESS:
logger.error(f"Failed to stop capture: {ret}")
return False
self.is_capturing = False
logger.info("Video capture stopped")
return True
def get_frame(self) -> Optional[QImage]:
"""
Capture a single frame from the camera.
Returns:
QImage if successful, None otherwise
"""
if not self.is_initialized:
logger.error("Camera not initialized")
return None
# Create numpy array from image memory
try:
array = ueye.get_data(
self.mem_ptr,
self.width,
self.height,
self.bits_per_pixel,
self.pitch,
copy=True
)
# Reshape to image dimensions
frame = np.reshape(array, (self.height, self.width, self.bytes_per_pixel))
# Convert to QImage (BGR to RGB)
height, width, channel = frame.shape
bytes_per_line = self.bytes_per_pixel * width
# Convert BGR to RGB
rgb_frame = frame[:, :, ::-1].copy()
q_image = QImage(
rgb_frame.data,
width,
height,
bytes_per_line,
QImage.Format.Format_RGB888
)
# Make a copy since the numpy array will be deleted
return q_image.copy()
except Exception as e:
logger.error(f"Failed to get frame: {e}")
self.error_occurred.emit(f"Failed to get frame: {e}")
return None
def set_exposure(self, exposure_ms: float) -> bool:
"""
Set camera exposure time.
Args:
exposure_ms: Exposure time in milliseconds
Returns:
True if successful, False otherwise
"""
if not self.is_initialized:
return False
exposure = ueye.c_double(exposure_ms)
ret = ueye.is_Exposure(
self.h_cam,
ueye.IS_EXPOSURE_CMD_SET_EXPOSURE,
exposure,
ueye.sizeof(exposure)
)
if ret == ueye.IS_SUCCESS:
logger.debug(f"Exposure set to {exposure_ms}ms")
return True
else:
logger.error(f"Failed to set exposure: {ret}")
return False
def get_exposure(self) -> Optional[float]:
"""
Get current exposure time.
Returns:
Exposure time in milliseconds, or None if failed
"""
if not self.is_initialized:
return None
exposure = ueye.c_double()
ret = ueye.is_Exposure(
self.h_cam,
ueye.IS_EXPOSURE_CMD_GET_EXPOSURE,
exposure,
ueye.sizeof(exposure)
)
if ret == ueye.IS_SUCCESS:
return exposure.value
else:
return None
def set_pixel_clock(self, pixel_clock_mhz: int) -> bool:
"""
Set camera pixel clock.
Args:
pixel_clock_mhz: Pixel clock in MHz
Returns:
True if successful, False otherwise
"""
if not self.is_initialized:
return False
ret = ueye.is_PixelClock(
self.h_cam,
ueye.IS_PIXELCLOCK_CMD_SET,
ueye.c_uint(pixel_clock_mhz),
ueye.sizeof(ueye.c_uint)
)
if ret == ueye.IS_SUCCESS:
logger.debug(f"Pixel clock set to {pixel_clock_mhz}MHz")
return True
else:
logger.error(f"Failed to set pixel clock: {ret}")
return False
def set_framerate(self, fps: float) -> bool:
"""
Set camera framerate.
Args:
fps: Frames per second
Returns:
True if successful, False otherwise
"""
if not self.is_initialized:
return False
new_fps = ueye.c_double(fps)
actual_fps = ueye.c_double()
ret = ueye.is_SetFrameRate(self.h_cam, new_fps, actual_fps)
if ret == ueye.IS_SUCCESS:
logger.debug(f"Framerate set to {fps}fps")
return True
else:
logger.error(f"Failed to set framerate: {ret}")
return False
def get_framerate(self) -> Optional[float]:
"""
Get current framerate.
Returns:
Framerate in fps, or None if failed
"""
if not self.is_initialized:
return None
fps = ueye.c_double()
ret = ueye.is_GetFramesPerSecond(self.h_cam, fps)
if ret == ueye.IS_SUCCESS:
return fps.value
else:
return None
def set_gain(self, master_gain: int) -> bool:
"""
Set camera master gain.
Args:
master_gain: Gain value (0-100)
Returns:
True if successful, False otherwise
"""
if not self.is_initialized:
return False
if master_gain < 0 or master_gain > 100:
logger.error(f"Gain value {master_gain} out of range (0-100)")
return False
ret = ueye.is_SetHardwareGain(
self.h_cam,
master_gain,
ueye.IS_IGNORE_PARAMETER,
ueye.IS_IGNORE_PARAMETER,
ueye.IS_IGNORE_PARAMETER
)
if ret == ueye.IS_SUCCESS:
logger.debug(f"Master gain set to {master_gain}")
return True
else:
logger.error(f"Failed to set gain: {ret}")
return False
def get_sensor_info(self) -> dict:
"""
Get camera sensor information.
Returns:
Dictionary with sensor information
"""
if not self.is_initialized:
return {}
return {
'sensor_name': self.sensor_info.strSensorName.decode('utf-8'),
'max_width': self.sensor_info.nMaxWidth.value,
'max_height': self.sensor_info.nMaxHeight.value,
'color_mode': self.sensor_info.nColorMode.value,
'pixel_size': self.sensor_info.wPixelSize.value / 100.0, # in µm
}
def __del__(self):
"""Destructor - ensure cleanup."""
self.cleanup()
class CameraStreamThread(QThread):
"""
Thread for continuous camera frame acquisition and streaming.
"""
frame_ready = pyqtSignal(QImage)
error_occurred = pyqtSignal(str)
def __init__(self, camera: UC480Camera):
"""
Initialize the camera stream thread.
Args:
camera: UC480Camera instance
"""
super().__init__()
self.camera = camera
self.running = False
def run(self):
"""Main thread loop for frame acquisition."""
self.running = True
if not self.camera.start_capture():
self.error_occurred.emit("Failed to start camera capture")
return
while self.running:
frame = self.camera.get_frame()
if frame is not None:
self.frame_ready.emit(frame)
else:
# Small delay on error to prevent CPU spinning
self.msleep(10)
self.camera.stop_capture()
def stop(self):
"""Stop the streaming thread."""
self.running = False
self.wait()