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
Administrator
Oil_Level_Recognition_System
Commits
872a1442
Commit
872a1442
authored
Nov 26, 2025
by
Yuhaibo
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
1
parent
dde5f108
Show whitespace changes
Inline
Side-by-side
Showing
27 changed files
with
97 additions
and
1038 deletions
+97
-1038
app.py
app.py
+0
-17
CLEANUP_PROGRESS.md
handlers/CLEANUP_PROGRESS.md
+14
-9
missionpanel_handler.py
handlers/videopage/missionpanel_handler.py
+2
-52
display_thread.py
handlers/videopage/thread_manager/threads/display_thread.py
+0
-10
storage_thread.py
handlers/videopage/thread_manager/threads/storage_thread.py
+2
-52
test_model_loading.py
...rs/videopage/thread_manager/threads/test_model_loading.py
+0
-134
annotationtool.py
widgets/datasetpage/annotationtool.py
+2
-56
crop_config_dialog.py
widgets/datasetpage/crop_config_dialog.py
+2
-51
crop_preview_panel.py
widgets/datasetpage/crop_preview_panel.py
+1
-41
datacollection_panel.py
widgets/datasetpage/datacollection_panel.py
+10
-40
datapreprocess_panel.py
widgets/datasetpage/datapreprocess_panel.py
+11
-156
test_crop_preview_integration.py
widgets/datasetpage/test_crop_preview_integration.py
+7
-65
test_labelme_integration.py
widgets/datasetpage/test_labelme_integration.py
+0
-48
training_panel.py
widgets/datasetpage/training_panel.py
+0
-5
videobrowser.py
widgets/datasetpage/videobrowser.py
+5
-52
videoclipper.py
widgets/datasetpage/videoclipper.py
+0
-32
menubar.py
widgets/menubar.py
+0
-1
modelset_page.py
widgets/modelpage/modelset_page.py
+5
-53
training_page.py
widgets/modelpage/training_page.py
+11
-44
style_manager.py
widgets/style_manager.py
+0
-5
channelpanel.py
widgets/videopage/channelpanel.py
+0
-21
curvepanel.py
widgets/videopage/curvepanel.py
+2
-38
general_set.py
widgets/videopage/general_set.py
+11
-15
historyvideopanel.py
widgets/videopage/historyvideopanel.py
+3
-3
logicsetting_dialogue.py
widgets/videopage/logicsetting_dialogue.py
+0
-4
missionpanel.py
widgets/videopage/missionpanel.py
+9
-30
modelsetting_dialogue.py
widgets/videopage/modelsetting_dialogue.py
+0
-4
No files found.
app.py
View file @
872a1442
...
...
@@ -1311,30 +1311,15 @@ class MainWindow(
def
showVideoPage
(
self
):
"""显示实时检测管理页面"""
print
(
f
"
\n
{'='*60}"
)
print
(
f
"[showVideoPage] ========== 切换到实时检测管理页面 =========="
)
print
(
f
"{'='*60}"
)
self
.
stackedWidget
.
setCurrentWidget
(
self
.
videoPage
)
# 🔥 确保切换到默认布局(videoLayoutStack 索引0)
if
hasattr
(
self
,
'videoLayoutStack'
):
print
(
f
"[showVideoPage] videoLayoutStack 当前索引: {self.videoLayoutStack.currentIndex()}"
)
print
(
f
"[showVideoPage] 切换 videoLayoutStack 到索引 0(默认布局)..."
)
self
.
videoLayoutStack
.
setCurrentIndex
(
0
)
self
.
_video_layout_mode
=
0
print
(
f
"[showVideoPage] 切换后索引: {self.videoLayoutStack.currentIndex()}"
)
# 显示当前控件信息
current_widget
=
self
.
videoLayoutStack
.
currentWidget
()
print
(
f
"[showVideoPage] 当前控件: {current_widget}"
)
if
current_widget
:
print
(
f
" - 可见性: {current_widget.isVisible()}"
)
print
(
f
" - 大小: {current_widget.size()}"
)
# 🔥 确保所有通道面板在默认布局中可见
if
hasattr
(
self
,
'channelPanels'
):
print
(
f
"[showVideoPage] 确保所有通道面板可见..."
)
for
i
,
channel_panel
in
enumerate
(
self
.
channelPanels
):
if
hasattr
(
self
,
'default_channel_positions'
)
and
i
<
len
(
self
.
default_channel_positions
):
# 确保通道面板在正确的父容器中
...
...
@@ -1345,10 +1330,8 @@ class MainWindow(
# 显示通道面板
channel_panel
.
show
()
print
(
f
" - 通道{i+1}: 可见性={channel_panel.isVisible()}, 父容器={channel_panel.parent()}"
)
self
.
statusBar
()
.
showMessage
(
self
.
tr
(
"当前页面: 实时检测管理"
))
print
(
f
"[showVideoPage] ========== 切换完成 ==========
\n
"
)
def
showModelPage
(
self
):
"""显示模型管理页面"""
...
...
handlers/CLEANUP_PROGRESS.md
View file @
872a1442
...
...
@@ -112,10 +112,14 @@
13.
✅
**widgets/videopage/missionpanel.py**
- 删除约20处print语句
14.
✅
**widgets/videopage/general_set.py**
- 删除约30处print语句
15.
✅
**widgets/videopage/curvepanel.py**
- 删除4处print语句
16.
✅
**widgets/videopage/historyvideopanel.py**
- 删除约1
5处print语句
16.
✅
**widgets/videopage/historyvideopanel.py**
- 删除约1
6处print语句和3处DEBUG注释
### 部分清理
17.
🔄
**handlers/videopage/missionpanel_handler.py**
- 已清理前330行,删除约20处print语句(文件共2050行,需继续清理)
17.
🔄
**handlers/videopage/missionpanel_handler.py**
- 已清理前880行,删除约50处print语句(文件共2004行,需继续清理)
### 新增完成清理
18.
✅
**widgets/datasetpage/crop_preview_panel.py**
- 删除约47处print语句和2处DEBUG注释
19.
✅
**widgets/datasetpage/datacollection_panel.py**
- 删除约20处print语句和1处DEBUG注释
## 清理进度说明
...
...
@@ -146,13 +150,13 @@
-
⏳ widgets/datasetpage/test_
*
.py
### 剩余工作量估算
-
已清理: 约
283处调试语句 (16
个文件完全清理 + 1个文件部分清理)
-
剩余: 约3
158
处调试语句
-
预计需要: 继续手动清理约4
7
个文件
-
已清理: 约
384处调试语句 (18
个文件完全清理 + 1个文件部分清理)
-
剩余: 约3
057
处调试语句
-
预计需要: 继续手动清理约4
5
个文件
### 本次清理总结 (2025-11-26 20:
30
)
-
完成文件数: 1
6
个完全清理 + 1个部分清理
-
删除调试语句: 约
283处
### 本次清理总结 (2025-11-26 20:
46
)
-
完成文件数: 1
8
个完全清理 + 1个部分清理
-
删除调试语句: 约
384处(包含print语句和DEBUG注释)
-
主要清理内容:
-
核心应用入口和窗口管理 (app.py)
-
视图布局切换管理 (view_handler.py)
...
...
@@ -161,8 +165,9 @@
-
曲线面板Handler (curvepanel_handler.py)
-
完整线程管理系统 (thread_manager.py, curve_thread.py, global_detection_thread.py, storage_thread.py, display_thread.py)
-
完整UI组件系统 (missionpanel.py, general_set.py, curvepanel.py, historyvideopanel.py)
-
数据集页面组件 (crop_preview_panel.py, datacollection_panel.py)
-
部分任务面板Handler (missionpanel_handler.py)
---
*最后更新: 2025-11-26 20:
30
*
*最后更新: 2025-11-26 20:
46
*
handlers/videopage/missionpanel_handler.py
View file @
872a1442
...
...
@@ -337,21 +337,17 @@ class MissionPanelHandler:
# 读取YAML文件
if
not
os
.
path
.
exists
(
file_path
):
print
(
f
"任务配置文件不存在: {filename}"
)
return
None
with
open
(
file_path
,
'r'
,
encoding
=
'utf-8'
)
as
f
:
config_data
=
yaml
.
safe_load
(
f
)
if
not
config_data
:
print
(
f
"任务配置文件为空: {filename}"
)
return
None
print
(
f
"已加载任务配置: {filename}"
)
return
config_data
except
Exception
as
e
:
print
(
f
"加载任务配置失败: {e}"
)
import
traceback
traceback
.
print_exc
()
return
None
...
...
@@ -375,7 +371,6 @@ class MissionPanelHandler:
config_file
=
os
.
path
.
join
(
project_root
,
'database'
,
'config'
,
'channel_config.yaml'
)
if
not
os
.
path
.
exists
(
config_file
):
print
(
f
"配置文件不存在: {config_file}"
)
return
False
# 读取现有配置
...
...
@@ -407,14 +402,9 @@ class MissionPanelHandler:
with
open
(
config_file
,
'w'
,
encoding
=
'utf-8'
)
as
f
:
yaml
.
dump
(
channel_config
,
f
,
allow_unicode
=
True
,
default_flow_style
=
False
,
sort_keys
=
False
)
print
(
f
"已同步任务信息到配置文件 {channel_id}:"
)
print
(
f
" - task_id: {task_id}"
)
print
(
f
" - task_name: {task_name}"
)
print
(
f
" - save_liquid_data_path: {save_liquid_data_path}"
)
return
True
except
Exception
as
e
:
print
(
f
"同步任务配置到文件失败 ({channel_id}): {e}"
)
import
traceback
traceback
.
print_exc
()
return
False
...
...
@@ -437,27 +427,19 @@ class MissionPanelHandler:
# 🔥 第一步:同步到 channel_config.yaml(独立业务,无论通道是否打开都执行)
sync_success
=
self
.
_syncTaskToConfigFile
(
channel_id
,
task_id
,
task_name
,
save_liquid_data_path
)
if
sync_success
:
print
(
f
"✅ 已同步任务到配置文件 {channel_id}: [{task_id}] {task_name}"
)
else
:
print
(
f
"⚠️ 同步任务到配置文件失败 {channel_id}"
)
pass
# 🔥 第二步:更新通道面板UI(可选操作,仅在通道已打开时执行)
# 检查是否有通道面板映射(从ChannelPanelHandler获取)
if
not
hasattr
(
self
,
'_channel_panels_map'
):
print
(
f
"⚠️ _channel_panels_map 不存在,跳过UI更新"
)
return
# 获取对应的通道面板
channel_panel
=
self
.
_channel_panels_map
.
get
(
channel_id
)
if
not
channel_panel
:
print
(
f
"ℹ️ 通道 {channel_id} 未打开,仅同步到配置文件"
)
return
# 通道面板存在,更新UI显示
print
(
f
"🔄 通道 {channel_id} 已打开,同时更新UI显示"
)
# 更新通道的channel_data,添加任务信息
channel_data
=
{
'task_id'
:
task_id
,
...
...
@@ -468,8 +450,6 @@ class MissionPanelHandler:
# 调用通道面板的updateChannel方法(更新内部存储)
if
hasattr
(
channel_panel
,
'updateChannel'
):
success
=
channel_panel
.
updateChannel
(
channel_id
,
channel_data
)
if
success
:
print
(
f
"✅ 已同步任务数据到通道面板内存 {channel_id}"
)
# 🔥 多任务支持:不再从全局 current_mission 读取,而是使用传入的任务信息
# 更新UI显示(从 save_liquid_data_path 提取文件夹名称)
...
...
@@ -485,18 +465,14 @@ class MissionPanelHandler:
# 如果文件夹名称为空或为"None",使用任务名称
if
folder_name
and
folder_name
.
lower
()
!=
"none"
:
channel_panel
.
setTaskInfo
(
folder_name
)
print
(
f
"✅ [多任务] 已更新通道面板UI显示 {channel_id}: {folder_name}"
)
else
:
# 使用任务名称作为备选
channel_panel
.
setTaskInfo
(
f
"{task_id}_{task_name}"
)
print
(
f
"✅ [多任务] 已更新通道面板UI显示 {channel_id}: {task_id}_{task_name}"
)
else
:
# 没有路径,使用任务名称
channel_panel
.
setTaskInfo
(
f
"{task_id}_{task_name}"
)
print
(
f
"✅ [多任务] 已更新通道面板UI显示 {channel_id}: {task_id}_{task_name}"
)
except
Exception
as
e
:
print
(
f
"❌ 更新通道任务信息失败 ({channel_id}): {e}"
)
import
traceback
traceback
.
print_exc
()
...
...
@@ -517,12 +493,10 @@ class MissionPanelHandler:
# 确保目录存在
if
not
os
.
path
.
exists
(
mission_dir
):
os
.
makedirs
(
mission_dir
)
print
(
f
" 创建任务配置目录: {mission_dir}"
)
return
mission_dir
except
Exception
as
e
:
print
(
f
" 获取任务配置路径失败: {e}"
)
return
None
def
_saveMissionConfig
(
self
,
task_info
):
...
...
@@ -576,11 +550,9 @@ class MissionPanelHandler:
with
open
(
file_path
,
'w'
,
encoding
=
'utf-8'
)
as
f
:
yaml
.
dump
(
config_data
,
f
,
allow_unicode
=
True
,
default_flow_style
=
False
,
sort_keys
=
False
)
print
(
f
" 任务配置已保存: {file_path}"
)
return
True
except
Exception
as
e
:
print
(
f
" 保存任务配置失败: {e}"
)
import
traceback
traceback
.
print_exc
()
return
False
...
...
@@ -621,12 +593,10 @@ class MissionPanelHandler:
# 确保目录存在
if
not
os
.
path
.
exists
(
mission_result_dir
):
os
.
makedirs
(
mission_result_dir
)
print
(
f
"📁 创建结果目录: {mission_result_dir}"
)
return
mission_result_dir
except
Exception
as
e
:
print
(
f
"❌ 获取结果路径失败: {e}"
)
return
None
def
_getYamlFilePath
(
self
,
task_info
):
...
...
@@ -672,7 +642,6 @@ class MissionPanelHandler:
# 获取结果目录路径
mission_result_dir
=
self
.
_getmission_resultPath
()
if
not
mission_result_dir
:
print
(
"❌ 无法获取结果目录路径"
)
return
None
# 构建任务文件夹名称:任务编号_任务名称
...
...
@@ -680,7 +649,6 @@ class MissionPanelHandler:
task_name
=
task_info
.
get
(
'task_name'
,
''
)
if
not
task_id
or
not
task_name
:
print
(
"❌ 任务编号或任务名称为空"
)
return
None
# 清理文件夹名称中的非法字符
...
...
@@ -693,15 +661,11 @@ class MissionPanelHandler:
# 创建任务文件夹
if
not
os
.
path
.
exists
(
task_folder_path
):
os
.
makedirs
(
task_folder_path
)
print
(
f
"📁 创建任务文件夹: {task_folder_path}"
)
else
:
print
(
f
"📁 任务文件夹已存在: {task_folder_path}"
)
# 返回创建的文件夹路径
return
task_folder_path
except
Exception
as
e
:
print
(
f
"❌ 创建结果文件夹失败: {e}"
)
import
traceback
traceback
.
print_exc
()
return
None
...
...
@@ -719,11 +683,9 @@ class MissionPanelHandler:
"""
try
:
if
not
yaml_file_path
or
not
os
.
path
.
exists
(
yaml_file_path
):
print
(
"⚠️ YAML文件不存在,跳过复制"
)
return
False
if
not
mission_result_folder_path
or
not
os
.
path
.
exists
(
mission_result_folder_path
):
print
(
"⚠️ 结果文件夹不存在,跳过复制"
)
return
False
# 复制YAML文件到结果文件夹
...
...
@@ -731,11 +693,9 @@ class MissionPanelHandler:
dest_yaml_path
=
os
.
path
.
join
(
mission_result_folder_path
,
yaml_filename
)
shutil
.
copy2
(
yaml_file_path
,
dest_yaml_path
)
print
(
f
"📄 YAML文件已复制: {dest_yaml_path}"
)
return
True
except
Exception
as
e
:
print
(
f
"❌ 复制YAML文件失败: {e}"
)
import
traceback
traceback
.
print_exc
()
return
False
...
...
@@ -767,15 +727,11 @@ class MissionPanelHandler:
# 删除对应的YAML配置文件
self
.
_deleteMissionConfig
(
task_id
,
task_name
)
#
🔥
重新加载任务列表(而不是只删除单行)
# 重新加载任务列表(而不是只删除单行)
# 这样可以确保分页数据和UI完全同步
self
.
_loadAllMissions
()
print
(
f
"✅ [任务删除] 成功删除任务: {task_id}_{task_name}"
)
print
(
f
"✅ [任务删除] 已重新加载任务列表"
)
except
Exception
as
e
:
print
(
f
"❌ [任务删除] 删除任务失败: {e}"
)
import
traceback
traceback
.
print_exc
()
...
...
@@ -798,7 +754,6 @@ class MissionPanelHandler:
# 清空表格
self
.
mission_panel
.
clearTable
()
print
(
"🗑️ 表格已清空,所有任务配置文件和结果文件夹已删除"
)
def
showMissionPanel
(
self
):
"""显示任务面板"""
...
...
@@ -826,7 +781,6 @@ class MissionPanelHandler:
# 获取任务配置路径
mission_dir
=
self
.
_getMissionConfigPath
()
if
not
mission_dir
or
not
os
.
path
.
exists
(
mission_dir
):
print
(
"️ 任务配置目录不存在,跳过加载"
)
return
# 扫描所有 .yaml 文件
...
...
@@ -848,7 +802,6 @@ class MissionPanelHandler:
config_data
=
yaml
.
safe_load
(
f
)
if
not
config_data
:
print
(
f
"️ 跳过空文件: {yaml_file}"
)
continue
# 构建任务信息
...
...
@@ -862,13 +815,11 @@ class MissionPanelHandler:
# 验证必填字段
if
not
task_info
[
'task_id'
]
or
not
task_info
[
'task_name'
]:
print
(
f
"️ 跳过无效配置: {yaml_file} (缺少task_id或task_name)"
)
continue
tasks_to_load
.
append
(
task_info
)
except
Exception
as
e
:
print
(
f
" 解析任务配置失败 ({yaml_file}): {e}"
)
continue
# 第二步:批量添加到任务面板(禁用实时刷新)
...
...
@@ -918,7 +869,6 @@ class MissionPanelHandler:
self
.
_updateChannelCellColors
()
except
Exception
as
e
:
print
(
f
" 加载任务配置失败: {e}"
)
import
traceback
traceback
.
print_exc
()
...
...
handlers/videopage/thread_manager/threads/display_thread.py
View file @
872a1442
...
...
@@ -43,10 +43,6 @@ class DisplayThread:
# 保存上次的液位线数据(用于历史数据回退)
last_liquid_positions
=
{}
# 调试计数器(用于追踪检测启动后的帧数)
debug_frame_count
=
0
last_detection_enabled
=
False
while
context
.
display_flag
:
try
:
frame_start_time
=
time
.
time
()
...
...
@@ -63,13 +59,7 @@ class DisplayThread:
display_frame
=
frame
# 检测状态变化时重置调试计数器
if
context
.
detection_enabled
and
not
last_detection_enabled
:
debug_frame_count
=
0
last_detection_enabled
=
context
.
detection_enabled
# 如果检测已启动,叠加液位线
if
context
.
detection_enabled
:
debug_frame_count
+=
1
# 从 detection_mission_results 队列读取液位线数据(非阻塞)
liquid_positions
=
{}
...
...
handlers/videopage/thread_manager/threads/storage_thread.py
View file @
872a1442
...
...
@@ -63,21 +63,8 @@ class StorageThread:
if
not
save_liquid_data_path
or
save_liquid_data_path
==
"0000"
:
return
# print(f" [存储线程-{channel_id}] 初始化")
# print(f" - 通道名称: {channel_name}")
# print(f" - 检测区域数: {len(area_names)}")
# print(f" - 曲线存储路径: {save_liquid_data_path}")
# 创建曲线存储目录
os
.
makedirs
(
save_liquid_data_path
,
exist_ok
=
True
)
# ========== 视频保存功能(占坑,暂不实现) ==========
# TODO: 未来实现检测结果视频的保存
# timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
# video_path = context.storage_path if context.storage_path else save_liquid_data_path
# display_video_path = os.path.join(video_path, f"display_{timestamp}.mp4")
# 创建CSV文件写入器(每个检测区域一个文件)
csv_files
=
{}
csv_writers
=
{}
...
...
@@ -94,9 +81,6 @@ class StorageThread:
# 存储线程不受 save_data_rate 限制,全速消费队列,避免数据丢失
# save_data_rate 仅用于配置文件记录,实际存储线程会保存所有检测结果
# print(f" [存储线程-{channel_id}] 启动成功")
# print(f" - 注意:存储线程全速运行,保存所有检测结果(不受save_data_rate限制)")
data_count
=
0
last_log_time
=
time
.
time
()
last_flush_time
=
time
.
time
()
# 记录上次刷新时间
...
...
@@ -109,44 +93,18 @@ class StorageThread:
# 从独立的 storage_data 队列读取,避免与曲线线程竞争
detection_mission_result
=
context
.
storage_data
.
get
(
timeout
=
0.1
)
# # 调试信息:存储线程接收数据
# print(f"\n [存储线程-{channel_id}] 接收到检测结果:")
# print(f" - detection_mission_result类型: {type(detection_mission_result)}")
# if detection_mission_result:
# print(f" - 包含的键: {detection_mission_result.keys()}")
# 解析液位高度数据
if
detection_mission_result
and
'liquid_line_positions'
in
detection_mission_result
:
liquid_positions
=
detection_mission_result
[
'liquid_line_positions'
]
current_time
=
datetime
.
now
()
.
strftime
(
"
%
Y-
%
m-
%
d-
%
H:
%
M:
%
S.
%
f"
)[:
-
3
]
# # 调试信息:液位位置数据
# print(f" [存储线程-{channel_id}] 液位位置数据:")
# for area_idx, position_data in liquid_positions.items():
# print(f" 目标{area_idx}:")
# print(f" - position_data键: {position_data.keys()}")
# print(f" - 包含'height_cm'? {'height_cm' in position_data}")
# print(f" - 包含'height_mm'? {'height_mm' in position_data}")
# if 'height_cm' in position_data:
# print(f" - height_cm值: {position_data['height_cm']}")
# if 'height_mm' in position_data:
# print(f" - height_mm值: {position_data['height_mm']}")
# 为每个检测区域写入数据
for
area_idx
,
position_data
in
liquid_positions
.
items
():
if
area_idx
in
csv_writers
:
# 统一使用mm单位,直接读取height_mm
height_mm
=
position_data
.
get
(
'height_mm'
,
0.0
)
# # 调试信息:读取到的高度值
# print(f" [存储线程-{channel_id}] 目标{area_idx} 读取高度:")
# print(f" - height_mm: {height_mm:.2f}mm")
# 存储mm值(保留1位小数,精度0.1mm)
height_decimal
=
round
(
height_mm
,
1
)
# print(f" - 最终写入CSV: {height_decimal}mm")
# 写入格式:时间戳 高度值(mm, 保留1位小数)
csv_writers
[
area_idx
]
.
write
(
f
"{current_time} {height_decimal:.1f}
\n
"
)
# 不再立即刷新,改为批量刷新
...
...
@@ -154,22 +112,20 @@ class StorageThread:
data_count
+=
1
# 定时批量刷新到磁盘(每0.5秒一次)
# 定时批量刷新到磁盘(每0.5秒一次)
current_time_check
=
time
.
time
()
if
current_time_check
-
last_flush_time
>=
flush_interval
:
for
csv_file
in
csv_writers
.
values
():
csv_file
.
flush
()
last_flush_time
=
current_time_check
# 每5秒打印一次统计
if
time
.
time
()
-
last_log_time
>
5.0
:
# print(f" [存储线程-{channel_id}] 已保存 {data_count} 条数据,队列大小: {context.storage_data.qsize()}")
last_log_time
=
time
.
time
()
except
queue
.
Empty
:
# 队列为空,继续等待
pass
except
Exception
as
e
:
# print(f" [存储线程-{channel_id}] CSV写入错误: {e}")
import
traceback
traceback
.
print_exc
()
...
...
@@ -180,21 +136,15 @@ class StorageThread:
# 队列的 get(timeout=0.1) 已经提供了适当的等待
except
Exception
as
e
:
# print(f" [存储线程-{channel_id}] 错误: {e}")
time
.
sleep
(
0.1
)
# 清理资源
# print(f" [存储线程-{channel_id}] 正在停止...")
# 关闭CSV文件
for
csv_file
in
csv_files
.
values
():
csv_file
.
close
()
# TODO: 未来需要释放视频写入器
# if display_writer:
# display_writer.release()
# print(f" [存储线程-{channel_id}] 已停止")
# TODO: 未来需要释放视频写入器
@staticmethod
def
_load_channel_config
(
channel_id
:
str
):
...
...
handlers/videopage/thread_manager/threads/test_model_loading.py
deleted
100644 → 0
View file @
dde5f108
# -*- coding: utf-8 -*-
"""
模型加载诊断脚本
用于测试检测线程的模型配置加载是否正常
"""
import
os
import
sys
import
yaml
# 添加项目根目录到路径
current_dir
=
os
.
path
.
dirname
(
os
.
path
.
abspath
(
__file__
))
project_root
=
os
.
path
.
dirname
(
os
.
path
.
dirname
(
os
.
path
.
dirname
(
os
.
path
.
dirname
(
current_dir
))))
sys
.
path
.
insert
(
0
,
project_root
)
from
detection_thread
import
DetectionThread
def
test_model_config_loading
():
"""测试模型配置加载"""
print
(
"="
*
80
)
print
(
"模型配置加载测试"
)
print
(
"="
*
80
)
# 测试各个通道
for
channel_id
in
[
'channel1'
,
'channel2'
,
'channel3'
,
'channel4'
]:
print
(
f
"
\n
{'='*80}"
)
print
(
f
"测试 {channel_id}"
)
print
(
'='
*
80
)
# 加载模型配置
model_config
=
DetectionThread
.
_load_model_config
(
channel_id
)
if
model_config
:
print
(
f
"[OK] [{channel_id}] 模型配置加载成功"
)
print
(
f
" 配置内容:"
)
for
key
,
value
in
model_config
.
items
():
if
key
==
'model_path'
:
print
(
f
" - {key}: {value}"
)
# 检查文件是否存在
if
os
.
path
.
exists
(
value
):
file_size
=
os
.
path
.
getsize
(
value
)
/
(
1024
*
1024
)
# MB
print
(
f
" [OK] 文件存在 ({file_size:.2f} MB)"
)
else
:
print
(
f
" [ERROR] 文件不存在!"
)
else
:
print
(
f
" - {key}: {value}"
)
else
:
print
(
f
"[ERROR] [{channel_id}] 模型配置加载失败"
)
# 加载标注配置
print
(
f
"
\n
尝试加载 {channel_id} 的标注配置..."
)
annotation_config
=
DetectionThread
.
_load_annotation_config
(
channel_id
)
if
annotation_config
:
print
(
f
"[OK] [{channel_id}] 标注配置加载成功"
)
boxes
=
annotation_config
.
get
(
'boxes'
,
[])
print
(
f
" - 检测区域数: {len(boxes)}"
)
print
(
f
" - 实际高度: {annotation_config.get('actual_heights', [])}"
)
else
:
print
(
f
"[ERROR] [{channel_id}] 标注配置加载失败"
)
def
test_default_config_structure
():
"""测试 default_config.yaml 的结构"""
print
(
"
\n
"
+
"="
*
80
)
print
(
"检查 default_config.yaml 配置结构"
)
print
(
"="
*
80
)
config_file
=
os
.
path
.
join
(
project_root
,
'database'
,
'config'
,
'default_config.yaml'
)
if
not
os
.
path
.
exists
(
config_file
):
print
(
f
" 配置文件不存在: {config_file}"
)
return
print
(
f
" 配置文件存在: {config_file}
\n
"
)
with
open
(
config_file
,
'r'
,
encoding
=
'utf-8'
)
as
f
:
config
=
yaml
.
safe_load
(
f
)
# 检查各通道的模型路径配置
print
(
"检查各通道模型路径配置:"
)
for
i
in
range
(
1
,
5
):
channel_id
=
f
'channel{i}'
model_path_key
=
f
'{channel_id}_model_path'
if
model_path_key
in
config
:
model_path
=
config
[
model_path_key
]
print
(
f
" {model_path_key}: {model_path}"
)
# 转换为绝对路径
if
not
os
.
path
.
isabs
(
model_path
):
model_path
=
model_path
.
replace
(
'/'
,
os
.
sep
)
.
replace
(
'
\\
'
,
os
.
sep
)
full_path
=
os
.
path
.
join
(
project_root
,
model_path
)
full_path
=
os
.
path
.
normpath
(
full_path
)
else
:
full_path
=
model_path
# 检查文件是否存在
if
os
.
path
.
exists
(
full_path
):
file_size
=
os
.
path
.
getsize
(
full_path
)
/
(
1024
*
1024
)
# MB
print
(
f
" 文件存在: {full_path} ({file_size:.2f} MB)"
)
else
:
print
(
f
" 文件不存在: {full_path}"
)
else
:
print
(
f
" {model_path_key}: 未配置"
)
# 检查全局模型配置
print
(
"
\n
检查全局模型配置:"
)
if
'model'
in
config
:
model_config
=
config
[
'model'
]
for
key
,
value
in
model_config
.
items
():
print
(
f
" - {key}: {value}"
)
else
:
print
(
" 未找到全局 model 配置"
)
# 检查GPU配置
print
(
"
\n
检查GPU配置:"
)
print
(
f
" - gpu_enabled: {config.get('gpu_enabled', '未配置')}"
)
print
(
f
" - default_device: {config.get('default_device', '未配置')}"
)
print
(
f
" - batch_processing_enabled: {config.get('batch_processing_enabled', '未配置')}"
)
print
(
f
" - default_batch_size: {config.get('default_batch_size', '未配置')}"
)
if
__name__
==
'__main__'
:
print
(
f
"项目根目录: {project_root}
\n
"
)
test_default_config_structure
()
print
(
"
\n
"
)
test_model_config_loading
()
print
(
"
\n
"
+
"="
*
80
)
print
(
"测试完成"
)
print
(
"="
*
80
)
widgets/datasetpage/annotationtool.py
View file @
872a1442
...
...
@@ -43,7 +43,6 @@ except ImportError:
try
:
from
datasetpage
import
AnnotationHandler
except
ImportError
as
e
:
print
(
f
"Warning: AnnotationHandler not available: {e}"
)
AnnotationHandler
=
None
# 导入labelme
...
...
@@ -57,7 +56,6 @@ try:
from
labelme.config
import
get_config
as
labelme_get_config
# 重命名避免冲突
LABELME_AVAILABLE
=
True
except
ImportError
as
e
:
print
(
f
"Warning: Labelme not available: {e}"
)
LABELME_AVAILABLE
=
False
LabelmeMainWindow
=
None
labelme_get_config
=
None
...
...
@@ -298,7 +296,6 @@ class AnnotationTool(QtWidgets.QWidget):
def
_initLabelme
(
self
):
"""初始化并嵌入Labelme标注工具"""
if
not
LABELME_AVAILABLE
:
print
(
"Labelme未安装,无法初始化标注工具"
)
return
try
:
...
...
@@ -382,8 +379,6 @@ class AnnotationTool(QtWidgets.QWidget):
}
"""
)
print
(
"[AnnotationTool] 已优化形状列表显示"
)
# 优化唯一标签列表的显示
if
hasattr
(
self
.
labelme_widget
,
'uniqLabelList'
):
uniq_list
=
self
.
labelme_widget
.
uniqLabelList
...
...
@@ -413,8 +408,6 @@ class AnnotationTool(QtWidgets.QWidget):
}
"""
)
print
(
"[AnnotationTool] 已优化标签列表显示"
)
# 调整dock面板的大小 - 这是关键!
if
hasattr
(
self
.
labelme_widget
,
'shape_dock'
):
shape_dock
=
self
.
labelme_widget
.
shape_dock
...
...
@@ -426,16 +419,13 @@ class AnnotationTool(QtWidgets.QWidget):
if
current_width
<
260
:
shape_dock
.
resize
(
260
,
shape_dock
.
height
())
print
(
f
"[AnnotationTool] 已调整形状面板宽度: {shape_dock.width()}px"
)
# 也调整标签dock面板(如果显示的话)
if
hasattr
(
self
.
labelme_widget
,
'label_dock'
):
label_dock
=
self
.
labelme_widget
.
label_dock
label_dock
.
setMinimumWidth
(
scale_w
(
220
))
# 🔥 响应式宽度
print
(
f
"[AnnotationTool] 已调整标签面板宽度"
)
except
Exception
as
e
:
p
rint
(
f
"[AnnotationTool] 优化标签显示失败: {e}"
)
p
ass
def
_connectLabelmeActions
(
self
):
"""连接工具栏按钮到labelme的功能"""
...
...
@@ -458,7 +448,7 @@ class AnnotationTool(QtWidgets.QWidget):
self
.
labelme_widget
.
adjustScale
)
except
Exception
as
e
:
p
rint
(
f
"连接labelme操作失败: {e}"
)
p
ass
def
_connectSignals
(
self
):
"""连接UI信号和槽"""
...
...
@@ -521,8 +511,6 @@ class AnnotationTool(QtWidgets.QWidget):
if
not
dir_path
or
not
osp
.
exists
(
dir_path
):
return
print
(
f
"检测到labelme打开目录: {dir_path}"
)
# 使用handler加载目录
if
self
.
annotation_handler
:
self
.
annotation_handler
.
setDirectory
(
dir_path
)
...
...
@@ -708,15 +696,12 @@ class AnnotationTool(QtWidgets.QWidget):
json_path: JSON标注文件路径(可选)
"""
if
self
.
labelme_widget
is
None
:
print
(
"Labelme未初始化"
)
return
try
:
# 使用labelme的loadFile方法加载图片
self
.
labelme_widget
.
loadFile
(
image_path
)
print
(
f
"已加载图片: {image_path}"
)
except
Exception
as
e
:
print
(
f
"加载图片失败: {e}"
)
import
traceback
traceback
.
print_exc
()
...
...
@@ -796,45 +781,6 @@ if __name__ == "__main__":
annotation_tool
.
lbl_total_stats
.
setText
(
f
"总数: {total}"
)
annotation_tool
.
lbl_annotated_stats
.
setText
(
f
"已标注: {annotated}"
)
# 打印测试说明
print
(
"
\n
"
+
"="
*
70
)
print
(
" AnnotationTool 数据标注工具组件测试程序"
)
print
(
"="
*
70
)
print
(
" 界面布局(两栏):"
)
print
(
"
\n
左侧面板 - 标注数据列表:"
)
print
(
" - 搜索框:支持按图片名称搜索"
)
print
(
" - 筛选选项:全部/已标注/未标注/待审核"
)
print
(
" - 标注列表:显示图片缩略图和基本信息"
)
print
(
" - 操作按钮:编辑、删除、导出"
)
print
(
" - 底部统计:总数、已标注数量"
)
print
(
"
\n
右侧面板 - Labelme标注工具区域:"
)
print
(
" - Labelme已完整嵌入到右侧面板"
)
print
(
" - 工具栏:放大、缩小、适应窗口(已连接到labelme功能)"
)
print
(
" - 包含labelme的全部标注功能:"
)
print
(
" * 多边形/矩形标注"
)
print
(
" * 标签管理"
)
print
(
" * 文件列表"
)
print
(
" * 标注列表"
)
print
(
"
\n
布局特点:"
)
print
(
" - 使用 QSplitter 实现可调整宽度的两栏布局"
)
print
(
" - 默认比例: 左:右 = 1:3"
)
print
(
" - 可以拖拽分割线调整各面板宽度"
)
print
(
"
\n
Labelme集成完成:"
)
print
(
" - self.labelme_widget: 嵌入的labelme主窗口实例"
)
print
(
" - self.labelme_container: labelme的容器"
)
print
(
" - self.labelme_layout: 容器的布局管理器"
)
print
(
" - loadImageForAnnotation(): 加载图片到labelme进行标注"
)
print
(
"
\n
使用方法:"
)
print
(
" - 左侧列表选择图片"
)
print
(
" - 调用 loadImageForAnnotation(image_path) 加载到labelme"
)
print
(
" - 在右侧labelme面板中进行标注"
)
print
(
" - Labelme保持独立性,所有原生功能完整保留"
)
print
(
"
\n
说明:"
)
print
(
" - Labelme已完整嵌入,功能独立完整"
)
print
(
" - 左侧面板按钮功能可后续完善"
)
print
(
" - 界面风格统一,适配现有系统"
)
print
(
"="
*
70
+
"
\n
"
)
window
.
show
()
sys
.
exit
(
app
.
exec_
())
widgets/datasetpage/crop_config_dialog.py
View file @
872a1442
...
...
@@ -341,7 +341,7 @@ class CropConfigDialog(QtWidgets.QDialog):
self
.
format_combo
.
setCurrentText
(
saved_format
)
except
Exception
as
e
:
p
rint
(
f
" 加载设置失败: {e}"
)
p
ass
def
_saveSettings
(
self
):
"""保存当前设置"""
...
...
@@ -352,7 +352,7 @@ class CropConfigDialog(QtWidgets.QDialog):
settings
.
setValue
(
"file_prefix"
,
self
.
prefix_edit
.
text
())
settings
.
setValue
(
"image_format"
,
self
.
format_combo
.
currentText
())
except
Exception
as
e
:
p
rint
(
f
" 保存设置失败: {e}"
)
p
ass
def
_onBrowse
(
self
):
"""浏览文件夹"""
...
...
@@ -441,8 +441,6 @@ class CropConfigDialog(QtWidgets.QDialog):
self
.
format_combo
.
setCurrentText
(
config
[
'image_format'
])
# ==================== 独立运行测试 ====================
if
__name__
==
"__main__"
:
"""独立运行测试"""
import
sys
...
...
@@ -455,54 +453,7 @@ if __name__ == "__main__":
default_frequency
=
10
)
# 打印测试说明
print
(
"
\n
"
+
"="
*
70
)
print
(
" 裁剪配置对话框测试程序"
)
print
(
"="
*
70
)
print
(
" 功能列表:"
)
print
(
"
\n
保存路径设置:"
)
print
(
" - 文本框显示当前路径"
)
print
(
" - 浏览按钮打开文件夹选择对话框"
)
print
(
" - 自动验证路径有效性"
)
print
(
"
\n
裁剪频率设置:"
)
print
(
" - SpinBox 设置裁剪间隔(1-1000帧)"
)
print
(
" - 快捷按钮:每帧、每5帧、每10帧、每30帧、每60帧"
)
print
(
" - 实时显示频率说明"
)
print
(
"
\n
文件命名设置:"
)
print
(
" - 设置文件名前缀"
)
print
(
" - 选择图片格式(jpg/png/bmp/tiff)"
)
print
(
" - 显示命名示例"
)
print
(
"
\n
设置持久化:"
)
print
(
" - 使用 QSettings 自动保存设置"
)
print
(
" - 下次打开自动恢复上次配置"
)
print
(
"
\n
界面特点:"
)
print
(
" - 分组布局,层次清晰"
)
print
(
" - 图标和emoji装饰"
)
print
(
" - 现代化的配色和圆角设计"
)
print
(
" - 响应式布局"
)
print
(
"="
*
70
+
"
\n
"
)
# 显示对话框
mission_result
=
dialog
.
exec_
()
# 打印结果
if
mission_result
==
QtWidgets
.
QDialog
.
Accepted
:
config
=
dialog
.
getConfig
()
print
(
"
\n
用户点击了【确定】按钮"
)
print
(
" 配置信息:"
)
print
(
f
" 保存路径: {config['save_liquid_data_path']}"
)
print
(
f
" 裁剪频率: 每 {config['crop_frequency']} 帧"
)
print
(
f
" 文件前缀: {config['file_prefix']}"
)
print
(
f
" 图片格式: {config['image_format']}"
)
print
(
f
"
\n
文件命名示例:"
)
print
(
f
" {config['file_prefix']}_0001.{config['image_format']}"
)
print
(
f
" {config['file_prefix']}_0002.{config['image_format']}"
)
print
(
f
" {config['file_prefix']}_0003.{config['image_format']}"
)
print
(
" ..."
)
else
:
print
(
"
\n
用户点击了【取消】按钮"
)
print
(
"
\n
"
+
"="
*
70
+
"
\n
"
)
sys
.
exit
(
0
)
widgets/datasetpage/crop_preview_panel.py
View file @
872a1442
...
...
@@ -252,10 +252,6 @@ class CropPreviewPanel(QtWidgets.QWidget):
auto_refresh: 是否自动刷新图片列表(默认True)
video_name: 视频名称,如果提供则只显示该视频的区域文件夹
"""
print
(
f
"[CropPreview] ========== 设置保存路径 =========="
)
print
(
f
"[CropPreview] 保存路径: {save_liquid_data_path}"
)
print
(
f
"[CropPreview] 视频名称: {video_name}"
)
print
(
f
"[CropPreview] 自动刷新: {auto_refresh}"
)
self
.
_save_liquid_data_path
=
save_liquid_data_path
self
.
_video_name
=
video_name
# 保存视频名称
...
...
@@ -284,15 +280,11 @@ class CropPreviewPanel(QtWidgets.QWidget):
if
auto_refresh
:
self
.
refreshImages
()
print
(
f
"[CropPreview] 找到 {len([p for p in self._region_paths if p])} 个区域文件夹"
)
print
(
f
"[CropPreview] ========== 设置完成 =========="
)
def
_findRegionPaths
(
self
):
"""查找所有区域文件夹(支持新旧命名格式,可根据视频名称过滤)"""
self
.
_region_paths
=
[]
if
not
self
.
_save_liquid_data_path
or
not
osp
.
exists
(
self
.
_save_liquid_data_path
):
print
(
f
"[CropPreview] 保存路径无效或不存在"
)
return
try
:
...
...
@@ -300,10 +292,6 @@ class CropPreviewPanel(QtWidgets.QWidget):
subdirs
=
[
d
for
d
in
os
.
listdir
(
self
.
_save_liquid_data_path
)
if
osp
.
isdir
(
osp
.
join
(
self
.
_save_liquid_data_path
,
d
))]
print
(
f
"[CropPreview] 扫描目录: {self._save_liquid_data_path}"
)
print
(
f
"[CropPreview] 发现子文件夹: {subdirs}"
)
print
(
f
"[CropPreview] 当前视频名称过滤: {self._video_name}"
)
# 筛选出区域文件夹
region_folders
=
[]
for
subdir
in
subdirs
:
...
...
@@ -312,22 +300,14 @@ class CropPreviewPanel(QtWidgets.QWidget):
# 匹配格式:视频名_区域X
if
subdir
.
startswith
(
f
"{self._video_name}_"
)
and
'区域'
in
subdir
:
region_folders
.
append
(
subdir
)
print
(
f
"[CropPreview] 匹配视频文件夹: {subdir}"
)
else
:
print
(
f
"[CropPreview] 跳过文件夹(不匹配当前视频): {subdir}"
)
else
:
# 未指定视频名称时,查找所有区域文件夹
if
'区域'
in
subdir
or
'region'
in
subdir
.
lower
():
region_folders
.
append
(
subdir
)
print
(
f
"[CropPreview] 匹配区域文件夹: {subdir}"
)
# 按文件夹名排序
region_folders
.
sort
()
if
self
.
_video_name
:
print
(
f
"[CropPreview] ==> 只显示视频 '{self._video_name}' 的区域文件夹"
)
print
(
f
"[CropPreview] ==> 最终找到 {len(region_folders)} 个区域文件夹: {region_folders}"
)
# 初始化所有区域为None
for
i
in
range
(
3
):
self
.
_region_paths
.
append
(
None
)
...
...
@@ -345,14 +325,8 @@ class CropPreviewPanel(QtWidgets.QWidget):
if
0
<=
region_index
<
3
:
region_path
=
osp
.
join
(
self
.
_save_liquid_data_path
,
folder
)
self
.
_region_paths
[
region_index
]
=
region_path
print
(
f
"[CropPreview] 区域{region_num} -> {folder} (索引{region_index})"
)
else
:
print
(
f
"[CropPreview] 跳过无效区域编号: {folder}"
)
else
:
print
(
f
"[CropPreview] 无法解析区域编号: {folder}"
)
except
Exception
as
e
:
print
(
f
"[CropPreview] 查找区域文件夹失败: {e}"
)
import
traceback
traceback
.
print_exc
()
# 降级到旧的查找方式
...
...
@@ -360,7 +334,6 @@ class CropPreviewPanel(QtWidgets.QWidget):
region_path
=
osp
.
join
(
self
.
_save_liquid_data_path
,
f
"region{i+1}"
)
if
osp
.
exists
(
region_path
):
self
.
_region_paths
.
append
(
region_path
)
print
(
f
"[CropPreview] (旧格式) 找到区域文件夹: region{i+1}"
)
else
:
self
.
_region_paths
.
append
(
None
)
...
...
@@ -383,8 +356,6 @@ class CropPreviewPanel(QtWidgets.QWidget):
# 更新统计
self
.
_updateStats
()
print
(
f
"[CropPreview] 刷新完成"
)
def
_loadRegionImages
(
self
,
region_index
,
region_path
):
"""
加载指定区域的图片
...
...
@@ -428,7 +399,6 @@ class CropPreviewPanel(QtWidgets.QWidget):
font
=
placeholder_item
.
font
()
font
.
setPointSize
(
10
)
placeholder_item
.
setFont
(
font
)
print
(
f
"[CropPreview] 区域{region_index+1} 等待图片生成..."
)
return
# 添加到网格(只显示最新的100张,避免加载过多)
...
...
@@ -458,10 +428,8 @@ class CropPreviewPanel(QtWidgets.QWidget):
# 设置文本居中对齐
item
.
setTextAlignment
(
Qt
.
AlignCenter
)
print
(
f
"[CropPreview] 区域{region_index+1} 加载 {len(files)} 张图片 (显示最新{len(display_files)}张)"
)
except
Exception
as
e
:
p
rint
(
f
"[CropPreview] 加载区域{region_index+1}图片失败: {e}"
)
p
ass
def
_generateThumbnail
(
self
,
image_path
):
"""
...
...
@@ -490,7 +458,6 @@ class CropPreviewPanel(QtWidgets.QWidget):
return
scaled_pixmap
except
Exception
as
e
:
print
(
f
"[CropPreview] 生成缩略图失败: {e}"
)
return
None
def
clearImages
(
self
):
...
...
@@ -523,8 +490,6 @@ class CropPreviewPanel(QtWidgets.QWidget):
# 更新统计
self
.
_updateStats
()
print
(
f
"[CropPreview] 清空图片列表,准备新任务"
)
def
addImage
(
self
,
region_index
,
image_path
):
"""
添加单张图片到指定区域(用于实时更新)
...
...
@@ -597,7 +562,6 @@ class CropPreviewPanel(QtWidgets.QWidget):
self
.
_current_region
=
index
self
.
_updateStats
()
self
.
regionChanged
.
emit
(
index
)
print
(
f
"[CropPreview] 切换到区域 {index+1}"
)
def
_onImageDoubleClicked
(
self
,
item
):
"""图片被双击 - 显示大图"""
...
...
@@ -710,14 +674,11 @@ class CropPreviewPanel(QtWidgets.QWidget):
# 更新统计
self
.
_updateStats
()
print
(
f
"[CropPreview] 已删除区域 {current_region + 1} 的文件"
)
except
Exception
as
e
:
if
DialogManager
:
DialogManager
.
show_critical
(
self
,
"错误"
,
f
"删除失败: {str(e)}"
)
else
:
QtWidgets
.
QMessageBox
.
critical
(
self
,
"错误"
,
f
"删除失败: {str(e)}"
)
print
(
f
"[CropPreview] 删除区域文件失败: {e}"
)
import
traceback
traceback
.
print_exc
()
...
...
@@ -799,7 +760,6 @@ class ImageViewDialog(QtWidgets.QDialog):
except
Exception
as
e
:
self
.
image_label
.
setText
(
f
"加载失败: {e}"
)
print
(
f
"[ImageViewDialog] 加载图片失败: {e}"
)
if
__name__
==
"__main__"
:
...
...
widgets/datasetpage/datacollection_panel.py
View file @
872a1442
...
...
@@ -101,12 +101,9 @@ class DataCollectionPanel(QtWidgets.QWidget):
def
_showWarning
(
self
,
title
,
message
):
"""显示警告对话框"""
print
(
f
"[DEBUG] _showWarning 被调用, DialogManager={'存在' if DialogManager else '不存在'}"
)
if
DialogManager
:
print
(
f
"[DEBUG] 使用 DialogManager.show_warning"
)
DialogManager
.
show_warning
(
self
,
title
,
message
)
else
:
print
(
f
"[DEBUG] 使用原生 QMessageBox.warning"
)
QtWidgets
.
QMessageBox
.
warning
(
self
,
title
,
message
)
def
_showInformation
(
self
,
title
,
message
):
...
...
@@ -261,10 +258,10 @@ class DataCollectionPanel(QtWidgets.QWidget):
from
..responsive_layout
import
scale_spacing
channel_select_layout
.
setSpacing
(
scale_spacing
(
5
))
channel_label
=
QtWidgets
.
QLabel
(
"选择通道:"
)
channel_label
.
setFixedWidth
(
scale_w
(
75
))
#
🔥
响应式宽度
channel_label
.
setFixedWidth
(
scale_w
(
75
))
# 响应式宽度
self
.
channel_combo
=
QtWidgets
.
QComboBox
()
self
.
channel_combo
.
setFixedWidth
(
scale_w
(
90
))
#
🔥
响应式宽度
self
.
channel_combo
.
setFixedWidth
(
scale_w
(
90
))
# 响应式宽度
# 向右移动5px
from
..responsive_layout
import
scale_margin
margin
=
scale_margin
(
5
)
...
...
@@ -389,7 +386,7 @@ class DataCollectionPanel(QtWidgets.QWidget):
# 右侧:通道预览区域 - 响应式布局
self
.
channel_preview
=
QtWidgets
.
QLabel
()
self
.
channel_preview
.
setFixedSize
(
scale_w
(
640
),
scale_h
(
480
))
#
🔥
响应式尺寸
self
.
channel_preview
.
setFixedSize
(
scale_w
(
640
),
scale_h
(
480
))
# 响应式尺寸
self
.
channel_preview
.
setAlignment
(
QtCore
.
Qt
.
AlignCenter
)
self
.
channel_preview
.
setStyleSheet
(
"""
QLabel {
...
...
@@ -455,10 +452,8 @@ class DataCollectionPanel(QtWidgets.QWidget):
if
osp
.
exists
(
path
)
and
osp
.
isdir
(
path
):
self
.
_root_path
=
path
self
.
_loadFolders
()
pass
return
True
else
:
pass
return
False
def
getRootPath
(
self
):
...
...
@@ -719,7 +714,7 @@ class DataCollectionPanel(QtWidgets.QWidget):
return
# 检查是否为视频文件
video_extensions
=
[
'.mp4'
,
'.avi'
,
'.mov'
,
'.mkv'
,
'.
wmv'
,
'.flv'
,
'.webm'
,
'.m4v
'
]
video_extensions
=
[
'.mp4'
,
'.avi'
,
'.mov'
,
'.mkv'
,
'.
flv'
,
'.wmv'
,
'.mpg'
,
'.mpeg
'
]
file_ext
=
osp
.
splitext
(
file_name
)[
1
]
.
lower
()
if
file_ext
in
video_extensions
:
...
...
@@ -902,56 +897,38 @@ class DataCollectionPanel(QtWidgets.QWidget):
def
_onDeleteFile
(
self
,
item
):
"""删除文件"""
print
(
"
\n
"
+
"="
*
60
)
print
(
"[调试] _onDeleteFile 方法被调用"
)
print
(
"="
*
60
)
if
not
item
:
print
(
"[调试] item 为空,返回"
)
return
# 获取文件路径
file_path
=
item
.
data
(
Qt
.
UserRole
)
print
(
f
"[调试] 文件路径: {file_path}"
)
if
not
file_path
or
not
osp
.
exists
(
file_path
):
print
(
"[调试] 文件不存在,显示警告"
)
self
.
_showWarning
(
"警告"
,
"文件不存在"
)
return
file_name
=
osp
.
basename
(
file_path
)
print
(
f
"[调试] 文件名: {file_name}"
)
# 确认删除 - 使用警告图标的统一对话框
print
(
"[调试] 显示确认删除对话框(使用 _showQuestionWarning)"
)
# 确认删除
if
self
.
_showQuestionWarning
(
"确认删除"
,
f
"确定要删除文件 '{file_name}' 吗?
\n\n
"
f
"文件将被移动到回收站"
f
"确定要删除文件 '{file_name}' 吗?
\n\n
文件将被移动到回收站"
):
print
(
"[调试] 用户确认删除"
)
try
:
# 使用Windows回收站删除
if
delete_file_to_recycle_bin
:
delete_file_to_recycle_bin
(
file_path
)
print
(
f
"[调试] 文件已移动到回收站: {file_path}"
)
else
:
raise
ImportError
(
"回收站工具未导入"
)
# 刷新内容列表
if
self
.
_current_folder
:
self
.
_loadFolderContent
(
self
.
_current_folder
)
print
(
" [调试] 已刷新文件夹内容"
)
print
(
" [调试] 显示成功提示(使用 _showInformation)"
)
self
.
_showInformation
(
"成功"
,
f
"文件已删除
\n
{file_name}"
)
except
Exception
as
e
:
print
(
f
"[调试] 删除文件失败: {e}"
)
print
(
"[调试] 显示错误提示(使用 _showCritical)"
)
self
.
_showCritical
(
"错误"
,
f
"删除文件失败:
\n
{str(e)}"
)
else
:
p
rint
(
"[调试] 用户取消删除"
)
p
ass
def
_onAddFolder
(
self
):
"""新增文件夹"""
...
...
@@ -1020,8 +997,6 @@ class DataCollectionPanel(QtWidgets.QWidget):
# 发射信号
self
.
folderAdded
.
emit
(
folder_path
)
except
Exception
as
e
:
self
.
_showCritical
(
"错误"
,
f
"创建文件夹失败: {e}"
)
...
...
@@ -1050,7 +1025,7 @@ class DataCollectionPanel(QtWidgets.QWidget):
elif
ext
in
[
'.jpg'
,
'.jpeg'
,
'.png'
,
'.bmp'
,
'.gif'
,
'.tiff'
]:
image_count
+=
1
except
Exception
as
e
:
p
rint
(
f
"[WARNING] 统计文件夹内容失败: {e}"
)
p
ass
# 构建文件信息提示
file_info
=
[]
...
...
@@ -1065,7 +1040,7 @@ class DataCollectionPanel(QtWidgets.QWidget):
else
:
message
=
f
"确定要删除文件夹 '{folder_name}' 吗?
\n\n
文件夹为空
\n\n
将被移动到回收站"
# 确认删除
- 使用警告图标的统一对话框
# 确认删除
if
self
.
_showQuestionWarning
(
"确认删除"
,
message
):
try
:
# 使用Windows回收站删除
...
...
@@ -1087,8 +1062,6 @@ class DataCollectionPanel(QtWidgets.QWidget):
# 发射信号
self
.
folderDeleted
.
emit
(
folder_path
)
except
Exception
as
e
:
self
.
_showCritical
(
"错误"
,
f
"删除文件夹失败: {e}"
)
...
...
@@ -1346,7 +1319,6 @@ def _loadRTSPConfig(self):
config_path
=
os
.
path
.
join
(
project_root
,
'database'
,
'config'
,
'default_config.yaml'
)
if
not
os
.
path
.
exists
(
config_path
):
print
(
f
"[WARNING] 配置文件不存在: {config_path}"
)
return
None
# 读取 YAML 配置文件
...
...
@@ -1373,11 +1345,9 @@ def _loadRTSPConfig(self):
}
if
channels
else
None
except
Exception
as
e
:
print
(
f
"[WARNING] 无法获取项目根目录或读取配置: {e}"
)
return
None
except
Exception
as
e
:
print
(
f
"[ERROR] 加载RTSP配置失败: {e}"
)
return
None
# 获取选择的通道方法
...
...
widgets/datasetpage/datapreprocess_panel.py
View file @
872a1442
...
...
@@ -596,7 +596,7 @@ class DataPreprocessPanel(QtWidgets.QWidget):
if
saved_format
:
self
.
crop_format_combo
.
setCurrentText
(
saved_format
)
except
Exception
as
e
:
p
rint
(
f
"加载裁剪配置失败: {e}"
)
p
ass
def
_saveCropConfig
(
self
):
"""保存裁剪配置"""
...
...
@@ -607,7 +607,7 @@ class DataPreprocessPanel(QtWidgets.QWidget):
settings
.
setValue
(
"prefix"
,
self
.
crop_prefix_edit
.
text
())
settings
.
setValue
(
"format"
,
self
.
crop_format_combo
.
currentText
())
except
Exception
as
e
:
p
rint
(
f
"保存裁剪配置失败: {e}"
)
p
ass
def
getCropConfig
(
self
):
"""获取裁剪配置"""
...
...
@@ -718,7 +718,6 @@ class DataPreprocessPanel(QtWidgets.QWidget):
self
.
folder_list
.
clear
()
if
not
osp
.
exists
(
self
.
_root_path
):
print
(
f
" 根目录不存在: {self._root_path}"
)
return
# 获取所有子文件夹
...
...
@@ -747,26 +746,19 @@ class DataPreprocessPanel(QtWidgets.QWidget):
def
_loadVideos
(
self
,
folder_name
):
"""加载视频网格"""
print
(
"
\n
"
+
"="
*
50
)
print
(
f
" 开始加载视频: {folder_name}"
)
self
.
video_grid
.
clear
()
folder_path
=
osp
.
join
(
self
.
_root_path
,
folder_name
)
print
(
f
" 完整路径: {folder_path}"
)
if
not
osp
.
exists
(
folder_path
):
print
(
f
" 文件夹不存在: {folder_path}"
)
return
try
:
# 支持的视频格式
video_extensions
=
[
'.mp4'
,
'.avi'
,
'.mov'
,
'.mkv'
,
'.flv'
,
'.wmv'
,
'.mpg'
,
'.mpeg'
]
print
(
f
" 支持的格式: {', '.join(video_extensions)}"
)
# 获取所有文件
all_files
=
os
.
listdir
(
folder_path
)
print
(
f
" 文件夹中共有 {len(all_files)} 个项目"
)
# 获取所有视频文件
files
=
[
f
for
f
in
all_files
...
...
@@ -774,10 +766,6 @@ class DataPreprocessPanel(QtWidgets.QWidget):
osp
.
splitext
(
f
)[
1
]
.
lower
()
in
video_extensions
]
files
.
sort
()
print
(
f
" 找到 {len(files)} 个视频文件:"
)
for
f
in
files
:
print
(
f
" - {f}"
)
# 添加到网格
for
file
in
files
:
item
=
QtWidgets
.
QListWidgetItem
(
self
.
video_grid
)
...
...
@@ -797,12 +785,10 @@ class DataPreprocessPanel(QtWidgets.QWidget):
thumbnail
=
self
.
_generateVideoThumbnail
(
file_path
)
if
thumbnail
:
item
.
setIcon
(
QtGui
.
QIcon
(
thumbnail
))
print
(
f
" 生成缩略图: {file}"
)
else
:
# 如果缩略图生成失败,使用系统标准图标
icon
=
self
.
style
()
.
standardIcon
(
QtWidgets
.
QStyle
.
SP_MediaPlay
)
item
.
setIcon
(
icon
)
print
(
f
" 使用默认图标: {file}"
)
# 设置文本居中对齐
item
.
setTextAlignment
(
Qt
.
AlignCenter
)
...
...
@@ -811,17 +797,10 @@ class DataPreprocessPanel(QtWidgets.QWidget):
self
.
lbl_video_stats
.
setText
(
f
"视频数: {len(files)}"
)
if
len
(
files
)
==
0
:
print
(
" 该文件夹中没有视频文件!"
)
print
(
" 提示:请确保文件夹中有视频文件,并且格式正确"
)
else
:
pass
print
(
"="
*
50
+
"
\n
"
)
except
Exception
as
e
:
pass
import
traceback
traceback
.
print_exc
()
def
_generateVideoThumbnail
(
self
,
video_path
):
"""生成视频缩略图(提取第一帧)"""
...
...
@@ -832,7 +811,6 @@ class DataPreprocessPanel(QtWidgets.QWidget):
cap
=
cv2
.
VideoCapture
(
video_path
)
if
not
cap
.
isOpened
():
print
(
f
"[DataPreprocess] 无法打开视频: {video_path}"
)
return
None
# 读取第一帧
...
...
@@ -840,7 +818,6 @@ class DataPreprocessPanel(QtWidgets.QWidget):
cap
.
release
()
if
not
ret
or
frame
is
None
:
print
(
f
"[DataPreprocess] 无法读取视频帧: {video_path}"
)
return
None
# 转换颜色空间 BGR -> RGB
...
...
@@ -862,7 +839,6 @@ class DataPreprocessPanel(QtWidgets.QWidget):
return
scaled_pixmap
except
Exception
as
e
:
print
(
f
"[DataPreprocess] 生成视频缩略图失败: {e}"
)
return
None
# ========== 槽函数 ==========
...
...
@@ -887,8 +863,6 @@ class DataPreprocessPanel(QtWidgets.QWidget):
folder_path
=
osp
.
join
(
self
.
_root_path
,
folder_name
)
self
.
folderSelected
.
emit
(
folder_path
)
print
(
f
" 选中文件夹: {folder_name}"
)
def
_onFolderDoubleClicked
(
self
,
item
):
"""文件夹被双击"""
folder_name
=
item
.
data
(
Qt
.
UserRole
)
...
...
@@ -901,79 +875,53 @@ class DataPreprocessPanel(QtWidgets.QWidget):
elif
os
.
name
==
'posix'
:
# macOS, Linux
import
subprocess
subprocess
.
Popen
([
'open'
,
folder_path
])
print
(
f
" 打开文件夹: {folder_path}"
)
except
Exception
as
e
:
pass
def
_onAddFolder
(
self
):
"""新增文件夹"""
print
(
"
\n
"
+
"="
*
60
)
print
(
"[调试] _onAddFolder 方法被调用"
)
print
(
"="
*
60
)
# 创建自定义对话框(完全控制按钮和窗口标志)
dialog
=
QtWidgets
.
QDialog
(
self
)
print
(
f
"[调试] QDialog 对象已创建: {dialog}"
)
dialog
.
setWindowTitle
(
"新增文件夹"
)
print
(
f
"[调试] 窗口标题已设置: 新增文件夹"
)
# 移除帮助按钮(问号按钮)
old_flags
=
dialog
.
windowFlags
()
print
(
f
"[调试] 原始窗口标志: {old_flags}"
)
print
(
f
"[调试] WindowContextHelpButtonHint 值: {QtCore.Qt.WindowContextHelpButtonHint}"
)
new_flags
=
old_flags
&
~
QtCore
.
Qt
.
WindowContextHelpButtonHint
dialog
.
setWindowFlags
(
new_flags
)
print
(
f
"[调试] 新窗口标志已设置: {new_flags}"
)
print
(
f
"[调试] 标志是否改变: {old_flags != new_flags}"
)
# 创建布局
layout
=
QtWidgets
.
QVBoxLayout
()
print
(
f
"[调试] QVBoxLayout 已创建"
)
# 添加标签
label
=
QtWidgets
.
QLabel
(
"请输入文件夹名称:"
)
layout
.
addWidget
(
label
)
print
(
f
"[调试] 标签已添加"
)
# 添加输入框
input_edit
=
QtWidgets
.
QLineEdit
()
layout
.
addWidget
(
input_edit
)
print
(
f
"[调试] 输入框已添加"
)
# 创建按钮布局
button_layout
=
QtWidgets
.
QHBoxLayout
()
ok_btn
=
QtWidgets
.
QPushButton
(
"确定"
)
cancel_btn
=
QtWidgets
.
QPushButton
(
"取消"
)
print
(
f
"[调试] 按钮已创建 - 确定: {ok_btn.text()}, 取消: {cancel_btn.text()}"
)
button_layout
.
addStretch
()
button_layout
.
addWidget
(
ok_btn
)
button_layout
.
addWidget
(
cancel_btn
)
layout
.
addLayout
(
button_layout
)
print
(
f
"[调试] 按钮布局已添加"
)
dialog
.
setLayout
(
layout
)
print
(
f
"[调试] 主布局已设置到对话框"
)
# 连接按钮信号
ok_btn
.
clicked
.
connect
(
dialog
.
accept
)
cancel_btn
.
clicked
.
connect
(
dialog
.
reject
)
print
(
f
"[调试] 按钮信号已连接"
)
# 设置输入框焦点
input_edit
.
setFocus
()
print
(
f
"[调试] 输入框焦点已设置"
)
# 显示对话框
print
(
f
"[调试] 即将显示对话框..."
)
ok
=
dialog
.
exec_
()
print
(
f
"[调试] 对话框已关闭,返回值: {ok} (1=Accepted, 0=Rejected)"
)
text
=
input_edit
.
text
()
print
(
f
"[调试] 输入框文本: '{text}'"
)
if
ok
and
text
:
folder_path
=
osp
.
join
(
self
.
_root_path
,
text
)
...
...
@@ -1029,7 +977,7 @@ class DataPreprocessPanel(QtWidgets.QWidget):
elif
ext
in
[
'.jpg'
,
'.jpeg'
,
'.png'
,
'.bmp'
,
'.gif'
,
'.tiff'
]:
image_count
+=
1
except
Exception
as
e
:
p
rint
(
f
"[WARNING] 统计文件夹内容失败: {e}"
)
p
ass
# 构建文件信息提示
file_info
=
[]
...
...
@@ -1050,7 +998,6 @@ class DataPreprocessPanel(QtWidgets.QWidget):
# 使用Windows回收站删除
if
delete_folder_to_recycle_bin
:
delete_folder_to_recycle_bin
(
folder_path
)
print
(
f
"[OK] 文件夹已移动到回收站: {folder_path}"
)
else
:
raise
ImportError
(
"回收站工具未导入"
)
...
...
@@ -1074,7 +1021,6 @@ class DataPreprocessPanel(QtWidgets.QWidget):
except
Exception
as
e
:
self
.
_showCritical
(
"错误"
,
f
"删除文件夹失败: {e}"
)
print
(
f
"[ERROR] 删除文件夹失败: {e}"
)
def
_onFolderListContextMenu
(
self
,
position
):
"""显示文件夹列表的右键菜单"""
...
...
@@ -1130,26 +1076,21 @@ class DataPreprocessPanel(QtWidgets.QWidget):
def
_onVideoClicked
(
self
,
item
):
"""视频被点击"""
print
(
"="
*
50
)
print
(
" _onVideoClicked 被调用"
)
video_path
=
item
.
data
(
Qt
.
UserRole
)
print
(
f
" 视频路径: {video_path}"
)
# 先停止之前的播放
self
.
_onStopVideo
()
# 保存当前视频路径
self
.
_current_video
=
video_path
# 更新显示
# 更新
当前视频
显示
video_name
=
osp
.
basename
(
video_path
)
self
.
lbl_current_video
.
setText
(
f
"当前视频: {video_name}"
)
# 启用裁剪按钮和播放按钮
print
(
f
" 启用裁剪按钮..."
)
self
.
btn_crop
.
setEnabled
(
True
)
self
.
btn_play
.
setEnabled
(
True
)
print
(
f
" 按钮状态: {'启用' if self.btn_crop.isEnabled() else '禁用'}"
)
# 加载视频的第一帧作为预览
self
.
_loadVideoFirstFrame
(
video_path
)
...
...
@@ -1157,9 +1098,6 @@ class DataPreprocessPanel(QtWidgets.QWidget):
# 发射信号
self
.
videoSelected
.
emit
(
video_path
)
print
(
f
"[OK] 选中视频: {video_name}"
)
print
(
"="
*
50
)
def
_onVideoDoubleClicked
(
self
,
item
):
"""视频被双击 - 使用系统默认播放器打开"""
video_path
=
item
.
data
(
Qt
.
UserRole
)
...
...
@@ -1170,18 +1108,14 @@ class DataPreprocessPanel(QtWidgets.QWidget):
elif
os
.
name
==
'posix'
:
# macOS, Linux
import
subprocess
subprocess
.
Popen
([
'open'
,
video_path
])
print
(
f
"▶ 播放视频: {video_path}"
)
except
Exception
as
e
:
QtWidgets
.
QMessageBox
.
critical
(
self
,
"错误"
,
f
"无法打开视频: {e}"
)
print
(
f
"[ERROR] 打开视频失败: {e}"
)
def
_onRefreshVideos
(
self
):
"""刷新视频列表"""
if
self
.
_current_folder
:
print
(
f
"[刷新] 正在刷新文件夹: {self._current_folder}"
)
self
.
_loadVideos
(
self
.
_current_folder
)
# 刷新后,通知handler更新视频网格样式(恢复绿色标识)
...
...
@@ -1190,7 +1124,6 @@ class DataPreprocessPanel(QtWidgets.QWidget):
# 使用QTimer延迟执行,确保视频列表已完全加载
from
qtpy.QtCore
import
QTimer
QTimer
.
singleShot
(
100
,
self
.
_handler
.
updateVideoGridStyles
)
print
(
f
"[刷新] 已安排更新视频网格样式"
)
# 隐藏刷新完成提示弹窗
# self._showInformation("刷新完成", f"已刷新文件夹 '{self._current_folder}' 中的视频列表")
...
...
@@ -1255,7 +1188,6 @@ class DataPreprocessPanel(QtWidgets.QWidget):
if
hasattr
(
self
,
'_handler'
)
and
self
.
_handler
:
if
hasattr
(
self
.
_handler
,
'releaseVideoCapture'
):
self
.
_handler
.
releaseVideoCapture
()
print
(
f
"[DataPreprocess] 已释放视频文件句柄"
)
# 获取当前文件名(不含扩展名)和扩展名
video_dir
=
osp
.
dirname
(
video_path
)
...
...
@@ -1307,14 +1239,11 @@ class DataPreprocessPanel(QtWidgets.QWidget):
f
"视频已重命名为: {new_video_name}"
)
print
(
f
"[OK] 重命名视频: {video_name} -> {new_video_name}"
)
except
Exception
as
e
:
QtWidgets
.
QMessageBox
.
critical
(
self
,
"错误"
,
f
"重命名视频失败:
\n
{str(e)}"
)
print
(
f
"[ERROR] 重命名视频失败: {e}"
)
def
_onDeleteVideo
(
self
,
item
):
"""删除视频"""
...
...
@@ -1356,12 +1285,8 @@ class DataPreprocessPanel(QtWidgets.QWidget):
if
self
.
_current_folder
:
self
.
_loadVideos
(
self
.
_current_folder
)
print
(
f
"[OK] 删除视频: {video_name}"
)
except
Exception
as
e
:
self
.
_showCritical
(
"错误"
,
f
"删除视频失败:
\n
{str(e)}"
)
print
(
f
"[ERROR] 删除视频失败: {e}"
)
def
_onBrowseCropPath
(
self
):
"""浏览裁剪保存路径"""
...
...
@@ -1408,17 +1333,10 @@ class DataPreprocessPanel(QtWidgets.QWidget):
# 发射信号
self
.
cropStarted
.
emit
(
config
)
print
(
f
"[CROP] 裁剪配置:"
)
print
(
f
" 视频: {osp.basename(self._current_video)}"
)
print
(
f
" 保存路径: {config['save_liquid_data_path']}"
)
print
(
f
" 裁剪频率: 每 {config['crop_frequency']} 帧"
)
print
(
f
" 提示: 使用 DataPreprocessHandler 可以启用可视化画框功能"
)
except
Exception
as
e
:
QtWidgets
.
QMessageBox
.
critical
(
self
,
"错误"
,
f
"打开配置对话框失败:
\n
{e}"
)
print
(
f
"[ERROR] 打开配置对话框失败: {e}"
)
def
_loadVideoFirstFrame
(
self
,
video_path
):
"""加载视频的第一帧作为预览"""
...
...
@@ -1429,7 +1347,6 @@ class DataPreprocessPanel(QtWidgets.QWidget):
cap
=
cv2
.
VideoCapture
(
video_path
)
if
not
cap
.
isOpened
():
print
(
f
" 无法打开视频: {video_path}"
)
self
.
video_preview
.
setText
(
"无法打开视频"
)
return
...
...
@@ -1456,17 +1373,13 @@ class DataPreprocessPanel(QtWidgets.QWidget):
# 显示
self
.
video_preview
.
setPixmap
(
scaled_pixmap
)
print
(
f
"[OK] 加载视频第一帧"
)
else
:
self
.
video_preview
.
setText
(
"无法读取视频帧"
)
print
(
f
" 无法读取视频帧"
)
except
ImportError
:
self
.
video_preview
.
setText
(
"缺少 OpenCV 库
\n
pip install opencv-python"
)
print
(
f
"[ERROR] 缺少 OpenCV 库"
)
except
Exception
as
e
:
self
.
video_preview
.
setText
(
f
"加载失败
\n
{str(e)}"
)
print
(
f
"[ERROR] 加载视频第一帧失败: {e}"
)
def
_onPlayVideo
(
self
):
"""播放视频"""
...
...
@@ -1503,8 +1416,6 @@ class DataPreprocessPanel(QtWidgets.QWidget):
self
.
_video_timer
.
timeout
.
connect
(
self
.
_updateVideoFrame
)
self
.
_video_timer
.
start
(
int
(
1000
/
fps
))
# 根据fps设置间隔
print
(
f
"▶ 开始播放视频 (FPS: {fps})"
)
except
ImportError
:
QtWidgets
.
QMessageBox
.
critical
(
self
,
"错误"
,
"缺少 OpenCV 库
\n\n
请安装: pip install opencv-python"
...
...
@@ -1513,7 +1424,6 @@ class DataPreprocessPanel(QtWidgets.QWidget):
QtWidgets
.
QMessageBox
.
critical
(
self
,
"错误"
,
f
"播放视频失败: {e}"
)
print
(
f
"[ERROR] 播放视频失败: {e}"
)
def
_onPauseVideo
(
self
):
"""暂停视频"""
...
...
@@ -1524,8 +1434,6 @@ class DataPreprocessPanel(QtWidgets.QWidget):
self
.
btn_play
.
setEnabled
(
True
)
self
.
btn_pause
.
setEnabled
(
False
)
print
(
f
"⏸ 暂停视频"
)
def
_onStopVideo
(
self
):
"""停止视频"""
# 停止定时器
...
...
@@ -1548,8 +1456,6 @@ class DataPreprocessPanel(QtWidgets.QWidget):
if
self
.
_current_video
:
self
.
_loadVideoFirstFrame
(
self
.
_current_video
)
print
(
f
"⏹ 停止视频"
)
def
_updateVideoFrame
(
self
):
"""更新视频画面"""
if
not
self
.
_video_capture
or
not
self
.
_video_playing
:
...
...
@@ -1585,7 +1491,7 @@ class DataPreprocessPanel(QtWidgets.QWidget):
self
.
_video_capture
.
set
(
cv2
.
CAP_PROP_POS_FRAMES
,
0
)
except
Exception
as
e
:
p
rint
(
f
"[ERROR] 更新视频画面失败: {e}"
)
p
ass
def
_onBrowseCropPath
(
self
):
"""浏览裁剪保存路径"""
...
...
@@ -1687,20 +1593,16 @@ if __name__ == "__main__":
window
.
setWindowTitle
(
"数据预处理面板测试"
)
window
.
resize
(
1200
,
700
)
# 创建数据预处理面板(使用测试目录,与数据采集共用根目录)
test_root
=
osp
.
join
(
osp
.
expanduser
(
"~"
),
"data_collection_test"
)
panel
=
DataPreprocessPanel
(
root_path
=
test_root
)
window
.
setCentralWidget
(
panel
)
# 创建测试文件夹和文件(如果不存在)
def
setup_test_data
():
"""创建测试数据"""
test_folder
=
osp
.
join
(
test_root
,
"测试视频文件夹"
)
if
not
osp
.
exists
(
test_folder
):
os
.
makedirs
(
test_folder
,
exist_ok
=
True
)
print
(
f
" 创建测试文件夹: {test_folder}"
)
# 创建一些空的测试视频文件(仅用于测试界面)
test_files
=
[
"test_video_1.mp4"
,
"test_video_2.avi"
,
...
...
@@ -1711,33 +1613,24 @@ if __name__ == "__main__":
if
not
osp
.
exists
(
fpath
):
with
open
(
fpath
,
'w'
)
as
f
:
f
.
write
(
"# 这是一个测试文件,仅用于界面测试"
)
print
(
f
" 创建测试文件: {fname}"
)
print
(
"
\n
[OK] 测试数据准备完成!"
)
print
(
f
" 测试目录: {test_root}"
)
print
(
f
" 请点击左侧的'测试视频文件夹'查看视频网格
\n
"
)
# 延迟创建测试数据(等待UI初始化)
QtCore
.
QTimer
.
singleShot
(
100
,
setup_test_data
)
QtCore
.
QTimer
.
singleShot
(
200
,
panel
.
refreshFolders
)
# 连接信号测试
def
on_folder_selected
(
folder_path
):
p
rint
(
f
" 文件夹选中信号: {folder_path}"
)
p
ass
def
on_folder_added
(
folder_path
):
p
rint
(
f
" 文件夹添加信号: {folder_path}"
)
p
ass
def
on_folder_deleted
(
folder_path
):
p
rint
(
f
" 文件夹删除信号: {folder_path}"
)
p
ass
def
on_video_selected
(
video_path
):
p
rint
(
f
" 视频选中信号: {video_path}"
)
p
ass
def
on_crop_started
(
crop_config
):
print
(
f
" 裁剪开始信号:"
)
for
key
,
value
in
crop_config
.
items
():
print
(
f
" {key}: {value}"
)
pass
panel
.
folderSelected
.
connect
(
on_folder_selected
)
panel
.
folderAdded
.
connect
(
on_folder_added
)
...
...
@@ -1745,44 +1638,6 @@ if __name__ == "__main__":
panel
.
videoSelected
.
connect
(
on_video_selected
)
panel
.
cropStarted
.
connect
(
on_crop_started
)
# 打印测试说明
print
(
"
\n
"
+
"="
*
70
)
print
(
" 数据预处理面板测试程序"
)
print
(
"="
*
70
)
print
(
"[OK] 界面布局(两栏):"
)
print
(
"
\n
左侧面板 - 目录管理:"
)
print
(
" - 显示根目录下的所有子文件夹"
)
print
(
" - 点击文件夹:选中并加载视频列表"
)
print
(
" - 双击文件夹:在系统文件管理器中打开"
)
print
(
" - 新增文件夹按钮:创建新的数据处理文件夹"
)
print
(
" - 删除文件夹按钮:删除选中的文件夹(含内容)"
)
print
(
" - 底部显示文件夹总数统计"
)
print
(
"
\n
右侧面板 - 视频网格显示和裁剪:"
)
print
(
" - 顶部区域裁剪按钮:打开裁剪配置对话框"
)
print
(
" - 视频网格显示:以图标模式显示文件夹中的所有视频"
)
print
(
" - 点击视频:选中视频,启用裁剪按钮"
)
print
(
" - 双击视频:使用系统默认播放器打开"
)
print
(
" - 底部显示视频统计和当前选中的视频"
)
print
(
"
\n
区域裁剪对话框:"
)
print
(
" - 帧范围设置:起始帧、结束帧、采样间隔"
)
print
(
" - 裁剪区域设置:X、Y坐标、宽度、高度"
)
print
(
" - 保存设置:保存目录、文件名前缀、图片格式"
)
print
(
" - 完整帧选项:可选择使用完整帧不裁剪"
)
print
(
"
\n
功能特点:"
)
print
(
" - 使用 QSplitter 实现可调整宽度的两栏布局"
)
print
(
" - 自动创建根目录(默认: ~/data_preprocess)"
)
print
(
" - 自动识别视频文件格式(mp4, avi, mov, mkv等)"
)
print
(
" - 完整的信号机制用于外部集成"
)
print
(
" - 友好的错误提示和确认对话框"
)
print
(
"
\n
测试操作:"
)
print
(
" 1. 点击'新增文件夹'创建数据处理目录"
)
print
(
" 2. 点击文件夹,右侧显示视频网格"
)
print
(
" 3. 点击视频进行选中"
)
print
(
" 4. 点击'区域裁剪'按钮打开裁剪配置对话框"
)
print
(
" 5. 在对话框中设置帧数、裁剪区域和保存路径"
)
print
(
" 6. 点击'开始裁剪'执行裁剪任务"
)
print
(
"="
*
70
+
"
\n
"
)
window
.
show
()
sys
.
exit
(
app
.
exec_
())
widgets/datasetpage/test_crop_preview_integration.py
View file @
872a1442
...
...
@@ -68,20 +68,14 @@ class IntegratedDatasetPage(QtWidgets.QWidget):
def
_initHandlers
(
self
):
"""初始化处理器"""
print
(
"
\n
[IntegratedDatasetPage] 初始化处理器..."
)
# 数据预处理处理器
self
.
preprocess_handler
=
DataPreprocessHandler
(
self
.
preprocess_panel
)
print
(
" ✓ 数据预处理处理器已创建"
)
# 裁剪预览处理器
self
.
preview_handler
=
CropPreviewHandler
(
self
.
preview_panel
)
print
(
" ✓ 裁剪预览处理器已创建"
)
def
_connectSignals
(
self
):
"""连接信号"""
print
(
"
\n
[IntegratedDatasetPage] 连接信号..."
)
# === 预处理面板信号 ===
# 文件夹被选中
...
...
@@ -120,30 +114,21 @@ class IntegratedDatasetPage(QtWidgets.QWidget):
# 切换区域
self
.
preview_panel
.
regionChanged
.
connect
(
self
.
_onRegionChanged
)
print
(
" ✓ 信号连接完成"
)
# ========== 数据预处理面板槽函数 ==========
def
_onFolderSelected
(
self
,
folder_path
):
"""文件夹被选中"""
p
rint
(
f
"
\n
[Folder] 选中: {osp.basename(folder_path)}"
)
p
ass
def
_onVideoSelected
(
self
,
video_path
):
"""视频被选中"""
p
rint
(
f
"
\n
[Video] 选中: {osp.basename(video_path)}"
)
p
ass
# ========== 数据预处理处理器槽函数 ==========
def
_onCropStarted
(
self
,
config
):
"""裁剪任务开始"""
save_liquid_data_path
=
config
.
get
(
'save_liquid_data_path'
,
''
)
crop_frequency
=
config
.
get
(
'crop_frequency'
,
1
)
print
(
f
"
\n
{'='*60}"
)
print
(
f
"[Crop] 裁剪任务开始"
)
print
(
f
" 保存路径: {save_liquid_data_path}"
)
print
(
f
" 裁剪频率: 每 {crop_frequency} 帧"
)
print
(
f
"{'='*60}"
)
# 启动预览监控
self
.
preview_handler
.
startMonitoring
(
save_liquid_data_path
)
...
...
@@ -153,16 +138,10 @@ class IntegratedDatasetPage(QtWidgets.QWidget):
def
_onCropProgress
(
self
,
progress
):
"""裁剪进度更新"""
if
progress
%
10
==
0
:
# 每10%打印一次
print
(
f
"[Crop] 进度: {progress}
%
"
)
pass
def
_onCropFinished
(
self
,
save_liquid_data_path
):
"""裁剪任务完成"""
print
(
f
"
\n
{'='*60}"
)
print
(
f
"[Crop] 裁剪任务完成!"
)
print
(
f
" 保存路径: {save_liquid_data_path}"
)
print
(
f
"{'='*60}
\n
"
)
# 刷新预览显示
self
.
preview_handler
.
refreshPanel
()
...
...
@@ -175,8 +154,6 @@ class IntegratedDatasetPage(QtWidgets.QWidget):
def
_onCropError
(
self
,
error_msg
):
"""裁剪错误"""
print
(
f
"
\n
[ERROR] 裁剪失败: {error_msg}"
)
# 显示错误提示
QtWidgets
.
QMessageBox
.
critical
(
self
,
"裁剪失败"
,
...
...
@@ -187,21 +164,21 @@ class IntegratedDatasetPage(QtWidgets.QWidget):
def
_onNewImageDetected
(
self
,
region_index
,
image_path
):
"""检测到新图片"""
p
rint
(
f
"[Preview] 新图片: 区域{region_index+1} - {osp.basename(image_path)}"
)
p
ass
def
_onFolderChanged
(
self
,
folder_path
):
"""监控文件夹变化"""
p
rint
(
f
"[Preview] 文件夹变化: {folder_path}"
)
p
ass
# ========== 预览面板槽函数 ==========
def
_onImageSelected
(
self
,
image_path
):
"""图片被选中"""
p
rint
(
f
"[Preview] 图片选中: {osp.basename(image_path)}"
)
p
ass
def
_onRegionChanged
(
self
,
region_index
):
"""切换区域"""
p
rint
(
f
"[Preview] 切换到区域{region_index+1}"
)
p
ass
def
main
():
...
...
@@ -226,41 +203,6 @@ def main():
except
:
pass
# 打印使用说明
print
(
"
\n
"
+
"="
*
70
)
print
(
" 裁剪图片预览集成测试程序"
)
print
(
"="
*
70
)
print
(
"
\n
布局说明:"
)
print
(
" 左侧面板 - 数据预处理"
)
print
(
" • 目录管理:查看和管理视频文件夹"
)
print
(
" • 视频预览:预览选中的视频"
)
print
(
" • 区域裁剪:配置裁剪参数并执行裁剪"
)
print
(
"
\n
右侧面板 - 裁剪预览"
)
print
(
" • 实时监控:自动检测新生成的裁剪图片"
)
print
(
" • 多区域显示:支持最多3个裁剪区域"
)
print
(
" • 图片预览:网格显示缩略图,双击查看大图"
)
print
(
"
\n
使用流程:"
)
print
(
" 1. 在左侧选择包含视频的文件夹"
)
print
(
" 2. 点击视频文件进行预览"
)
print
(
" 3. 点击'区域裁剪'按钮配置裁剪参数"
)
print
(
" 4. 在视频预览区域画框选择裁剪区域(最多3个)"
)
print
(
" 5. 按 C 键确认裁剪配置"
)
print
(
" 6. 在弹出的对话框中设置保存路径和参数"
)
print
(
" 7. 点击'开始裁剪'执行任务"
)
print
(
" 8. 右侧预览面板会实时显示裁剪生成的图片"
)
print
(
"
\n
快捷键:"
)
print
(
" • 画框模式:鼠标拖动绘制裁剪区域"
)
print
(
" • C 键:确认当前裁剪配置"
)
print
(
" • R 键:重置所有裁剪框"
)
print
(
"
\n
功能特性:"
)
print
(
" ✓ 实时监控文件夹变化"
)
print
(
" ✓ 自动更新图片显示"
)
print
(
" ✓ 支持多区域独立显示"
)
print
(
" ✓ 双击图片查看原始大小"
)
print
(
" ✓ 显示详细统计信息"
)
print
(
"="
*
70
+
"
\n
"
)
# 显示窗口
window
.
show
()
# 运行应用
...
...
widgets/datasetpage/test_labelme_integration.py
View file @
872a1442
...
...
@@ -24,10 +24,6 @@ from annotationtool import AnnotationTool
def
test_labelme_integration
():
"""测试labelme集成"""
print
(
"
\n
"
+
"="
*
70
)
print
(
" Labelme集成测试"
)
print
(
"="
*
70
)
# 创建应用
app
=
QtWidgets
.
QApplication
(
sys
.
argv
)
...
...
@@ -37,22 +33,10 @@ def test_labelme_integration():
window
.
resize
(
1600
,
900
)
# 创建标注工具
print
(
"
\n
[1/4] 创建AnnotationTool实例..."
)
annotation_tool
=
AnnotationTool
()
window
.
setCentralWidget
(
annotation_tool
)
# 检查labelme是否成功初始化
print
(
"
\n
[2/4] 检查Labelme初始化状态..."
)
if
annotation_tool
.
labelme_widget
is
not
None
:
print
(
" Labelme已成功嵌入"
)
print
(
f
" - Labelme实例类型: {type(annotation_tool.labelme_widget).__name__}"
)
print
(
f
" - Labelme容器: {type(annotation_tool.labelme_container).__name__}"
)
else
:
print
(
" Labelme未能初始化"
)
print
(
" 请检查labelme是否已安装: pip install labelme"
)
# 添加测试数据
print
(
"
\n
[3/4] 添加测试数据到列表..."
)
test_images
=
[
{
"name"
:
"sample_001.jpg"
,
...
...
@@ -81,8 +65,6 @@ def test_labelme_integration():
item
.
setText
(
f
" {img_data['name']}
\n
{img_data['resolution']}"
)
item
.
setData
(
Qt
.
UserRole
,
img_data
)
print
(
f
" 已添加 {len(test_images)} 个测试图片"
)
# 更新统计
annotation_tool
.
lbl_total_stats
.
setText
(
f
"总数: {len(test_images)}"
)
annotation_tool
.
lbl_annotated_stats
.
setText
(
"已标注: 0"
)
...
...
@@ -90,7 +72,6 @@ def test_labelme_integration():
# 连接列表点击事件(演示如何加载图片)
def
on_item_clicked
(
item
):
data
=
item
.
data
(
Qt
.
UserRole
)
print
(
f
"
\n
点击了图片: {data['name']}"
)
# 更新详细信息
annotation_tool
.
lbl_image_name
.
setText
(
data
[
"name"
])
...
...
@@ -102,36 +83,10 @@ def test_labelme_integration():
# 如果图片存在,加载到labelme
if
osp
.
exists
(
data
[
"path"
]):
print
(
f
" 加载图片到labelme: {data['path']}"
)
annotation_tool
.
loadImageForAnnotation
(
data
[
"path"
])
else
:
print
(
f
" ️ 图片不存在: {data['path']}"
)
print
(
" (这是测试数据,实际使用时需要提供真实图片路径)"
)
annotation_tool
.
annotation_list
.
itemClicked
.
connect
(
on_item_clicked
)
# 显示使用说明
print
(
"
\n
[4/4] 启动测试窗口..."
)
print
(
"
\n
"
+
"-"
*
70
)
print
(
" 使用说明"
)
print
(
"-"
*
70
)
print
(
"
\n
界面布局:"
)
print
(
" 左侧: 标注数据列表"
)
print
(
" 右侧: Labelme标注工具"
)
print
(
"
\n
测试步骤:"
)
print
(
" 1. 查看右侧是否显示labelme界面"
)
print
(
" 2. 点击左侧列表中的图片项"
)
print
(
" 3. 观察详细信息是否更新"
)
print
(
" 4. 测试工具栏按钮(放大、缩小、适应窗口)"
)
print
(
" 5. 如果有真实图片,可以在labelme中进行标注"
)
print
(
"
\n
加载自定义图片:"
)
print
(
" 在右侧labelme中点击 '打开' 或 '打开目录' 加载图片"
)
print
(
"
\n
快捷键:"
)
print
(
" - Ctrl+O: 打开图片"
)
print
(
" - Ctrl+S: 保存标注"
)
print
(
" - 更多快捷键请参考labelme工具栏"
)
print
(
"-"
*
70
)
# 显示窗口
window
.
show
()
...
...
@@ -157,9 +112,6 @@ def test_labelme_integration():
"详细错误信息请查看控制台。"
)
print
(
"
\n
测试窗口已启动"
)
print
(
"="
*
70
+
"
\n
"
)
sys
.
exit
(
app
.
exec_
())
...
...
widgets/datasetpage/training_panel.py
View file @
872a1442
...
...
@@ -560,16 +560,11 @@ if __name__ == "__main__":
panel
=
TrainingPanel
()
window
.
setCentralWidget
(
panel
)
# 连接信号测试
def
on_start_training
(
params
):
print
(
f
"开始训练:"
)
for
key
,
value
in
params
.
items
():
print
(
f
" {key}: {value}"
)
panel
.
append_training_log
(
"训练已开始...
\n
"
)
panel
.
set_training_active
(
True
)
def
on_stop_training
():
print
(
"停止训练"
)
panel
.
append_training_log
(
"训练已停止
\n
"
)
panel
.
set_training_active
(
False
)
...
...
widgets/datasetpage/videobrowser.py
View file @
872a1442
...
...
@@ -270,7 +270,6 @@ class VideoBrowser(QtWidgets.QWidget):
path: 根目录路径
"""
if
not
osp
.
exists
(
path
):
print
(
f
" 路径不存在: {path}"
)
return
False
self
.
_root_path
=
path
...
...
@@ -283,7 +282,6 @@ class VideoBrowser(QtWidgets.QWidget):
# 展开根节点
self
.
tree_view
.
expand
(
root_index
)
print
(
f
" 设置根目录: {path}"
)
return
True
def
getRootPath
(
self
):
...
...
@@ -316,7 +314,7 @@ class VideoBrowser(QtWidgets.QWidget):
if
ext
in
self
.
_video_extensions
:
videos
.
append
(
file_path
)
except
Exception
as
e
:
print
(
f
" 获取视频列表失败: {e}"
)
return
[]
return
videos
...
...
@@ -362,13 +360,10 @@ class VideoBrowser(QtWidgets.QWidget):
# 发送信号
self
.
videoSelected
.
emit
(
video_files
[
0
])
print
(
f
" 加载视频文件: {video_count} 个,当前: {osp.basename(video_files[0])}"
)
else
:
print
(
f
" 文件夹中没有视频文件"
)
self
.
preview_label
.
setText
(
"该文件夹中没有视频文件"
)
except
Exception
as
e
:
print
(
f
" 加载视频失败: {e}"
)
self
.
stats_label
.
setText
(
"加载失败"
)
def
_clearVideoInfo
(
self
):
...
...
@@ -413,8 +408,6 @@ class VideoBrowser(QtWidgets.QWidget):
self
.
btn_play
.
setEnabled
(
True
)
self
.
btn_open_folder
.
setEnabled
(
True
)
print
(
f
" 更新视频信息: {file_name}"
)
# ========== 槽函数 ==========
def
_onFolderClicked
(
self
,
index
):
...
...
@@ -423,7 +416,6 @@ class VideoBrowser(QtWidgets.QWidget):
return
folder_path
=
self
.
file_model
.
filePath
(
index
)
print
(
f
" 点击文件夹: {folder_path}"
)
# 加载该文件夹中的视频
self
.
_loadVideosFromFolder
(
folder_path
)
...
...
@@ -447,7 +439,6 @@ class VideoBrowser(QtWidgets.QWidget):
current_folder
=
self
.
getCurrentFolder
()
if
current_folder
:
self
.
_loadVideosFromFolder
(
current_folder
)
print
(
" 刷新文件夹"
)
def
_onSelectRoot
(
self
):
"""选择根目录"""
...
...
@@ -475,10 +466,7 @@ class VideoBrowser(QtWidgets.QWidget):
elif
os
.
name
==
'posix'
:
# macOS 或 Linux
import
subprocess
subprocess
.
Popen
([
'xdg-open'
,
self
.
_current_video_path
])
print
(
f
"▶ 播放视频: {osp.basename(self._current_video_path)}"
)
except
Exception
as
e
:
print
(
f
" 播放视频失败: {e}"
)
QtWidgets
.
QMessageBox
.
warning
(
self
,
"播放失败"
,
...
...
@@ -499,10 +487,8 @@ class VideoBrowser(QtWidgets.QWidget):
elif
os
.
name
==
'posix'
:
# macOS 或 Linux
import
subprocess
subprocess
.
Popen
([
'xdg-open'
,
folder_path
])
print
(
f
" 打开文件夹: {folder_path}"
)
except
Exception
as
e
:
p
rint
(
f
" 打开文件夹失败: {e}"
)
p
ass
if
__name__
==
"__main__"
:
...
...
@@ -520,52 +506,19 @@ if __name__ == "__main__":
video_browser
=
VideoBrowser
()
window
.
setCentralWidget
(
video_browser
)
# 连接信号测试
def
on_folder_selected
(
folder_path
):
p
rint
(
f
" 选中文件夹: {folder_path}"
)
p
ass
def
on_video_selected
(
video_path
):
p
rint
(
f
" 选中视频: {video_path}"
)
p
ass
def
on_video_played
(
video_path
):
p
rint
(
f
"▶ 播放视频: {video_path}"
)
p
ass
video_browser
.
folderSelected
.
connect
(
on_folder_selected
)
video_browser
.
videoSelected
.
connect
(
on_video_selected
)
video_browser
.
videoPlayed
.
connect
(
on_video_played
)
# 打印测试说明
print
(
"
\n
"
+
"="
*
70
)
print
(
" VideoBrowser 视频浏览器测试程序"
)
print
(
"="
*
70
)
print
(
" 组件功能:"
)
print
(
"
\n
左侧面板 - 文件夹浏览:"
)
print
(
" - 显示目录树结构"
)
print
(
" - 点击文件夹自动加载第一个视频"
)
print
(
" - 双击文件夹展开/折叠"
)
print
(
" - 点击'选择根目录'切换根路径"
)
print
(
" - 点击'刷新'重新加载"
)
print
(
" - 显示统计信息(文件夹数、视频数)"
)
print
(
"
\n
右侧面板 - 视频预览:"
)
print
(
" - 视频预览区域(黑色背景)"
)
print
(
" - 自动显示第一个视频的信息"
)
print
(
" - 支持格式: .mp4, .avi, .mkv, .mov, .wmv, .flv, .webm, .m4v"
)
print
(
" - 点击'播放'按钮使用系统播放器打开"
)
print
(
" - 点击'打开文件夹'在资源管理器中查看"
)
print
(
"
\n
视频信息:"
)
print
(
" - 文件名"
)
print
(
" - 文件大小(MB/GB)"
)
print
(
" - 完整路径"
)
print
(
"
\n
信号:"
)
print
(
" - folderSelected: 文件夹被选中"
)
print
(
" - videoSelected: 视频被自动加载"
)
print
(
" - videoPlayed: 视频开始播放"
)
print
(
"
\n
提示:"
)
print
(
" - 可以通过代码设置自定义根目录"
)
print
(
" - 支持拖拽分割器调整左右面板大小"
)
print
(
" - 点击文件夹自动加载该文件夹中的第一个视频"
)
print
(
"="
*
70
+
"
\n
"
)
window
.
show
()
sys
.
exit
(
app
.
exec_
())
widgets/datasetpage/videoclipper.py
View file @
872a1442
...
...
@@ -340,7 +340,6 @@ if __name__ == "__main__":
video_clipper
=
VideoClipper
()
window
.
setCentralWidget
(
video_clipper
)
# 添加测试数据到照片目录
test_folders
=
[
(
"项目A"
,
25
),
(
"项目B"
,
18
),
...
...
@@ -370,39 +369,8 @@ if __name__ == "__main__":
video_clipper
.
photo_tree
.
setColumnWidth
(
0
,
150
)
video_clipper
.
photo_tree
.
setColumnWidth
(
1
,
60
)
# 更新统计信息
video_clipper
.
lbl_photo_stats
.
setText
(
f
"照片: {sum(c for _, c in test_folders)} | 文件夹: {len(test_folders)}"
)
# 打印测试说明
print
(
"
\n
"
+
"="
*
70
)
print
(
" VideoClipper 视频裁剪器组件测试程序"
)
print
(
"="
*
70
)
print
(
" 界面布局(三栏):"
)
print
(
"
\n
左侧面板 - 照片目录:"
)
print
(
" - 树形结构显示文件夹"
)
print
(
" - 显示每个文件夹的照片数量"
)
print
(
" - 刷新按钮"
)
print
(
" - 底部统计信息"
)
print
(
"
\n
中间面板 - 裁剪图片展示:"
)
print
(
" - 滚动区域显示裁剪的图片"
)
print
(
" - 清空和保存按钮"
)
print
(
" - 底部统计裁剪数量"
)
print
(
"
\n
右侧面板 - 视频预览:"
)
print
(
" - 视频预览显示区(黑色背景)"
)
print
(
" - 视频信息卡片(文件名、大小、时长、分辨率)"
)
print
(
" - 控制按钮(选择视频、播放/暂停、裁剪当前帧)"
)
print
(
" - 进度条"
)
print
(
" - 时间显示(当前时间 / 总时长)"
)
print
(
"
\n
布局特点:"
)
print
(
" - 使用 QSplitter 实现可调整宽度的三栏布局"
)
print
(
" - 默认比例: 左:中:右 = 1:2:2"
)
print
(
" - 可以拖拽分割线调整各面板宽度"
)
print
(
"
\n
说明:"
)
print
(
" - 当前为纯UI界面,不包含功能实现"
)
print
(
" - 所有按钮和控件已预留,可后续添加功能"
)
print
(
" - 界面风格统一,适配现有系统"
)
print
(
"="
*
70
+
"
\n
"
)
window
.
show
()
sys
.
exit
(
app
.
exec_
())
widgets/menubar.py
View file @
872a1442
...
...
@@ -203,7 +203,6 @@ if __name__ == "__main__":
menubar
=
MenuBar
(
main_window
)
main_window
.
setMenuBar
(
menubar
)
# 添加测试菜单项(带图标)
menubar
.
addFileAction
(
"open"
,
"打开"
,
lambda
:
print
(
"打开文件"
),
...
...
widgets/modelpage/modelset_page.py
View file @
872a1442
...
...
@@ -104,9 +104,11 @@ class ModelSetPage(QtWidgets.QWidget):
self
.
_connectSignals
()
self
.
_setupShortcuts
()
# 使用延迟加载确保 UI 完全初始化后再加载模型
# 这样可以避免 handler 未初始化的问题
QtCore
.
QTimer
.
singleShot
(
100
,
self
.
loadModelsFromConfig
)
# 移除自动加载,改为手动加载模式
# 用户可以通过以下方式加载模型:
# 1. 按 F5 刷新
# 2. 右键点击空白区域刷新
# 3. 点击刷新按钮(如果有)
def
_initUI
(
self
):
"""初始化UI - 简约labelme风格(三栏充实布局版)"""
...
...
@@ -331,7 +333,6 @@ class ModelSetPage(QtWidgets.QWidget):
item
=
self
.
model_set_list
.
itemAt
(
pos
)
if
item
is
None
:
# 右键点击了空白区域,触发刷新
print
(
"[ModelSetPage] 检测到空白区域右键点击,刷新模型列表"
)
self
.
loadModelsFromConfig
()
return
True
return
super
(
ModelSetPage
,
self
)
.
eventFilter
(
obj
,
event
)
...
...
@@ -467,11 +468,6 @@ class ModelSetPage(QtWidgets.QWidget):
- classes: 类别文件路径(可选)
- description: 模型描述(可选)
"""
print
(
"="
*
70
)
print
(
" [ModelSetPage] 接收到新模型创建信号"
)
print
(
f
" 模型信息: {model_info}"
)
print
(
"="
*
70
)
try
:
# 1. 验证模型信息
model_name
=
model_info
.
get
(
'name'
,
''
)
.
strip
()
...
...
@@ -556,7 +552,6 @@ class ModelSetPage(QtWidgets.QWidget):
# 6. 添加到模型参数字典
self
.
_model_params
[
model_name
]
=
model_params
print
(
f
" [ModelSetPage] 已添加模型参数: {model_name}"
)
# 7. 添加到列表显示
# 先检查是否已存在于列表中
...
...
@@ -568,7 +563,6 @@ class ModelSetPage(QtWidgets.QWidget):
if
model_name
not
in
existing_items
:
self
.
addModelToList
(
model_name
)
print
(
f
" [ModelSetPage] 已添加到列表: {model_name}"
)
else
:
# 更新现有项
for
i
in
range
(
self
.
model_set_list
.
count
()):
...
...
@@ -578,7 +572,6 @@ class ModelSetPage(QtWidgets.QWidget):
# 保留(默认)标记
if
"(默认)"
in
item_text
:
self
.
model_set_list
.
item
(
i
)
.
setText
(
f
"{model_name}(默认)"
)
print
(
f
" [ModelSetPage] 已更新列表项: {model_name}"
)
break
# 8. 选中新创建的模型
...
...
@@ -599,11 +592,7 @@ class ModelSetPage(QtWidgets.QWidget):
f
"模型已添加到模型列表中。"
)
print
(
f
"✅ [ModelSetPage] 新模型 '{model_name}' 创建成功!"
)
print
(
"="
*
70
)
except
Exception
as
e
:
print
(
f
" [ERROR] 创建新模型失败: {e}"
)
import
traceback
traceback
.
print_exc
()
showCritical
(
...
...
@@ -619,7 +608,6 @@ class ModelSetPage(QtWidgets.QWidget):
# 发射信号通知外部模型已被选中
self
.
modelSetClicked
.
emit
(
display_name
)
print
(
f
"✅ [ModelSetPage] 模型已选中: {model_name}"
)
def
_onModelSetDoubleClicked
(
self
,
item
):
"""模型集列表项被双击"""
...
...
@@ -772,30 +760,23 @@ class ModelSetPage(QtWidgets.QWidget):
def
refreshModelList
(
self
):
"""刷新模型列表显示"""
try
:
print
(
f
"🔄 [UI刷新] 开始刷新模型列表,当前模型参数数量: {len(self._model_params)}"
)
# 清空现有列表
self
.
model_set_list
.
clear
()
# 重新添加所有模型
for
model_name
in
self
.
_model_params
.
keys
():
print
(
f
"🔄 [UI刷新] 添加模型到列表: {model_name}"
)
self
.
model_set_list
.
addItem
(
model_name
)
# 如果是默认模型,添加标记
if
model_name
==
self
.
_current_default_model
:
item
=
self
.
model_set_list
.
item
(
self
.
model_set_list
.
count
()
-
1
)
item
.
setText
(
f
"{model_name} [默认]"
)
print
(
f
"✅ [UI刷新] 标记默认模型: {model_name}"
)
# 更新统计信息
self
.
_updateStats
()
self
.
_updateModelOrder
()
print
(
f
"✅ [UI刷新] 模型列表刷新完成,显示 {self.model_set_list.count()} 个模型"
)
except
Exception
as
e
:
print
(
f
"❌ [UI刷新] 刷新模型列表时发生异常: {e}"
)
import
traceback
traceback
.
print_exc
()
...
...
@@ -1112,11 +1093,9 @@ class ModelSetPage(QtWidgets.QWidget):
try
:
# 优先委托给 handler 处理
if
self
.
_parent
and
hasattr
(
self
.
_parent
,
'model_set_handler'
):
print
(
"[OK] 使用 handler 加载模型"
)
self
.
_parent
.
model_set_handler
.
loadModelsFromConfig
()
else
:
# 备用方案:如果 handler 未初始化,使用本地方法加载
print
(
"[警告] handler 未初始化,使用本地方法加载模型"
)
self
.
_loadModelsLocally
()
except
Exception
as
e
:
...
...
@@ -1131,7 +1110,6 @@ class ModelSetPage(QtWidgets.QWidget):
# 1. 加载配置文件
config
=
self
.
_loadConfigFile
()
if
not
config
:
print
(
"[警告] 无法加载配置文件"
)
return
# 2. 从配置文件提取通道模型
...
...
@@ -1161,11 +1139,9 @@ class ModelSetPage(QtWidgets.QWidget):
if
len
(
all_models
)
>
0
:
self
.
model_set_list
.
setCurrentRow
(
0
)
print
(
f
"[OK] 本地加载完成,共加载 {len(all_models)} 个模型"
)
self
.
_updateModelOrder
()
except
Exception
as
e
:
print
(
f
"[错误] 本地加载模型失败: {e}"
)
import
traceback
traceback
.
print_exc
()
...
...
@@ -1176,17 +1152,13 @@ class ModelSetPage(QtWidgets.QWidget):
config_path
=
current_dir
/
"database"
/
"config"
/
"default_config.yaml"
if
not
config_path
.
exists
():
print
(
f
"[警告] 配置文件不存在: {config_path}"
)
return
None
print
(
f
"[信息] 正在加载配置文件: {config_path}"
)
with
open
(
config_path
,
'r'
,
encoding
=
'utf-8'
)
as
f
:
config
=
yaml
.
safe_load
(
f
)
print
(
f
"[信息] 配置文件加载成功"
)
return
config
except
Exception
as
e
:
print
(
f
"[错误] 加载配置文件失败: {e}"
)
return
None
def
_extractChannelModels
(
self
,
config
):
...
...
@@ -1224,9 +1196,6 @@ class ModelSetPage(QtWidgets.QWidget):
'source'
:
'config'
,
'is_default'
:
i
==
1
# channel1 作为默认
})
print
(
f
" [信息] 找到通道{i}模型: {model_path}"
)
elif
model_path
:
print
(
f
" [警告] 通道{i}模型路径不存在: {model_path}"
)
return
models
...
...
@@ -1247,11 +1216,8 @@ class ModelSetPage(QtWidgets.QWidget):
for
model_dir
,
dir_type
in
model_dirs
:
if
not
model_dir
.
exists
():
print
(
f
"[信息] {dir_type}目录不存在: {model_dir}"
)
continue
print
(
f
"[信息] 正在扫描{dir_type}目录: {model_dir}"
)
# 遍历所有子目录,并按目录名排序(确保模型1最先)
sorted_subdirs
=
sorted
(
model_dir
.
iterdir
(),
key
=
lambda
x
:
x
.
name
if
x
.
is_dir
()
else
''
)
...
...
@@ -1267,7 +1233,6 @@ class ModelSetPage(QtWidgets.QWidget):
'format'
:
'dat'
,
'model_type'
:
dir_type
})
print
(
f
"[信息] 找到{dir_type}: {model_file}"
)
# 然后查找 .pt 文件
for
model_file
in
sorted
(
subdir
.
glob
(
"*.pt"
)):
...
...
@@ -1279,12 +1244,8 @@ class ModelSetPage(QtWidgets.QWidget):
'format'
:
'pt'
,
'model_type'
:
dir_type
})
print
(
f
"[信息] 找到{dir_type}: {model_file}"
)
print
(
f
"[信息] 模型扫描完成,共找到 {len(models)} 个模型"
)
except
Exception
as
e
:
print
(
f
"[错误] 扫描模型目录失败: {e}"
)
import
traceback
traceback
.
print_exc
()
...
...
@@ -1451,16 +1412,13 @@ class ModelSetPage(QtWidgets.QWidget):
if
config_path
.
exists
():
import
shutil
shutil
.
copy2
(
config_path
,
backup_path
)
print
(
f
" [信息] 已备份配置文件: {backup_path}"
)
# 保存新配置
with
open
(
config_path
,
'w'
,
encoding
=
'utf-8'
)
as
f
:
yaml
.
dump
(
config
,
f
,
default_flow_style
=
False
,
allow_unicode
=
True
,
indent
=
2
)
print
(
f
" [信息] 配置文件已保存: {config_path}"
)
return
True
except
Exception
as
e
:
print
(
f
" [错误] 保存配置文件失败: {e}"
)
return
False
...
...
@@ -1470,7 +1428,6 @@ if __name__ == "__main__":
app
=
QtWidgets
.
QApplication
(
sys
.
argv
)
# 测试模型参数数据
test_model_params
=
{
"预训练模型1"
:
{
"name"
:
"预训练模型1(默认)"
,
...
...
@@ -1536,11 +1493,6 @@ if __name__ == "__main__":
page
.
addModelToList
(
"预训练模型2"
)
page
.
addModelToList
(
"预训练模型3"
)
# 连接信号
page
.
modelSetClicked
.
connect
(
lambda
name
:
print
(
f
" 点击模型: {name}"
))
page
.
addSetClicked
.
connect
(
lambda
:
print
(
" 添加模型集"
))
page
.
modelSettingsClicked
.
connect
(
lambda
:
print
(
" 模型设置"
))
window
.
show
()
sys
.
exit
(
app
.
exec_
())
widgets/modelpage/training_page.py
View file @
872a1442
...
...
@@ -104,14 +104,12 @@ class TrainingPage(QtWidgets.QWidget):
if
self
.
_log_font_size
<
14
:
# 最大字体大小限制
self
.
_log_font_size
+=
1
self
.
_updateLogFontSize
()
print
(
f
"[日志字体] 字体大小增加到: {self._log_font_size}pt"
)
def
_decreaseFontSize
(
self
):
"""减少日志字体大小"""
if
self
.
_log_font_size
>
6
:
# 最小字体大小限制
self
.
_log_font_size
-=
1
self
.
_updateLogFontSize
()
print
(
f
"[日志字体] 字体大小减少到: {self._log_font_size}pt"
)
def
_updateLogFontSize
(
self
):
"""更新日志字体大小"""
...
...
@@ -132,7 +130,6 @@ class TrainingPage(QtWidgets.QWidget):
"""清空日志内容"""
self
.
train_log_text
.
clear
()
self
.
train_log_text
.
setPlainText
(
"日志已清空...
\n
系统已就绪,请配置参数后点击
\"
开始升级
\"
按钮。"
)
print
(
"[日志] 日志内容已清空"
)
def
_initUI
(
self
):
"""初始化UI"""
...
...
@@ -158,7 +155,7 @@ class TrainingPage(QtWidgets.QWidget):
ResponsiveLayout
.
apply_to_widget
(
params_group
,
min_height
=
380
,
max_height
=
420
)
left_layout
.
addWidget
(
params_group
,
0
)
# 不设置伸缩因子,保持固定尺寸
#
=== 模型测试区域 ===
#
模型测试区域
test_group
=
QtWidgets
.
QWidget
()
test_group
.
setStyleSheet
(
"""
...
...
@@ -254,12 +251,9 @@ class TrainingPage(QtWidgets.QWidget):
# 将视频面板添加到显示布局中
display_layout
.
addWidget
(
self
.
video_panel
)
print
(
f
"[视频面板] 已创建并添加到显示布局"
)
# 🔥 设置整体窗口的最小尺寸 - 使用响应式布局
min_w
,
min_h
=
scale_w
(
1000
),
scale_h
(
700
)
self
.
setMinimumSize
(
min_w
,
min_h
)
print
(
f
"[窗口] 最小尺寸设置: {min_w}x{min_h} (响应式)"
)
# 将左侧显示面板添加到 splitter
test_group_splitter
.
addWidget
(
left_display_widget
)
...
...
@@ -279,7 +273,6 @@ class TrainingPage(QtWidgets.QWidget):
right_control_layout
=
QtWidgets
.
QVBoxLayout
(
right_control_widget
)
ResponsiveLayout
.
apply_to_layout
(
right_control_layout
,
base_spacing
=
12
,
base_margins
=
12
)
# 测试模型选择
test_model_layout
=
QtWidgets
.
QVBoxLayout
()
from
..responsive_layout
import
scale_spacing
test_model_layout
.
setSpacing
(
scale_spacing
(
5
))
...
...
@@ -309,7 +302,6 @@ class TrainingPage(QtWidgets.QWidget):
test_model_layout
.
addWidget
(
self
.
test_model_combo
)
right_control_layout
.
addLayout
(
test_model_layout
)
# 测试文件选择
test_file_layout
=
QtWidgets
.
QVBoxLayout
()
from
..responsive_layout
import
scale_spacing
test_file_layout
.
setSpacing
(
scale_spacing
(
5
))
...
...
@@ -342,16 +334,13 @@ class TrainingPage(QtWidgets.QWidget):
# 添加垂直间距
right_control_layout
.
addSpacing
(
20
)
# 测试按钮区域(水平布局,与"开始升级"按钮保持一致)
test_button_layout
=
QtWidgets
.
QHBoxLayout
()
ResponsiveLayout
.
apply_to_layout
(
test_button_layout
,
base_spacing
=
10
,
base_margins
=
0
)
# 开始标注按钮(使用系统默认样式 + 响应式布局)
self
.
start_annotation_btn
=
QtWidgets
.
QPushButton
(
"开始标注"
)
self
.
start_annotation_btn
.
setMinimumWidth
(
scale_w
(
80
))
test_button_layout
.
addWidget
(
self
.
start_annotation_btn
)
# 开始测试按钮(使用系统默认样式 + 响应式布局)
self
.
start_test_btn
=
QtWidgets
.
QPushButton
(
"开始测试"
)
self
.
start_test_btn
.
setMinimumWidth
(
scale_w
(
80
))
test_button_layout
.
addWidget
(
self
.
start_test_btn
)
...
...
@@ -362,7 +351,6 @@ class TrainingPage(QtWidgets.QWidget):
# 底部弹性空间
right_control_layout
.
addStretch
()
# 初始化测试状态标志
self
.
_is_testing
=
False
# 将右侧控件添加到 splitter
...
...
@@ -374,8 +362,7 @@ class TrainingPage(QtWidgets.QWidget):
test_group_splitter
.
setStretchFactor
(
0
,
6
)
# 左侧伸缩因子(6:1 比例)
test_group_splitter
.
setStretchFactor
(
1
,
1
)
# 右侧伸缩因子
# 将测试组添加到左侧布局,设置伸缩因子为1,让它占据剩余空间
left_layout
.
addWidget
(
test_group
,
1
)
# 🔥 设置伸缩因子为1,让测试组占据剩余空间
left_layout
.
addWidget
(
test_group
,
1
)
# === 右侧:日志输出区 ===
right_widget
=
QtWidgets
.
QWidget
()
...
...
@@ -467,12 +454,10 @@ class TrainingPage(QtWidgets.QWidget):
right_min_w
=
scale_w
(
500
)
left_widget
.
setMinimumWidth
(
left_min_w
)
# 左侧最小宽度,保证参数文字可读
right_widget
.
setMinimumWidth
(
right_min_w
)
# 增加右侧最小宽度,确保训练指标在同一行显示
print
(
f
"[分栏器] 最小尺寸: 左侧={left_min_w}px, 右侧={right_min_w}px (响应式)"
)
# 🔥 调整分栏器比例 - 给训练日志更多空间 (左:右 = 2:3)
content_splitter
.
setStretchFactor
(
0
,
2
)
# 左侧测试区域
content_splitter
.
setStretchFactor
(
1
,
3
)
# 右侧日志区域获得更多空间
print
(
f
"[分栏器] 比例设置: 左侧(测试)=40
%
, 右侧(日志)=60
%
"
)
main_layout
.
addWidget
(
content_splitter
)
...
...
@@ -480,8 +465,7 @@ class TrainingPage(QtWidgets.QWidget):
self
.
start_train_btn
.
clicked
.
connect
(
self
.
startTrainingClicked
.
emit
)
self
.
stop_train_btn
.
clicked
.
connect
(
self
.
_onStopOrContinueClicked
)
# 初始化日志字体大小
print
(
f
"[日志字体] 初始化字体大小: {self._log_font_size}pt"
)
pass
def
_createParametersGroup
(
self
):
"""创建参数配置组"""
...
...
@@ -712,8 +696,6 @@ class TrainingPage(QtWidgets.QWidget):
# 应用到整个group(包括标签)
FontManager
.
applyToWidgetRecursive
(
group
)
print
(
f
"✅ [升级参数配置] 已应用全局字体管理器到所有文本框和控件"
)
return
group
def
_browseModel
(
self
):
...
...
@@ -901,9 +883,6 @@ class TrainingPage(QtWidgets.QWidget):
self
.
stop_train_btn
.
update
()
self
.
start_train_btn
.
update
()
print
(
f
"[DEBUG] switchToContinueMode 完成,按钮文本已改为: {self.stop_train_btn.text()}"
)
print
(
f
"[DEBUG] stop_train_btn.isEnabled()={self.stop_train_btn.isEnabled()}, start_train_btn.isEnabled()={self.start_train_btn.isEnabled()}"
)
def
switchToStopMode
(
self
):
"""切换到停止升级模式(使用系统默认样式)"""
self
.
_is_training_stopped
=
False
...
...
@@ -1059,10 +1038,9 @@ class TrainingPage(QtWidgets.QWidget):
'source'
:
'train_model'
,
'format'
:
'pt'
})
print
(
f
"[TrainingPage] 扫描到 .pt 模型: {model_file.name}"
)
except
Exception
as
e
:
p
rint
(
f
"[TrainingPage] 扫描模型目录失败: {e}"
)
p
ass
return
models
...
...
@@ -1169,7 +1147,6 @@ class TrainingPage(QtWidgets.QWidget):
return
remembered
except
Exception
as
e
:
print
(
f
"[TrainingPage] 读取测试模型记忆失败: {e}"
)
return
None
def
_saveTestModelMemory
(
self
,
model_path
):
...
...
@@ -1189,10 +1166,8 @@ class TrainingPage(QtWidgets.QWidget):
with
open
(
config_path
,
'w'
,
encoding
=
'utf-8'
)
as
f
:
yaml
.
dump
(
config
,
f
,
allow_unicode
=
True
,
default_flow_style
=
False
)
print
(
f
"[TrainingPage] 已保存测试模型记忆: {model_path}"
)
except
Exception
as
e
:
p
rint
(
f
"[TrainingPage] 保存测试模型记忆失败: {e}"
)
p
ass
def
getSelectedTestModel
(
self
):
"""获取选中的测试模型路径"""
...
...
@@ -1209,6 +1184,10 @@ class TrainingPage(QtWidgets.QWidget):
# 🔥 改为从QComboBox获取数据
return
self
.
test_file_input
.
currentData
()
or
""
def
isTestingInProgress
(
self
):
"""检查是否正在测试中"""
return
self
.
_is_testing
def
setTestButtonState
(
self
,
is_testing
):
"""设置测试按钮状态(开始测试 <-> 停止测试)"""
self
.
_is_testing
=
is_testing
...
...
@@ -1230,25 +1209,16 @@ class TrainingPage(QtWidgets.QWidget):
QPushButton:hover {
background-color: #c82333;
}
QPushButton:pressed {
background-color: #bd2130;
}
"""
)
print
(
"[TrainingPage] 按钮状态已切换: 停止测试"
)
else
:
# 切换为"开始测试"状态
self
.
start_test_btn
.
setText
(
"开始测试"
)
self
.
start_test_btn
.
setStyleSheet
(
""
)
# 清除样式,恢复系统默认
print
(
"[TrainingPage] 按钮状态已切换: 开始测试"
)
def
isTestingInProgress
(
self
):
"""检查是否正在测试中"""
return
self
.
_is_testing
# 恢复默认样式
self
.
start_test_btn
.
setStyleSheet
(
""
)
def
_onTemplateChecked
(
self
,
button
):
"""处理模板复选框选中事件"""
template_num
=
self
.
template_button_group
.
id
(
button
)
print
(
f
"[模板] 用户选择了模板{template_num}"
)
# 加载并应用模板配置
self
.
_loadTemplateConfig
(
template_num
)
...
...
@@ -1266,18 +1236,15 @@ class TrainingPage(QtWidgets.QWidget):
config_path
=
project_root
/
"database"
/
"config"
/
"train_configs"
/
f
"template_{template_num}.yaml"
if
not
config_path
.
exists
():
print
(
f
"[模板] ⚠️ 配置文件不存在: {config_path}"
)
return
try
:
with
open
(
config_path
,
'r'
,
encoding
=
'utf-8'
)
as
f
:
config
=
yaml
.
safe_load
(
f
)
print
(
f
"[模板] ✅ 成功加载模板{template_num}配置"
)
self
.
_applyTemplateConfig
(
config
,
template_num
)
except
Exception
as
e
:
print
(
f
"[模板] ❌ 加载配置失败: {e}"
)
import
traceback
traceback
.
print_exc
()
...
...
widgets/style_manager.py
View file @
872a1442
...
...
@@ -964,12 +964,10 @@ if __name__ == '__main__':
# 应用全局字体
FontManager
.
applyToApplication
(
app
)
# 创建测试窗口
window
=
QtWidgets
.
QWidget
()
window
.
setWindowTitle
(
"统一样式管理器测试"
)
layout
=
QtWidgets
.
QVBoxLayout
()
# 测试字体管理器
label1
=
QtWidgets
.
QLabel
(
"默认字体 - Microsoft YaHei 12pt Normal"
)
applyDefaultFont
(
label1
)
layout
.
addWidget
(
label1
)
...
...
@@ -978,14 +976,12 @@ if __name__ == '__main__':
applyTitleFont
(
label2
)
layout
.
addWidget
(
label2
)
# 测试按钮样式管理器
button1
=
createTextButton
(
"确认"
,
parent
=
window
)
layout
.
addWidget
(
button1
)
button2
=
createTextButton
(
"这是一个很长的按钮文本"
,
parent
=
window
)
layout
.
addWidget
(
button2
)
# 测试标题文本框样式管理器
title_group
,
title_text
=
createTitleTextBox
(
window
,
"模型描述"
,
...
...
@@ -994,7 +990,6 @@ if __name__ == '__main__':
)
layout
.
addWidget
(
title_group
)
# 测试对话框管理器
def
test_dialog
():
result
=
show_question
(
window
,
"确认"
,
"确定要执行此操作吗?"
)
if
result
:
...
...
widgets/videopage/channelpanel.py
View file @
872a1442
...
...
@@ -443,12 +443,9 @@ class ChannelPanel(QtWidgets.QWidget):
# 只显示文件夹名称
text
=
task_folder_name
.
strip
()
self
.
taskLabel
.
setText
(
text
)
# 有任务时启用面板
self
.
_setDisabled
(
False
)
else
:
# 没有任务信息时显示未分配任务
self
.
taskLabel
.
setText
(
"未分配任务"
)
# 无任务时禁用面板
self
.
_setDisabled
(
True
)
self
.
taskLabel
.
adjustSize
()
...
...
@@ -458,7 +455,6 @@ class ChannelPanel(QtWidgets.QWidget):
def
clearTaskInfo
(
self
):
"""清空任务信息显示"""
self
.
taskLabel
.
setText
(
"未分配任务"
)
# 清空任务时禁用面板
self
.
_setDisabled
(
True
)
self
.
taskLabel
.
adjustSize
()
self
.
_positionTaskLabel
()
...
...
@@ -560,27 +556,15 @@ class ChannelPanel(QtWidgets.QWidget):
current_mission: 当前任务路径或名称
"""
if
self
.
_debug_mode
and
hasattr
(
self
,
'debugLabel'
):
# 🔥 添加调试信息
# print(f"[DEBUG] setCurrentMission 被调用: current_mission = {current_mission}")
if
current_mission
:
mission_str
=
str
(
current_mission
)
.
strip
()
# print(f"[DEBUG] mission_str = '{mission_str}'")
# 检查是否是 "None" 字符串或空字符串
if
mission_str
.
lower
()
==
'none'
or
not
mission_str
:
# print(f"[DEBUG] 设置为未分配任务(mission_str为空或'none')")
self
.
debugLabel
.
setText
(
"未分配任务"
)
else
:
# 🔥 修改:显示完整的 current_mission 值
# 统一路径分隔符为 /
normalized_path
=
mission_str
.
replace
(
'
\\
'
,
'/'
)
# print(f"[DEBUG] 设置debug标签为: {normalized_path}")
# 直接显示完整路径
self
.
debugLabel
.
setText
(
normalized_path
)
else
:
# print(f"[DEBUG] current_mission 为 None,设置debug标签为'未分配任务'")
self
.
debugLabel
.
setText
(
"未分配任务"
)
self
.
debugLabel
.
adjustSize
()
...
...
@@ -752,13 +736,10 @@ if __name__ == "__main__":
# 创建通道面板(现在是QWidget,可以直接作为中央部件)
channel_panel
=
ChannelPanel
(
"通道1"
,
main_window
)
# 连接信号用于测试
def
on_channel_connected
(
cam_id
):
# 模拟连接成功后切换状态
channel_panel
.
setConnected
(
True
)
def
on_channel_disconnected
(
cam_id
):
# 模拟断开成功后切换状态
channel_panel
.
setConnected
(
False
)
def
on_curve_clicked
():
...
...
@@ -775,8 +756,6 @@ if __name__ == "__main__":
channel_panel
.
curveClicked
.
connect
(
on_curve_clicked
)
channel_panel
.
amplifyClicked
.
connect
(
on_amplify_clicked
)
channel_panel
.
channelEdited
.
connect
(
on_edit_clicked
)
# 添加测试数据
test_channels
=
[
{
'id'
:
str
(
uuid
.
uuid4
()),
...
...
widgets/videopage/curvepanel.py
View file @
872a1442
...
...
@@ -784,8 +784,6 @@ class CurvePanel(QtWidgets.QWidget):
if
self
.
chk_auto_follow
.
isChecked
():
min_time
=
max_time
-
view_width
self
.
plot_widget
.
setXRange
(
min_time
,
max_time
,
padding
=
0
)
# 调试:打印X轴范围
# print(f"X轴范围: {datetime.datetime.fromtimestamp(min_time).strftime('%H:%M:%S')} ~ {datetime.datetime.fromtimestamp(max_time).strftime('%H:%M:%S')}")
def
setYRangeAuto
(
self
,
max_value
):
"""
...
...
@@ -930,7 +928,6 @@ if __name__ == "__main__":
curve_panel
=
CurvePanel
()
window
.
setCentralWidget
(
curve_panel
)
# 添加测试通道(通道名_检测窗口名)
curve_panel
.
addChannel
(
"channel1"
,
"通道1"
,
"1"
,
"#1f77b4"
)
curve_panel
.
addChannel
(
"channel2"
,
"通道2"
,
"2"
,
"#ff7f0e"
)
curve_panel
.
addChannel
(
"channel3"
,
"通道3"
,
"3"
,
"#2ca02c"
)
...
...
@@ -945,7 +942,6 @@ if __name__ == "__main__":
# 计算经过的秒数(用于生成波形)
elapsed
=
(
current_time
-
start_time
)
.
total_seconds
()
# 为每个通道生成不同的测试数据(整数值,范围在0-23之间)
value1
=
int
(
16
+
3
*
np
.
sin
(
elapsed
*
0.1
)
+
np
.
random
.
randn
()
*
1.5
)
value2
=
int
(
15
+
2
*
np
.
cos
(
elapsed
*
0.15
)
+
np
.
random
.
randn
()
*
1.2
)
value3
=
int
(
14
+
3
*
np
.
sin
(
elapsed
*
0.08
+
1
)
+
np
.
random
.
randn
()
*
1.0
)
...
...
@@ -965,51 +961,19 @@ if __name__ == "__main__":
timer
.
timeout
.
connect
(
update_test_data
)
timer
.
start
(
100
)
# 100ms更新一次,10Hz刷新率
# 连接信号测试
def
on_curve_updated
(
channel_id
,
t
,
v
):
pass
# 静默处理,避免打印过多
def
on_channel_switched
(
channel_id
):
p
rint
(
f
" 切换到通道: {channel_id}"
)
p
ass
def
on_data_exported
(
channel_id
,
file_path
):
p
rint
(
f
" 数据已导出: {channel_id} -> {file_path}"
)
p
ass
curve_panel
.
curveDataUpdated
.
connect
(
on_curve_updated
)
curve_panel
.
channelSwitched
.
connect
(
on_channel_switched
)
curve_panel
.
dataExported
.
connect
(
on_data_exported
)
print
(
"
\n
"
+
"="
*
70
)
print
(
" CurvePanel 高性能曲线面板测试程序"
)
print
(
"="
*
70
)
print
(
" 已添加3个测试通道(通道1_1、通道2_2、通道3_3)"
)
print
(
" 正在模拟实时数据更新 (10Hz)"
)
print
(
"
\n
数据格式:"
)
print
(
" - CSV格式:时间戳 数值"
)
print
(
" - 时间格式:2025-09-28-17:37:39.220"
)
print
(
" - 文件名:通道名_检测窗口名称_时间戳.csv"
)
print
(
" - 图例显示:通道名_检测窗口名"
)
print
(
"
\n
可以尝试以下操作:"
)
print
(
" 通道管理:"
)
print
(
" - 下拉框切换不同通道"
)
print
(
" - 点击'添加通道'动态添加新通道"
)
print
(
" - 点击'清空数据'清除当前通道数据"
)
print
(
"
\n
视图控制:"
)
print
(
" - 鼠标左键拖动:左右平移(X轴)或上下平移(Y轴)"
)
print
(
" - 鼠标滚轮:放大/缩小时间轴或数值轴"
)
print
(
" - 点击'查看全部':显示所有历史数据(自动调整XY轴)"
)
print
(
" - 点击'重置视图':显示最新10秒数据(Y:0-23)"
)
print
(
" - '自动跟随'开关:启用后自动滚动显示最新时间数据"
)
print
(
" - '自动Y轴'开关:启用后自动扩展Y轴以适应数据"
)
print
(
"
\n
数据操作:"
)
print
(
" - 点击'导出数据':保存为CSV文件"
)
print
(
" - 点击'保存图片':导出为PNG图片"
)
print
(
" - '自动保存'开关:定时自动保存数据(每2分钟)"
)
print
(
"
\n
坐标轴:"
)
print
(
" - X轴(横坐标):时间戳,显示实际时间(HH:MM:SS),支持左右拖动和缩放"
)
print
(
" - Y轴(纵坐标):数值,范围0-23,支持上下拖动和缩放"
)
print
(
" - Y轴限制:最小值不能小于0(只显示非负值)"
)
print
(
"="
*
70
+
"
\n
"
)
window
.
show
()
sys
.
exit
(
app
.
exec_
())
...
...
widgets/videopage/general_set.py
View file @
872a1442
...
...
@@ -742,8 +742,7 @@ class GeneralSetPanel(QtWidgets.QWidget):
self
.
model_setting_widget
.
readModelDescriptionRequested
.
emit
(
absolute_path
)
except
Exception
as
e
:
import
traceback
traceback
.
print_exc
()
pass
def
_autoSaveModelPath
(
self
,
model_path
):
"""
...
...
@@ -1846,7 +1845,7 @@ class AnnotationWidget(QtWidgets.QWidget):
elif
self
.
annotation_engine
.
step
==
1
:
# 标注底部点模式:删除最后一个检测框(回退到画框模式)
if
len
(
self
.
annotation_engine
.
boxes
)
>
0
:
removed_box
=
self
.
annotation_engine
.
boxes
.
pop
()
self
.
annotation_engine
.
boxes
.
pop
()
# 同时删除对应的区域名称和高度
if
len
(
self
.
area_names
)
>
0
:
...
...
@@ -1863,7 +1862,7 @@ class AnnotationWidget(QtWidgets.QWidget):
elif
self
.
annotation_engine
.
step
==
2
:
# 标注顶部点模式:删除最后一个底部点(回退到标注底部点模式)
if
len
(
self
.
annotation_engine
.
bottom_points
)
>
0
:
removed_point
=
self
.
annotation_engine
.
bottom_points
.
pop
()
self
.
annotation_engine
.
bottom_points
.
pop
()
# 回到标注底部点模式
self
.
annotation_engine
.
step
=
1
...
...
@@ -1882,7 +1881,7 @@ class AnnotationWidget(QtWidgets.QWidget):
# 发送标注数据请求信号给handler
self
.
annotationDataRequested
.
emit
()
else
:
pass
return
def
keyPressEvent
(
self
,
event
):
"""键盘事件处理 - 增强版快捷键绑定"""
...
...
@@ -1955,27 +1954,27 @@ class AnnotationWidget(QtWidgets.QWidget):
return
# 删除检测框
removed_box
=
self
.
annotation_engine
.
boxes
.
pop
(
area_index
)
self
.
annotation_engine
.
boxes
.
pop
(
area_index
)
# 删除对应的底部点
if
area_index
<
len
(
self
.
annotation_engine
.
bottom_points
):
removed_bottom
=
self
.
annotation_engine
.
bottom_points
.
pop
(
area_index
)
self
.
annotation_engine
.
bottom_points
.
pop
(
area_index
)
# 删除对应的顶部点
if
area_index
<
len
(
self
.
annotation_engine
.
top_points
):
removed_top
=
self
.
annotation_engine
.
top_points
.
pop
(
area_index
)
self
.
annotation_engine
.
top_points
.
pop
(
area_index
)
# 删除对应的区域名称
if
area_index
<
len
(
self
.
area_names
):
removed_name
=
self
.
area_names
.
pop
(
area_index
)
self
.
area_names
.
pop
(
area_index
)
# 删除对应的区域高度
if
area_index
<
len
(
self
.
area_heights
):
removed_height
=
self
.
area_heights
.
pop
(
area_index
)
self
.
area_heights
.
pop
(
area_index
)
# 删除对应的区域状态
if
area_index
<
len
(
self
.
area_states
):
removed_state
=
self
.
area_states
.
pop
(
area_index
)
self
.
area_states
.
pop
(
area_index
)
# 更新显示
self
.
_updateDisplay
()
...
...
@@ -1996,8 +1995,6 @@ class AnnotationWidget(QtWidgets.QWidget):
self
.
_help_visible
=
not
self
.
_help_visible
pass
# 更新显示
self
.
_updateDisplay
()
...
...
@@ -2010,7 +2007,6 @@ class AnnotationWidget(QtWidgets.QWidget):
self
.
_ensureAreaConfig
(
area_index
)
# 设置高度
old_height
=
self
.
area_heights
[
area_index
]
self
.
area_heights
[
area_index
]
=
height_value
# 更新显示
...
...
@@ -2047,4 +2043,4 @@ class AnnotationWidget(QtWidgets.QWidget):
def
showAnnotationError
(
self
,
message
):
"""显示标注错误(由handler调用)"""
pass
return
widgets/videopage/historyvideopanel.py
View file @
872a1442
...
...
@@ -134,7 +134,7 @@ class HistoryVideoPanel(QtWidgets.QWidget):
self
.
nameLabel
.
setText
(
""
)
self
.
nameLabel
.
hide
()
# 隐藏标签
# 创建
debug模式静态文本控件(叠加在视频区域顶部中间,仅在debug
模式下显示)
# 创建
调试模式静态文本控件(叠加在视频区域顶部中间,仅在调试
模式下显示)
self
.
debugLabel
=
QtWidgets
.
QLabel
(
self
.
videoLabel
)
self
.
debugLabel
.
setText
(
""
)
# 初始为空,等待设置current_mission
self
.
debugLabel
.
setStyleSheet
(
"""
...
...
@@ -150,7 +150,7 @@ class HistoryVideoPanel(QtWidgets.QWidget):
self
.
debugLabel
.
adjustSize
()
# 定位到顶部中间
self
.
_positionDebugLabel
()
# 根据
debug_mode
控制显示/隐藏
# 根据
调试模式
控制显示/隐藏
self
.
debugLabel
.
setVisible
(
self
.
_debug_mode
)
# 🔥 历史视频面板不显示任务信息标签
...
...
@@ -780,7 +780,7 @@ if __name__ == "__main__":
# 连接信号用于测试
def
on_channel_name_changed
(
channel_id
,
new_name
):
p
rint
(
f
"📝 通道名称改变: {channel_id} -> {new_name}"
)
p
ass
history_panel
.
channelNameChanged
.
connect
(
on_channel_name_changed
)
...
...
widgets/videopage/logicsetting_dialogue.py
View file @
872a1442
...
...
@@ -116,7 +116,6 @@ if __name__ == "__main__":
app
=
QtWidgets
.
QApplication
(
sys
.
argv
)
# 测试逻辑配置数据
test_config
=
{
'detection_mode'
:
'continuous'
,
'high_threshold'
:
80.0
,
...
...
@@ -134,9 +133,6 @@ if __name__ == "__main__":
dialog
=
LogicSettingDialog
(
logic_config
=
test_config
,
channel_id
=
'channel1'
)
if
dialog
.
exec_
()
==
QtWidgets
.
QDialog
.
Accepted
:
config
=
dialog
.
getLogicConfig
()
print
(
"=== 获取的逻辑配置 ==="
)
for
key
,
value
in
config
.
items
():
print
(
f
"{key}: {value}"
)
sys
.
exit
(
0
)
widgets/videopage/missionpanel.py
View file @
872a1442
...
...
@@ -95,9 +95,6 @@ class MissionPanel(QtWidgets.QWidget):
self
.
_initUI
()
self
.
_connectSignals
()
# 🔥 暂时禁用全局字体管理器,测试是否解决重复显示问题
# FontManager.applyToWidgetRecursive(self)
# 自动应用默认配置
self
.
_applyDefaultConfig
()
...
...
@@ -757,7 +754,8 @@ class MissionPanel(QtWidgets.QWidget):
def
_testFontException
(
self
):
"""
测试字体例外机制是否正常工作
"""
"""
pass
def
updateRow
(
self
,
row_index
,
row_data
):
...
...
@@ -1421,8 +1419,6 @@ class MissionPanel(QtWidgets.QWidget):
if
not
addr
.
startswith
(
'rtsp://'
):
addr
=
'rtsp://'
+
addr
print
(
f
"🔧 [MissionPanel] 请求测试通道{channel_id}连接: {addr}"
)
# 发送调试信号给handler处理
self
.
channelDebugRequested
.
emit
(
channel_id
,
addr
)
...
...
@@ -1529,35 +1525,26 @@ class MissionPanel(QtWidgets.QWidget):
''
# 曲线列(按钮会自动添加)
]
self
.
addRow
(
row_data
,
task_info
)
print
(
f
" 任务已添加到表格: {task_info['task_name']}"
)
def
removeTaskRow
(
self
,
row_index
):
"""删除任务行(由handler调用)"""
if
self
.
removeRow
(
row_index
):
print
(
f
" 已删除任务行: {row_index}"
)
self
.
removeRow
(
row_index
)
def
_showWarningDialog
(
self
,
title
,
message
):
"""显示警告对话框(内部使用)"""
print
(
f
"
\n
[对话框调试] 创建警告对话框: {title}"
)
msg_box
=
QtWidgets
.
QMessageBox
(
self
)
msg_box
.
setWindowTitle
(
title
)
msg_box
.
setText
(
message
)
msg_box
.
setIcon
(
QtWidgets
.
QMessageBox
.
NoIcon
)
# 不显示内容区域图标
print
(
f
" - 设置内容区域图标: NoIcon"
)
# 设置左上角图标为系统警告图标
warning_icon
=
msg_box
.
style
()
.
standardIcon
(
QtWidgets
.
QStyle
.
SP_MessageBoxWarning
)
msg_box
.
setWindowIcon
(
warning_icon
)
print
(
f
" - 设置窗口图标: SP_MessageBoxWarning"
)
print
(
f
" - 窗口图标是否为空: {warning_icon.isNull()}"
)
# 移除帮助按钮
original_flags
=
msg_box
.
windowFlags
()
msg_box
.
setWindowFlags
(
msg_box
.
windowFlags
()
&
~
QtCore
.
Qt
.
WindowContextHelpButtonHint
)
print
(
f
" - 原始窗口标志: {original_flags}"
)
print
(
f
" - 最终窗口标志: {msg_box.windowFlags()}"
)
# 设置文字水平和垂直居中
msg_box
.
setStyleSheet
(
"""
...
...
@@ -1569,9 +1556,7 @@ class MissionPanel(QtWidgets.QWidget):
qproperty-alignment: 'AlignCenter';
}
"""
)
print
(
f
" - 已设置样式表"
)
print
(
f
" - 显示对话框..."
)
msg_box
.
exec_
()
def
showConfirmDialog
(
self
,
title
,
message
):
...
...
@@ -1679,17 +1664,15 @@ class MissionPanel(QtWidgets.QWidget):
def
_onFirstPage
(
self
):
"""跳转到首页"""
if
self
.
_current_page
!=
1
:
if
self
.
_current_page
>
1
:
self
.
_current_page
=
1
self
.
_updatePagination
()
print
(
f
" 跳转到首页"
)
def
_onPrevPage
(
self
):
"""上一页"""
if
self
.
_current_page
>
1
:
self
.
_current_page
-=
1
self
.
_updatePagination
()
print
(
f
" 上一页 -> 第 {self._current_page} 页"
)
def
_onNextPage
(
self
):
"""下一页"""
...
...
@@ -1697,7 +1680,6 @@ class MissionPanel(QtWidgets.QWidget):
if
self
.
_current_page
<
total_pages
:
self
.
_current_page
+=
1
self
.
_updatePagination
()
print
(
f
" 下一页 -> 第 {self._current_page} 页"
)
def
_onLastPage
(
self
):
"""跳转到末页"""
...
...
@@ -1705,7 +1687,6 @@ class MissionPanel(QtWidgets.QWidget):
if
self
.
_current_page
!=
total_pages
:
self
.
_current_page
=
total_pages
self
.
_updatePagination
()
print
(
f
" 跳转到末页"
)
def
_onGoToPage
(
self
):
"""跳转到指定页"""
...
...
@@ -1716,7 +1697,6 @@ class MissionPanel(QtWidgets.QWidget):
if
1
<=
page_num
<=
total_pages
:
self
.
_current_page
=
page_num
self
.
_updatePagination
()
print
(
f
" 跳转到第 {page_num} 页"
)
self
.
page_input
.
clear
()
else
:
self
.
_showWarningDialog
(
...
...
@@ -1754,7 +1734,7 @@ if __name__ == "__main__":
# 曲线按钮点击处理
def
on_curve_button_clicked
(
row
,
col
):
row_data
=
table
.
getRowData
(
row
)
p
rint
(
f
" 点击曲线按钮: 行 {row}, 数据: {row_data}"
)
p
ass
# 添加数据(按钮会自动添加)
for
row_data
,
user_data
in
test_data
:
...
...
@@ -1762,7 +1742,7 @@ if __name__ == "__main__":
# 连接按钮点击信号
def
on_button_signal
(
row
,
col
):
p
rint
(
f
" 按钮点击信号: 行 {row}, 列 {col}"
)
p
ass
table
.
buttonClicked
.
connect
(
on_button_signal
)
...
...
@@ -1770,15 +1750,14 @@ if __name__ == "__main__":
def
on_item_selected
(
row_index
):
row_data
=
table
.
getRowData
(
row_index
)
user_data
=
table
.
getUserData
(
row_index
)
print
(
f
" 选中行 {row_index}: {row_data}"
)
print
(
f
" 用户数据: {user_data}"
)
pass
def
on_item_double_clicked
(
row_index
):
row_data
=
table
.
getRowData
(
row_index
)
p
rint
(
f
" 双击行 {row_index}: {row_data}"
)
p
ass
def
on_item_changed
(
row
,
col
,
new_value
):
p
rint
(
f
" 单元格改变 ({row}, {col}): {new_value}"
)
p
ass
table
.
itemSelected
.
connect
(
on_item_selected
)
table
.
itemDoubleClicked
.
connect
(
on_item_double_clicked
)
...
...
widgets/videopage/modelsetting_dialogue.py
View file @
872a1442
...
...
@@ -282,8 +282,6 @@ class ModelSettingDialog(QtWidgets.QDialog):
if
model_path
:
self
.
readModelDescriptionRequested
.
emit
(
model_path
)
print
(
f
"[对话框] 用户双击选择模型: {model_path}"
)
def
_updateCurrentModelInfo
(
self
,
model_path
):
"""
更新当前模型信息显示
...
...
@@ -312,7 +310,6 @@ class ModelSettingDialog(QtWidgets.QDialog):
# 选中当前模型项
self
.
modelListWidget
.
setCurrentItem
(
item
)
item
.
setSelected
(
True
)
print
(
f
"[模型设置对话框] 已在列表中高亮当前模型: {model_path}"
)
break
def
_readModelDescription
(
self
,
model_path
):
...
...
@@ -395,7 +392,6 @@ if __name__ == "__main__":
app
=
QtWidgets
.
QApplication
(
sys
.
argv
)
# 测试模型配置数据
test_config
=
{
'model_path'
:
''
,
}
...
...
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