Commit 307fb918 by Yuhaibo

1

parent 4f8ebab8
......@@ -249,13 +249,13 @@ class MissionPanelHandler:
channel_task_list.append(f"{ch_key} 已有任务: {existing_task}")
message = "以下通道已有任务:\n\n" + "\n".join(channel_task_list)
message += f"\n\n确定要重新分配任务 [{task_id}_{task_name}] 吗?"
message += f"\n\n确定要切换任务 [{task_id}_{task_name}] 吗?"
# 🔥 创建自定义确认对话框(标题栏显示警告图标,内容区域无图标,中文按钮)
msg_box = QtWidgets.QMessageBox(
self.mission_panel if hasattr(self, 'mission_panel') else None
)
msg_box.setWindowTitle("重新分配任务确认")
msg_box.setWindowTitle("切换任务确认")
msg_box.setText(message)
msg_box.setIcon(QtWidgets.QMessageBox.NoIcon) # 内容区域不显示图标
......@@ -300,6 +300,9 @@ class MissionPanelHandler:
# 更新通道面板的任务信息和配置文件
self._updateChannelTaskInfo(channel_id, task_id, task_name, mission_result_folder_path)
# 🔥 更新完所有通道后,一次性更新任务行的通道列颜色
self._setTaskRowChannelColor(task_folder_name, 0, '#000000') # channel_num参数在这里不重要
# 🔥 确认任务分配,高亮选中的行
if self.mission_panel:
self.mission_panel.confirmTaskAssignment()
......@@ -308,7 +311,6 @@ class MissionPanelHandler:
self._refreshAllTaskStatus()
except Exception as e:
print(f"处理任务选中失败: {e}")
import traceback
traceback.print_exc()
......@@ -326,7 +328,6 @@ class MissionPanelHandler:
try:
mission_dir = self._getMissionConfigPath()
if not mission_dir:
print("无法获取任务配置路径")
return None
# 构建文件名
......@@ -518,7 +519,6 @@ class MissionPanelHandler:
# 获取任务配置路径
mission_dir = self._getMissionConfigPath()
if not mission_dir:
print(" 无法获取任务配置路径")
return False
# 构建文件名:任务编号_任务名称.yaml
......@@ -526,7 +526,6 @@ class MissionPanelHandler:
task_name = task_info.get('task_name', '')
if not task_id or not task_name:
print(" 任务编号或任务名称为空")
return False
# 清理文件名中的非法字符
......@@ -625,7 +624,6 @@ class MissionPanelHandler:
return os.path.join(mission_dir, filename)
except Exception as e:
print(f"❌ 获取YAML文件路径失败: {e}")
return None
def _createmission_resultFolder(self, task_info):
......@@ -832,7 +830,7 @@ class MissionPanelHandler:
# 检查是否有通道正在使用这个任务
is_task_in_use = self._isTaskInUse(task_folder_name)
actual_status = "已配置" if is_task_in_use else "未配置"
actual_status = "已启动" if is_task_in_use else "未启动"
# 🔥 将通道列表拆分为4个独立的列
selected_channels = task_info.get('selected_channels', [])
......@@ -876,22 +874,17 @@ class MissionPanelHandler:
"""
更新任务面板中通道列的文本颜色
只改变当前正在运行该通道任务的那一行的通道标签颜色:
- 检测中(detection_flag=True)且是该通道当前任务:绿色文本
- 其他情况:默认黑色文本
通道列颜色规则:
- 默认:灰色(未分配任务)
- 该通道被分配了任务(channelmission有值):黑色
- 检测中且是该通道当前任务:绿色
"""
try:
print(f"\n{'='*60}")
print(f" [调试] 开始更新通道列颜色")
print(f"{'='*60}")
# 获取所有任务的行数据
if not hasattr(self.mission_panel, '_all_rows_data'):
print(f"⚠️ [调试] mission_panel 没有 _all_rows_data 属性")
return
all_rows = self.mission_panel._all_rows_data
print(f"📊 [调试] 任务面板总行数: {len(all_rows)}")
# 遍历4个通道,获取每个通道当前运行的任务
channel_current_missions = {}
......@@ -909,14 +902,9 @@ class MissionPanelHandler:
else:
current_mission = str(current_mission_obj)
channel_current_missions[ch_idx] = current_mission
print(f"📝 [调试] {mission_var_name} = {current_mission} (类型: {type(current_mission_obj).__name__})")
else:
print(f" [调试] {mission_var_name} = None")
else:
print(f"❌ [调试] 没有 {mission_var_name} 属性")
print(f"📋 [调试] 当前运行的任务: {channel_current_missions}")
# 只有非"未分配任务"才记录
if current_mission and current_mission != "未分配任务":
channel_current_missions[ch_idx] = current_mission
# 遍历每一行任务
for row_idx, row_info in enumerate(all_rows):
......@@ -930,7 +918,6 @@ class MissionPanelHandler:
else:
task_name = None
print(f"\n🔹 [调试] 行{row_idx}: 任务={task_name}, 使用通道={selected_channels}")
# 遍历4个通道列
for ch_idx in range(4):
......@@ -947,23 +934,21 @@ class MissionPanelHandler:
current_mission_for_channel = channel_current_missions.get(ch_idx)
is_current_mission = (current_mission_for_channel == task_name)
print(f" 🔸 {channel_name}: 检测中={is_detecting}, 当前任务={current_mission_for_channel}, 是当前任务={is_current_mission}")
# 检查该通道是否被分配了任务(channelmission有值且不是"未分配任务")
has_assigned_mission = (ch_idx in channel_current_missions)
if is_detecting and is_current_mission:
# 检测中且是当前任务:设置文本为绿色
self.mission_panel.setCellTextColor(row_idx, col_idx, '#00AA00')
print(f" ✅ [设置绿色] 行{row_idx} 列{col_idx} {channel_name}")
elif has_assigned_mission:
# 该通道被分配了任务:设置文本为黑色
self.mission_panel.setCellTextColor(row_idx, col_idx, '#000000')
else:
# 其他情况:保持灰色(不修改颜色)
# 通道列默认就是灰色,不需要重新设置
print(f" [保持灰色] 行{row_idx} 列{col_idx} {channel_name} (检测={is_detecting}, 当前={is_current_mission})")
print(f"{'='*60}")
print(f"✅ [调试] 通道列颜色更新完成")
print(f"{'='*60}\n")
# 其他情况:保持灰色
self.mission_panel.setCellTextColor(row_idx, col_idx, '#808080')
except Exception as e:
print(f"⚠️ [更新通道颜色] 失败: {e}")
import traceback
traceback.print_exc()
......@@ -982,11 +967,9 @@ class MissionPanelHandler:
detect_var_name = f'{channel_id}detect'
if hasattr(self, detect_var_name):
is_detecting = getattr(self, detect_var_name, False)
print(f" [检测状态] {channel_id}: {detect_var_name}={is_detecting}")
return is_detecting
return False
except Exception as e:
print(f"⚠️ [检查检测状态] {channel_id} 失败: {e}")
return False
def _deleteMissionConfig(self, task_id, task_name):
......@@ -1000,7 +983,6 @@ class MissionPanelHandler:
try:
mission_dir = self._getMissionConfigPath()
if not mission_dir:
print(" 无法获取任务配置路径")
return
# 构建文件名
......@@ -1012,12 +994,8 @@ class MissionPanelHandler:
# 删除文件
if os.path.exists(file_path):
os.remove(file_path)
print(f"️ 已删除任务配置文件: {filename}")
else:
print(f"️ 任务配置文件不存在: {filename}")
except Exception as e:
print(f" 删除任务配置文件失败: {e}")
import traceback
traceback.print_exc()
......@@ -1026,14 +1004,12 @@ class MissionPanelHandler:
try:
mission_dir = self._getMissionConfigPath()
if not mission_dir or not os.path.exists(mission_dir):
print("️ 任务配置目录不存在")
return
# 扫描所有 .yaml 文件
yaml_files = [f for f in os.listdir(mission_dir) if f.endswith('.yaml')]
if not yaml_files:
print(" 没有任务配置文件需要删除")
return
deleted_count = 0
......@@ -1171,10 +1147,7 @@ class MissionPanelHandler:
project_root = get_project_root()
config_path = os.path.join(project_root, 'database', 'config', 'default_config.yaml')
print(f"📂 [MissionPanelHandler] 读取配置文件: {config_path}")
if not os.path.exists(config_path):
print("⚠️ [MissionPanelHandler] 配置文件不存在,返回空配置")
return {'channels': {}}
with open(config_path, 'r', encoding='utf-8') as f:
......@@ -1192,11 +1165,9 @@ class MissionPanelHandler:
'address': channel_info.get('address', '')
}
print(f"✅ [MissionPanelHandler] 已加载 {len(channels)} 个通道配置")
return {'channels': channels}
except Exception as e:
print(f"❌ [MissionPanelHandler] 加载通道配置失败: {e}")
import traceback
traceback.print_exc()
return {'channels': {}}
......@@ -1210,8 +1181,6 @@ class MissionPanelHandler:
project_root = get_project_root()
config_path = os.path.join(project_root, 'database', 'config', 'default_config.yaml')
print(f"💾 [MissionPanelHandler] 保存配置到: {config_path}")
# 读取现有配置
if os.path.exists(config_path):
with open(config_path, 'r', encoding='utf-8') as f:
......@@ -1233,17 +1202,14 @@ class MissionPanelHandler:
'name': channel_info.get('name', f'通道{channel_id}'),
'address': address
}
print(f" - {channel_key}: {channel_info.get('name')} -> {address}")
# 保存配置
with open(config_path, 'w', encoding='utf-8') as f:
yaml.dump(config, f, allow_unicode=True, default_flow_style=False, sort_keys=False)
print("✅ [MissionPanelHandler] 通道配置已同步到 default_config.yaml")
return True
except Exception as e:
print(f"❌ [MissionPanelHandler] 保存通道配置失败: {e}")
import traceback
traceback.print_exc()
return False
......@@ -1251,11 +1217,8 @@ class MissionPanelHandler:
def _refreshChannelPanelNames(self):
"""刷新所有通道面板的名称显示"""
try:
print("🔄 [MissionPanelHandler] 开始刷新通道面板名称...")
# 检查是否有通道面板映射(从 ChannelPanelHandler 继承)
if not hasattr(self, '_channel_panels_map'):
print("⚠️ [MissionPanelHandler] _channel_panels_map 不存在,无法刷新")
return
# 从 default_config.yaml 重新加载通道配置
......@@ -1266,7 +1229,6 @@ class MissionPanelHandler:
config_path = os.path.join(project_root, 'database', 'config', 'default_config.yaml')
if not os.path.exists(config_path):
print("⚠️ [MissionPanelHandler] 配置文件不存在")
return
with open(config_path, 'r', encoding='utf-8') as f:
......@@ -1291,18 +1253,13 @@ class MissionPanelHandler:
# 更新面板显示的名称
if hasattr(panel, 'setChannelName'):
panel.setChannelName(channel_name)
print(f" ✅ {channel_id} 名称已更新为: {channel_name}")
print("✅ [MissionPanelHandler] 通道面板名称刷新完成")
except Exception as e:
print(f"❌ [MissionPanelHandler] 刷新通道面板名称失败: {e}")
import traceback
traceback.print_exc()
def _handleChannelDebug(self, channel_id, address):
"""处理通道调试请求 - 测试RTSP连接"""
print(f"🔧 [MissionPanelHandler] 开始测试通道{channel_id}连接: {address}")
# 在新线程中测试RTSP连接,避免阻塞UI
from qtpy.QtCore import QThread
......@@ -1319,83 +1276,44 @@ class MissionPanelHandler:
def run(self):
cap = None
try:
print(f" 🔌 [RTSP测试] 尝试连接: {self.address}")
# 使用HKcapture类进行连接测试
print(f" 🔧 [RTSP测试] 使用HKcapture类...")
cap = HKcapture(self.address)
# 检测设备类型
if cap.is_hikvision:
print(f" 📋 [RTSP测试] 检测到海康威视设备")
else:
print(f" 📋 [RTSP测试] 检测到普通RTSP流")
# 尝试打开连接
print(f" 🔗 [RTSP测试] 正在打开连接...")
if not cap.open():
print(f" ❌ [RTSP测试] 无法打开连接")
print(f" 💡 [RTSP测试] 可能原因:")
if cap.is_hikvision:
print(f" 1. IP地址或端口错误")
print(f" 2. 用户名或密码错误")
print(f" 3. 相机未开机或网络不通")
print(f" 4. 海康SDK初始化失败")
else:
print(f" 1. RTSP地址格式错误")
print(f" 2. 网络连接问题")
print(f" 3. 相机认证失败")
self.finished.emit(False, "无法打开连接")
return
print(f" ✅ [RTSP测试] 连接已打开")
# 尝试开始捕获
print(f" 📹 [RTSP测试] 正在启动视频捕获...")
if not cap.start_capture():
print(f" ❌ [RTSP测试] 无法启动视频捕获")
self.finished.emit(False, "无法启动捕获")
cap.release()
return
print(f" ✅ [RTSP测试] 视频捕获已启动")
# 等待并读取帧
import time
max_retries = 10
success = False
print(f" ⏳ [RTSP测试] 等待视频帧...")
for i in range(max_retries):
ret, frame = cap.read()
if ret and frame is not None and frame.size > 0:
print(f" ✅ [RTSP测试] 第{i+1}次尝试成功!帧大小: {frame.shape}")
success = True
break
else:
print(f" ⏳ [RTSP测试] 第{i+1}次尝试... ret={ret}")
time.sleep(0.2) # 等待200ms
if success:
print(f" 🎉 [RTSP测试] 测试成功!")
self.finished.emit(True, "连接成功")
else:
print(f" ❌ [RTSP测试] 尝试{max_retries}次后仍无法读取帧")
print(f" 💡 [RTSP测试] 可能原因:")
print(f" 1. 视频流缓冲时间过长")
print(f" 2. 网络延迟过高")
print(f" 3. 相机编码格式不支持")
self.finished.emit(False, "无法读取视频帧")
except Exception as e:
print(f" ❌ [RTSP测试] 连接失败: {e}")
import traceback
traceback.print_exc()
self.finished.emit(False, f"连接失败: {str(e)}")
finally:
# 确保释放资源
if cap is not None:
print(f" 🔄 [RTSP测试] 释放资源...")
cap.release()
# 创建测试线程
......@@ -1403,8 +1321,6 @@ class MissionPanelHandler:
# 连接完成信号
def on_test_finished(success, message):
print(f" 📊 [RTSP测试] 测试结果: {'成功' if success else '失败'} - {message}")
# 更新UI按钮状态
if hasattr(self, 'mission_panel') and self.mission_panel:
self.mission_panel.updateDebugButtonStatus(channel_id, success, message)
......@@ -1448,10 +1364,8 @@ class MissionPanelHandler:
# 如果通道有任务且不是当前要分配的任务,记录下来
if existing_task and existing_task != new_task_name:
channels_with_tasks.append((channel_key, existing_task))
print(f" 检测到 {channel_key} 已有任务: {existing_task}")
except Exception as e:
print(f"❌ 检查通道任务失败: {e}")
import traceback
traceback.print_exc()
......@@ -1496,7 +1410,7 @@ class MissionPanelHandler:
直接更新指定通道的任务标签显示(支持多任务并行)
使用新的变量名方式(channel1mission, channel2mission等)直接更新标签,
不影响其他通道的任务标签。双击任务不会改变任务面板中的状态
不影响其他通道的任务标签。同时将任务行对应的通道列置黑
Args:
channel_id: 通道ID(如 'channel1')
......@@ -1535,11 +1449,85 @@ class MissionPanelHandler:
else:
print(f"❌ [_updateChannelMissionLabel] 未找到变量: {mission_var_name}")
# 注意:不在这里调用 _setTaskRowChannelColor,而是在 _handleTaskSelected 中更新完所有通道后再调用
except Exception as e:
print(f"❌ [_updateChannelMissionLabel] 更新通道任务标签失败 ({channel_id}): {e}")
import traceback
traceback.print_exc()
def _setTaskRowChannelColor(self, task_folder_name, channel_num, color):
"""
设置任务行对应通道列的颜色
新逻辑:一个通道列只有一行能被置黑
1. 先将所有任务行的所有通道列重置为灰色
2. 然后根据所有通道的 channelmission 状态,将对应的通道列置黑
Args:
task_folder_name: 任务文件夹名称(如 '1_1')
channel_num: 当前操作的通道编号(1-4)
color: 颜色值(如 '#000000' 黑色)
"""
try:
if not hasattr(self.mission_panel, '_all_rows_data'):
return
# 获取所有通道的当前任务状态
channel_missions = {}
for ch_idx in range(4):
ch_num = ch_idx + 1
mission_var_name = f'channel{ch_num}mission'
if hasattr(self, mission_var_name):
mission_label = getattr(self, mission_var_name)
current_mission = mission_label.text()
channel_missions[ch_num] = current_mission
all_rows = self.mission_panel._all_rows_data
# 🔥 第一步:将所有任务行的所有通道列重置为灰色
for row_idx, row_info in enumerate(all_rows):
user_data = row_info.get('user_data', {})
mission_result_folder_path = user_data.get('mission_result_folder_path', '')
selected_channels = user_data.get('selected_channels', [])
if mission_result_folder_path and selected_channels:
import os
current_task_name = os.path.basename(mission_result_folder_path)
# 将该任务行的所有选中通道列置灰
for channel_name in selected_channels:
if channel_name.startswith('通道'):
ch_num = int(channel_name.replace('通道', ''))
col_idx = 3 + (ch_num - 1)
self.mission_panel.setCellTextColor(row_idx, col_idx, '#808080')
# 🔥 第二步:根据 channelmission 状态,将对应的通道列置黑
for ch_num, current_mission in channel_missions.items():
if current_mission and current_mission != '未分配任务':
# 查找该任务对应的行
for row_idx, row_info in enumerate(all_rows):
user_data = row_info.get('user_data', {})
mission_result_folder_path = user_data.get('mission_result_folder_path', '')
selected_channels = user_data.get('selected_channels', [])
if mission_result_folder_path:
import os
current_task_name = os.path.basename(mission_result_folder_path)
# 如果找到匹配的任务行且该通道被选中
if current_task_name == current_mission:
channel_name = f'通道{ch_num}'
if channel_name in selected_channels:
col_idx = 3 + (ch_num - 1)
self.mission_panel.setCellTextColor(row_idx, col_idx, '#000000')
break
except Exception as e:
import traceback
traceback.print_exc()
def _isTaskInUse(self, task_folder_name):
"""
检查指定任务是否被任何通道使用
......@@ -1582,8 +1570,8 @@ class MissionPanelHandler:
刷新任务面板中所有任务的状态显示
根据当前通道使用情况,动态更新每个任务的状态:
- 被通道使用的任务:已配置
- 未被通道使用的任务:未配置
- 被通道使用的任务:已启动
- 未被通道使用的任务:未启动
"""
try:
if not hasattr(self, 'mission_panel'):
......@@ -1607,7 +1595,7 @@ class MissionPanelHandler:
# 检查任务是否被使用
is_in_use = self._isTaskInUse(task_folder_name)
new_status = "已配置" if is_in_use else "未配置"
new_status = "已启动" if is_in_use else "未启动"
# 更新状态显示
current_status = status_item.text()
......@@ -1615,7 +1603,7 @@ class MissionPanelHandler:
status_item.setText(new_status)
print(f"🔄 [状态刷新] 任务 {task_folder_name}: {current_status} → {new_status}")
# 更新行颜色(未配置的任务显示灰色)
# 更新行颜色(未启动的任务显示灰色)
self._updateRowColorForQTableWidgetItem(row, new_status)
except Exception as e:
......@@ -1629,7 +1617,7 @@ class MissionPanelHandler:
Args:
task_folder_name: 任务文件夹名称(如 "21321_312312")
new_status: 新状态(如 "已配置")
new_status: 新状态(如 "已启动")
"""
try:
if not hasattr(self, 'mission_panel'):
......@@ -1686,7 +1674,7 @@ class MissionPanelHandler:
table = self.mission_panel.table
# 🔥 完全使用字体管理器,不使用CSS样式表
is_gray = (status == "未配置")
is_gray = (status == "未启动")
print(f" [调试] 设置颜色: {'灰色' if is_gray else '默认黑色'}")
# 更新所有列的字体颜色(除了按钮列)
......@@ -1772,7 +1760,7 @@ class MissionPanelHandler:
else:
print(f"⚠️ [调试] 列{col}既无widget也无item")
print(f"✅ [_updateRowColor] 已更新行 {row_index} 颜色为: {'灰色' if status == '未配置' else '默认'}")
print(f"✅ [_updateRowColor] 已更新行 {row_index} 颜色为: {'灰色' if status == '未启动' else '默认'}")
except Exception as e:
print(f"❌ [_updateRowColor] 更新行颜色失败: {e}")
......@@ -1782,7 +1770,7 @@ class MissionPanelHandler:
def _updateRowColorForQTableWidgetItem(self, row_index, status):
"""
根据状态更新行的字体颜色 - 适配纯QTableWidgetItem方案
只对未配置任务设置灰色,已配置任务保持默认颜色(不强制设置黑色)
只对未启动任务设置灰色,已启动任务保持默认颜色(不强制设置黑色)
Args:
row_index: 行索引
......@@ -1796,9 +1784,9 @@ class MissionPanelHandler:
return
table = self.mission_panel.table
is_gray = (status == "未配置")
is_gray = (status == "未启动")
# 🔥 只对未配置任务设置灰色,已配置任务不修改颜色(保持双击置黑的效果)
# 🔥 只对未启动任务设置灰色,已启动任务不修改颜色(保持双击置黑的效果)
if is_gray:
# 更新所有列的字体颜色为灰色(除了按钮列)
for col in range(table.columnCount()):
......@@ -1808,14 +1796,14 @@ class MissionPanelHandler:
item = table.item(row_index, col)
if item:
# 未配置:灰色文字
# 未启动:灰色文字
item.setForeground(QtGui.QColor(128, 128, 128))
print(f" [调试] 设置灰色文字: 行={row_index}, 列={col}")
print(f"✅ [_updateRowColorForQTableWidgetItem] 已更新行 {row_index} 颜色为: 灰色")
else:
# 已配置任务:不修改颜色,保持现有颜色(可能是双击置黑的黑色)
print(f"✅ [_updateRowColorForQTableWidgetItem] 跳过已配置任务行 {row_index},保持现有颜色")
# 已启动任务:不修改颜色,保持现有颜色(可能是双击置黑的黑色)
print(f"✅ [_updateRowColorForQTableWidgetItem] 跳过已启动任务行 {row_index},保持现有颜色")
except Exception as e:
print(f"❌ [_updateRowColorForQTableWidgetItem] 更新行颜色失败: {e}")
......@@ -1900,17 +1888,17 @@ class MissionPanelHandler:
status_item.setText("检测中")
status_item.setForeground(QtGui.QColor(0, 128, 0)) # 绿色
else:
# 有通道未检测:显示"已配置"
status_item.setText("已配置")
# 有通道未检测:显示"已启动"
status_item.setText("已启动")
status_item.setForeground(QtGui.QColor(0, 0, 0)) # 黑色
else:
# 不是正在执行的任务,检查是否被配置
is_task_in_use = self._isTaskInUse(task_folder_name)
status_text = "已配置" if is_task_in_use else "未配置"
status_text = "已启动" if is_task_in_use else "未启动"
status_item.setText(status_text)
# 设置颜色:已配置为黑色,未配置为灰色
if status_text == "已配置":
# 设置颜色:已启动为黑色,未启动为灰色
if status_text == "已启动":
status_item.setForeground(QtGui.QColor(0, 0, 0)) # 黑色
else:
status_item.setForeground(QtGui.QColor(128, 128, 128)) # 灰色
......
# 液位检测系统环境配置指南
本文档提供了如何在新设备上配置液位检测系统运行环境的详细步骤。
## 系统要求
- Windows 10/11 操作系统
- NVIDIA GPU (支持CUDA)
- 至少8GB内存,推荐16GB或更高
- 至少10GB可用磁盘空间
## 安装步骤
### 1. 安装Anaconda或Miniconda
[Anaconda官网](https://www.anaconda.com/download/)[Miniconda官网](https://docs.conda.io/en/latest/miniconda.html)下载并安装。
### 2. 创建虚拟环境
打开Anaconda Prompt或命令行,执行以下命令:
```bash
# 创建名为yeweienv的Python 3.10环境
conda create -n yeweienv python=3.10
# 激活环境
conda activate yeweienv
```
### 3. 安装CUDA支持(如果需要GPU加速)
如果您的系统有NVIDIA GPU并希望使用GPU加速,请安装适合的CUDA版本:
```bash
# 安装CUDA工具包(与torch 2.5.1+cu121兼容的CUDA 12.1)
conda install -c nvidia cuda-toolkit=12.1
```
### 4. 安装依赖项
使用提供的requirements.txt文件安装所有依赖项:
```bash
# 确保当前目录包含requirements.txt文件
cd 路径/到/液位检测系统/widgets
# 安装依赖
pip install -r requirements.txt
```
### 5. 验证安装
安装完成后,可以运行以下命令验证关键包是否正确安装:
```bash
# 验证PyQt5
python -c "from PyQt5.QtWidgets import QApplication; print('PyQt5 installed successfully')"
# 验证PyTorch和CUDA
python -c "import torch; print(f'PyTorch version: {torch.__version__}'); print(f'CUDA available: {torch.cuda.is_available()}')"
# 验证Ultralytics
python -c "from ultralytics import YOLO; print('Ultralytics installed successfully')"
```
### 6. 运行系统
完成上述步骤后,您可以通过以下命令运行液位检测系统:
```bash
# 切换到项目根目录
cd 路径/到/液位检测系统
# 运行主程序
python __main__.py
```
## 常见问题
### PyQt5安装失败
如果PyQt5安装失败,可以尝试:
```bash
pip install PyQt5==5.15.11 --index-url=https://pypi.douban.com/simple
```
### CUDA相关错误
确保安装了与PyTorch兼容的CUDA版本。如果不需要GPU加速,可以安装CPU版本:
```bash
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
```
### 其他问题
如遇其他问题,请联系系统管理员或开发人员获取支持。
---
*注:此环境配置指南基于yeweienv环境导出,最后更新于2025年11月。*
# 液位检测系统依赖项
# 基于yeweienv环境导出
# 核心依赖
numpy>=1.24.0
PyQt5==5.15.11
PyQt5-Qt5==5.15.2
PyQt5-sip==12.17.0
qtpy==2.4.3
PyYAML==6.0.2
opencv-python>=4.8.0
pillow>=11.0.0
pandas>=2.3.0
scipy>=1.15.0
matplotlib>=3.7.0
# 深度学习框架
torch==2.5.1+cu121
torchvision==0.20.1+cu121
torchaudio==2.5.1+cu121
ultralytics==8.3.163
# 图像处理
scikit-image==0.25.2
tifffile==2025.5.10
# 工具依赖
pyinstaller==6.15.0
pyarmor==9.2.0
pywin32==311
psutil==7.0.0
watchdog==6.0.0
# 可选依赖
pyqtgraph==0.13.7
natsort==8.4.0
......@@ -150,11 +150,27 @@ class MissionPanel(QtWidgets.QWidget):
self.table = QtWidgets.QTableWidget()
# 设置表格基本属性
self.table.setAlternatingRowColors(True) # 交替行颜色
self.table.setAlternatingRowColors(False) # 统一使用白色背景,不使用交替行颜色
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) # 整行选择
self.table.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) # 禁用选择,取消高亮
self.table.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) # 禁用Qt默认选择
self.table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) # 默认不可编辑
# 🔥 当前选中的行索引(手动管理)
self._current_selected_row = -1
# 清除所有选择样式
self.table.setStyleSheet("""
QTableWidget {
selection-background-color: transparent;
selection-color: inherit;
gridline-color: #d0d0d0;
}
QTableWidget::item {
border: 1px solid transparent;
padding: 2px;
}
""")
# 🔥 启用右键菜单
self.table.setContextMenuPolicy(Qt.CustomContextMenu)
self.table.customContextMenuRequested.connect(self._showContextMenu)
......@@ -162,7 +178,7 @@ class MissionPanel(QtWidgets.QWidget):
# 🔥 待高亮的行索引(用于延迟高亮,等待用户确认)
self._pending_highlight_row = None
# 安装事件过滤器,拦截单击事件(只允许双击选中)
# 安装事件过滤器,处理键盘事件
self.table.viewport().installEventFilter(self)
# 设置表格的尺寸策略
......@@ -585,7 +601,7 @@ class MissionPanel(QtWidgets.QWidget):
self.btn_debug.installEventFilter(self)
# 表格信号
self.table.itemDoubleClicked.connect(self._onItemDoubleClicked)
self.table.itemClicked.connect(self._onItemClicked)
self.table.cellChanged.connect(self._onCellChanged)
# 安装键盘事件过滤器(处理Delete键删除任务)
......@@ -683,8 +699,8 @@ class MissionPanel(QtWidgets.QWidget):
row_index = self.table.rowCount()
self.table.insertRow(row_index)
# 检查是否为未配置状态
is_unconfigured = len(row_data) > 2 and str(row_data[2]) == "未配置"
# 检查是否为未启动状态
is_unconfigured = len(row_data) > 2 and str(row_data[2]) == "未启动"
for col_index, value in enumerate(row_data):
# 跳过曲线列(将由按钮占据)
......@@ -700,7 +716,7 @@ class MissionPanel(QtWidgets.QWidget):
# 🔥 通道列始终设置为灰色字体
if self.CHANNEL_START_COLUMN <= col_index < self.CHANNEL_START_COLUMN + self.CHANNEL_COUNT:
item.setForeground(QtGui.QColor(128, 128, 128)) # 设置灰色文字
# 🔥 如果是未配置状态且不是通道列,设置灰色前景色
# 🔥 如果是未启动状态且不是通道列,设置灰色前景色
elif is_unconfigured:
item.setForeground(QtGui.QColor(128, 128, 128)) # 设置灰色文字
......@@ -714,7 +730,7 @@ class MissionPanel(QtWidgets.QWidget):
def _setRowDefaultColor(self, row_index, row_data):
"""
设置指定行为默认颜色(用于已配置状态)- 纯QTableWidgetItem方案
设置指定行为默认颜色(用于已启动状态)- 纯QTableWidgetItem方案
Args:
row_index: 行索引
......@@ -733,7 +749,7 @@ class MissionPanel(QtWidgets.QWidget):
def _setRowGrayColor(self, row_index, row_data):
"""
设置指定行为灰色字体(用于未配置状态)- 纯QTableWidgetItem方案
设置指定行为灰色字体(用于未启动状态)- 纯QTableWidgetItem方案
Args:
row_index: 行索引
......@@ -863,16 +879,32 @@ class MissionPanel(QtWidgets.QWidget):
def getSelectedRow(self):
"""
获取当前选中的行索引
获取当前选中的行索引(在全部数据中的索引)
Returns:
int: 行索引,如果没有选中返回-1
int: 全局行索引,如果没有选中返回-1
"""
selected_items = self.table.selectedItems()
if not selected_items:
return -1
return self._current_selected_row
def setSelectedRow(self, global_row_index):
"""
设置选中的行(程序化选择)
return selected_items[0].row()
Args:
global_row_index: 全局行索引
"""
# 清除之前的选中状态
self._clearRowHighlight()
if global_row_index < 0 or global_row_index >= len(self._all_rows_data):
self._current_selected_row = -1
return
# 设置当前选中行
self._current_selected_row = global_row_index
# 应用黑色边框高亮
self._applyRowHighlight(global_row_index)
def getSelectedRowData(self):
"""
......@@ -887,6 +919,43 @@ class MissionPanel(QtWidgets.QWidget):
return self.getRowData(row_index)
def _clearRowHighlight(self):
"""清除所有行的高亮边框"""
for row_idx in range(self.table.rowCount()):
for col_idx in range(self.table.columnCount()):
item = self.table.item(row_idx, col_idx)
if item:
# 恢复透明边框
item.setData(Qt.UserRole + 1, None) # 清除高亮标记
self._updateItemStyle(item, False)
def _applyRowHighlight(self, global_row_index):
"""应用黑色边框高亮到指定行"""
# 计算该行在当前页面中的索引
start_idx = (self._current_page - 1) * self._page_size
end_idx = start_idx + self._page_size
# 检查该行是否在当前页面中
if start_idx <= global_row_index < end_idx:
table_row = global_row_index - start_idx
# 为该行的所有单元格添加黑色边框
for col_idx in range(self.table.columnCount()):
item = self.table.item(table_row, col_idx)
if item:
# 标记为高亮状态
item.setData(Qt.UserRole + 1, True)
self._updateItemStyle(item, True)
def _updateItemStyle(self, item, is_highlighted):
"""更新单元格样式"""
if is_highlighted:
# 使用深灰色背景表示选中状态(类似黑色边框的效果)
item.setBackground(QtGui.QBrush(QtGui.QColor(220, 220, 220))) # 深灰色背景表示选中
else:
# 恢复默认样式
item.setBackground(QtGui.QBrush(QtGui.QColor(255, 255, 255))) # 白色背景
def clearTable(self):
"""清空表格所有数据"""
self._all_rows_data.clear()
......@@ -1087,19 +1156,27 @@ class MissionPanel(QtWidgets.QWidget):
def _onItemDoubleClicked(self, item):
"""单元格被双击 - 恢复任务分配功能"""
def _onItemClicked(self, item):
"""单元格被单击 - 任务分配功能"""
if item:
row = item.row()
# 获取表格中的行索引
table_row = item.row()
# 转换为全局行索引
start_idx = (self._current_page - 1) * self._page_size
global_row = start_idx + table_row
# 发送行选中信号
self.itemSelected.emit(row)
# 🔥 手动设置选中行(黑色边框高亮)
self.setSelectedRow(global_row)
# 发送选中信号
self.itemSelected.emit(global_row)
# 获取任务信息并发送任务选中信号给handler
user_data = self.getUserData(row)
user_data = self.getUserData(table_row)
if user_data:
# 🔥 保存当前击的行索引,等待handler确认后再置黑
self._pending_highlight_row = row
# 🔥 保存当前击的行索引,等待handler确认后再置黑
self._pending_highlight_row = global_row
self.taskSelected.emit(user_data)
def _assignTaskToChannels(self, row):
......@@ -1138,7 +1215,7 @@ class MissionPanel(QtWidgets.QWidget):
self._pending_highlight_row = None
def _highlightSelectedRow(self, selected_row):
"""简化高亮逻辑:只将双击行文字颜色置黑"""
"""简化高亮逻辑:只将单击行文字颜色置黑(跳过通道列)"""
try:
# 只处理选中的行,将文字颜色置黑
for col in range(self.table.columnCount()):
......@@ -1146,9 +1223,13 @@ class MissionPanel(QtWidgets.QWidget):
if col == self.CURVE_BUTTON_COLUMN:
continue
# 🔥 跳过通道列(通道列颜色由 channelmission 状态控制)
if self.CHANNEL_START_COLUMN <= col < self.CHANNEL_START_COLUMN + self.CHANNEL_COUNT:
continue
item = self.table.item(selected_row, col)
if item:
# 双击行:所有列(包括通道列)都设置为黑色文字
# 单击行:非通道列设置为黑色文字
item.setForeground(QtGui.QColor(0, 0, 0)) # 黑色文字
# 不设置背景色,保持原有背景
......@@ -1282,25 +1363,8 @@ class MissionPanel(QtWidgets.QWidget):
self._onDeleteKeyPressed()
return True
# 拦截表格的单击事件(只允许双击选中)
if obj == self.table.viewport():
if event.type() == QtCore.QEvent.MouseButtonPress:
if event.button() == Qt.LeftButton:
# 拦截单击事件,阻止选中
return True
elif event.type() == QtCore.QEvent.MouseButtonDblClick:
# 🔥 手动处理双击事件
if event.button() == Qt.LeftButton:
# 获取双击位置的item
pos = event.pos()
item = self.table.itemAt(pos)
if item:
# 直接调用双击处理方法
self._onItemDoubleClicked(item)
# 允许双击事件继续传播
return False
# 不再拦截单击事件,允许单击触发任务分配
# 移除了双击事件的特殊处理,改为使用标准的 itemClicked 信号
# 调试按钮的左右键处理
if obj == self.btn_debug:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment