2025-06-07 10:45:09 +08:00
|
|
|
|
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:
|
2025-07-07 15:33:56 +08:00
|
|
|
|
from PySide6.QtWidgets import QMessageBox, QFileDialog
|
2025-06-07 10:45:09 +08:00
|
|
|
|
from PySide6.QtCore import Qt, Signal
|
|
|
|
|
|
USE_PYSIDE6 = True
|
|
|
|
|
|
except ImportError:
|
2025-07-07 15:33:56 +08:00
|
|
|
|
from PyQt5.QtWidgets import QMessageBox, QFileDialog
|
2025-06-07 10:45:09 +08:00
|
|
|
|
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
|
|
|
|
|
|
|
2025-07-07 15:33:56 +08:00
|
|
|
|
# 导入本地图像播放器
|
|
|
|
|
|
from utils.local_image_player import LocalImagePlayer
|
|
|
|
|
|
# 导入配置加载器
|
|
|
|
|
|
from utils.config_loader import ConfigLoader
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
2025-06-27 15:14:30 +08:00
|
|
|
|
from PySide6.QtCore import QObject
|
2025-07-07 15:33:56 +08:00
|
|
|
|
import json
|
2025-06-27 15:14:30 +08:00
|
|
|
|
|
|
|
|
|
|
class CameraSettingsWidget(QObject):
|
|
|
|
|
|
"""相机设置控制器,管理相机设置并提供与主窗口相机显示部分的通信
|
|
|
|
|
|
注意:这是一个QObject控制器,不是QWidget,只处理逻辑,UI控件需要从父组件获取"""
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
2025-06-27 15:14:30 +08:00
|
|
|
|
# 定义信号
|
2025-06-07 10:45:09 +08:00
|
|
|
|
signal_camera_connection = Signal(bool, str) # 相机连接状态信号 (是否连接, 错误消息)
|
|
|
|
|
|
signal_camera_params_changed = Signal(float, float, float) # 相机参数变化信号 (曝光, 增益, 帧率)
|
|
|
|
|
|
signal_camera_error = Signal(str) # 相机错误信号
|
2025-06-12 17:29:35 +08:00
|
|
|
|
settings_changed = Signal() # 设置变更信号,与 SettingsWindow 兼容
|
2025-07-07 15:33:56 +08:00
|
|
|
|
signal_local_mode_changed = Signal(bool) # 本地图像模式变更信号 (是否启用)
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
|
|
|
|
|
def __init__(self, parent=None):
|
2025-06-27 15:14:30 +08:00
|
|
|
|
"""初始化相机设置控制器
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
parent: 父组件,需要包含所有必要的UI控件
|
|
|
|
|
|
"""
|
|
|
|
|
|
# 作为QObject初始化
|
2025-06-07 10:45:09 +08:00
|
|
|
|
super().__init__(parent)
|
|
|
|
|
|
|
2025-06-27 15:14:30 +08:00
|
|
|
|
# 记录父组件引用
|
|
|
|
|
|
self.parent = parent
|
|
|
|
|
|
|
2025-06-07 10:45:09 +08:00
|
|
|
|
# 获取相机管理器实例
|
|
|
|
|
|
self.camera_manager = CameraManager.get_instance()
|
|
|
|
|
|
|
2025-07-07 15:33:56 +08:00
|
|
|
|
# 加载配置
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.config_loader = ConfigLoader()
|
|
|
|
|
|
self.camera_config = self.config_loader.get_config("camera")
|
|
|
|
|
|
# 使用 .get() 来安全地访问 'display'
|
|
|
|
|
|
self.display_config = self.camera_config.get("display", {})
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logging.error(f"加载相机配置文件失败: {e}")
|
|
|
|
|
|
self.camera_config = {}
|
|
|
|
|
|
self.display_config = {}
|
|
|
|
|
|
|
|
|
|
|
|
# 创建本地图像播放器
|
|
|
|
|
|
self.local_player = LocalImagePlayer()
|
|
|
|
|
|
|
|
|
|
|
|
# 本地图像模式状态
|
|
|
|
|
|
self.local_mode_enabled = False
|
|
|
|
|
|
self.is_playing = False
|
|
|
|
|
|
|
2025-06-27 15:14:30 +08:00
|
|
|
|
# 初始化日志记录
|
|
|
|
|
|
logging.debug("CameraSettingsWidget初始化开始")
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
2025-06-27 15:14:30 +08:00
|
|
|
|
# 从父组件获取所有需要的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)
|
2025-07-07 15:33:56 +08:00
|
|
|
|
self.preview_status = getattr(parent, 'preview_status', None)
|
|
|
|
|
|
|
|
|
|
|
|
# 本地图像模式相关控件
|
|
|
|
|
|
self.local_mode_check = getattr(parent, 'local_mode_check', None)
|
|
|
|
|
|
self.folder_path_edit = getattr(parent, 'folder_path_edit', None)
|
|
|
|
|
|
self.folder_path_button = getattr(parent, 'folder_path_button', None)
|
|
|
|
|
|
self.local_framerate_slider = getattr(parent, 'local_framerate_slider', None)
|
|
|
|
|
|
self.local_framerate_value = getattr(parent, 'local_framerate_value', None)
|
|
|
|
|
|
self.loop_playback_check = getattr(parent, 'loop_playback_check', None)
|
|
|
|
|
|
self.play_button = getattr(parent, 'play_button', None)
|
|
|
|
|
|
self.stop_button = getattr(parent, 'stop_button', None)
|
2025-06-27 15:14:30 +08:00
|
|
|
|
|
|
|
|
|
|
# 检查是否成功获取到了所有必要的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
|
2025-07-07 15:33:56 +08:00
|
|
|
|
self.preview_status = None
|
|
|
|
|
|
self.local_mode_check = None
|
|
|
|
|
|
self.folder_path_edit = None
|
|
|
|
|
|
self.folder_path_button = None
|
|
|
|
|
|
self.local_framerate_slider = None
|
|
|
|
|
|
self.local_framerate_value = None
|
|
|
|
|
|
self.loop_playback_check = None
|
|
|
|
|
|
self.play_button = None
|
|
|
|
|
|
self.stop_button = None
|
2025-06-27 15:14:30 +08:00
|
|
|
|
|
2025-07-07 15:33:56 +08:00
|
|
|
|
# 连接本地图像播放器信号
|
|
|
|
|
|
self.connect_local_player_signals()
|
|
|
|
|
|
|
2025-06-07 10:45:09 +08:00
|
|
|
|
# 连接信号和槽
|
|
|
|
|
|
self.connect_signals()
|
2025-06-27 15:14:30 +08:00
|
|
|
|
|
2025-06-28 13:02:34 +08:00
|
|
|
|
# 初始化相机参数范围 - 注意:曝光参数的实际范围已改为线性映射
|
2025-06-07 10:45:09 +08:00
|
|
|
|
self.frame_rate_min = 1.0
|
|
|
|
|
|
self.frame_rate_max = 60.0
|
2025-06-28 13:02:34 +08:00
|
|
|
|
# 以下曝光范围仅用于兼容旧代码,实际的映射在update_exposure_value方法中实现
|
|
|
|
|
|
self.exposure_min = 0.0 # 0 μs (最小值)
|
|
|
|
|
|
self.exposure_max = 20000.0 # 20000 μs (20ms) - 调整后的最大值
|
2025-06-07 10:45:09 +08:00
|
|
|
|
self.gain_min = 0.0
|
|
|
|
|
|
self.gain_max = 15.0
|
|
|
|
|
|
|
2025-07-07 15:33:56 +08:00
|
|
|
|
# 从配置加载本地模式设置
|
|
|
|
|
|
self.load_local_mode_settings()
|
|
|
|
|
|
|
|
|
|
|
|
# 更新本地模式UI
|
|
|
|
|
|
self.update_local_mode_ui()
|
|
|
|
|
|
|
2025-06-07 10:45:09 +08:00
|
|
|
|
# 枚举设备
|
|
|
|
|
|
self.refresh_devices()
|
|
|
|
|
|
|
2025-07-07 15:33:56 +08:00
|
|
|
|
def connect_local_player_signals(self):
|
|
|
|
|
|
"""连接本地图像播放器的信号"""
|
|
|
|
|
|
if self.local_player:
|
|
|
|
|
|
self.local_player.signal_status.connect(self.handle_local_player_status)
|
|
|
|
|
|
self.local_player.signal_error.connect(self.handle_local_player_error)
|
|
|
|
|
|
self.local_player.signal_progress.connect(self.handle_local_player_progress)
|
|
|
|
|
|
self.local_player.signal_frame_ready.connect(self.handle_local_player_frame)
|
|
|
|
|
|
self.local_player.signal_completed.connect(self.handle_local_player_completed)
|
|
|
|
|
|
|
2025-06-07 10:45:09 +08:00
|
|
|
|
def connect_signals(self):
|
|
|
|
|
|
"""连接信号和槽"""
|
|
|
|
|
|
# 设备选择和刷新
|
2025-06-27 15:14:30 +08:00
|
|
|
|
if hasattr(self, 'refresh_button'):
|
|
|
|
|
|
self.refresh_button.clicked.connect(self.refresh_devices)
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
|
|
|
|
|
# 相机控制按钮
|
|
|
|
|
|
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)
|
2025-07-07 15:33:56 +08:00
|
|
|
|
|
|
|
|
|
|
# 本地图像模式相关按钮和控件
|
|
|
|
|
|
if self.local_mode_check:
|
|
|
|
|
|
self.local_mode_check.stateChanged.connect(self.toggle_local_mode)
|
|
|
|
|
|
|
|
|
|
|
|
if self.folder_path_button:
|
|
|
|
|
|
self.folder_path_button.clicked.connect(self.choose_image_folder)
|
|
|
|
|
|
|
|
|
|
|
|
if self.local_framerate_slider:
|
|
|
|
|
|
self.local_framerate_slider.valueChanged.connect(self.update_local_framerate_value)
|
|
|
|
|
|
|
|
|
|
|
|
if self.loop_playback_check:
|
|
|
|
|
|
self.loop_playback_check.stateChanged.connect(self.update_loop_playback)
|
|
|
|
|
|
|
|
|
|
|
|
if self.play_button:
|
|
|
|
|
|
self.play_button.clicked.connect(self.play_local_images)
|
|
|
|
|
|
|
|
|
|
|
|
if self.stop_button:
|
|
|
|
|
|
self.stop_button.clicked.connect(self.stop_local_images)
|
2025-06-27 15:14:30 +08:00
|
|
|
|
|
2025-06-07 10:45:09 +08:00
|
|
|
|
def refresh_devices(self):
|
|
|
|
|
|
"""刷新设备列表"""
|
2025-06-27 15:14:30 +08:00
|
|
|
|
logging.info("【设备刷新】开始...")
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
2025-06-27 15:14:30 +08:00
|
|
|
|
# 直接检查是否能访问正确的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:
|
2025-07-07 15:33:56 +08:00
|
|
|
|
# 修复:安全获取 display 键,如果不存在则使用默认值
|
|
|
|
|
|
if isinstance(device, dict):
|
|
|
|
|
|
display_name = device.get("display", f"设备 {len(devList)}")
|
|
|
|
|
|
devList.append(display_name)
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 如果整个设备对象不是字典,则添加一个默认名称
|
|
|
|
|
|
devList.append(f"设备 {len(devList)}")
|
2025-06-27 15:14:30 +08:00
|
|
|
|
logging.info(f"【设备刷新】找到 {len(devList)} 个设备: {devList}")
|
|
|
|
|
|
else:
|
2025-07-07 15:33:56 +08:00
|
|
|
|
devList = ["未发现相机设备"]
|
2025-06-27 15:14:30 +08:00
|
|
|
|
|
2025-07-07 15:33:56 +08:00
|
|
|
|
# 3. 清空并更新下拉列表
|
|
|
|
|
|
self.camera_combo.clear()
|
|
|
|
|
|
for dev in devList:
|
|
|
|
|
|
self.camera_combo.addItem(dev)
|
|
|
|
|
|
|
|
|
|
|
|
# 4. 更新UI状态
|
|
|
|
|
|
self.update_controls()
|
|
|
|
|
|
|
2025-06-07 10:45:09 +08:00
|
|
|
|
def get_selected_device_index(self):
|
2025-06-27 15:14:30 +08:00
|
|
|
|
"""获取当前选中的设备索引,参考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'}'")
|
2025-06-07 10:45:09 +08:00
|
|
|
|
return -1
|
|
|
|
|
|
|
|
|
|
|
|
def update_controls(self):
|
|
|
|
|
|
"""更新控件状态"""
|
|
|
|
|
|
is_open = self.camera_manager.isOpen
|
|
|
|
|
|
is_grabbing = self.camera_manager.isGrabbing
|
2025-06-27 15:14:30 +08:00
|
|
|
|
|
|
|
|
|
|
# 使用安全的方式访问控件
|
|
|
|
|
|
if hasattr(self, 'refresh_button') and self.refresh_button is not None:
|
|
|
|
|
|
self.refresh_button.setEnabled(not is_open)
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
2025-06-27 15:14:30 +08:00
|
|
|
|
if hasattr(self, 'camera_combo') and self.camera_combo is not None:
|
|
|
|
|
|
self.camera_combo.setEnabled(not is_open)
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
2025-06-27 15:14:30 +08:00
|
|
|
|
has_valid_selection = self.get_selected_device_index() != -1
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
2025-06-27 15:14:30 +08:00
|
|
|
|
if hasattr(self, 'open_button') and self.open_button is not None:
|
|
|
|
|
|
self.open_button.setEnabled(not is_open and has_valid_selection)
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
2025-06-27 15:14:30 +08:00
|
|
|
|
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)
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
|
|
|
|
|
def open_camera(self):
|
2025-06-30 18:24:24 +08:00
|
|
|
|
"""打开相机"""
|
2025-06-27 15:14:30 +08:00
|
|
|
|
try:
|
2025-06-30 18:24:24 +08:00
|
|
|
|
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}")
|
|
|
|
|
|
|
|
|
|
|
|
# 使用相机管理器打开设备
|
2025-06-27 15:14:30 +08:00
|
|
|
|
success = self.camera_manager.open_device(device_index)
|
2025-06-30 18:24:24 +08:00
|
|
|
|
|
2025-06-27 15:14:30 +08:00
|
|
|
|
if success:
|
2025-06-30 18:24:24 +08:00
|
|
|
|
logging.info(f"相机已成功打开")
|
|
|
|
|
|
|
|
|
|
|
|
# 更新按钮状态
|
|
|
|
|
|
self.update_controls()
|
|
|
|
|
|
|
|
|
|
|
|
# 向父窗口发送连接状态变化信号
|
2025-06-27 15:14:30 +08:00
|
|
|
|
self.signal_camera_connection.emit(True, "")
|
2025-06-30 18:24:24 +08:00
|
|
|
|
|
|
|
|
|
|
# 更新配置
|
|
|
|
|
|
config_loader = ConfigLoader.get_instance()
|
|
|
|
|
|
config_loader.set_value('camera.enabled', True)
|
|
|
|
|
|
config_loader.save_config()
|
|
|
|
|
|
|
|
|
|
|
|
# 通知设置已更改
|
|
|
|
|
|
self.settings_changed.emit()
|
|
|
|
|
|
|
|
|
|
|
|
return True
|
2025-06-27 15:14:30 +08:00
|
|
|
|
else:
|
2025-06-30 18:24:24 +08:00
|
|
|
|
error_msg = "打开相机失败"
|
|
|
|
|
|
self.signal_camera_error.emit(error_msg)
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
2025-06-27 15:14:30 +08:00
|
|
|
|
except Exception as e:
|
2025-06-30 18:24:24 +08:00
|
|
|
|
error_msg = f"打开相机时发生异常: {str(e)}"
|
|
|
|
|
|
logging.error(error_msg)
|
|
|
|
|
|
self.signal_camera_error.emit(error_msg)
|
|
|
|
|
|
return False
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
|
|
|
|
|
def close_camera(self):
|
|
|
|
|
|
"""关闭相机"""
|
2025-06-30 18:24:24 +08:00
|
|
|
|
try:
|
|
|
|
|
|
logging.info("尝试关闭相机")
|
|
|
|
|
|
|
|
|
|
|
|
# 使用相机管理器关闭设备
|
|
|
|
|
|
self.camera_manager.close_device()
|
|
|
|
|
|
|
|
|
|
|
|
# 更新按钮状态
|
|
|
|
|
|
self.update_controls()
|
|
|
|
|
|
|
|
|
|
|
|
# 向父窗口发送连接状态变化信号
|
|
|
|
|
|
self.signal_camera_connection.emit(False, "")
|
|
|
|
|
|
|
|
|
|
|
|
# 更新配置
|
|
|
|
|
|
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
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
|
|
|
|
|
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())
|
2025-06-27 15:14:30 +08:00
|
|
|
|
if self.camera_manager.start_grabbing(window_id):
|
|
|
|
|
|
self.test_button.setText("停止预览")
|
|
|
|
|
|
except Exception as e:
|
2025-06-28 13:02:34 +08:00
|
|
|
|
parent_widget = self.parent if hasattr(self, "parent") else None
|
|
|
|
|
|
QMessageBox.warning(parent_widget, "错误", f"开始预览失败: {e}")
|
2025-06-27 15:14:30 +08:00
|
|
|
|
|
|
|
|
|
|
self.update_controls()
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
|
|
|
|
|
def update_exposure_value(self, value):
|
2025-06-28 13:02:34 +08:00
|
|
|
|
"""更新曝光值显示(使用线性映射)"""
|
|
|
|
|
|
# 直接使用滑块值(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")
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
2025-06-28 13:02:34 +08:00
|
|
|
|
# 使用线性映射计算曝光滑块值
|
|
|
|
|
|
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)
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
2025-06-28 13:02:34 +08:00
|
|
|
|
# 其他参数仍使用原始映射方法
|
2025-06-27 15:14:30 +08:00
|
|
|
|
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))
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
2025-06-28 13:02:34 +08:00
|
|
|
|
logging.debug(f"获取相机参数: 曝光时间={exposure_time}μs -> 滑块值={exposure_slider_value}, 增益={gain}dB, 帧率={frame_rate}fps")
|
2025-06-07 10:45:09 +08:00
|
|
|
|
self.signal_camera_params_changed.emit(exposure_time, gain, frame_rate)
|
|
|
|
|
|
|
|
|
|
|
|
def set_camera_params(self):
|
|
|
|
|
|
"""设置相机参数"""
|
|
|
|
|
|
if not self.camera_manager.isOpen:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
2025-06-28 13:02:34 +08:00
|
|
|
|
# 使用线性映射计算曝光时间
|
|
|
|
|
|
min_exp = 0 # 0 μs
|
|
|
|
|
|
max_exp = 50000 # 50000 μs
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
2025-06-28 13:02:34 +08:00
|
|
|
|
# 根据滑块值(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
|
|
|
|
|
|
|
|
|
|
|
|
# 增益和帧率保持原来的映射方式
|
2025-06-07 10:45:09 +08:00
|
|
|
|
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)
|
|
|
|
|
|
|
2025-06-28 13:02:34 +08:00
|
|
|
|
logging.debug(f"设置相机参数: 曝光滑块值={slider_value} -> 曝光时间={exposure_time}μs, 增益={gain}dB, 帧率={frame_rate}fps")
|
2025-06-27 15:14:30 +08:00
|
|
|
|
self.camera_manager.set_parameters(frame_rate, exposure_time, gain)
|
|
|
|
|
|
self.signal_camera_params_changed.emit(exposure_time, gain, frame_rate)
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
|
|
|
|
|
def save_camera_params(self):
|
2025-06-12 17:29:35 +08:00
|
|
|
|
"""保存相机参数到配置文件"""
|
2025-06-07 10:45:09 +08:00
|
|
|
|
if not self.camera_manager.isOpen:
|
|
|
|
|
|
return
|
2025-06-12 17:29:35 +08:00
|
|
|
|
|
|
|
|
|
|
exposure = self.exposure_slider.value()
|
|
|
|
|
|
gain = self.gain_slider.value()
|
|
|
|
|
|
frame_rate = self.framerate_slider.value()
|
|
|
|
|
|
|
2025-06-28 13:02:34 +08:00
|
|
|
|
parent_widget = self.parent if hasattr(self, "parent") else None
|
|
|
|
|
|
|
2025-06-27 15:14:30 +08:00
|
|
|
|
if self.camera_manager.save_params_to_config(exposure, gain, frame_rate):
|
2025-06-28 13:02:34 +08:00
|
|
|
|
QMessageBox.information(parent_widget, "成功", "相机参数已保存")
|
2025-06-27 15:14:30 +08:00
|
|
|
|
self.settings_changed.emit()
|
2025-06-12 17:29:35 +08:00
|
|
|
|
else:
|
2025-06-28 13:02:34 +08:00
|
|
|
|
QMessageBox.critical(parent_widget, "错误", "保存相机参数失败")
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
|
|
|
|
|
def closeEvent(self, event):
|
|
|
|
|
|
"""窗口关闭事件"""
|
|
|
|
|
|
if self.camera_manager.isOpen:
|
|
|
|
|
|
self.camera_manager.close_device()
|
2025-06-27 15:14:30 +08:00
|
|
|
|
super().closeEvent(event)
|
2025-07-07 15:33:56 +08:00
|
|
|
|
|
|
|
|
|
|
def load_local_mode_settings(self):
|
|
|
|
|
|
"""加载本地图像模式的配置"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 使用 .get() 防止因缺少 local_mode 键而崩溃
|
|
|
|
|
|
config = self.camera_config.get("local_mode", {})
|
|
|
|
|
|
self.local_mode_enabled = config.get("enabled", False)
|
|
|
|
|
|
self.local_player.framerate = config.get("framerate", 15)
|
|
|
|
|
|
self.local_player.loop = config.get("loop", True)
|
|
|
|
|
|
folder_path = config.get("folder_path", "")
|
|
|
|
|
|
|
|
|
|
|
|
# 更新UI组件
|
|
|
|
|
|
if self.local_mode_check:
|
|
|
|
|
|
self.local_mode_check.setChecked(self.local_mode_enabled)
|
|
|
|
|
|
if self.local_framerate_slider:
|
|
|
|
|
|
self.local_framerate_slider.setValue(self.local_player.framerate)
|
|
|
|
|
|
if self.loop_playback_check:
|
|
|
|
|
|
self.loop_playback_check.setChecked(self.local_player.loop)
|
|
|
|
|
|
if self.folder_path_edit:
|
|
|
|
|
|
self.folder_path_edit.setText(folder_path)
|
|
|
|
|
|
|
|
|
|
|
|
self.local_player.folder_path = folder_path
|
|
|
|
|
|
|
|
|
|
|
|
logging.info(f"加载本地图像模式设置: 启用={self.local_mode_enabled}, 帧率={self.local_player.framerate}, 循环={self.local_player.loop}")
|
|
|
|
|
|
|
|
|
|
|
|
# 触发模式变更信号,通知主窗口和其他组件
|
|
|
|
|
|
if self.local_mode_enabled:
|
|
|
|
|
|
self.signal_local_mode_changed.emit(True)
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logging.error(f"加载本地图像模式设置时发生错误: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
def save_local_mode_settings(self):
|
|
|
|
|
|
"""保存本地图像模式的配置"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 获取当前设置
|
|
|
|
|
|
enabled = self.local_mode_enabled
|
|
|
|
|
|
folder_path = self.folder_path_edit.text() if self.folder_path_edit else ""
|
|
|
|
|
|
framerate = self.local_framerate_slider.value() if self.local_framerate_slider else 15
|
|
|
|
|
|
loop = self.loop_playback_check.isChecked() if self.loop_playback_check else True
|
|
|
|
|
|
|
|
|
|
|
|
# 创建配置字典
|
|
|
|
|
|
config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "app_config.json")
|
|
|
|
|
|
|
|
|
|
|
|
# 读取现有配置(如果存在)
|
|
|
|
|
|
config = {}
|
|
|
|
|
|
if os.path.exists(config_path):
|
|
|
|
|
|
try:
|
|
|
|
|
|
with open(config_path, 'r', encoding='utf-8') as f:
|
|
|
|
|
|
config = json.load(f)
|
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
|
logging.warning(f"配置文件格式错误: {config_path}")
|
|
|
|
|
|
config = {}
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logging.warning(f"读取配置文件失败: {e}")
|
|
|
|
|
|
config = {}
|
|
|
|
|
|
|
|
|
|
|
|
# 更新本地图像模式设置
|
|
|
|
|
|
if "local_image_mode" not in config:
|
|
|
|
|
|
config["local_image_mode"] = {}
|
|
|
|
|
|
|
|
|
|
|
|
config["local_image_mode"].update({
|
|
|
|
|
|
"enabled": enabled,
|
|
|
|
|
|
"folder_path": folder_path,
|
|
|
|
|
|
"framerate": framerate,
|
|
|
|
|
|
"loop": loop
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
# 保存配置
|
|
|
|
|
|
with open(config_path, 'w', encoding='utf-8') as f:
|
|
|
|
|
|
json.dump(config, f, indent=4, ensure_ascii=False)
|
|
|
|
|
|
|
|
|
|
|
|
logging.info(f"已保存本地图像模式设置: enabled={enabled}, folder='{folder_path}', "
|
|
|
|
|
|
f"framerate={framerate}, loop={loop}")
|
|
|
|
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logging.error(f"保存本地图像模式设置失败: {e}")
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def update_local_mode_ui(self):
|
|
|
|
|
|
"""更新本地图像模式UI状态"""
|
|
|
|
|
|
# 获取当前复选框状态(如果复选框存在)
|
|
|
|
|
|
enabled = self.local_mode_check.isChecked() if self.local_mode_check else self.local_mode_enabled
|
|
|
|
|
|
|
|
|
|
|
|
# 更新内部状态
|
|
|
|
|
|
if enabled != self.local_mode_enabled:
|
|
|
|
|
|
logging.info(f"本地图像模式状态变化: {self.local_mode_enabled} -> {enabled}")
|
|
|
|
|
|
self.local_mode_enabled = enabled
|
|
|
|
|
|
|
|
|
|
|
|
# 打印日志,帮助调试
|
|
|
|
|
|
logging.debug(f"更新本地图像模式UI: 启用={enabled}, 按钮状态={self.folder_path_button is not None}")
|
|
|
|
|
|
|
|
|
|
|
|
# 更新相关控件状态
|
|
|
|
|
|
if self.folder_path_edit:
|
|
|
|
|
|
self.folder_path_edit.setEnabled(enabled)
|
|
|
|
|
|
logging.debug(f"文件夹路径编辑框已设置为: {enabled}")
|
|
|
|
|
|
|
|
|
|
|
|
if self.folder_path_button:
|
|
|
|
|
|
self.folder_path_button.setEnabled(enabled)
|
|
|
|
|
|
logging.debug(f"文件夹选择按钮已设置为: {enabled}")
|
|
|
|
|
|
|
|
|
|
|
|
if self.local_framerate_slider:
|
|
|
|
|
|
self.local_framerate_slider.setEnabled(enabled)
|
|
|
|
|
|
|
|
|
|
|
|
if self.loop_playback_check:
|
|
|
|
|
|
self.loop_playback_check.setEnabled(enabled)
|
|
|
|
|
|
|
|
|
|
|
|
if self.play_button:
|
|
|
|
|
|
has_folder = bool(self.folder_path_edit and self.folder_path_edit.text())
|
|
|
|
|
|
self.play_button.setEnabled(enabled and has_folder)
|
|
|
|
|
|
logging.debug(f"播放按钮已设置为: {enabled and has_folder}, 有文件夹={has_folder}")
|
|
|
|
|
|
|
|
|
|
|
|
if self.stop_button:
|
|
|
|
|
|
self.stop_button.setEnabled(enabled and self.is_playing)
|
|
|
|
|
|
|
|
|
|
|
|
# 强制更新UI
|
|
|
|
|
|
if self.parent:
|
|
|
|
|
|
self.parent.update()
|
|
|
|
|
|
|
|
|
|
|
|
def toggle_local_mode(self, state):
|
|
|
|
|
|
"""切换本地图像模式"""
|
|
|
|
|
|
# 修复:直接比较整数值,Qt.Checked.value 是 2
|
|
|
|
|
|
is_checked = (state == 2) # 或者 state == Qt.Checked.value
|
|
|
|
|
|
|
|
|
|
|
|
# 停止可能正在进行的播放
|
|
|
|
|
|
if self.is_playing:
|
|
|
|
|
|
self.stop_local_images()
|
|
|
|
|
|
|
|
|
|
|
|
# 打印详细日志,帮助调试
|
|
|
|
|
|
logging.info(f"切换本地图像模式: {state} -> {is_checked}")
|
|
|
|
|
|
|
|
|
|
|
|
# 先设置相机管理器的本地模式
|
|
|
|
|
|
self.camera_manager.set_local_mode(is_checked)
|
|
|
|
|
|
|
|
|
|
|
|
# 更新本地模式状态
|
|
|
|
|
|
self.local_mode_enabled = is_checked
|
|
|
|
|
|
|
|
|
|
|
|
# 更新UI状态
|
|
|
|
|
|
self.update_local_mode_ui()
|
|
|
|
|
|
|
|
|
|
|
|
# 如果打开本地模式,关闭相机;如果关闭本地模式,尝试打开之前的相机
|
|
|
|
|
|
if is_checked:
|
|
|
|
|
|
# 如果相机正在工作,先关闭它
|
|
|
|
|
|
if self.camera_manager.isOpen and not self.camera_manager.local_mode:
|
|
|
|
|
|
self.close_camera()
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 如果存在实际相机,尝试打开最后选择的相机
|
|
|
|
|
|
if self.camera_manager.has_real_camera:
|
|
|
|
|
|
# 获取当前选择的设备索引
|
|
|
|
|
|
device_index = self.get_selected_device_index()
|
|
|
|
|
|
if device_index >= 0:
|
|
|
|
|
|
# 关闭虚拟相机
|
|
|
|
|
|
if self.camera_manager.isOpen:
|
|
|
|
|
|
self.camera_manager.close_device()
|
|
|
|
|
|
# 开启实际相机
|
|
|
|
|
|
self.open_camera()
|
|
|
|
|
|
|
|
|
|
|
|
# 发送模式变更信号
|
|
|
|
|
|
self.signal_local_mode_changed.emit(is_checked)
|
|
|
|
|
|
|
|
|
|
|
|
# 保存设置
|
|
|
|
|
|
self.save_local_mode_settings()
|
|
|
|
|
|
|
|
|
|
|
|
logging.info(f"本地图像模式已{'启用' if is_checked else '禁用'}")
|
|
|
|
|
|
|
|
|
|
|
|
# 刷新设备列表
|
|
|
|
|
|
self.refresh_devices()
|
|
|
|
|
|
|
|
|
|
|
|
def choose_image_folder(self):
|
|
|
|
|
|
"""选择图像文件夹"""
|
|
|
|
|
|
# 检查复选框状态而不是类属性
|
|
|
|
|
|
is_enabled = self.local_mode_check.isChecked() if self.local_mode_check else False
|
|
|
|
|
|
|
|
|
|
|
|
# 即使控件状态可能不对,也尝试打开文件选择对话框
|
|
|
|
|
|
logging.info(f"选择图像文件夹: 本地模式启用={is_enabled}, 按钮状态={self.folder_path_button is not None}")
|
|
|
|
|
|
|
|
|
|
|
|
# 获取当前设置的文件夹路径作为初始目录
|
|
|
|
|
|
current_path = self.folder_path_edit.text() if self.folder_path_edit else ""
|
|
|
|
|
|
if not current_path or not os.path.isdir(current_path):
|
|
|
|
|
|
current_path = os.path.expanduser("~") # 默认使用用户主目录
|
|
|
|
|
|
|
|
|
|
|
|
# 打开文件夹选择对话框
|
|
|
|
|
|
folder_path = QFileDialog.getExistingDirectory(self.parent, "选择图像文件夹", current_path)
|
|
|
|
|
|
|
|
|
|
|
|
if folder_path:
|
|
|
|
|
|
logging.info(f"已选择图像文件夹: {folder_path}")
|
|
|
|
|
|
|
|
|
|
|
|
# 更新路径显示
|
|
|
|
|
|
if self.folder_path_edit:
|
|
|
|
|
|
self.folder_path_edit.setText(folder_path)
|
|
|
|
|
|
|
|
|
|
|
|
# 设置到本地播放器
|
|
|
|
|
|
if self.local_player:
|
|
|
|
|
|
self.local_player.set_folder(folder_path)
|
|
|
|
|
|
|
|
|
|
|
|
# 更新UI状态
|
|
|
|
|
|
self.update_local_mode_ui()
|
|
|
|
|
|
|
|
|
|
|
|
# 保存设置
|
|
|
|
|
|
self.save_local_mode_settings()
|
|
|
|
|
|
else:
|
|
|
|
|
|
logging.info("未选择任何文件夹")
|
|
|
|
|
|
|
|
|
|
|
|
def update_local_framerate_value(self, value):
|
|
|
|
|
|
"""更新本地模式帧率显示"""
|
|
|
|
|
|
if self.local_framerate_value:
|
|
|
|
|
|
self.local_framerate_value.setText(f"{value} fps")
|
|
|
|
|
|
|
|
|
|
|
|
# 更新本地播放器帧率
|
|
|
|
|
|
if self.local_player:
|
|
|
|
|
|
self.local_player.set_framerate(value)
|
|
|
|
|
|
|
|
|
|
|
|
def update_loop_playback(self, state):
|
|
|
|
|
|
"""更新循环播放设置"""
|
|
|
|
|
|
loop = (state == Qt.Checked)
|
|
|
|
|
|
|
|
|
|
|
|
# 更新本地播放器设置
|
|
|
|
|
|
if self.local_player:
|
|
|
|
|
|
self.local_player.set_loop(loop)
|
|
|
|
|
|
|
|
|
|
|
|
# 保存设置
|
|
|
|
|
|
self.save_local_mode_settings()
|
|
|
|
|
|
|
|
|
|
|
|
def play_local_images(self):
|
|
|
|
|
|
"""播放本地图像序列"""
|
|
|
|
|
|
if not self.local_mode_enabled:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
if self.is_playing:
|
|
|
|
|
|
# 如果正在播放,则暂停/恢复
|
|
|
|
|
|
if self.local_player:
|
|
|
|
|
|
if self.local_player.is_paused:
|
|
|
|
|
|
self.local_player.resume_playback()
|
|
|
|
|
|
self.play_button.setText("暂停")
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.local_player.pause_playback()
|
|
|
|
|
|
self.play_button.setText("继续")
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 开始播放
|
|
|
|
|
|
if self.local_player:
|
|
|
|
|
|
folder_path = self.folder_path_edit.text() if self.folder_path_edit else ""
|
|
|
|
|
|
|
|
|
|
|
|
if not folder_path:
|
|
|
|
|
|
QMessageBox.warning(self.parent, "错误", "请先选择图像文件夹")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 设置播放参数
|
|
|
|
|
|
framerate = self.local_framerate_slider.value() if self.local_framerate_slider else 15
|
|
|
|
|
|
loop = self.loop_playback_check.isChecked() if self.loop_playback_check else True
|
|
|
|
|
|
|
|
|
|
|
|
self.local_player.set_framerate(framerate)
|
|
|
|
|
|
self.local_player.set_loop(loop)
|
|
|
|
|
|
|
|
|
|
|
|
# 确保文件夹路径已设置
|
|
|
|
|
|
if not self.local_player.folder_path:
|
|
|
|
|
|
self.local_player.set_folder(folder_path)
|
|
|
|
|
|
|
|
|
|
|
|
# 开始播放
|
|
|
|
|
|
if self.local_player.start_playback():
|
|
|
|
|
|
self.is_playing = True
|
|
|
|
|
|
self.play_button.setText("暂停")
|
|
|
|
|
|
self.stop_button.setEnabled(True)
|
|
|
|
|
|
|
|
|
|
|
|
# 更新预览状态
|
|
|
|
|
|
if self.preview_status:
|
|
|
|
|
|
self.preview_status.setText("播放中...")
|
|
|
|
|
|
|
|
|
|
|
|
# 关键修复:发送信号通知主窗口更新UI,显示相机画面
|
|
|
|
|
|
logging.warning("====> 开始播放本地图像序列,发送信号通知主窗口更新UI")
|
|
|
|
|
|
# 先确保本地模式已启用
|
|
|
|
|
|
self.camera_manager.set_local_mode(True)
|
|
|
|
|
|
# 发送信号,通知主窗口更新UI
|
|
|
|
|
|
self.signal_local_mode_changed.emit(True)
|
|
|
|
|
|
|
|
|
|
|
|
logging.info(f"开始播放本地图像序列,帧率={framerate} fps,循环={loop}")
|
|
|
|
|
|
|
|
|
|
|
|
def stop_local_images(self):
|
|
|
|
|
|
"""停止播放本地图像序列"""
|
|
|
|
|
|
if self.local_player and self.is_playing:
|
|
|
|
|
|
self.local_player.stop_playback()
|
|
|
|
|
|
self.is_playing = False
|
|
|
|
|
|
self.play_button.setText("播放")
|
|
|
|
|
|
self.stop_button.setEnabled(False)
|
|
|
|
|
|
|
|
|
|
|
|
# 更新预览状态
|
|
|
|
|
|
if self.preview_status:
|
|
|
|
|
|
self.preview_status.setText("已停止")
|
|
|
|
|
|
|
|
|
|
|
|
logging.info("停止播放本地图像序列")
|
|
|
|
|
|
|
|
|
|
|
|
def handle_local_player_status(self, message):
|
|
|
|
|
|
"""处理本地播放器状态消息"""
|
|
|
|
|
|
if self.preview_status:
|
|
|
|
|
|
self.preview_status.setText(message)
|
|
|
|
|
|
|
|
|
|
|
|
def handle_local_player_error(self, message):
|
|
|
|
|
|
"""处理本地播放器错误消息"""
|
|
|
|
|
|
logging.error(f"本地图像播放器错误: {message}")
|
|
|
|
|
|
|
|
|
|
|
|
if self.preview_status:
|
|
|
|
|
|
self.preview_status.setText(f"错误: {message}")
|
|
|
|
|
|
|
|
|
|
|
|
QMessageBox.warning(self.parent, "错误", message)
|
|
|
|
|
|
|
|
|
|
|
|
def handle_local_player_progress(self, current, total):
|
|
|
|
|
|
"""处理本地播放器进度消息"""
|
|
|
|
|
|
if self.preview_status:
|
|
|
|
|
|
self.preview_status.setText(f"播放中... {current}/{total}")
|
|
|
|
|
|
|
|
|
|
|
|
def handle_local_player_frame(self, frame):
|
|
|
|
|
|
"""处理本地播放器帧准备好消息"""
|
|
|
|
|
|
# 确保本地模式已启用
|
|
|
|
|
|
if not self.local_mode_enabled or not self.local_player:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 添加明显的调试日志
|
|
|
|
|
|
logging.warning(f"====> 接收到本地图像帧: {frame.shape if frame is not None else 'None'}")
|
|
|
|
|
|
|
|
|
|
|
|
# 获取窗口ID (默认方法)
|
|
|
|
|
|
window_id = int(self.preview_frame.winId()) if self.preview_frame else 0
|
|
|
|
|
|
|
|
|
|
|
|
# 将帧传递给camera_manager处理
|
|
|
|
|
|
logging.warning(f"====> 传递给camera_manager处理,窗口ID: {window_id}")
|
|
|
|
|
|
success = self.camera_manager.handle_local_frame(frame, window_id)
|
|
|
|
|
|
if not success:
|
|
|
|
|
|
logging.error("相机管理器处理本地图像帧失败")
|
|
|
|
|
|
|
|
|
|
|
|
# 修复:查找主窗口的改进方式
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 获取应用实例
|
|
|
|
|
|
from PySide6.QtWidgets import QApplication
|
|
|
|
|
|
app = QApplication.instance()
|
|
|
|
|
|
|
|
|
|
|
|
# 查找主窗口
|
|
|
|
|
|
from widgets.main_window import MainWindow
|
|
|
|
|
|
main_window = None
|
|
|
|
|
|
|
|
|
|
|
|
# 遍历所有顶层窗口
|
|
|
|
|
|
for window in app.topLevelWidgets():
|
|
|
|
|
|
logging.warning(f"====> 找到顶层窗口: {window.__class__.__name__}")
|
|
|
|
|
|
if isinstance(window, MainWindow):
|
|
|
|
|
|
main_window = window
|
|
|
|
|
|
logging.warning("====> 找到 MainWindow 实例")
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
# 如果通过顶层窗口没找到,尝试通过父窗口查找
|
|
|
|
|
|
if not main_window and hasattr(self, 'parent') and self.parent:
|
|
|
|
|
|
parent = self.parent
|
|
|
|
|
|
# 循环查找父窗口,直到找到 MainWindow 或达到顶层
|
|
|
|
|
|
while parent:
|
|
|
|
|
|
logging.warning(f"====> 检查父窗口: {parent.__class__.__name__}")
|
|
|
|
|
|
if isinstance(parent, MainWindow):
|
|
|
|
|
|
main_window = parent
|
|
|
|
|
|
logging.warning("====> 在父窗口链中找到 MainWindow 实例")
|
|
|
|
|
|
break
|
|
|
|
|
|
if hasattr(parent, 'parent'):
|
|
|
|
|
|
parent = parent.parent()
|
|
|
|
|
|
else:
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
# 如果找到了主窗口,更新相机显示
|
|
|
|
|
|
if main_window:
|
|
|
|
|
|
logging.warning(f"====> MainWindow 实例 ID: {id(main_window)}")
|
|
|
|
|
|
|
|
|
|
|
|
# 添加详细调试日志,查看主窗口的属性
|
|
|
|
|
|
logging.warning(f"====> MainWindow 的属性: {dir(main_window)}")
|
|
|
|
|
|
|
|
|
|
|
|
# 检查 camera_display 是否存在
|
|
|
|
|
|
has_camera_display = hasattr(main_window, 'camera_display')
|
|
|
|
|
|
logging.warning(f"====> MainWindow 是否有 camera_display 属性: {has_camera_display}")
|
|
|
|
|
|
|
|
|
|
|
|
if has_camera_display:
|
|
|
|
|
|
camera_display = main_window.camera_display
|
|
|
|
|
|
logging.warning(f"====> camera_display 对象 ID: {id(camera_display) if camera_display else 'None'}")
|
|
|
|
|
|
logging.warning(f"====> camera_display 是否为 None: {camera_display is None}")
|
|
|
|
|
|
|
|
|
|
|
|
# 检查 material_placeholder 是否存在
|
|
|
|
|
|
has_placeholder = hasattr(main_window, 'material_placeholder')
|
|
|
|
|
|
logging.warning(f"====> MainWindow 是否有 material_placeholder 属性: {has_placeholder}")
|
|
|
|
|
|
|
|
|
|
|
|
# 检查camera_enabled状态
|
|
|
|
|
|
current_camera_enabled = getattr(main_window, 'camera_enabled', False)
|
|
|
|
|
|
logging.warning(f"====> 当前 camera_enabled 状态: {current_camera_enabled}")
|
|
|
|
|
|
|
|
|
|
|
|
# 关键修复:确保camera_enabled为True
|
|
|
|
|
|
if hasattr(main_window, 'camera_enabled'):
|
|
|
|
|
|
logging.warning("====> 设置 camera_enabled 为 True")
|
|
|
|
|
|
main_window.camera_enabled = True
|
|
|
|
|
|
|
|
|
|
|
|
# 检查是否已调用过 init_camera_display 方法
|
|
|
|
|
|
if hasattr(main_window, 'init_camera_display'):
|
|
|
|
|
|
logging.warning("====> MainWindow 有 init_camera_display 方法")
|
|
|
|
|
|
# 尝试再次调用初始化方法
|
|
|
|
|
|
logging.warning("====> 尝试再次调用 init_camera_display 方法")
|
|
|
|
|
|
main_window.init_camera_display()
|
|
|
|
|
|
else:
|
|
|
|
|
|
logging.error("MainWindow 没有 init_camera_display 方法")
|
|
|
|
|
|
|
|
|
|
|
|
# 再次检查camera_display是否已创建
|
|
|
|
|
|
if hasattr(main_window, 'camera_display') and main_window.camera_display:
|
|
|
|
|
|
logging.warning("====> init_camera_display后,camera_display已创建")
|
|
|
|
|
|
# 确保相机显示组件可见
|
|
|
|
|
|
main_window.camera_display.setVisible(True)
|
|
|
|
|
|
main_window.camera_display.raise_()
|
|
|
|
|
|
|
|
|
|
|
|
# 隐藏占位符
|
|
|
|
|
|
if hasattr(main_window, 'material_placeholder') and main_window.material_placeholder:
|
|
|
|
|
|
main_window.material_placeholder.setVisible(False)
|
|
|
|
|
|
|
|
|
|
|
|
# 调用update_local_frame方法
|
|
|
|
|
|
if hasattr(main_window.camera_display, 'update_local_frame'):
|
|
|
|
|
|
main_window.camera_display.update_local_frame(frame)
|
|
|
|
|
|
logging.warning("====> 直接调用camera_display.update_local_frame成功")
|
|
|
|
|
|
else:
|
|
|
|
|
|
logging.error("camera_display没有update_local_frame方法")
|
|
|
|
|
|
|
|
|
|
|
|
# 强制更新UI
|
|
|
|
|
|
main_window.update_camera_ui(True)
|
|
|
|
|
|
else:
|
|
|
|
|
|
logging.error("MainWindow中没有找到camera_display组件或它仍然是None")
|
|
|
|
|
|
else:
|
|
|
|
|
|
logging.error("未找到MainWindow实例")
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logging.error(f"尝试直接更新主窗口相机组件失败: {str(e)}")
|
|
|
|
|
|
import traceback
|
|
|
|
|
|
logging.error(traceback.format_exc())
|
|
|
|
|
|
|
|
|
|
|
|
# 保留原来的第二种方法作为备选
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 尝试使用Qt方式更新图像
|
|
|
|
|
|
if not success:
|
|
|
|
|
|
from PySide6.QtGui import QImage, QPixmap
|
|
|
|
|
|
|
|
|
|
|
|
# 将OpenCV的RGB图像转换为Qt的QImage
|
|
|
|
|
|
height, width, channel = frame.shape
|
|
|
|
|
|
bytes_per_line = channel * width
|
|
|
|
|
|
q_img = QImage(frame.data, width, height, bytes_per_line, QImage.Format_RGB888)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建QPixmap
|
|
|
|
|
|
pixmap = QPixmap.fromImage(q_img)
|
|
|
|
|
|
|
|
|
|
|
|
# 获取主窗口实例
|
|
|
|
|
|
from PySide6.QtWidgets import QApplication
|
|
|
|
|
|
app = QApplication.instance()
|
|
|
|
|
|
from widgets.main_window import MainWindow
|
|
|
|
|
|
|
|
|
|
|
|
for window in app.topLevelWidgets():
|
|
|
|
|
|
if isinstance(window, MainWindow):
|
|
|
|
|
|
main_window = window
|
|
|
|
|
|
# 确保camera_enabled为True
|
|
|
|
|
|
if hasattr(main_window, 'camera_enabled'):
|
|
|
|
|
|
main_window.camera_enabled = True
|
|
|
|
|
|
|
|
|
|
|
|
# 尝试初始化相机显示
|
|
|
|
|
|
if hasattr(main_window, 'init_camera_display'):
|
|
|
|
|
|
main_window.init_camera_display()
|
|
|
|
|
|
|
|
|
|
|
|
if hasattr(main_window, 'camera_display') and main_window.camera_display:
|
|
|
|
|
|
# 确保相机显示组件可见
|
|
|
|
|
|
main_window.camera_display.setVisible(True)
|
|
|
|
|
|
main_window.camera_display.raise_()
|
|
|
|
|
|
|
|
|
|
|
|
# 给相机显示组件设置属性,强制更新
|
|
|
|
|
|
main_window.camera_display._current_pixmap = pixmap
|
|
|
|
|
|
main_window.camera_display.update() # 强制重绘
|
|
|
|
|
|
|
|
|
|
|
|
logging.warning("====> 通过设置_current_pixmap并强制更新成功")
|
|
|
|
|
|
|
|
|
|
|
|
# 隐藏占位符
|
|
|
|
|
|
if hasattr(main_window, 'material_placeholder'):
|
|
|
|
|
|
main_window.material_placeholder.setVisible(False)
|
|
|
|
|
|
|
|
|
|
|
|
# 强制更新UI
|
|
|
|
|
|
main_window.update_camera_ui(True)
|
|
|
|
|
|
return
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logging.error(f"尝试使用Qt方式更新图像失败: {e}")
|
|
|
|
|
|
import traceback
|
|
|
|
|
|
logging.error(traceback.format_exc())
|
|
|
|
|
|
|
|
|
|
|
|
def handle_local_player_completed(self):
|
|
|
|
|
|
"""处理本地播放器播放完成消息"""
|
|
|
|
|
|
if not self.local_player.loop:
|
|
|
|
|
|
self.is_playing = False
|
|
|
|
|
|
self.play_button.setText("播放")
|
|
|
|
|
|
|
|
|
|
|
|
# 更新预览状态
|
|
|
|
|
|
if self.preview_status:
|
|
|
|
|
|
self.preview_status.setText("播放完成")
|