Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
O
Oil_Level_Recognition_System
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
柳青君
Oil_Level_Recognition_System
Commits
307fb918
Commit
307fb918
authored
Nov 27, 2025
by
Yuhaibo
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
1
parent
4f8ebab8
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
358 additions
and
170 deletions
+358
-170
missionpanel_handler.py
handlers/videopage/missionpanel_handler.py
+114
-126
installation_guide.md
widgets/installation_guide.md
+100
-0
requirements.txt
widgets/requirements.txt
+36
-0
missionpanel.py
widgets/videopage/missionpanel.py
+108
-44
No files found.
handlers/videopage/missionpanel_handler.py
View file @
307fb918
...
...
@@ -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
))
# 灰色
...
...
widgets/installation_guide.md
0 → 100644
View file @
307fb918
# 液位检测系统环境配置指南
本文档提供了如何在新设备上配置液位检测系统运行环境的详细步骤。
## 系统要求
-
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月。*
widgets/requirements.txt
0 → 100644
View file @
307fb918
# 液位检测系统依赖项
# 基于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
widgets/videopage/missionpanel.py
View file @
307fb918
...
...
@@ -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
.
item
DoubleClicked
.
connect
(
self
.
_onItemDouble
Clicked
)
self
.
table
.
item
Clicked
.
connect
(
self
.
_onItem
Clicked
)
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
_onItem
Double
Clicked
(
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
:
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment