import sys import os import logging from ctypes import * from math import log10 from camera.CameraParams_const import * # 添加相机模块路径 sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), "camera")) try: from PySide6.QtWidgets import QMessageBox from PySide6.QtCore import Qt, Signal USE_PYSIDE6 = True except ImportError: from PyQt5.QtWidgets import QMessageBox from PyQt5.QtCore import Qt from PyQt5.QtCore import pyqtSignal as Signal USE_PYSIDE6 = False # 导入相机相关模块 from camera.MvCameraControl_class import MvCamera from camera.CamOperation_class import CameraOperation from camera.MvErrorDefine_const import * from camera.CameraParams_header import * from ui.settings_ui import SettingsUI # 导入相机管理器 from widgets.camera_manager import CameraManager from PySide6.QtCore import QObject class CameraSettingsWidget(QObject): """相机设置控制器,管理相机设置并提供与主窗口相机显示部分的通信 注意:这是一个QObject控制器,不是QWidget,只处理逻辑,UI控件需要从父组件获取""" # 定义信号 signal_camera_connection = Signal(bool, str) # 相机连接状态信号 (是否连接, 错误消息) signal_camera_params_changed = Signal(float, float, float) # 相机参数变化信号 (曝光, 增益, 帧率) signal_camera_error = Signal(str) # 相机错误信号 settings_changed = Signal() # 设置变更信号,与 SettingsWindow 兼容 def __init__(self, parent=None): """初始化相机设置控制器 Args: parent: 父组件,需要包含所有必要的UI控件 """ # 作为QObject初始化 super().__init__(parent) # 记录父组件引用 self.parent = parent # 获取相机管理器实例 self.camera_manager = CameraManager.get_instance() # 初始化日志记录 logging.debug("CameraSettingsWidget初始化开始") # 从父组件获取所有需要的UI控件 if parent is not None: logging.info(f"父控件类型: {type(parent).__name__}") # 从父组件获取所有需要的UI控件 self.camera_combo = getattr(parent, 'camera_combo', None) self.refresh_button = getattr(parent, 'refresh_button', None) self.open_button = getattr(parent, 'open_button', None) self.close_button = getattr(parent, 'close_button', None) self.test_button = getattr(parent, 'test_button', None) self.exposure_slider = getattr(parent, 'exposure_slider', None) self.gain_slider = getattr(parent, 'gain_slider', None) self.framerate_slider = getattr(parent, 'framerate_slider', None) self.exposure_value = getattr(parent, 'exposure_value', None) self.gain_value = getattr(parent, 'gain_value', None) self.framerate_value = getattr(parent, 'framerate_value', None) self.get_params_button = getattr(parent, 'get_params_button', None) self.set_params_button = getattr(parent, 'set_params_button', None) self.save_camera_button = getattr(parent, 'save_camera_button', None) self.preview_frame = getattr(parent, 'preview_frame', None) # 检查是否成功获取到了所有必要的UI控件 if self.camera_combo is None: logging.error("未能从父组件获取camera_combo") else: logging.info(f"获取到父组件的camera_combo: ID={id(self.camera_combo)}, 项目数={self.camera_combo.count()}") # 测试向下拉框添加项目 try: self.camera_combo.addItem("控制器初始化测试项") logging.info(f"成功添加测试项到camera_combo,当前项目数={self.camera_combo.count()}") except Exception as e: logging.error(f"添加测试项失败: {e}") else: logging.error("CameraSettingsWidget必须有一个包含UI控件的父组件") # 创建缺少的UI控件引用,这些引用将为None self.camera_combo = None self.refresh_button = None self.open_button = None self.close_button = None self.test_button = None self.exposure_slider = None self.gain_slider = None self.framerate_slider = None self.exposure_value = None self.gain_value = None self.framerate_value = None self.get_params_button = None self.set_params_button = None self.save_camera_button = None self.preview_frame = None # 连接信号和槽 self.connect_signals() # 初始化相机参数范围 - 注意:曝光参数的实际范围已改为线性映射 self.frame_rate_min = 1.0 self.frame_rate_max = 60.0 # 以下曝光范围仅用于兼容旧代码,实际的映射在update_exposure_value方法中实现 self.exposure_min = 0.0 # 0 μs (最小值) self.exposure_max = 20000.0 # 20000 μs (20ms) - 调整后的最大值 self.gain_min = 0.0 self.gain_max = 15.0 # 枚举设备 self.refresh_devices() def connect_signals(self): """连接信号和槽""" # 设备选择和刷新 if hasattr(self, 'refresh_button'): self.refresh_button.clicked.connect(self.refresh_devices) # 相机控制按钮 self.open_button.clicked.connect(self.open_camera) self.close_button.clicked.connect(self.close_camera) self.test_button.clicked.connect(self.test_camera) # 参数滑块 self.exposure_slider.valueChanged.connect(self.update_exposure_value) self.gain_slider.valueChanged.connect(self.update_gain_value) self.framerate_slider.valueChanged.connect(self.update_frame_rate_value) # 参数操作按钮 self.get_params_button.clicked.connect(self.get_camera_params) self.set_params_button.clicked.connect(self.set_camera_params) self.save_camera_button.clicked.connect(self.save_camera_params) def refresh_devices(self): """刷新设备列表""" logging.info("【设备刷新】开始...") # 直接检查是否能访问正确的combobox if not hasattr(self, 'camera_combo') or self.camera_combo is None: logging.error("【设备刷新】无法访问camera_combo,尝试从父组件获取") # 尝试从父组件获取 if hasattr(self, 'parent') and self.parent is not None: if hasattr(self.parent, 'camera_combo'): self.camera_combo = self.parent.camera_combo logging.info(f"【设备刷新】已从父组件获取camera_combo: {id(self.camera_combo)}") else: logging.error("【设备刷新】父组件也没有camera_combo") return else: logging.error("【设备刷新】无法获取camera_combo且没有父组件") return # 直接测试下拉框是否可用 try: # 先清理 self.camera_combo.clear() # 添加一个测试项 self.camera_combo.addItem("刷新中...") logging.info(f"【设备刷新】成功添加测试项,当前项目数={self.camera_combo.count()}") # 更新UI self.camera_combo.update() self.camera_combo.repaint() except Exception as e: logging.error(f"【设备刷新】测试下拉框失败: {e}") # 1. 枚举设备 try: devices_info = self.camera_manager.enum_devices() if not devices_info: logging.warning("【设备刷新】未发现任何相机设备。") devices_info = [] except Exception as e: logging.error(f"【设备刷新】枚举设备时发生错误: {e}") devices_info = [] # 2. 准备要显示到下拉列表的数据 devList = [] if devices_info: for device in devices_info: devList.append(device["display"]) logging.info(f"【设备刷新】找到 {len(devList)} 个设备: {devList}") else: devList.append("未发现相机设备") logging.info(f"【设备刷新】将显示默认值 '未发现相机设备'") # 3. 更新UI上的下拉列表 try: if hasattr(self, 'camera_combo') and self.camera_combo is not None: logging.info(f"【设备刷新】开始更新下拉列表,当前状态: 项目数={self.camera_combo.count()}, 是否可见={self.camera_combo.isVisible()}") self.camera_combo.blockSignals(True) self.camera_combo.clear() # 确保项目数清零 if self.camera_combo.count() > 0: logging.warning(f"【设备刷新】clear()后项目数仍为 {self.camera_combo.count()}") # 直接添加项目 - 单个添加,避免批量添加可能的问题 for item in devList: self.camera_combo.addItem(item) logging.debug(f"【设备刷新】已添加项目: {item}") # 确保设置当前项目 if self.camera_combo.count() > 0: self.camera_combo.setCurrentIndex(0) logging.info(f"【设备刷新】已设置当前索引为0,显示文本: {self.camera_combo.currentText()}") else: logging.error("【设备刷新】未能添加任何项目到下拉列表") self.camera_combo.blockSignals(False) # 强制更新UI self.camera_combo.update() self.camera_combo.repaint() # 确保ComboBox有足够的尺寸显示内容 self.camera_combo.adjustSize() logging.info(f"【设备刷新】下拉列表更新完成。当前项目数: {self.camera_combo.count()}, 当前文本: {self.camera_combo.currentText()}") else: logging.error("【设备刷新】无法更新下拉列表,camera_combo不存在") except Exception as e: logging.error(f"【设备刷新】更新下拉列表时发生错误: {e}") # 4. 更新其他控件的状态 try: self.update_controls() logging.info("【设备刷新】控件状态已更新。") except Exception as e: logging.error(f"【设备刷新】更新控件状态时发生错误: {e}") # 5. 如果下拉列表仍然为空,尝试最后一次强制添加 try: if hasattr(self, 'camera_combo') and self.camera_combo is not None: if self.camera_combo.count() == 0: logging.warning("【设备刷新】下拉列表仍然为空,尝试强制添加项目") self.camera_combo.addItem("未发现相机设备(强制添加)") self.camera_combo.setCurrentIndex(0) self.camera_combo.update() self.camera_combo.repaint() except Exception as e: logging.error(f"【设备刷新】强制添加项目时发生错误: {e}") def get_selected_device_index(self): """获取当前选中的设备索引,参考BasicDemo.py的TxtWrapBy实现""" try: if not hasattr(self, 'camera_combo') or self.camera_combo is None: logging.error("无法获取设备索引:camera_combo不存在") return -1 if self.camera_combo.count() == 0: logging.warning("设备下拉列表为空") return -1 if self.camera_combo.currentText() == "未发现相机设备": return -1 current_text = self.camera_combo.currentText() start = current_text.find("[") + 1 end = current_text.find("]", start) if start <= 0 or end <= 0 or start >= end: logging.error(f"设备文本格式不正确: '{current_text}'") return -1 return int(current_text[start:end]) except Exception as e: logging.error(f"获取设备索引时出错: {e}, 文本: '{self.camera_combo.currentText() if hasattr(self, 'camera_combo') and self.camera_combo is not None else 'N/A'}'") return -1 def update_controls(self): """更新控件状态""" is_open = self.camera_manager.isOpen is_grabbing = self.camera_manager.isGrabbing # 使用安全的方式访问控件 if hasattr(self, 'refresh_button') and self.refresh_button is not None: self.refresh_button.setEnabled(not is_open) if hasattr(self, 'camera_combo') and self.camera_combo is not None: self.camera_combo.setEnabled(not is_open) has_valid_selection = self.get_selected_device_index() != -1 if hasattr(self, 'open_button') and self.open_button is not None: self.open_button.setEnabled(not is_open and has_valid_selection) if hasattr(self, 'close_button') and self.close_button is not None: self.close_button.setEnabled(is_open) if hasattr(self, 'test_button') and self.test_button is not None: self.test_button.setEnabled(is_open and not is_grabbing) if hasattr(self, 'get_params_button') and self.get_params_button is not None: self.get_params_button.setEnabled(is_open) if hasattr(self, 'set_params_button') and self.set_params_button is not None: self.set_params_button.setEnabled(is_open) if hasattr(self, 'save_camera_button') and self.save_camera_button is not None: self.save_camera_button.setEnabled(is_open) if hasattr(self, 'exposure_slider') and self.exposure_slider is not None: self.exposure_slider.setEnabled(is_open) if hasattr(self, 'gain_slider') and self.gain_slider is not None: self.gain_slider.setEnabled(is_open) if hasattr(self, 'framerate_slider') and self.framerate_slider is not None: self.framerate_slider.setEnabled(is_open) def open_camera(self): """打开相机""" try: device_index = self.get_selected_device_index() if device_index < 0: error_msg = "未选择相机设备" self.signal_camera_error.emit(error_msg) return False logging.info(f"尝试打开相机设备,索引: {device_index}") # 使用相机管理器打开设备 success = self.camera_manager.open_device(device_index) if success: logging.info(f"相机已成功打开") # 更新按钮状态 self.update_controls() # 向父窗口发送连接状态变化信号 self.signal_camera_connection.emit(True, "") # 更新配置 from utils.config_loader import ConfigLoader config_loader = ConfigLoader.get_instance() config_loader.set_value('camera.enabled', True) config_loader.save_config() # 通知设置已更改 self.settings_changed.emit() return True else: error_msg = "打开相机失败" self.signal_camera_error.emit(error_msg) return False except Exception as e: error_msg = f"打开相机时发生异常: {str(e)}" logging.error(error_msg) self.signal_camera_error.emit(error_msg) return False def close_camera(self): """关闭相机""" try: logging.info("尝试关闭相机") # 使用相机管理器关闭设备 self.camera_manager.close_device() # 更新按钮状态 self.update_controls() # 向父窗口发送连接状态变化信号 self.signal_camera_connection.emit(False, "") # 更新配置 from utils.config_loader import ConfigLoader config_loader = ConfigLoader.get_instance() config_loader.set_value('camera.enabled', False) config_loader.save_config() # 通知设置已更改 self.settings_changed.emit() logging.info("相机已关闭") return True except Exception as e: error_msg = f"关闭相机时发生异常: {str(e)}" logging.error(error_msg) self.signal_camera_error.emit(error_msg) return False def test_camera(self): """测试相机(在预览窗口显示图像)""" if not self.camera_manager.isOpen: return if self.camera_manager.isGrabbing: self.camera_manager.stop_grabbing() self.test_button.setText("开始预览") else: try: window_id = int(self.preview_frame.winId()) if self.camera_manager.start_grabbing(window_id): self.test_button.setText("停止预览") except Exception as e: parent_widget = self.parent if hasattr(self, "parent") else None QMessageBox.warning(parent_widget, "错误", f"开始预览失败: {e}") self.update_controls() def update_exposure_value(self, value): """更新曝光值显示(使用线性映射)""" # 直接使用滑块值(0-100)映射到曝光范围(1000-50000 μs) # 使用UI中设置的实际曝光范围 min_exp = 0 # 0 μs max_exp = 50000 # 线性映射 actual_value = min_exp + (value * (max_exp - min_exp) / 100.0) # 防止溢出 if actual_value > max_exp: actual_value = max_exp logging.warning(f"曝光值过大,已限制为{actual_value}μs") # 显示整数值,μs级别不需要小数点 self.exposure_value.setText(f"{int(actual_value)} μs") logging.debug(f"曝光滑块值: {value}, 映射后曝光值: {int(actual_value)}μs") def update_gain_value(self, value): """更新增益值显示""" actual_value = self.gain_min + (value / 100.0) * (self.gain_max - self.gain_min) self.gain_value.setText(f"{actual_value:.1f} dB") def update_frame_rate_value(self, value): """更新帧率值显示""" actual_value = self.frame_rate_min + (value / 100.0) * (self.frame_rate_max - self.frame_rate_min) self.framerate_value.setText(f"{actual_value:.1f} fps") def get_camera_params(self): """获取相机参数""" if not self.camera_manager.isOpen: return params = self.camera_manager.get_parameters() if not params: return exposure_time, gain, frame_rate = params # 使用线性映射计算曝光滑块值 min_exp = 0 # 0 μs max_exp = 20000 # 20000 μs # 将获取到的曝光时间限制在有效范围内 if exposure_time < min_exp: exposure_time = min_exp elif exposure_time > max_exp: exposure_time = max_exp # 线性映射到滑块值(0-100) exposure_slider_value = int(((exposure_time - min_exp) / (max_exp - min_exp)) * 100) # 确保滑块值在有效范围内 if exposure_slider_value < 0: exposure_slider_value = 0 elif exposure_slider_value > 100: exposure_slider_value = 100 self.exposure_slider.setValue(exposure_slider_value) # 其他参数仍使用原始映射方法 self.gain_slider.setValue(int(((gain - self.gain_min) / (self.gain_max - self.gain_min)) * 100)) self.framerate_slider.setValue(int(((frame_rate - self.frame_rate_min) / (self.frame_rate_max - self.frame_rate_min)) * 100)) logging.debug(f"获取相机参数: 曝光时间={exposure_time}μs -> 滑块值={exposure_slider_value}, 增益={gain}dB, 帧率={frame_rate}fps") self.signal_camera_params_changed.emit(exposure_time, gain, frame_rate) def set_camera_params(self): """设置相机参数""" if not self.camera_manager.isOpen: return # 使用线性映射计算曝光时间 min_exp = 0 # 0 μs max_exp = 50000 # 50000 μs # 根据滑块值(0-100)线性映射到曝光时间 slider_value = self.exposure_slider.value() exposure_time = min_exp + (slider_value * (max_exp - min_exp) / 100.0) # 确保曝光时间在有效范围内 if exposure_time < min_exp: exposure_time = min_exp elif exposure_time > max_exp: exposure_time = max_exp # 增益和帧率保持原来的映射方式 gain = self.gain_min + (self.gain_slider.value() / 100.0) * (self.gain_max - self.gain_min) frame_rate = self.frame_rate_min + (self.framerate_slider.value() / 100.0) * (self.frame_rate_max - self.frame_rate_min) logging.debug(f"设置相机参数: 曝光滑块值={slider_value} -> 曝光时间={exposure_time}μs, 增益={gain}dB, 帧率={frame_rate}fps") self.camera_manager.set_parameters(frame_rate, exposure_time, gain) self.signal_camera_params_changed.emit(exposure_time, gain, frame_rate) def save_camera_params(self): """保存相机参数到配置文件""" if not self.camera_manager.isOpen: return exposure = self.exposure_slider.value() gain = self.gain_slider.value() frame_rate = self.framerate_slider.value() parent_widget = self.parent if hasattr(self, "parent") else None if self.camera_manager.save_params_to_config(exposure, gain, frame_rate): QMessageBox.information(parent_widget, "成功", "相机参数已保存") self.settings_changed.emit() else: QMessageBox.critical(parent_widget, "错误", "保存相机参数失败") def closeEvent(self, event): """窗口关闭事件""" if self.camera_manager.isOpen: self.camera_manager.close_device() super().closeEvent(event)