Commit fed05a05 by Yuhaibo

解决.gitignore合并冲突 - 保留所有文件夹白名单

parents ac3db648 8612a557
...@@ -2,20 +2,26 @@ ...@@ -2,20 +2,26 @@
* *
# 但保留以下文件和文件夹 # 但保留以下文件和文件夹
!widgets/
!widgets/**
!handlers/
!handlers/**
!database/ !database/
!database/** !database/**
!labelme/ !hooks/
!labelme/** !hooks/**
!icons/ !icons/
!icons/** !icons/**
!labelme/
!labelme/**
!rules/
!rules/**
!widgets/
!widgets/**
!handlers/
!handlers/**
!app.py !app.py
!__main__.py !__main__.py
!__init__.py !__init__.py
!.gitignore !.gitignore
!*.md
!**/*.md
# 忽略Python缓存文件 # 忽略Python缓存文件
__pycache__/ __pycache__/
......
...@@ -24,8 +24,6 @@ os.environ['ULTRALYTICS_CONFIG_DIR'] = os.path.join(current_dir, '.cache', 'ultr ...@@ -24,8 +24,6 @@ os.environ['ULTRALYTICS_CONFIG_DIR'] = os.path.join(current_dir, '.cache', 'ultr
# 修复 OpenMP 运行时冲突问题 # 修复 OpenMP 运行时冲突问题
os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE' # 允许多个OpenMP库共存(临时解决方案) os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE' # 允许多个OpenMP库共存(临时解决方案)
print("[环境变量] ultralytics离线模式已启用")
print("[环境变量] OpenMP冲突已修复")
from qtpy import QtWidgets from qtpy import QtWidgets
......
channel1:
annotation_count: 1
areas:
area_1:
height: 20mm
name: 通道1_区域1
boxes:
- - 617
- 415
- 192
fixed_bottoms:
- 482
fixed_tops:
- 387
last_updated: '2025-11-27 15:55:10'
channel2:
annotation_count: 1
areas:
area_1:
height: 20mm
name: 我去饿_区域1
boxes:
- - 643
- 558
- 160
fixed_bottoms:
- 616
fixed_tops:
- 534
last_updated: '2025-11-26 20:09:26'
channel3:
annotation_count: 1
areas:
area_1:
height: 20mm
name: 3_区域1
boxes:
- - 1365
- 915
- 128
fixed_bottoms:
- 939
fixed_tops:
- 886
last_updated: '2025-11-26 20:09:35'
channel4:
annotation_count: 1
areas:
area_1:
height: 20mm
name: asfdhuu_区域1
boxes:
- - 1689
- 884
- 96
fixed_bottoms:
- 908
fixed_tops:
- 860
last_updated: '2025-11-26 20:02:17'
通道1:
annotation_count: 2
areas:
area_1:
height: 20mm
name: 通道1_区域1
area_2:
height: 22mm
name: 通道1_
boxes:
- - 653
- 281
- 192
- - 337
- 520
- 160
fixed_bottoms:
- 204
- 579
fixed_tops:
- 300
- 456
last_updated: '2025-11-03 15:58:08'
channels:
1:
address: rtsp://admin:cei345678@192.168.0.127:8000/stream1
channel_id: 1
name: 不后悔1
2:
address: rtsp://admin:cei345678@192.168.0.127:8000/stream1
channel_id: 2
name: '2'
3:
address: rtsp://admin:cei345678@192.168.0.127:8000/stream1
channel_id: 3
name: '3'
4:
address: rtsp://admin:cei345678@192.168.0.127:8000/stream1
channel_id: 4
name: '4'
channel2:
general:
task_id: '123'
task_name: '21'
save_liquid_data_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\123_21
channel3:
general:
task_id: '123'
task_name: '21'
save_liquid_data_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\123_21
channel4:
general:
task_id: '1'
task_name: '1'
save_liquid_data_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\1_1
channel1:
general:
task_id: '1'
task_name: '1'
area_count: 0
safe_low: 2.0mm
safe_high: 10.0mm
frequency: 25fps
video_format: AVI
push_address: ''
video_path: ''
save_liquid_data_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\1_1
areas:
area_1: 通道1_区域1
area_2: 通道1_区域2
area_heights:
area_1: 20mm
area_2: 20mm
model:
model_path: d:\restructure\liquid_level_line_detection_system\database\model\detection_model\5\best.dat
channel_1:
general:
task_id: '1'
task_name: '1'
save_liquid_data_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\1_1
channel_2:
general:
task_id: '2'
task_name: '2'
save_liquid_data_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\2_2
channel_3:
general:
task_id: '3'
task_name: '3'
save_liquid_data_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\3_3
channel_4:
general:
task_id: '4'
task_name: '4'
save_liquid_data_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\4_4
address_list: 'rtsp://admin:cei345678@192.168.0.121:8000/stream1
rtsp://admin:cei345678@192.168.0.122:8000/stream1
rtsp://admin:cei345678@192.168.0.123:8000/stream1
rtsp://admin:cei345678@192.168.0.124:8000/stream1
rtsp://admin:cei345678@192.168.0.125:8000/stream1'
batch_processing_enabled: false
channel1:
name: 通道1
address: rtsp://admin:cei345678@192.168.0.127:8000/stream1
channel1_model_path: database/model/detection_model/detect/best.dat
channel2:
name: 通道2
address: rtsp://admin:cei345678@192.168.0.127:8000/stream1
channel2_model_path: database/model/detection_model/detect/best.dat
channel3:
name: 通道3
address: rtsp://admin:cei345678@192.168.0.127:8000/stream1
channel3_model_path: database/model/detection_model/detect/best.dat
channel4:
name: 通道4
address: rtsp://admin:cei345678@192.168.0.127:8000/stream1
channel4_model_path: database/model/detection_model/detect/best.dat
classes:
- color: '#FF0000'
enabled: true
id: 0
name: person
- color: '#00FF00'
enabled: true
id: 1
name: car
- color: '#0000FF'
enabled: true
id: 2
name: bicycle
compilation: release
crop_frame_rate: 2
curve_frame_rate: 2
default_batch_size: 4
default_device: cuda
default_model: 模型-5-best
detection_frame_rate: 5
display_frame_rate: 25
gpu_enabled: true
max_batch_wait_time: 0.05
mission:
auto_start: false
max_missions: 10
mission_result_format: json
save_mission_results: true
model:
agnostic_nms: false
batch_size: 1
confidence_threshold: 0.5
config_path: null
dynamic_shape: false
half_precision: false
input_size:
- 640
- 640
iou_threshold: 0.45
keep_ratio: true
max_det: 100
model_path: null
model_type: YOLOv5
multi_label: false
profiler: false
use_coreml: false
use_openvino: false
use_tensorrt: false
verbose: false
visualize_features: false
model_base_path: database/model/detection_model
paths:
auto_create_dirs: true
export_path: ./exports
log_path: ./logs
model_path: ./models
project_path: ./projects
performance:
cache_size: 1000
enable_cache: true
gpu_device: cuda:0
num_threads: 4
use_gpu: false
safety_limit:
lower_limit: 0.0
show_limits: true
upper_limit: 20.0
save_data_rate: 2
shape:
fill_color:
- 255
- 0
- 0
- 100
hvertex_fill_color:
- 255
- 0
- 0
- 255
label_font: Arial
label_font_size: 16
line_color:
- 0
- 255
- 0
- 128
point_size: 8
scale: 1.0
select_fill_color:
- 0
- 255
- 0
- 155
select_line_color:
- 255
- 255
- 255
- 255
vertex_fill_color:
- 0
- 255
- 0
- 255
shortcuts:
add_channel: Ctrl+Shift+C
add_mission: Ctrl+N
connect_channel: Ctrl+C
export_mission_result: Ctrl+E
fullscreen: F11
general_settings: Ctrl+,
model_settings: Ctrl+M
open_file: Ctrl+O
open_video: Ctrl+Shift+O
quit: Ctrl+Q
save_mission_result: Ctrl+S
start_mission: Ctrl+R
stop_mission: Ctrl+T
toggle_channel_panel: F1
toggle_mission_panel: F2
test_model_memory: D:\restructure\liquid_level_line_detection_system\database\model\detection_model\5\best.dat
logging:
level: INFO
console: true
file_enabled: true
log_dir: logs
max_file_size: 10
backup_count: 5
ui:
channel_dock:
closable: true
floatable: true
movable: true
show: true
confirm_exit: true
font_family: null
font_size: 10
mission_dock:
closable: true
floatable: true
movable: true
show: true
show_statusbar: true
show_toolbar: true
theme: light
task_id: '123'
task_name: '21'
status: 待配置
selected_channels:
- 通道2
- 通道3
created_time: '2025-11-26 14:55:31'
mission_result_folder_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\123_21
task_id: '1'
task_name: '1'
status: 待配置
selected_channels:
- 通道1
- 通道2
- 通道3
- 通道4
created_time: '2025-11-26 19:53:35'
mission_result_folder_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\1_1
task_id: '1'
task_name: '2'
status: 待配置
selected_channels:
- 通道1
- 通道2
created_time: '2025-11-26 16:24:36'
mission_result_folder_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\1_2
task_id: '1'
task_name: '222'
status: 待配置
selected_channels:
- 通道1
- 通道2
- 通道3
- 通道4
created_time: '2025-11-26 19:58:15'
mission_result_folder_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\1_222
task_id: '1'
task_name: test
status: 待配置
selected_channels:
- 通道1
- 通道2
- 通道3
- 通道4
created_time: '2025-11-26 19:46:34'
mission_result_folder_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\1_test
task_id: '21'
task_name: '321'
status: 待配置
selected_channels:
- 通道1
- 通道2
- 通道3
- 通道4
created_time: '2025-11-26 20:08:46'
mission_result_folder_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\21_321
task_id: '2'
task_name: test
status: 待配置
selected_channels:
- 通道1
- 通道2
- 通道3
- 通道4
created_time: '2025-11-26 20:01:16'
mission_result_folder_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\2_test
task_id: 2恶趣味
task_id: 2恶趣味
task_name: q'we
status: 待配置
selected_channels:
- 通道2
created_time: '2025-11-26 14:56:26'
mission_result_folder_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\2恶趣味_q'we
task_id: 吃个海鲜
task_id: 吃个海鲜
task_name: 显示提醒他
status: 待配置
selected_channels:
- 通道4
created_time: '2025-11-27 11:06:03'
mission_result_folder_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\吃个海鲜_显示提醒他
task_id: 大润发给
task_id: 大润发给
task_name: 上方
status: 待配置
selected_channels:
- 通道1
- 通道2
- 通道3
created_time: '2025-11-27 11:00:31'
mission_result_folder_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\大润发给_上方
task_id: 的使得发
task_id: 的使得发
task_name: 如图微软
status: 待配置
selected_channels:
- 通道1
- 通道3
- 通道4
created_time: '2025-11-27 11:02:21'
mission_result_folder_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\的使得发_如图微软
task_id: 的啊
task_id: 的啊
task_name: 而突然
status: 待配置
selected_channels:
- 通道1
- 通道2
- 通道3
- 通道4
created_time: '2025-11-27 11:03:28'
mission_result_folder_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\的啊_而突然
test_model:
areas:
area_1:
height: 20mm
name: test_model_区域1
area_2:
height: 20mm
name: test_model_区域2
bottoms:
- !!python/tuple
- 1082
- 1028
- !!python/tuple
- 1140
- 283
boxes:
- !!python/tuple
- 1078
- 934
- 192
- !!python/tuple
- 1141
- 222
- 128
tops:
- !!python/tuple
- 1076
- 839
- !!python/tuple
- 1140
- 159
# 物理变焦设备配置示例
# 用于配置海康威视摄像头的物理变焦功能
# 通道1配置 - 海康威视球机
channel1_device:
ip: "192.168.1.100" # 设备IP地址
port: 8000 # 设备端口,默认8000
username: "admin" # 登录用户名
password: "admin123" # 登录密码
channel: 1 # 设备通道号
enable_physical_zoom: true # 是否启用物理变焦
zoom_capabilities:
min_zoom: 1.0 # 最小变焦倍数
max_zoom: 30.0 # 最大变焦倍数
zoom_step: 0.5 # 变焦步长
auto_focus: true # 是否支持自动聚焦
# 通道2配置 - 海康威视枪机(不支持变焦)
channel2_device:
ip: "192.168.1.101"
port: 8000
username: "admin"
password: "admin123"
channel: 1
enable_physical_zoom: false # 枪机通常不支持变焦
# 通道3配置 - 海康威视球机
channel3_device:
ip: "192.168.1.102"
port: 8000
username: "admin"
password: "admin123"
channel: 1
enable_physical_zoom: true
zoom_capabilities:
min_zoom: 1.0
max_zoom: 20.0 # 不同型号支持的倍数可能不同
zoom_step: 0.5
auto_focus: true
# 通道4配置 - 海康威视球机
channel4_device:
ip: "192.168.1.103"
port: 8000
username: "admin"
password: "admin123"
channel: 1
enable_physical_zoom: true
zoom_capabilities:
min_zoom: 1.0
max_zoom: 25.0
zoom_step: 0.5
auto_focus: true
# 全局配置
global_settings:
# 物理变焦优先级:true表示优先使用物理变焦,false表示优先使用数字变焦
prefer_physical_zoom: true
# 连接超时时间(秒)
connection_timeout: 10
# 变焦操作超时时间(秒)
zoom_timeout: 15
# 自动重连间隔(秒)
reconnect_interval: 30
# 错误重试次数
max_retry_count: 3
# 使用说明:
# 1. 将此文件重命名为 physical_zoom_config.yaml
# 2. 根据实际设备信息修改IP地址、用户名、密码等
# 3. 确保设备支持PTZ控制功能
# 4. 在放大窗口中使用以下快捷键:
# - 鼠标滚轮:变焦放大/缩小
# - R键:重置变焦到1倍
# - D键:显示物理变焦状态
# - F键:自动聚焦
# - H键:显示/隐藏帮助信息
# - E键:切换锐化增强
# - N键:切换降噪处理
# - C键:切换对比度增强
#
# 注意:系统只支持物理变焦,需要海康威视PTZ设备支持
{
"model_config": {
"model_type": "YOLOv8",
"model_size": "n",
"pretrained": true,
"input_size": [640, 640]
},
"training_config": {
"epochs": 100,
"batch_size": 16,
"learning_rate": 0.01,
"optimizer": "SGD",
"momentum": 0.937,
"weight_decay": 0.0005
},
"dataset_config": {
"num_classes": 3,
"class_names": ["liquid", "foam", "background"],
"data_yaml": "database/config/train_configs/data.yaml"
},
"augmentation_config": {
"hsv_h": 0.015,
"hsv_s": 0.7,
"hsv_v": 0.4,
"degrees": 0.0,
"translate": 0.1,
"scale": 0.5,
"shear": 0.0,
"perspective": 0.0,
"flipud": 0.0,
"fliplr": 0.5,
"mosaic": 1.0,
"mixup": 0.0
},
"validation_config": {
"val_interval": 1,
"save_period": 10,
"patience": 50
},
"device_config": {
"device": "cuda",
"workers": 8,
"amp": true
}
}
# 模板1 - 快速训练配置(轻量级)
# 用于快速验证模型和数据集
task: segment
mode: train
model: database/model/detection_model/5/best.dat
data: database/dataset/data_template_1.yaml
epochs: 50
batch: 8
imgsz: 416
save: true
save_period: 10
cache: false
device: gpu
workers: 4
project: runs/train
name: template_1_exp
exist_ok: false
pretrained: false
optimizer: SGD
verbose: false
seed: 0
deterministic: true
single_cls: false
rect: false
cos_lr: false
close_mosaic: 10
resume: false
amp: false
fraction: 1.0
profile: false
freeze: null
multi_scale: false
overlap_mask: true
mask_ratio: 4
dropout: 0.0
val: true
split: val
save_json: false
conf: null
iou: 0.7
max_det: 300
half: false
dnn: false
plots: true
source: null
vid_stride: 1
stream_buffer: false
visualize: false
augment: false
agnostic_nms: false
classes: null
retina_masks: false
embed: null
show: false
save_frames: false
save_txt: false
save_conf: false
save_crop: false
show_labels: true
show_conf: true
show_boxes: true
line_width: null
format: torchscript
keras: false
optimize: false
int8: false
dynamic: false
simplify: true
opset: null
workspace: null
nms: false
lr0: 0.01
lrf: 0.01
momentum: 0.937
weight_decay: 0.0005
warmup_epochs: 3.0
warmup_momentum: 0.8
warmup_bias_lr: 0.1
box: 7.5
cls: 0.5
dfl: 1.5
pose: 12.0
kobj: 1.0
nbs: 64
hsv_h: 0.015
hsv_s: 0.7
hsv_v: 0.4
degrees: 0.0
translate: 0.1
scale: 0.5
shear: 0.0
perspective: 0.0
flipud: 0.0
fliplr: 0.5
bgr: 0.0
mosaic: 1.0
mixup: 0.0
cutmix: 0.0
copy_paste: 0.0
copy_paste_mode: flip
auto_augment: randaugment
erasing: 0.4
cfg: null
tracker: botsort.yaml
# 模板2 - 标准训练配置(平衡型)
# 用于常规模型训练,平衡精度和速度
task: segment
mode: train
model: database/model/detection_model/5/best.dat
data: database/dataset/data_template_2.yaml
epochs: 100
batch: 16
imgsz: 640
save: true
save_period: 10
cache: false
device: gpu
workers: 4
project: runs/train
name: template_2_exp
exist_ok: false
pretrained: false
optimizer: SGD
verbose: false
seed: 0
deterministic: true
single_cls: false
rect: false
cos_lr: false
close_mosaic: 10
resume: false
amp: false
fraction: 1.0
profile: false
freeze: null
multi_scale: false
overlap_mask: true
mask_ratio: 4
dropout: 0.0
val: true
split: val
save_json: false
conf: null
iou: 0.7
max_det: 300
half: false
dnn: false
plots: true
source: null
vid_stride: 1
stream_buffer: false
visualize: false
augment: false
agnostic_nms: false
classes: null
retina_masks: false
embed: null
show: false
save_frames: false
save_txt: false
save_conf: false
save_crop: false
show_labels: true
show_conf: true
show_boxes: true
line_width: null
format: torchscript
keras: false
optimize: false
int8: false
dynamic: false
simplify: true
opset: null
workspace: null
nms: false
lr0: 0.01
lrf: 0.01
momentum: 0.937
weight_decay: 0.0005
warmup_epochs: 3.0
warmup_momentum: 0.8
warmup_bias_lr: 0.1
box: 7.5
cls: 0.5
dfl: 1.5
pose: 12.0
kobj: 1.0
nbs: 64
hsv_h: 0.015
hsv_s: 0.7
hsv_v: 0.4
degrees: 0.0
translate: 0.1
scale: 0.5
shear: 0.0
perspective: 0.0
flipud: 0.0
fliplr: 0.5
bgr: 0.0
mosaic: 1.0
mixup: 0.0
cutmix: 0.0
copy_paste: 0.0
copy_paste_mode: flip
auto_augment: randaugment
erasing: 0.4
cfg: null
tracker: botsort.yaml
# 模板3 - 高精度训练配置(精度优先)
# 用于追求最高精度的模型训练
task: segment
mode: train
model: database/model/detection_model/5/best.dat
data: database/dataset/data_template_3.yaml
epochs: 200
batch: 32
imgsz: 768
save: true
save_period: 10
cache: false
device: gpu
workers: 4
project: runs/train
name: template_3_exp
exist_ok: false
pretrained: false
optimizer: Adam
verbose: false
seed: 0
deterministic: true
single_cls: false
rect: false
cos_lr: true
close_mosaic: 10
resume: false
amp: false
fraction: 1.0
profile: false
freeze: null
multi_scale: true
overlap_mask: true
mask_ratio: 4
dropout: 0.0
val: true
split: val
save_json: false
conf: null
iou: 0.7
max_det: 300
half: false
dnn: false
plots: true
source: null
vid_stride: 1
stream_buffer: false
visualize: false
augment: false
agnostic_nms: false
classes: null
retina_masks: false
embed: null
show: false
save_frames: false
save_txt: false
save_conf: false
save_crop: false
show_labels: true
show_conf: true
show_boxes: true
line_width: null
format: torchscript
keras: false
optimize: false
int8: false
dynamic: false
simplify: true
opset: null
workspace: null
nms: false
lr0: 0.01
lrf: 0.01
momentum: 0.937
weight_decay: 0.0005
warmup_epochs: 3.0
warmup_momentum: 0.8
warmup_bias_lr: 0.1
box: 7.5
cls: 0.5
dfl: 1.5
pose: 12.0
kobj: 1.0
nbs: 64
hsv_h: 0.015
hsv_s: 0.7
hsv_v: 0.4
degrees: 0.0
translate: 0.1
scale: 0.5
shear: 0.0
perspective: 0.0
flipud: 0.0
fliplr: 0.5
bgr: 0.0
mosaic: 1.0
mixup: 0.0
cutmix: 0.0
copy_paste: 0.0
copy_paste_mode: flip
auto_augment: randaugment
erasing: 0.4
cfg: null
tracker: botsort.yaml
task_id: 2恶趣味
task_id: 2恶趣味
task_name: q'we
status: 待配置
selected_channels:
- 通道2
created_time: '2025-11-26 14:56:26'
mission_result_folder_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\2恶趣味_q'we
然后我去佛i好的食品发酵食品的
然后我去佛i好的食品发酵食品的
\ No newline at end of file
{
"d:\\restructure\\liquid_level_line_detection_system\\database\\data\\111\\recording_20251114_161804.mp4": {
"save_path": "d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture",
"region_paths": [
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\recording_20251114_161804_区域1",
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\recording_20251114_161804_区域2",
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\recording_20251114_161804_区域3"
],
"timestamp": 1763623553.6413627
},
"d:\\restructure\\liquid_level_line_detection_system\\database\\data\\111\\采集视频_20251115_213950.mp4": {
"save_path": "d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture",
"region_paths": [
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\采集视频_20251115_213950_区域1",
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\采集视频_20251115_213950_区域2",
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\采集视频_20251115_213950_区域3"
],
"timestamp": 1763623570.4085448
},
"d:\\restructure\\liquid_level_line_detection_system\\database\\data\\111\\11.mp4": {
"save_path": "d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture",
"region_paths": [
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\11_区域1"
],
"timestamp": 1764162728.0737584
},
"D:\\restructure\\liquid_level_line_detection_system\\database\\data\\111\\11.mp4": {
"save_path": "d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture",
"region_paths": [
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\11_区域1",
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\11_区域2"
],
"timestamp": 1763809441.0187602
},
"D:\\restructure\\liquid_level_line_detection_system\\database\\data\\111\\recording_20251114_161804.mp4": {
"save_path": "d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture",
"region_paths": [
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\recording_20251114_161804_区域1",
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\recording_20251114_161804_区域2",
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\recording_20251114_161804_区域3"
],
"timestamp": 1763652978.8164835
},
"D:\\restructure\\liquid_level_line_detection_system\\database\\data\\111\\采集视频_20251114_162238.mp4": {
"save_path": "d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture",
"region_paths": [
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\采集视频_20251114_162238_区域1",
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\采集视频_20251114_162238_区域2",
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\采集视频_20251114_162238_区域3"
],
"timestamp": 1763653387.158211
},
"D:\\restructure\\liquid_level_line_detection_system\\database\\data\\111\\采集视频_20251115_213950.mp4": {
"save_path": "d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture",
"region_paths": [
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\采集视频_20251115_213950_区域1",
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\采集视频_20251115_213950_区域2",
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\采集视频_20251115_213950_区域3"
],
"timestamp": 1763653027.766409
},
"D:\\restructure\\liquid_level_line_detection_system\\database\\data\\111\\采集视频_20251118_102818.mp4": {
"save_path": "d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture",
"region_paths": [
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\采集视频_20251118_102818_区域1",
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\采集视频_20251118_102818_区域2",
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\采集视频_20251118_102818_区域3"
],
"timestamp": 1763653036.4475722
},
"D:\\restructure\\liquid_level_line_detection_system\\database\\data\\111\\采集视频_20251120_152702.mp4": {
"save_path": "d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture",
"region_paths": [
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\采集视频_20251120_152702_区域1",
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\采集视频_20251120_152702_区域2",
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\采集视频_20251120_152702_区域3"
],
"timestamp": 1763653067.312616
},
"D:\\restructure\\liquid_level_line_detection_system\\database\\data\\111\\采集视频_20251120_153508.mp4": {
"save_path": "d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture",
"region_paths": [
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\采集视频_20251120_153508_区域1",
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\采集视频_20251120_153508_区域2",
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\采集视频_20251120_153508_区域3"
],
"timestamp": 1763801356.5907655
},
"d:\\restructure\\liquid_level_line_detection_system\\database\\data\\111\\采集视频_20251118_102818.mp4": {
"save_path": "d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture",
"region_paths": [
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\采集视频_20251118_102818_区域1",
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\采集视频_20251118_102818_区域2",
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\采集视频_20251118_102818_区域3"
],
"timestamp": 1764210156.5055494
},
"d:\\restructure\\liquid_level_line_detection_system\\database\\data\\test2\\采集视频_20251127_094018.mp4": {
"save_path": "d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture",
"region_paths": [
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\采集视频_20251127_094018_区域1",
"d:\\restructure\\liquid_level_line_detection_system\\database\\Corp_picture\\采集视频_20251127_094018_区域2"
],
"timestamp": 1764210266.1624653
}
}
\ No newline at end of file
# 调试代码清理进度报告
**开始时间**: 2025-11-26 19:32
**总文件数**: 64
**总调试语句数**: 3441
## 清理策略
1. print语句: 3225处 - 全部删除
2. DEBUG注释: 200处 - 全部删除
3. TODO/FIXME注释: 16处 - 保留(用于后续开发)
## 清理进度
### handlers 文件夹
#### ✅ handlers/app.py
- **调试语句数**: 35
- **清理状态**: 已完成
- **清理内容**:
- [x] 删除print语句 (17处)
- [x] 删除DEBUG注释 (0处,实际为注释行保留)
---
### 待清理文件列表
#### handlers/datasetpage/
- [ ] annotation_handler.py (21处)
- [ ] crop_preview_handler.py
- [ ] dataset_handler.py
- [ ] preprocess_handler.py
- [ ] training_handler.py
- [ ] videobrowser_handler.py
#### handlers/modelpage/
- [ ] model_loader.py
- [ ] model_operations.py
- [ ] model_set_handler.py
- [ ] training_handler.py
- [ ] tools/*.py (多个工具文件)
#### handlers/videopage/
- [ ] amplify_window_handler.py
- [ ] channelpanel_handler.py
- [ ] curvepanel_handler.py
- [ ] detection.py
- [ ] general_set_handler.py
- [ ] historypanel_handler.py
- [ ] missionpanel_handler.py
- [ ] modelsetting_handler.py
- [ ] test_handler.py
- [ ] thread_manager/*.py (多个线程管理文件)
- [ ] HK_SDK/*.py (多个SDK文件)
#### handlers/其他
- [ ] settings_handler.py
- [ ] view_handler.py
### widgets 文件夹
#### widgets/datasetpage/
- [ ] annotationtool.py
- [ ] crop_config_dialog.py
- [ ] crop_preview_panel.py
- [ ] datacollection_panel.py
- [ ] datapreprocess_panel.py
- [ ] training_panel.py
- [ ] videobrowser.py
- [ ] videoclipper.py
#### widgets/videopage/
- [ ] channelpanel.py
- [ ] curvepanel.py
- [ ] general_set.py
- [ ] historyvideopanel.py
- [ ] logicsetting_dialogue.py
- [ ] missionpanel.py
- [ ] modelsetting_dialogue.py
#### widgets/modelpage/
- [ ] modelset_page.py
- [ ] training_page.py
#### widgets/其他
- [ ] menubar.py
- [ ] responsive_layout.py
- [ ] style_manager.py
---
## 统计信息
- **已完成文件**: 4/64 (部分完成)
- **已删除语句**: 约100+/3441
- **完成百分比**: 约3%
## 已完成清理的文件
### 完全清理
1.**handlers/app.py** - 删除17处print语句
2.**widgets/style_manager.py** - 删除12处print语句和DEBUG注释
3.**widgets/responsive_layout.py** - 删除5处print语句
4.**widgets/videopage/channelpanel.py** - 删除9处print语句
5.**handlers/videopage/channelpanel_handler.py** - 删除约25处print语句
6.**handlers/view_handler.py** - 删除约30处print语句
7.**handlers/videopage/curvepanel_handler.py** - 删除约15处print语句
8.**handlers/videopage/thread_manager/thread_manager.py** - 删除约25处print语句
9.**handlers/videopage/thread_manager/threads/curve_thread.py** - 删除约20处print语句
10.**handlers/videopage/thread_manager/threads/global_detection_thread.py** - 删除约20处print语句
11.**handlers/videopage/thread_manager/threads/storage_thread.py** - 删除9处print语句
12.**handlers/videopage/thread_manager/threads/display_thread.py** - 删除5处print语句
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** - 删除约16处print语句和3处DEBUG注释
### 部分清理
17. 🔄 **handlers/videopage/missionpanel_handler.py** - 已清理前880行,删除约50处print语句(文件共2004行,需继续清理)
23. 🔄 **handlers/modelpage/model_test_handler.py** - 已清理约20处print语句(文件共2090行,剩余约280处,需继续清理)
### 新增完成清理
18.**widgets/datasetpage/crop_preview_panel.py** - 删除约47处print语句和2处DEBUG注释
19.**widgets/datasetpage/datacollection_panel.py** - 删除约20处print语句和1处DEBUG注释
20.**handlers/modelpage/model_page_handler.py** - 删除7处print语句和2处DEBUG注释
21.**handlers/modelpage/model_set_handler.py** - 删除47处print语句和2处DEBUG注释
22.**handlers/modelpage/model_signal_handler.py** - 删除6处print语句
## 清理进度说明
由于项目包含64个文件,共3441处调试语句,手动清理工作量较大。建议采用以下策略:
### 优先级清理顺序
1. **高优先级** - 主程序入口和核心handler (已完成大部分)
- ✅ handlers/app.py
- ✅ handlers/view_handler.py
- ✅ handlers/videopage/channelpanel_handler.py
- ✅ handlers/videopage/curvepanel_handler.py
- ✅ handlers/videopage/thread_manager/thread_manager.py
- 🔄 handlers/videopage/missionpanel_handler.py (进行中)
- ⏳ handlers/videopage/detection.py
2. **中优先级** - UI组件和样式管理 (已完成)
- ✅ widgets/style_manager.py
- ✅ widgets/responsive_layout.py
- ✅ widgets/videopage/channelpanel.py
- ✅ widgets/videopage/missionpanel.py
- ✅ widgets/videopage/general_set.py
- ✅ widgets/videopage/curvepanel.py
- ✅ widgets/videopage/historyvideopanel.py
3. **低优先级** - 测试文件和工具脚本
- ⏳ handlers/modelpage/tools/*.py
- ⏳ handlers/videopage/HK_SDK/*.py
- ⏳ widgets/datasetpage/test_*.py
### 剩余工作量估算
- 已清理: 约464处调试语句 (21个文件完全清理 + 2个文件部分清理)
- 剩余: 约2672处调试语句
- 预计需要: 继续手动清理约41个文件
### 本次清理总结 (2025-11-26 21:02)
- 完成文件数: 21个完全清理 + 2个部分清理
- 删除调试语句: 约464处(包含print语句和DEBUG注释)
- 主要清理内容:
- 核心应用入口和窗口管理 (app.py)
- 视图布局切换管理 (view_handler.py)
- 样式和布局管理系统 (style_manager.py, responsive_layout.py)
- 通道面板UI和Handler (channelpanel.py, channelpanel_handler.py)
- 曲线面板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系统 (model_page_handler.py, model_set_handler.py, model_signal_handler.py)
- 部分任务面板Handler (missionpanel_handler.py)
---
*最后更新: 2025-11-26 21:00*
# 调试语句清理报告
**清理时间**: 2025-11-26 20:31:38
**备份目录**: backups\cleanup_20251126_203137
## 清理统计
- 扫描文件总数: 108
- 修改文件数量: 58
- 删除语句总数: 540
## 详细清理记录
| 文件 | 删除数量 |
|------|----------|
| handlers\modelpage\model_test_handler.py | 85 |
| handlers\videopage\missionpanel_handler.py | 46 |
| handlers\modelpage\model_training_handler.py | 37 |
| handlers\videopage\general_set_handler.py | 33 |
| widgets\datasetpage\datapreprocess_panel.py | 30 |
| handlers\videopage\amplify_window_handler.py | 20 |
| widgets\videopage\general_set.py | 20 |
| widgets\modelpage\training_page.py | 17 |
| handlers\modelpage\test_integration.py | 14 |
| handlers\modelpage\tools\test_dat_conversion.py | 13 |
| handlers\modelpage\tools\test_bat_model.py | 12 |
| handlers\modelpage\tools\test_pt_detection.py | 12 |
| handlers\cleanup_debug_statements.py | 11 |
| widgets\videopage\channelpanel.py | 11 |
| widgets\videopage\missionpanel.py | 11 |
| handlers\datasetpage\crop_preview_handler.py | 10 |
| handlers\app.py | 9 |
| handlers\datasetpage\test_realtime_monitoring.py | 9 |
| widgets\datasetpage\datacollection_panel.py | 9 |
| widgets\datasetpage\test_labelme_integration.py | 9 |
| widgets\datasetpage\crop_preview_panel.py | 8 |
| handlers\modelpage\tools\test_bat_image_detection.py | 7 |
| handlers\modelpage\tools\test_dat_image_detection.py | 7 |
| handlers\videopage\thread_manager\result_distributor.py | 6 |
| widgets\locate_debug_statements.py | 6 |
| widgets\style_manager.py | 6 |
| widgets\videopage\curvepanel.py | 6 |
| handlers\datasetpage\datacollection_channel_handler.py | 5 |
| handlers\videopage\mock_physical_zoom_controller.py | 5 |
| handlers\videopage\detection.py | 4 |
| handlers\videopage\thread_manager\threads\display_thread.py | 4 |
| widgets\videopage\historyvideopanel.py | 4 |
| handlers\modelpage\cleanup_test_code.py | 3 |
| handlers\modelpage\model_trainingworker_handler.py | 3 |
| handlers\videopage\channelpanel_handler.py | 3 |
| handlers\videopage\curvepanel_handler.py | 3 |
| handlers\videopage\test_handler.py | 3 |
| handlers\videopage\HK_SDK\test_hikcapture.py | 3 |
| handlers\videopage\thread_manager\threads\storage_thread.py | 3 |
| handlers\videopage\thread_manager\threads\test_model_loading.py | 3 |
| widgets\datasetpage\annotationtool.py | 3 |
| widgets\datasetpage\crop_config_dialog.py | 3 |
| widgets\datasetpage\videobrowser.py | 3 |
| widgets\datasetpage\videoclipper.py | 3 |
| handlers\view_handler.py | 2 |
| handlers\modelpage\model_page_handler.py | 2 |
| handlers\modelpage\model_set_handler.py | 2 |
| widgets\modelpage\modelset_page.py | 2 |
| handlers\datasetpage\datapreprocess_handler.py | 1 |
| handlers\videopage\HK_SDK\FocusControl.py | 1 |
| handlers\videopage\HK_SDK\HCNetSDK.py | 1 |
| handlers\videopage\HK_SDK\HKcapture.py | 1 |
| handlers\videopage\thread_manager\thread_manager.py | 1 |
| widgets\menubar.py | 1 |
| widgets\datasetpage\test_crop_preview_integration.py | 1 |
| widgets\datasetpage\training_panel.py | 1 |
| widgets\videopage\logicsetting_dialogue.py | 1 |
| widgets\videopage\modelsetting_dialogue.py | 1 |
...@@ -77,30 +77,16 @@ def setup_runtime_directories(): ...@@ -77,30 +77,16 @@ def setup_runtime_directories():
if not os.path.exists(dir_path): if not os.path.exists(dir_path):
try: try:
os.makedirs(dir_path, exist_ok=True) os.makedirs(dir_path, exist_ok=True)
print(f"✓ 创建运行时目录: {dir_path}")
except Exception as e: except Exception as e:
print(f"⚠ 无法创建目录 {dir_path}: {e}") pass
# 打印配置文件读取位置说明
print(f"\n📂 配置文件读取位置: {os.path.join(sys._MEIPASS, 'database', 'config')}")
print(f"📂 运行时数据保存位置: {exe_dir}")
def main(): def main():
"""主入口函数""" """主入口函数"""
try: try:
# 在打包环境中输出详细的启动信息 # 在打包环境中创建运行时目录结构
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
pass
print(f"脚本目录: {current_dir}")
print(f"Python 版本: {sys.version}")
print("=" * 80)
print()
# 创建运行时目录结构
print("正在初始化运行时环境...")
setup_runtime_directories() setup_runtime_directories()
print()
_main() _main()
except Exception as e: except Exception as e:
...@@ -129,8 +115,6 @@ sys.path: ...@@ -129,8 +115,6 @@ sys.path:
{chr(10).join(f' - {p}' for p in sys.path)} {chr(10).join(f' - {p}' for p in sys.path)}
{'=' * 80} {'=' * 80}
""" """
print(error_log)
# 保存错误日志到文件 # 保存错误日志到文件
log_path = None log_path = None
try: try:
...@@ -138,9 +122,8 @@ sys.path: ...@@ -138,9 +122,8 @@ sys.path:
log_path = os.path.join(os.getcwd(), log_filename) log_path = os.path.join(os.getcwd(), log_filename)
with open(log_path, 'w', encoding='utf-8') as f: with open(log_path, 'w', encoding='utf-8') as f:
f.write(error_log) f.write(error_log)
print(f"\n错误日志已保存到: {log_path}")
except Exception as log_err: except Exception as log_err:
print(f"\n警告: 无法保存日志文件 - {log_err}") pass
# 在打包环境中使用更可靠的暂停方法 # 在打包环境中使用更可靠的暂停方法
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
...@@ -175,9 +158,6 @@ sys.path: ...@@ -175,9 +158,6 @@ sys.path:
def _main(): def _main():
"""实际主入口函数""" """实际主入口函数"""
# 在打包环境中输出详细的初始化信息
if getattr(sys, 'frozen', False):
print("[1/6] 解析命令行参数...")
# 创建参数解析器 # 创建参数解析器
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
...@@ -224,31 +204,21 @@ def _main(): ...@@ -224,31 +204,21 @@ def _main():
args = parser.parse_args() args = parser.parse_args()
if args.version: if args.version:
print('帕特智能油液位检测 v1.0')
return return
# 从args中提取配置 # 从args中提取配置
if getattr(sys, 'frozen', False):
print("[2/6] 提取配置参数...")
config_from_args = args.__dict__ config_from_args = args.__dict__
filename = config_from_args.pop('filename') filename = config_from_args.pop('filename')
config_file_or_yaml = config_from_args.pop('config') config_file_or_yaml = config_from_args.pop('config')
version = config_from_args.pop('version') version = config_from_args.pop('version')
# 获取配置(三层级联) # 获取配置(三层级联)
if getattr(sys, 'frozen', False):
print(f"[3/6] 加载配置文件: {config_file_or_yaml}")
print(f" 配置文件存在: {os.path.exists(config_file_or_yaml)}")
log_file_path = setup_logging(args.logger_level) log_file_path = setup_logging(args.logger_level)
logging.getLogger(__name__).debug("Logger level set to %s", args.logger_level) logging.getLogger(__name__).debug("Logger level set to %s", args.logger_level)
config = get_config(config_file_or_yaml, config_from_args) config = get_config(config_file_or_yaml, config_from_args)
if getattr(sys, 'frozen', False):
print(" 配置加载成功")
# 创建Qt应用 # 创建Qt应用
if getattr(sys, 'frozen', False):
print("[4/6] 创建 Qt 应用...")
app = QtWidgets.QApplication(sys.argv) app = QtWidgets.QApplication(sys.argv)
app.setApplicationName('Detection') app.setApplicationName('Detection')
app.setOrganizationName('Detection') app.setOrganizationName('Detection')
...@@ -256,21 +226,11 @@ def _main(): ...@@ -256,21 +226,11 @@ def _main():
# 应用全局字体配置 # 应用全局字体配置
FontManager.applyToApplication(app) FontManager.applyToApplication(app)
if getattr(sys, 'frozen', False):
print(" Qt 应用创建成功")
# 创建主窗口 # 创建主窗口
if getattr(sys, 'frozen', False):
print("[5/6] 创建主窗口...")
win = MainWindow( win = MainWindow(
config=config, config=config,
filename=filename, filename=filename,
) )
if getattr(sys, 'frozen', False):
print(" 主窗口创建成功")
if getattr(sys, 'frozen', False):
print("[6/6] 显示窗口并启动事件循环...")
win.show() win.show()
# 启动事件循环 # 启动事件循环
......
...@@ -83,7 +83,6 @@ class AnnotationHandler(QtCore.QObject): ...@@ -83,7 +83,6 @@ class AnnotationHandler(QtCore.QObject):
bool: 是否成功 bool: 是否成功
""" """
if not dir_path or not osp.exists(dir_path): if not dir_path or not osp.exists(dir_path):
print(f"目录不存在: {dir_path}")
return False return False
# 停止之前的监控 # 停止之前的监控
...@@ -116,18 +115,15 @@ class AnnotationHandler(QtCore.QObject): ...@@ -116,18 +115,15 @@ class AnnotationHandler(QtCore.QObject):
# 添加目录到监控列表 # 添加目录到监控列表
if self.current_dir not in self.file_watcher.directories(): if self.current_dir not in self.file_watcher.directories():
self.file_watcher.addPath(self.current_dir) self.file_watcher.addPath(self.current_dir)
print(f"[AnnotationHandler] 开始监控目录: {self.current_dir}")
# 启动定时刷新 # 启动定时刷新
if not self.refresh_timer.isActive(): if not self.refresh_timer.isActive():
self.refresh_timer.start(self.refresh_interval) self.refresh_timer.start(self.refresh_interval)
print(f"[AnnotationHandler] 启动定时刷新 (间隔: {self.refresh_interval}ms)")
self.monitoring_enabled = True self.monitoring_enabled = True
return True return True
except Exception as e: except Exception as e:
print(f"[AnnotationHandler] 启动监控失败: {e}")
return False return False
def stopMonitoring(self): def stopMonitoring(self):
...@@ -147,14 +143,12 @@ class AnnotationHandler(QtCore.QObject): ...@@ -147,14 +143,12 @@ class AnnotationHandler(QtCore.QObject):
self.refresh_timer.stop() self.refresh_timer.stop()
self.monitoring_enabled = False self.monitoring_enabled = False
print(f"[AnnotationHandler] 停止监控")
except Exception as e: except Exception as e:
print(f"[AnnotationHandler] 停止监控失败: {e}") pass
def _onDirectoryChanged(self, path): def _onDirectoryChanged(self, path):
"""目录内容变化回调""" """目录内容变化回调"""
print(f"[AnnotationHandler] 检测到目录变化: {path}")
# 重新扫描文件 # 重新扫描文件
old_files = set(self.image_files) old_files = set(self.image_files)
...@@ -164,13 +158,11 @@ class AnnotationHandler(QtCore.QObject): ...@@ -164,13 +158,11 @@ class AnnotationHandler(QtCore.QObject):
# 检测新增文件 # 检测新增文件
added_files = new_files - old_files added_files = new_files - old_files
for file_path in added_files: for file_path in added_files:
print(f"[AnnotationHandler] 新增文件: {osp.basename(file_path)}")
self.fileAdded.emit(file_path) self.fileAdded.emit(file_path)
# 检测删除文件 # 检测删除文件
removed_files = old_files - new_files removed_files = old_files - new_files
for file_path in removed_files: for file_path in removed_files:
print(f"[AnnotationHandler] 删除文件: {osp.basename(file_path)}")
if file_path in self.file_info_dict: if file_path in self.file_info_dict:
del self.file_info_dict[file_path] del self.file_info_dict[file_path]
self.fileRemoved.emit(file_path) self.fileRemoved.emit(file_path)
...@@ -183,7 +175,6 @@ class AnnotationHandler(QtCore.QObject): ...@@ -183,7 +175,6 @@ class AnnotationHandler(QtCore.QObject):
def _onFileChanged(self, path): def _onFileChanged(self, path):
"""文件变化回调""" """文件变化回调"""
print(f"[AnnotationHandler] 检测到文件变化: {osp.basename(path)}")
# 刷新该文件的信息 # 刷新该文件的信息
if path in self.file_info_dict: if path in self.file_info_dict:
...@@ -212,7 +203,6 @@ class AnnotationHandler(QtCore.QObject): ...@@ -212,7 +203,6 @@ class AnnotationHandler(QtCore.QObject):
if json_exists_now != json_existed_before: if json_exists_now != json_existed_before:
# JSON文件状态发生变化 # JSON文件状态发生变化
print(f"[AnnotationHandler] JSON状态变化: {osp.basename(image_path)} - {'新增' if json_exists_now else '删除'}")
self.refreshFileInfo(image_path) self.refreshFileInfo(image_path)
has_changes = True has_changes = True
elif json_exists_now: elif json_exists_now:
...@@ -222,7 +212,6 @@ class AnnotationHandler(QtCore.QObject): ...@@ -222,7 +212,6 @@ class AnnotationHandler(QtCore.QObject):
# 重新获取JSON信息来检查 # 重新获取JSON信息来检查
json_info = self.getJsonInfo(json_path) json_info = self.getJsonInfo(json_path)
if json_info['shapes_count'] != old_info['shapes_count']: if json_info['shapes_count'] != old_info['shapes_count']:
print(f"[AnnotationHandler] JSON内容变化: {osp.basename(image_path)}")
self.refreshFileInfo(image_path) self.refreshFileInfo(image_path)
has_changes = True has_changes = True
except Exception as e: except Exception as e:
...@@ -235,7 +224,7 @@ class AnnotationHandler(QtCore.QObject): ...@@ -235,7 +224,7 @@ class AnnotationHandler(QtCore.QObject):
self.fileListUpdated.emit(self.getAllFileInfoList()) self.fileListUpdated.emit(self.getAllFileInfoList())
except Exception as e: except Exception as e:
print(f"[AnnotationHandler] 定时刷新出错: {e}") pass
def setRefreshInterval(self, interval_ms): def setRefreshInterval(self, interval_ms):
""" """
...@@ -249,7 +238,6 @@ class AnnotationHandler(QtCore.QObject): ...@@ -249,7 +238,6 @@ class AnnotationHandler(QtCore.QObject):
if self.refresh_timer.isActive(): if self.refresh_timer.isActive():
self.refresh_timer.stop() self.refresh_timer.stop()
self.refresh_timer.start(self.refresh_interval) self.refresh_timer.start(self.refresh_interval)
print(f"[AnnotationHandler] 刷新间隔已更新: {interval_ms}ms")
def scanImageFiles(self): def scanImageFiles(self):
""" """
...@@ -272,12 +260,10 @@ class AnnotationHandler(QtCore.QObject): ...@@ -272,12 +260,10 @@ class AnnotationHandler(QtCore.QObject):
# 排序 # 排序
self.image_files.sort() self.image_files.sort()
print(f"扫描到 {len(self.image_files)} 个图片文件")
return True return True
except Exception as e: except Exception as e:
print(f"扫描文件夹失败: {e}")
return False return False
def loadFileInfoList(self): def loadFileInfoList(self):
...@@ -332,7 +318,7 @@ class AnnotationHandler(QtCore.QObject): ...@@ -332,7 +318,7 @@ class AnnotationHandler(QtCore.QObject):
else: else:
info['file_size_str'] = f"{file_size / (1024 * 1024):.1f} MB" info['file_size_str'] = f"{file_size / (1024 * 1024):.1f} MB"
except Exception as e: except Exception as e:
print(f"获取文件大小失败: {e}") pass
# 获取分辨率 # 获取分辨率
try: try:
...@@ -349,7 +335,7 @@ class AnnotationHandler(QtCore.QObject): ...@@ -349,7 +335,7 @@ class AnnotationHandler(QtCore.QObject):
QtCore.Qt.SmoothTransformation QtCore.Qt.SmoothTransformation
) )
except Exception as e: except Exception as e:
print(f"获取图片信息失败: {e}") pass
# 如果有JSON文件,获取JSON信息 # 如果有JSON文件,获取JSON信息
if info['has_json']: if info['has_json']:
...@@ -390,7 +376,7 @@ class AnnotationHandler(QtCore.QObject): ...@@ -390,7 +376,7 @@ class AnnotationHandler(QtCore.QObject):
info['modified_time'] = datetime.fromtimestamp(mtime).strftime('%Y-%m-%d %H:%M:%S') info['modified_time'] = datetime.fromtimestamp(mtime).strftime('%Y-%m-%d %H:%M:%S')
except Exception as e: except Exception as e:
print(f"读取JSON文件失败: {e}") pass
return info return info
...@@ -534,11 +520,9 @@ class AnnotationHandler(QtCore.QObject): ...@@ -534,11 +520,9 @@ class AnnotationHandler(QtCore.QObject):
with open(output_path, 'w', encoding='utf-8') as f: with open(output_path, 'w', encoding='utf-8') as f:
json.dump(export_data, f, ensure_ascii=False, indent=2) json.dump(export_data, f, ensure_ascii=False, indent=2)
print(f"统计信息已导出到: {output_path}")
return True return True
except Exception as e: except Exception as e:
print(f"导出统计信息失败: {e}")
return False return False
......
...@@ -175,7 +175,6 @@ class DataCollectionChannelHandler: ...@@ -175,7 +175,6 @@ class DataCollectionChannelHandler:
def _connectDataCollectionChannelThread(self, channel_source): def _connectDataCollectionChannelThread(self, channel_source):
"""在后台线程中连接数据采集通道(使用与实时监测管理相同的方式)""" """在后台线程中连接数据采集通道(使用与实时监测管理相同的方式)"""
try: try:
print(f"[DEBUG] 开始连接通道: {channel_source}")
success = False success = False
error_detail = "" error_detail = ""
...@@ -185,14 +184,12 @@ class DataCollectionChannelHandler: ...@@ -185,14 +184,12 @@ class DataCollectionChannelHandler:
if channel_config: if channel_config:
success = self._connectRealChannel(channel_config) success = self._connectRealChannel(channel_config)
if not success: if not success:
error_detail = f"无法连接到配置的通道 {channel_source}\n请检查:\n1. 相机是否已开机并连接到网络\n2. IP地址和端口是否正确" error_detail = f"无法连接到配置的通道 {channel_source}\n请检查:\n 相机是否已开机并连接到网络\n2. IP地址和端口是否正确"
else: else:
# 如果没有配置,尝试直接连接USB通道 # 如果没有配置,尝试直接连接USB通道
print(f"[DEBUG] 尝试连接USB通道: {channel_source}")
success = self._connectUSBChannel(channel_source) success = self._connectUSBChannel(channel_source)
print(f"[DEBUG] USB通道连接结果: {success}")
if not success: if not success:
error_detail = f"无法连接到USB通道 {channel_source}\n请检查:\n1. USB相机是否已连接\n2. 相机驱动是否已安装\n3. 相机是否被其他程序占用" error_detail = f"无法连接到USB通道 {channel_source}\n请检查:\nUSB相机是否已连接\n2. 相机驱动是否已安装\n3. 相机是否被其他程序占用"
else: else:
# 如果是RTSP地址,直接连接 # 如果是RTSP地址,直接连接
success = self._connectRTSPChannel(channel_source) success = self._connectRTSPChannel(channel_source)
...@@ -200,8 +197,6 @@ class DataCollectionChannelHandler: ...@@ -200,8 +197,6 @@ class DataCollectionChannelHandler:
error_detail = f"无法连接到RTSP地址\n请检查:\n1. 网络连接是否正常\n2. RTSP地址格式是否正确\n3. 相机是否支持RTSP协议\n\n地址:{channel_source}" error_detail = f"无法连接到RTSP地址\n请检查:\n1. 网络连接是否正常\n2. RTSP地址格式是否正确\n3. 相机是否支持RTSP协议\n\n地址:{channel_source}"
if not success: if not success:
print(f"[DEBUG] 连接失败,显示错误对话框")
print(f"[DEBUG] 错误详情: {error_detail}")
self._showDataCollectionChannelError( self._showDataCollectionChannelError(
"相机连接失败", "相机连接失败",
error_detail if error_detail else "无法打开通道设备,请检查通道是否连接正常或配置是否正确" error_detail if error_detail else "无法打开通道设备,请检查通道是否连接正常或配置是否正确"
...@@ -756,32 +751,22 @@ class DataCollectionChannelHandler: ...@@ -756,32 +751,22 @@ class DataCollectionChannelHandler:
def _showDataCollectionChannelError(self, title, message): def _showDataCollectionChannelError(self, title, message):
"""显示数据采集通道错误(线程安全)""" """显示数据采集通道错误(线程安全)"""
print(f"[DEBUG] _showDataCollectionChannelError 被调用")
print(f"[DEBUG] 标题: {title}")
print(f"[DEBUG] 消息: {message}")
# 使用信号发射错误信息(线程安全) # 使用信号发射错误信息(线程安全)
print(f"[DEBUG] 准备发射错误信号")
self._dc_error_signal.error.emit(title, message) self._dc_error_signal.error.emit(title, message)
print(f"[DEBUG] 错误信号已发射")
def _showDataCollectionChannelErrorUI(self, title, message): def _showDataCollectionChannelErrorUI(self, title, message):
"""在主线程中显示数据采集通道错误""" """在主线程中显示数据采集通道错误"""
try: try:
print(f"[DEBUG] _showDataCollectionChannelErrorUI 被调用,准备显示对话框")
if DialogManager: if DialogManager:
DialogManager.show_critical(self, title, message) DialogManager.show_critical(self, title, message)
else: else:
QtWidgets.QMessageBox.critical(self, title, message) QtWidgets.QMessageBox.critical(self, title, message)
print(f"[DEBUG] 错误对话框已显示")
if hasattr(self, 'statusBar'): if hasattr(self, 'statusBar'):
self.statusBar().showMessage(f"错误: {message}") self.statusBar().showMessage(f"错误: {message}")
except Exception as e: except Exception as e:
print(f"[DEBUG] 显示错误对话框时发生异常: {e}") pass
import traceback
traceback.print_exc()
def getDataCollectionChannelStatus(self): def getDataCollectionChannelStatus(self):
""" """
...@@ -868,6 +853,9 @@ class DataCollectionChannelHandler: ...@@ -868,6 +853,9 @@ class DataCollectionChannelHandler:
self._dc_video_writer.release() self._dc_video_writer.release()
self._dc_video_writer = None self._dc_video_writer = None
# 等待文件系统完成写入(确保文件完全写入磁盘)
time.sleep(0.5)
return True return True
except Exception as e: except Exception as e:
......
# model_page_handler.py 导入路径修复说明
## 文件位置变更
### 变更前
```
widgets/modelpage/model_page_handler.py
```
### 变更后
```
handlers/modelpage/model_page_handler.py ✅
```
---
## 导入路径修复
### 问题分析
文件从 `widgets/modelpage/` 移动到 `handlers/modelpage/` 后,所有导入路径都需要相应调整:
#### 目录结构
```
项目根目录/
├── widgets/
│ └── modelpage/
│ ├── model_loader.py
│ ├── model_operations.py
│ ├── modelset_page.py
│ └── training_page.py
└── handlers/
└── modelpage/
├── model_page_handler.py ← 当前文件位置
└── model_training_handler.py
```
---
## 修复的导入路径
### 1. ModelLoader 和 ModelOperations
#### ❌ 修复前(错误)
```python
from .model_loader import ModelLoader
from .model_operations import ModelOperations
```
**问题**
- 使用相对导入 `.`,表示从当前包 `handlers.modelpage` 导入
- 但这两个文件在 `widgets.modelpage` 包中,不在当前包
#### ✅ 修复后(正确)
```python
# 导入插件接口(从 widgets.modelpage)
try:
# 优先使用相对导入
from ...widgets.modelpage.model_loader import ModelLoader
from ...widgets.modelpage.model_operations import ModelOperations
except ImportError:
try:
# 备用:绝对导入
from widgets.modelpage.model_loader import ModelLoader
from widgets.modelpage.model_operations import ModelOperations
except ImportError:
# 如果都失败,创建占位类
class ModelLoader:
def __init__(self, handler=None):
pass
class ModelOperations:
def __init__(self, handler=None):
pass
```
**说明**
- `...widgets.modelpage`:向上3层(`modelpage``handlers` → 项目根),然后进入 `widgets/modelpage`
- 添加备用的绝对导入和占位类,确保导入失败时不会崩溃
---
### 2. ModelSetPage 和 TrainingPage
#### ❌ 修复前(错误)
```python
from .modelset_page import ModelSetPage
from .training_page import TrainingPage
```
**问题**:同样使用了错误的相对导入
#### ✅ 修复后(正确)
```python
# 导入页面组件(从 widgets.modelpage)
try:
from ...widgets.modelpage.modelset_page import ModelSetPage
from ...widgets.modelpage.training_page import TrainingPage
except ImportError:
try:
from widgets.modelpage.modelset_page import ModelSetPage
from widgets.modelpage.training_page import TrainingPage
except ImportError as e:
print(f"[ERROR] 无法导入必要的页面组件: {e}")
return QtWidgets.QWidget()
```
**说明**
- 使用相对导入 `...widgets.modelpage` 从正确的包导入
- 提供备用的绝对导入
- 添加错误处理
---
### 3. ModelTrainingHandler
#### ❌ 修复前(错误)
```python
from ...handlers.modelpage.model_training_handler import ModelTrainingHandler
```
**问题**
- 使用了错误的相对路径 `...handlers.modelpage`
- 应该使用 `.model_training_handler`,因为在同一个包内
#### ✅ 修复后(正确)
```python
# 导入训练处理器
try:
# 优先使用相对导入(同一包内)
from .model_training_handler import ModelTrainingHandler
except ImportError:
try:
# 备用:绝对导入
from handlers.modelpage.model_training_handler import ModelTrainingHandler
except ImportError:
ModelTrainingHandler = None
print("[WARNING] 无法导入ModelTrainingHandler,训练功能将不可用")
```
**说明**
- `.model_training_handler`:从当前包 `handlers.modelpage` 导入
- 只需要一层相对导入,因为在同一个目录下
---
## 相对导入层级说明
### 相对导入语法
```python
from . import xxx # 当前包
from .. import xxx # 父包(上一层)
from ... import xxx # 祖父包(上两层)
from .... import xxx # 曾祖父包(上三层)
```
### 当前文件位置导入路径
当前文件:`handlers/modelpage/model_page_handler.py`
| 目标模块 | 相对路径 | 层级解释 |
|---------|---------|---------|
| `handlers.modelpage.model_training_handler` | `.model_training_handler` | 同一包内 |
| `handlers.model_load_handler` | `..model_load_handler` | 父包(handlers) |
| `widgets.modelpage.model_loader` | `...widgets.modelpage.model_loader` | 祖父包(项目根) → widgets |
| 项目根目录模块 | `...module_name` | 祖父包(项目根) |
### 路径计算示例
`handlers/modelpage/model_page_handler.py` 导入 `widgets/modelpage/model_loader.py`
```
当前位置: handlers/modelpage/model_page_handler.py
目标位置: widgets/modelpage/model_loader.py
相对路径计算:
1. 从 model_page_handler.py 向上到 modelpage/ (.)
2. 从 modelpage/ 向上到 handlers/ (..)
3. 从 handlers/ 向上到 项目根/ (...)
4. 从 项目根/ 进入 widgets/modelpage/ (...widgets.modelpage)
5. 导入 model_loader.py (...widgets.modelpage.model_loader)
最终导入语句:
from ...widgets.modelpage.model_loader import ModelLoader
```
---
## 修复总结
### 修复的导入数量
-**ModelLoader** - 从 widgets.modelpage 导入
-**ModelOperations** - 从 widgets.modelpage 导入
-**ModelSetPage** - 从 widgets.modelpage 导入
-**TrainingPage** - 从 widgets.modelpage 导入
-**ModelTrainingHandler** - 从 handlers.modelpage 导入(同一包)
### 修复原则
1. **跨包导入**(widgets ↔ handlers):
- 使用多层相对导入 `...widgets.xxx``...handlers.xxx`
- 提供绝对导入作为备用
2. **同包导入**(handlers.modelpage 内部):
- 使用单层相对导入 `.module_name`
- 提供绝对导入作为备用
3. **错误处理**
- 所有导入都包裹在 try-except 中
- 提供多层备用方案
- 导入失败时不会导致程序崩溃
---
## 验证检查
### ✅ Linter 检查
```bash
# 无 linter 错误
No linter errors found.
```
### 🔍 导入测试
```python
# 测试导入是否成功
try:
from handlers.modelpage.model_page_handler import ModelPageHandler
print("✅ 导入成功")
except ImportError as e:
print(f"❌ 导入失败: {e}")
```
### 📋 检查清单
- [x] ModelLoader 导入路径正确
- [x] ModelOperations 导入路径正确
- [x] ModelSetPage 导入路径正确
- [x] TrainingPage 导入路径正确
- [x] ModelTrainingHandler 导入路径正确
- [x] 无 linter 错误
- [x] 提供多层备用导入方案
- [x] 添加错误处理
---
## 相关文件
### 当前文件
- `handlers/modelpage/model_page_handler.py` - 已修复
### 依赖文件
- `widgets/modelpage/model_loader.py` - 插件接口
- `widgets/modelpage/model_operations.py` - 插件接口
- `widgets/modelpage/modelset_page.py` - 页面组件
- `widgets/modelpage/training_page.py` - 页面组件
- `handlers/modelpage/model_training_handler.py` - 训练处理器
---
## 注意事项
### ⚠️ 文件位置重要性
- 文件位置决定了导入路径
- 移动文件后必须更新所有导入语句
- 相对导入层级取决于文件在包结构中的位置
### 💡 最佳实践
1. **优先使用相对导入**:便于重构和移动包
2. **提供备用绝对导入**:增加兼容性
3. **添加错误处理**:防止导入失败导致崩溃
4. **添加注释说明**:帮助理解导入路径
### 🔄 如果再次移动文件
如果将来再次移动 `model_page_handler.py`,需要:
1. 重新计算相对导入的层级
2. 更新所有 `from ...` 导入语句
3. 测试所有导入是否成功
4. 运行 linter 检查
---
## 测试建议
### 单元测试
```python
def test_imports():
"""测试所有导入是否成功"""
try:
from handlers.modelpage.model_page_handler import ModelPageHandler
assert ModelPageHandler is not None
# 测试创建实例
handler = ModelPageHandler()
assert handler is not None
print("✅ 所有导入测试通过")
except Exception as e:
print(f"❌ 导入测试失败: {e}")
raise
```
### 集成测试
1. 启动应用程序
2. 访问模型管理页面
3. 确认所有功能正常
4. 检查控制台无导入错误
---
**修复时间**: 2025-11-06
**问题类型**: 导入路径错误(文件位置变更)
**影响范围**: model_page_handler.py
**修复状态**: ✅ 已完成
**测试状态**: ✅ Linter 检查通过
...@@ -3,7 +3,8 @@ ...@@ -3,7 +3,8 @@
## 目标 ## 目标
`model_training_handler.py` 中所有模型测试相关的代码迁移到 `model_test_handler.py` `model_training_handler.py` 中所有模型测试相关的代码迁移到 `model_test_handler.py`
## 需要迁移的方法列表 ## 需要迁移的方法列表14324141
### 1. 测试入口和控制方法 ### 1. 测试入口和控制方法
- [x] `_handleStartTest()` - 开始/停止测试按钮处理 (第2005-2014行) - [x] `_handleStartTest()` - 开始/停止测试按钮处理 (第2005-2014行)
......
...@@ -4,6 +4,9 @@ ...@@ -4,6 +4,9 @@
**已完成**:成功将 `app.py` 第1000行后的模型管理功能代码独立出来,并按照功能分为不同的文件。 **已完成**:成功将 `app.py` 第1000行后的模型管理功能代码独立出来,并按照功能分为不同的文件。
<<<<<<< HEAD
## 创建的文件结构
=======
## 创建的文件结构qfaefsf ## 创建的文件结构qfaefsf
...@@ -116,4 +119,5 @@ self._onModelSettings() ...@@ -116,4 +119,5 @@ self._onModelSettings()
## 总结 ## 总结
成功将 `app.py` 中第1000行后的模型管理功能代码(约893行)独立出来,按照功能分为5个不同的处理器文件,提高了代码的可维护性和可扩展性,同时保持了原有功能的完整性。 成功将 `app.py` 中第1000行后的模型管理功能代码(约893行)独立出来,按照功能分为5个不同的处理器文件,提高了代码的可维护性和可扩展性,同时保持了原有功能的完整性。
\ No newline at end of file >>>>>>> main
...@@ -53,7 +53,6 @@ def backup_file(filepath): ...@@ -53,7 +53,6 @@ def backup_file(filepath):
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_path = f"{filepath}.backup_{timestamp}" backup_path = f"{filepath}.backup_{timestamp}"
shutil.copy2(filepath, backup_path) shutil.copy2(filepath, backup_path)
print(f"✅ 已备份原文件到: {backup_path}")
return backup_path return backup_path
def find_method_range(lines, method_name): def find_method_range(lines, method_name):
...@@ -92,16 +91,12 @@ def find_method_range(lines, method_name): ...@@ -92,16 +91,12 @@ def find_method_range(lines, method_name):
def cleanup_test_code(filepath): def cleanup_test_code(filepath):
"""清理测试代码""" """清理测试代码"""
print("\n" + "=" * 60)
print("开始清理测试代码")
print("=" * 60)
# 读取文件 # 读取文件
with open(filepath, 'r', encoding='utf-8') as f: with open(filepath, 'r', encoding='utf-8') as f:
lines = f.readlines() lines = f.readlines()
original_line_count = len(lines) original_line_count = len(lines)
print(f"\n原文件行数: {original_line_count}")
# 记录删除的方法 # 记录删除的方法
deleted_methods = [] deleted_methods = []
...@@ -113,9 +108,6 @@ def cleanup_test_code(filepath): ...@@ -113,9 +108,6 @@ def cleanup_test_code(filepath):
if result: if result:
start, end = result start, end = result
method_lines = end - start method_lines = end - start
print(f"\n找到方法: {method_name}")
print(f" 位置: 第 {start + 1} 行到第 {end} 行")
print(f" 行数: {method_lines} 行")
# 删除方法 # 删除方法
del lines[start:end] del lines[start:end]
...@@ -126,9 +118,6 @@ def cleanup_test_code(filepath): ...@@ -126,9 +118,6 @@ def cleanup_test_code(filepath):
'lines': method_lines 'lines': method_lines
}) })
deleted_lines += method_lines deleted_lines += method_lines
print(f" ✅ 已删除")
else:
print(f"\n⚠️ 未找到方法: {method_name}")
# 写回文件 # 写回文件
with open(filepath, 'w', encoding='utf-8') as f: with open(filepath, 'w', encoding='utf-8') as f:
...@@ -136,20 +125,6 @@ def cleanup_test_code(filepath): ...@@ -136,20 +125,6 @@ def cleanup_test_code(filepath):
new_line_count = len(lines) new_line_count = len(lines)
# 生成报告
print("\n" + "=" * 60)
print("清理完成")
print("=" * 60)
print(f"\n原文件行数: {original_line_count}")
print(f"新文件行数: {new_line_count}")
print(f"删除行数: {deleted_lines}")
print(f"删除方法数: {len(deleted_methods)}")
# 详细报告
print("\n删除的方法:")
for method in reversed(deleted_methods):
print(f" - {method['name']}: {method['lines']} 行 (第 {method['start']}-{method['end']} 行)")
# 生成报告文件 # 生成报告文件
report_path = str(filepath).replace('.py', '_cleanup_report.txt') report_path = str(filepath).replace('.py', '_cleanup_report.txt')
with open(report_path, 'w', encoding='utf-8') as f: with open(report_path, 'w', encoding='utf-8') as f:
...@@ -167,25 +142,18 @@ def cleanup_test_code(filepath): ...@@ -167,25 +142,18 @@ def cleanup_test_code(filepath):
f.write(f" - {method['name']}: {method['lines']} 行 (第 {method['start']}-{method['end']} 行)\n") f.write(f" - {method['name']}: {method['lines']} 行 (第 {method['start']}-{method['end']} 行)\n")
f.write("\n" + "=" * 60 + "\n") f.write("\n" + "=" * 60 + "\n")
print(f"\n✅ 清理报告已保存到: {report_path}")
return deleted_methods return deleted_methods
def verify_file(filepath): def verify_file(filepath):
"""验证文件语法""" """验证文件语法"""
print("\n" + "=" * 60)
print("验证文件语法")
print("=" * 60)
try: try:
with open(filepath, 'r', encoding='utf-8') as f: with open(filepath, 'r', encoding='utf-8') as f:
code = f.read() code = f.read()
compile(code, filepath, 'exec') compile(code, filepath, 'exec')
print("✅ 文件语法正确")
return True return True
except SyntaxError as e: except SyntaxError as e:
print(f"❌ 语法错误: {e}")
return False return False
def main(): def main():
...@@ -194,25 +162,11 @@ def main(): ...@@ -194,25 +162,11 @@ def main():
filepath = script_dir / 'model_training_handler.py' filepath = script_dir / 'model_training_handler.py'
if not filepath.exists(): if not filepath.exists():
print(f"❌ 文件不存在: {filepath}")
return False return False
print("=" * 60)
print("模型训练处理器代码清理工具")
print("=" * 60)
print(f"\n目标文件: {filepath}")
print(f"\n将删除以下方法:")
for method in METHODS_TO_DELETE:
print(f" - {method}")
print(f"\n将保留以下方法:")
for method in METHODS_TO_KEEP:
print(f" - {method}")
# 确认 # 确认
response = input("\n是否继续?(y/n): ") response = input("\n是否继续?(y/n): ")
if response.lower() != 'y': if response.lower() != 'y':
print("已取消")
return False return False
# 备份 # 备份
...@@ -224,26 +178,13 @@ def main(): ...@@ -224,26 +178,13 @@ def main():
# 验证 # 验证
if verify_file(filepath): if verify_file(filepath):
print("\n🎉 清理成功!")
print(f"\n下一步:")
print(f" 1. 运行集成测试: python test_integration.py")
print(f" 2. 测试应用程序功能")
print(f" 3. 如有问题,可从备份恢复: {backup_path}")
return True return True
else: else:
print("\n❌ 清理后文件有语法错误,正在恢复...")
shutil.copy2(backup_path, filepath) shutil.copy2(backup_path, filepath)
print(f"✅ 已从备份恢复")
return False return False
except Exception as e: except Exception as e:
print(f"\n❌ 清理失败: {e}")
import traceback
traceback.print_exc()
print("\n正在从备份恢复...")
shutil.copy2(backup_path, filepath) shutil.copy2(backup_path, filepath)
print(f"✅ 已从备份恢复")
return False return False
if __name__ == "__main__": if __name__ == "__main__":
......
...@@ -79,17 +79,11 @@ class ModelPageHandler: ...@@ -79,17 +79,11 @@ class ModelPageHandler:
self._model_operations = ModelOperations(handler=handlers['model_set_handler']) self._model_operations = ModelOperations(handler=handlers['model_set_handler'])
def create_model_page(self): def create_model_page(self):
""" """创建模型页面(已弃用,保留以兼容旧代码)"""
创建模型管理页面(已弃用) pass
注意:此方法已不再使用,推荐直接使用 app.py 中的 _createModelPage() 方法。
保留此方法仅用于向后兼容。
Returns:
QtWidgets.QWidget: 模型页面
"""
print("[WARNING] create_model_page() 方法已弃用,请使用 app.py 中的 _createModelPage()")
def _createModelPage(self):
"""创建模型页面的实际实现"""
# 导入页面组件(从 widgets.modelpage) # 导入页面组件(从 widgets.modelpage)
try: try:
from ...widgets.modelpage.modelset_page import ModelSetPage from ...widgets.modelpage.modelset_page import ModelSetPage
...@@ -99,8 +93,7 @@ class ModelPageHandler: ...@@ -99,8 +93,7 @@ class ModelPageHandler:
from widgets.modelpage.modelset_page import ModelSetPage from widgets.modelpage.modelset_page import ModelSetPage
from widgets.modelpage.training_page import TrainingPage from widgets.modelpage.training_page import TrainingPage
except ImportError as e: except ImportError as e:
print(f"[ERROR] 无法导入必要的页面组件: {e}") return None
return QtWidgets.QWidget()
# 导入训练处理器 # 导入训练处理器
try: try:
...@@ -112,7 +105,6 @@ class ModelPageHandler: ...@@ -112,7 +105,6 @@ class ModelPageHandler:
from handlers.modelpage.model_training_handler import ModelTrainingHandler from handlers.modelpage.model_training_handler import ModelTrainingHandler
except ImportError: except ImportError:
ModelTrainingHandler = None ModelTrainingHandler = None
print("[WARNING] 无法导入ModelTrainingHandler,训练功能将不可用")
# 创建主页面容器 # 创建主页面容器
page = QtWidgets.QWidget() page = QtWidgets.QWidget()
...@@ -130,8 +122,7 @@ class ModelPageHandler: ...@@ -130,8 +122,7 @@ class ModelPageHandler:
# 创建训练处理器 # 创建训练处理器
if ModelTrainingHandler: if ModelTrainingHandler:
self._parent.training_handler = ModelTrainingHandler() self._parent.training_handler = ModelTrainingHandler()
self._parent.training_handler._set_main_window(self._parent) self._parent.training_handler.connectSignals()
print("[OK] 训练处理器初始化成功")
else: else:
self._parent.training_handler = None self._parent.training_handler = None
...@@ -139,10 +130,6 @@ class ModelPageHandler: ...@@ -139,10 +130,6 @@ class ModelPageHandler:
self._parent.trainingPage = TrainingPage(parent=self._parent) self._parent.trainingPage = TrainingPage(parent=self._parent)
self._parent.modelStackWidget.addWidget(self._parent.trainingPage) self._parent.modelStackWidget.addWidget(self._parent.trainingPage)
# 连接训练处理器
if self._parent.training_handler:
self._parent.training_handler.connectToTrainingPanel(self._parent.trainingPage)
# 默认显示第一个页面(模型集管理) # 默认显示第一个页面(模型集管理)
self._parent.modelStackWidget.setCurrentIndex(0) self._parent.modelStackWidget.setCurrentIndex(0)
...@@ -167,12 +154,7 @@ class ModelPageHandler: ...@@ -167,12 +154,7 @@ class ModelPageHandler:
self._handlers['model_signal_handler'].setupModelPageConnections() self._handlers['model_signal_handler'].setupModelPageConnections()
def add_test_models_to_list(self): def add_test_models_to_list(self):
""" """添加测试模型到列表(已弃用)"""
添加测试模型到模型列表(已弃用)
注意:此方法已不再使用,模型应通过"模型集管理"页面手动添加。
"""
print("[WARNING] add_test_models_to_list() 方法已弃用")
pass pass
# ==================== 信号处理方法 ==================== # ==================== 信号处理方法 ====================
...@@ -189,21 +171,11 @@ class ModelPageHandler: ...@@ -189,21 +171,11 @@ class ModelPageHandler:
QtWidgets.QMessageBox.warning(self._parent, "错误", f"运行模型测试失败: {e}") QtWidgets.QMessageBox.warning(self._parent, "错误", f"运行模型测试失败: {e}")
def on_browse_test_file(self): def on_browse_test_file(self):
""" """浏览测试文件(已弃用)"""
浏览测试文件(已弃用)
注意:模型测试功能已移除,此方法不再使用。
"""
print("[WARNING] on_browse_test_file() 方法已弃用")
pass pass
def on_browse_save_liquid_data_path(self): def on_browse_save_liquid_data_path(self):
""" """浏览保存路径(已弃用)"""
浏览保存路径(已弃用)
注意:此方法已不再使用。
"""
print("[WARNING] on_browse_save_liquid_data_path() 方法已弃用")
pass pass
def on_add_model_set(self): def on_add_model_set(self):
......
...@@ -79,13 +79,10 @@ class ModelSignalHandler: ...@@ -79,13 +79,10 @@ class ModelSignalHandler:
# self.modelSetPage.setDefaultRequested.connect( # self.modelSetPage.setDefaultRequested.connect(
# self.model_set_handler.setAsDefaultModel # self.model_set_handler.setAsDefaultModel
# ) # )
if hasattr(self.modelSetPage, 'modelSetClicked'):
print("[OK] ModelSetPage 业务逻辑信号已连接到 model_set_handler") self.modelSetPage.modelSetClicked.connect(self._onModelSetPageModelSelected)
else:
print("[警告] model_set_handler 未初始化,业务逻辑信号未连接")
except Exception as e: except Exception as e:
print(f"[ModelSignalHandler] 建立连接时出错: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
...@@ -96,7 +93,6 @@ class ModelSignalHandler: ...@@ -96,7 +93,6 @@ class ModelSignalHandler:
Args: Args:
model_name: 选中的模型名称 model_name: 选中的模型名称
""" """
print(f" [ModelSignalHandler] ModelSetPage 选中模型: {model_name}")
# 这里可以添加额外的处理逻辑,比如更新状态栏 # 这里可以添加额外的处理逻辑,比如更新状态栏
if hasattr(self, 'statusBar'): if hasattr(self, 'statusBar'):
self.statusBar().showMessage(f"当前选中模型: {model_name}") self.statusBar().showMessage(f"当前选中模型: {model_name}")
...@@ -108,7 +104,7 @@ class ModelSignalHandler: ...@@ -108,7 +104,7 @@ class ModelSignalHandler:
Args: Args:
model_name: 新添加的模型名称 model_name: 新添加的模型名称
""" """
print(f" [ModelSignalHandler] 新模型已添加: {model_name}") pass
def _onModelDeleted(self, model_name): def _onModelDeleted(self, model_name):
""" """
...@@ -117,6 +113,4 @@ class ModelSignalHandler: ...@@ -117,6 +113,4 @@ class ModelSignalHandler:
Args: Args:
model_name: 删除的模型名称 model_name: 删除的模型名称
""" """
print(f" [ModelSignalHandler] 模型已删除: {model_name}") pass
...@@ -62,7 +62,7 @@ class DatToCfgConverter: ...@@ -62,7 +62,7 @@ class DatToCfgConverter:
print(f"❌ 目录不存在: {directory}") print(f"❌ 目录不存在: {directory}")
return False return False
print(f"\n🔍 扫描目录: {directory}") print(f"\n 扫描目录: {directory}")
# 查找所有.dat文件 # 查找所有.dat文件
if recursive: if recursive:
......
...@@ -32,7 +32,7 @@ class BatImageDetector: ...@@ -32,7 +32,7 @@ class BatImageDetector:
Returns: Returns:
是否加载成功 是否加载成功
""" """
print(f"🔍 加载.bat模型: {model_path}") print(f" 加载.bat模型: {model_path}")
try: try:
# 检查文件是否存在 # 检查文件是否存在
...@@ -277,7 +277,7 @@ def main(): ...@@ -277,7 +277,7 @@ def main():
detector = BatImageDetector() detector = BatImageDetector()
try: try:
print("🔍 开始.bat模型图片检测测试") print(" 开始.bat模型图片检测测试")
print("=" * 50) print("=" * 50)
# 1. 测试模型加载 # 1. 测试模型加载
......
...@@ -34,7 +34,7 @@ class BatModelTester: ...@@ -34,7 +34,7 @@ class BatModelTester:
Returns: Returns:
是否加载成功 是否加载成功
""" """
print(f"🔍 测试模型加载: {model_path}") print(f" 测试模型加载: {model_path}")
try: try:
# 检查文件是否存在 # 检查文件是否存在
......
...@@ -36,7 +36,7 @@ def test_dat_conversion(): ...@@ -36,7 +36,7 @@ def test_dat_conversion():
print(f"📁 找到 {len(test_pt_files)} 个.pt文件用于测试") print(f"📁 找到 {len(test_pt_files)} 个.pt文件用于测试")
for pt_file in test_pt_files: for pt_file in test_pt_files:
print(f"\n🔍 测试文件: {pt_file}") print(f"\n 测试文件: {pt_file}")
try: try:
# 检查文件大小 # 检查文件大小
...@@ -120,7 +120,7 @@ def test_dat_loading(): ...@@ -120,7 +120,7 @@ def test_dat_loading():
print(f"📁 找到 {len(dat_files)} 个.dat文件") print(f"📁 找到 {len(dat_files)} 个.dat文件")
for dat_file in dat_files: for dat_file in dat_files:
print(f"\n🔍 测试加载: {dat_file}") print(f"\n 测试加载: {dat_file}")
try: try:
# 测试加载.dat模型(内部会自动解码) # 测试加载.dat模型(内部会自动解码)
......
...@@ -32,7 +32,7 @@ class DatImageDetector: ...@@ -32,7 +32,7 @@ class DatImageDetector:
Returns: Returns:
是否加载成功 是否加载成功
""" """
print(f"🔍 加载.dat模型: {model_path}") print(f" 加载.dat模型: {model_path}")
try: try:
# 检查文件是否存在 # 检查文件是否存在
...@@ -191,7 +191,7 @@ def main(): ...@@ -191,7 +191,7 @@ def main():
detector = DatImageDetector() detector = DatImageDetector()
try: try:
print("🔍 开始.dat模型图片分割测试") print(" 开始.dat模型图片分割测试")
print("=" * 50) print("=" * 50)
# 1. 测试模型加载 # 1. 测试模型加载
......
...@@ -30,7 +30,7 @@ class PtModelTester: ...@@ -30,7 +30,7 @@ class PtModelTester:
Returns: Returns:
是否加载成功 是否加载成功
""" """
print(f"🔍 加载.pt模型: {model_path}") print(f" 加载.pt模型: {model_path}")
try: try:
# 检查文件是否存在 # 检查文件是否存在
...@@ -422,7 +422,7 @@ def main(): ...@@ -422,7 +422,7 @@ def main():
tester = PtModelTester() tester = PtModelTester()
try: try:
print("🔍 开始.pt模型检测测试") print(" 开始.pt模型检测测试")
print("=" * 50) print("=" * 50)
# 1. 测试模型加载 # 1. 测试模型加载
......
...@@ -116,14 +116,21 @@ class SettingsHandler: ...@@ -116,14 +116,21 @@ class SettingsHandler:
def showAbout(self): def showAbout(self):
"""显示关于对话框""" """显示关于对话框"""
QtWidgets.QMessageBox.about( try:
self, from widgets.style_manager import DialogManager
self.tr("关于"), except ImportError:
self.tr( DialogManager = None
"<h2>帕特智能油液位检测</h2>"
"<p>版本: 1.0</p>" message = (
) "<h2>帕特智能油液位检测</h2>"
"<p>版本: 1.0</p>"
) )
if DialogManager:
DialogManager.show_about(self, "关于", message)
else:
# 降级方案:使用标准对话框
QtWidgets.QMessageBox.about(self, "关于", message)
def showDocumentation(self): def showDocumentation(self):
"""显示帮助文档 - 打开用户手册""" """显示帮助文档 - 打开用户手册"""
......
# 自动标点功能模块使用说明
## 功能概述
`auto_dot.py` 模块实现了基于YOLO分割掩码的自动标点功能,可以自动检测容器的顶部和底部位置,替代人工手动标点。
## 核心特性
- **输入**: 图片 + 检测框
- **输出**: 点位置信息 + 标注后的图片
- **检测方法**:
1. **liquid底部 + air顶部** (最可靠)
2. **liquid底部 + liquid顶部** (次选)
3. **air底部 + air顶部** (备选)
## 独立调试
### 1. 准备测试数据
将测试图片放置到:
```
D:\restructure\liquid_level_line_detection_system\test_data\test_image.jpg
```
### 2. 配置检测框
编辑 `auto_dot.py` 中的 `test_auto_dot()` 函数,修改 `boxes` 参数:
```python
# 方式1: [x1, y1, x2, y2] 格式
boxes = [
[100, 200, 300, 600], # 第一个容器
[400, 200, 600, 600], # 第二个容器
]
# 方式2: [cx, cy, size] 格式
boxes = [
[200, 400, 400], # 中心点(200, 400), 尺寸400
]
```
### 3. 运行测试
```bash
cd D:\restructure\liquid_level_line_detection_system\handlers\videopage
python auto_dot.py
```
### 4. 查看结果
- **控制台输出**: 详细的检测过程和结果
- **标注图片**: `D:\restructure\liquid_level_line_detection_system\test_output\auto_dot_result.jpg`
## API 使用示例
```python
from handlers.videopage.auto_dot import AutoDotDetector
import cv2
# 1. 创建检测器
detector = AutoDotDetector(
model_path="path/to/model.dat",
device='cuda' # 或 'cpu'
)
# 2. 加载图片
image = cv2.imread("test_image.jpg")
# 3. 定义检测框
boxes = [
[100, 200, 300, 600], # [x1, y1, x2, y2]
]
# 4. 执行检测
result = detector.detect_container_boundaries(
image=image,
boxes=boxes,
conf_threshold=0.5
)
# 5. 获取结果
if result['success']:
for container in result['containers']:
print(f"容器 {container['index']}:")
print(f" 顶部: ({container['top_x']}, {container['top']})")
print(f" 底部: ({container['bottom_x']}, {container['bottom']})")
print(f" 高度: {container['height']}px")
print(f" 置信度: {container['confidence']:.3f}")
# 保存标注图片
cv2.imwrite("result.jpg", result['annotated_image'])
```
## 输出数据结构
```python
{
'success': bool, # 检测是否成功
'containers': [
{
'index': int, # 容器索引
'top': int, # 顶部y坐标
'bottom': int, # 底部y坐标
'top_x': int, # 顶部x坐标
'bottom_x': int, # 底部x坐标
'height': int, # 容器高度(像素)
'confidence': float, # 检测置信度
'method': str # 检测方法
},
...
],
'annotated_image': np.ndarray # 标注后的图片
}
```
## 检测方法说明
### 方法1: liquid_air (最可靠)
- **容器底部**: liquid掩码的最低点
- **容器顶部**: air掩码的最高点
- **适用场景**: 同时检测到液体和空气
### 方法2: liquid_only (次选)
- **容器底部**: liquid掩码的最低点
- **容器顶部**: liquid掩码的最高点
- **适用场景**: 只检测到液体,未检测到空气
### 方法3: air_only (备选)
- **容器底部**: air掩码的最低点
- **容器顶部**: air掩码的最高点
- **适用场景**: 只检测到空气,未检测到液体
## 可视化标注
标注图片包含:
- **绿色圆点**: 容器顶部
- **红色圆点**: 容器底部
- **青色连线**: 容器高度
- **水平参考线**: 顶部和底部的水平位置
- **文字标注**: Top-N, Bottom-N, 高度值
## 注意事项
1. **模型路径**: 确保模型文件存在且可访问
2. **检测框位置**: 检测框应覆盖完整的容器区域
3. **置信度阈值**: 默认0.5,可根据实际情况调整
4. **GPU加速**: 建议使用CUDA加速,提高检测速度
## 调试技巧
1. **查看控制台输出**: 详细的检测过程日志
2. **检查标注图片**: 验证检测结果的准确性
3. **调整检测框**: 如果检测失败,尝试调整检测框的位置和大小
4. **降低置信度**: 如果检测不到掩码,尝试降低 `conf_threshold`
## 接入系统
调试成功后,可以在主系统中调用:
```python
from handlers.videopage.auto_dot import AutoDotDetector
# 在标注页面添加"自动标点"按钮
# 点击后调用 detector.detect_container_boundaries()
# 将返回的 top/bottom 坐标填充到标注点位置
```
...@@ -611,9 +611,7 @@ class LiquidDetectionEngine: ...@@ -611,9 +611,7 @@ class LiquidDetectionEngine:
masks = mission_result.masks.data.cpu().numpy() > 0.5 masks = mission_result.masks.data.cpu().numpy() > 0.5
classes = mission_result.boxes.cls.cpu().numpy().astype(int) classes = mission_result.boxes.cls.cpu().numpy().astype(int)
confidences = mission_result.boxes.conf.cpu().numpy() confidences = mission_result.boxes.conf.cpu().numpy()
print(f"[检测-目标{idx}] YOLO检测到 {len(masks)} 个对象")
else: else:
print(f"[检测-目标{idx}] ⚠️ YOLO未检测到任何mask")
return None return None
# 收集所有mask信息 # 收集所有mask信息
...@@ -621,7 +619,6 @@ class LiquidDetectionEngine: ...@@ -621,7 +619,6 @@ class LiquidDetectionEngine:
for i in range(len(masks)): for i in range(len(masks)):
class_name = self.model.names[classes[i]] class_name = self.model.names[classes[i]]
conf = confidences[i] conf = confidences[i]
print(f"[检测-目标{idx}] 对象{i+1}: {class_name} (置信度: {conf:.3f})")
if confidences[i] >= 0.5: if confidences[i] >= 0.5:
resized_mask = cv2.resize( resized_mask = cv2.resize(
...@@ -630,10 +627,7 @@ class LiquidDetectionEngine: ...@@ -630,10 +627,7 @@ class LiquidDetectionEngine:
) > 0.5 ) > 0.5
all_masks_info.append((resized_mask, class_name, confidences[i])) all_masks_info.append((resized_mask, class_name, confidences[i]))
print(f"[检测-目标{idx}] 收集到 {len(all_masks_info)} 个有效mask (置信度>=0.5)")
if len(all_masks_info) == 0: if len(all_masks_info) == 0:
print(f"[检测-目标{idx}] ⚠️ 没有置信度>=0.5的对象,无法计算液位")
return None return None
# ️ 关键修复:将原图坐标转换为裁剪图像坐标 # ️ 关键修复:将原图坐标转换为裁剪图像坐标
...@@ -656,11 +650,6 @@ class LiquidDetectionEngine: ...@@ -656,11 +650,6 @@ class LiquidDetectionEngine:
idx idx
) )
if liquid_height is None:
print(f"[检测-目标{idx}] ⚠️ _enhanced_liquid_detection返回None,无法确定液位")
else:
print(f"[检测-目标{idx}] ✅ 检测到液位: {liquid_height:.2f}mm")
return liquid_height return liquid_height
except Exception as e: except Exception as e:
...@@ -684,14 +673,6 @@ class LiquidDetectionEngine: ...@@ -684,14 +673,6 @@ class LiquidDetectionEngine:
""" """
pixel_per_mm = container_pixel_height / container_height_mm pixel_per_mm = container_pixel_height / container_height_mm
print(f"\n[液位分析-目标{idx}] ========== 开始分析 ==========")
print(f"[液位分析-目标{idx}] 输入参数:")
print(f" - all_masks_info数量: {len(all_masks_info)}")
print(f" - container_bottom: {container_bottom}px (裁剪图像坐标)")
print(f" - container_pixel_height: {container_pixel_height}px")
print(f" - container_height_mm: {container_height_mm}mm")
print(f" - pixel_per_mm: {pixel_per_mm:.3f}px/mm")
# 分离不同类别的mask # 分离不同类别的mask
liquid_masks = [] liquid_masks = []
foam_masks = [] foam_masks = []
...@@ -705,14 +686,8 @@ class LiquidDetectionEngine: ...@@ -705,14 +686,8 @@ class LiquidDetectionEngine:
elif class_name == 'air': elif class_name == 'air':
air_masks.append(mask) air_masks.append(mask)
print(f"[液位分析-目标{idx}] mask分类:")
print(f" - liquid: {len(liquid_masks)}个")
print(f" - foam: {len(foam_masks)}个")
print(f" - air: {len(air_masks)}个")
# 方法1:直接liquid检测(优先) # 方法1:直接liquid检测(优先)
if liquid_masks: if liquid_masks:
print(f"[液位分析-目标{idx}] 使用方法1: 直接liquid检测")
# 找到最上层的液体mask # 找到最上层的液体mask
topmost_y = float('inf') topmost_y = float('inf')
for i, mask in enumerate(liquid_masks): for i, mask in enumerate(liquid_masks):
...@@ -727,27 +702,9 @@ class LiquidDetectionEngine: ...@@ -727,27 +702,9 @@ class LiquidDetectionEngine:
liquid_height_px = container_bottom - topmost_y liquid_height_px = container_bottom - topmost_y
liquid_height_mm = liquid_height_px / pixel_per_mm liquid_height_mm = liquid_height_px / pixel_per_mm
print(f"[液位分析-目标{idx}] ========== 计算结果 ==========")
print(f"[液位分析-目标{idx}] 坐标信息(裁剪图像坐标系):")
print(f" - 液面最上层y: {topmost_y}px")
print(f" - 容器底部y: {container_bottom}px")
print(f"[液位分析-目标{idx}] 计算过程:")
print(f" - 液位像素高度 = {container_bottom}px - {topmost_y}px = {liquid_height_px}px")
print(f" - 像素/毫米比例 = {container_pixel_height}px / {container_height_mm}mm = {pixel_per_mm:.3f}px/mm")
print(f" - 液位毫米高度 = {liquid_height_px}px / {pixel_per_mm:.3f}px/mm = {liquid_height_mm:.2f}mm")
print(f"[液位分析-目标{idx}] 边界检查:")
print(f" - 原始值: {liquid_height_mm:.2f}mm")
print(f" - 容器总高度: {container_height_mm}mm")
print(f" - 最终返回值: {max(0, min(liquid_height_mm, container_height_mm)):.2f}mm")
print(f"[液位分析-目标{idx}] ========== 计算完成 ==========\n")
return max(0, min(liquid_height_mm, container_height_mm)) return max(0, min(liquid_height_mm, container_height_mm))
else:
print(f"[液位分析-目标{idx}] ⚠️ liquid mask存在但无法找到有效的顶部y坐标")
# 方法2:foam边界分析(备选)- 连续3帧未检测到liquid时启用 # 方法2:foam边界分析(备选)- 连续3帧未检测到liquid时启用
print(f"[液位分析-目标{idx}] 方法1失败,尝试方法2: foam边界分析")
print(f"[液位分析-目标{idx}] no_liquid_count={self.no_liquid_count[idx]}, 需要>=3才启用foam分析")
if self.no_liquid_count[idx] >= 3: if self.no_liquid_count[idx] >= 3:
if len(foam_masks) >= 2: if len(foam_masks) >= 2:
...@@ -777,7 +734,6 @@ class LiquidDetectionEngine: ...@@ -777,7 +734,6 @@ class LiquidDetectionEngine:
liquid_height_mm = liquid_height_px / pixel_per_mm liquid_height_mm = liquid_height_px / pixel_per_mm
return max(0, min(liquid_height_mm, container_height_mm)) return max(0, min(liquid_height_mm, container_height_mm))
print(f"[液位分析-目标{idx}] ⚠️ 所有方法都无法确定液位,返回None")
return None return None
def _apply_kalman_filter(self, observation, idx, container_height_mm): def _apply_kalman_filter(self, observation, idx, container_height_mm):
......
...@@ -117,6 +117,7 @@ class GeneralSetPanelHandler: ...@@ -117,6 +117,7 @@ class GeneralSetPanelHandler:
widget.annotationEngineRequested.connect(self._handleAnnotationEngineRequest) widget.annotationEngineRequested.connect(self._handleAnnotationEngineRequest)
widget.frameLoadRequested.connect(self._handleFrameLoadRequest) widget.frameLoadRequested.connect(self._handleFrameLoadRequest)
widget.annotationDataRequested.connect(self._handleAnnotationDataRequest) widget.annotationDataRequested.connect(self._handleAnnotationDataRequest)
widget.liveFrameRequested.connect(self._handleLiveFrameRequest)
def _handleRefreshModelList(self, model_widget=None): def _handleRefreshModelList(self, model_widget=None):
"""处理刷新模型列表请求""" """处理刷新模型列表请求"""
...@@ -264,7 +265,6 @@ class GeneralSetPanelHandler: ...@@ -264,7 +265,6 @@ class GeneralSetPanelHandler:
if self.general_set_panel: if self.general_set_panel:
self.general_set_panel.setTaskIdOptions(task_ids) self.general_set_panel.setTaskIdOptions(task_ids)
print(f"[Handler] 已加载 {len(task_ids)} 个任务编号选项")
except Exception as e: except Exception as e:
print(f"[Handler] 加载任务ID选项失败: {e}") print(f"[Handler] 加载任务ID选项失败: {e}")
import traceback import traceback
...@@ -405,10 +405,6 @@ class GeneralSetPanelHandler: ...@@ -405,10 +405,6 @@ class GeneralSetPanelHandler:
model_config = default_config.get('model', {}).copy() model_config = default_config.get('model', {}).copy()
model_config['model_path'] = absolute_path model_config['model_path'] = absolute_path
print(f"[Handler] 加载通道 {channel_id} 的模型配置:")
print(f" 相对路径: {channel_model_path}")
print(f" 绝对路径: {absolute_path}")
# 调用widget的方法应用配置 # 调用widget的方法应用配置
if self.general_set_panel: if self.general_set_panel:
self.general_set_panel.applyModelConfigFromHandler( self.general_set_panel.applyModelConfigFromHandler(
...@@ -693,26 +689,15 @@ class GeneralSetPanelHandler: ...@@ -693,26 +689,15 @@ class GeneralSetPanelHandler:
channel_frame = None channel_frame = None
if self.general_set_panel and self.general_set_panel.channel_id: if self.general_set_panel and self.general_set_panel.channel_id:
channel_frame = self.getLatestFrame(self.general_set_panel.channel_id) channel_frame = self.getLatestFrame(self.general_set_panel.channel_id)
if channel_frame is not None:
pass
# 如果没有获取到通道画面,使用测试图像 # 如果没有获取到通道画面,弹出提示框并返回
if channel_frame is None: if channel_frame is None:
pass QtWidgets.QMessageBox.warning(
import numpy as np self.main_window,
"获取画面失败",
channel_frame = np.zeros((720, 1280, 3), dtype=np.uint8) "获取通道画面失败,请先连接通道"
channel_frame[:] = (100, 120, 140) # 灰色背景 )
return
# 添加文字说明
cv2.putText(channel_frame, "Test Annotation Frame", (50, 50),
cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2)
cv2.putText(channel_frame, "Draw detection areas and mark liquid levels", (50, 100),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (200, 200, 200), 1)
# 添加测试区域
cv2.rectangle(channel_frame, (200, 200), (400, 400), (0, 255, 0), 2)
cv2.rectangle(channel_frame, (500, 300), (700, 500), (0, 0, 255), 2)
# 2. 保存原始帧用于标注结果显示 # 2. 保存原始帧用于标注结果显示
self._annotation_source_frame = channel_frame.copy() if channel_frame is not None else None self._annotation_source_frame = channel_frame.copy() if channel_frame is not None else None
...@@ -724,6 +709,9 @@ class GeneralSetPanelHandler: ...@@ -724,6 +709,9 @@ class GeneralSetPanelHandler:
if self.general_set_panel and self.general_set_panel.channel_name: if self.general_set_panel and self.general_set_panel.channel_name:
annotation_widget.setChannelName(self.general_set_panel.channel_name) annotation_widget.setChannelName(self.general_set_panel.channel_name)
# 3.5. 初始化物理变焦控制器
self._initPhysicalZoomForAnnotation(annotation_widget)
# 4. 连接标注完成信号 # 4. 连接标注完成信号
def on_annotation_completed(boxes, bottoms, tops): def on_annotation_completed(boxes, bottoms, tops):
print(f"\n[DEBUG] ========== 标注完成回调 ==========") print(f"\n[DEBUG] ========== 标注完成回调 ==========")
...@@ -789,6 +777,9 @@ class GeneralSetPanelHandler: ...@@ -789,6 +777,9 @@ class GeneralSetPanelHandler:
# 5. 加载图像并显示标注界面 # 5. 加载图像并显示标注界面
if annotation_widget.loadFrame(channel_frame): if annotation_widget.loadFrame(channel_frame):
# 启用实时画面预览
annotation_widget.enableLivePreview(True)
# 🔥 关键修复:延迟显示窗口,确保全屏应用后再显示 # 🔥 关键修复:延迟显示窗口,确保全屏应用后再显示
# 这样可以确保标注帧在全屏模式下立即显示 # 这样可以确保标注帧在全屏模式下立即显示
QtCore.QTimer.singleShot(150, annotation_widget.show) QtCore.QTimer.singleShot(150, annotation_widget.show)
...@@ -1246,6 +1237,73 @@ class GeneralSetPanelHandler: ...@@ -1246,6 +1237,73 @@ class GeneralSetPanelHandler:
if self.annotation_widget: if self.annotation_widget:
self.annotation_widget.showAnnotationError(f"获取标注数据失败: {str(e)}") self.annotation_widget.showAnnotationError(f"获取标注数据失败: {str(e)}")
def _handleLiveFrameRequest(self):
"""处理实时画面请求"""
try:
# 获取通道最新画面
if self.general_set_panel and self.general_set_panel.channel_id:
channel_frame = self.getLatestFrame(self.general_set_panel.channel_id)
# 更新标注界面的画面
if channel_frame is not None and self.annotation_widget:
self.annotation_widget.updateLiveFrame(channel_frame)
except Exception as e:
pass
def _initPhysicalZoomForAnnotation(self, annotation_widget):
"""为标注界面初始化物理变焦控制器"""
try:
# 尝试导入物理变焦控制器
try:
from handlers.videopage.physical_zoom_controller import PhysicalZoomController
except ImportError:
try:
from physical_zoom_controller import PhysicalZoomController
except ImportError:
return
# 获取通道配置
if not self.general_set_panel or not self.general_set_panel.channel_id:
return
channel_id = self.general_set_panel.channel_id
# 从配置文件获取设备IP
config = self._getChannelConfig(channel_id)
if not config:
return
device_ip = config.get('address', '')
if not device_ip or 'rtsp://' not in device_ip:
return
# 提取IP地址
import re
match = re.search(r'@(\d+\.\d+\.\d+\.\d+)', device_ip)
if not match:
return
device_ip = match.group(1)
# 创建物理变焦控制器
physical_zoom_controller = PhysicalZoomController(
device_ip=device_ip,
username='admin',
password='cei345678',
channel=1
)
# 尝试连接设备
if physical_zoom_controller.connect_device():
# 设置到标注界面
annotation_widget.setPhysicalZoomController(physical_zoom_controller)
print(f"[标注界面] 物理变焦已启用 ({device_ip})")
else:
print(f"[标注界面] 物理变焦设备连接失败")
except Exception as e:
print(f"[标注界面] 初始化物理变焦失败: {e}")
def showGeneralSetPanel(self): def showGeneralSetPanel(self):
"""显示常规设置面板""" """显示常规设置面板"""
from widgets.videopage.general_set import GeneralSetPanel from widgets.videopage.general_set import GeneralSetPanel
......
...@@ -144,10 +144,6 @@ class ModelSettingHandler: ...@@ -144,10 +144,6 @@ class ModelSettingHandler:
model_config['model_path'] = absolute_path model_config['model_path'] = absolute_path
config_source = f"default_config.yaml → {channel_model_key} + model (全局参数)" config_source = f"default_config.yaml → {channel_model_key} + model (全局参数)"
print(f"[Handler] 加载通道 {channel_id} 的模型配置")
print(f" 相对路径: {channel_model_path}")
print(f" 绝对路径: {absolute_path}")
print(f" model_config['model_path'] = {model_config.get('model_path', 'None')}")
else: else:
# 使用全局模型配置 # 使用全局模型配置
model_config = default_config.get('model', {}).copy() model_config = default_config.get('model', {}).copy()
......
...@@ -241,9 +241,9 @@ class ModelPoolManager: ...@@ -241,9 +241,9 @@ class ModelPoolManager:
project_root = os.path.dirname(os.path.dirname(os.path.dirname(current_dir))) project_root = os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))
config_file_path = os.path.join(project_root, 'database', 'config', 'default_config.yaml') config_file_path = os.path.join(project_root, 'database', 'config', 'default_config.yaml')
print(f"🔍 [模型池管理器] 当前目录: {current_dir}") print(f" [模型池管理器] 当前目录: {current_dir}")
print(f"🔍 [模型池管理器] 项目根目录: {project_root}") print(f" [模型池管理器] 项目根目录: {project_root}")
print(f"🔍 [模型池管理器] 配置文件路径: {config_file_path}") print(f" [模型池管理器] 配置文件路径: {config_file_path}")
if not os.path.exists(config_file_path): if not os.path.exists(config_file_path):
print(f"❌ [模型池管理器] 配置文件不存在: {config_file_path}") print(f"❌ [模型池管理器] 配置文件不存在: {config_file_path}")
...@@ -338,7 +338,7 @@ class ModelPoolManager: ...@@ -338,7 +338,7 @@ class ModelPoolManager:
print(f"📝 [模型池管理器] 发现模型: {model_id} -> {model_path}") print(f"📝 [模型池管理器] 发现模型: {model_id} -> {model_path}")
print(f" - 使用通道: {', '.join(channels)}") print(f" - 使用通道: {', '.join(channels)}")
print(f"🔍 [模型池管理器] 扫描完成,发现 {len(unique_models)} 个唯一模型") print(f" [模型池管理器] 扫描完成,发现 {len(unique_models)} 个唯一模型")
return unique_models return unique_models
except Exception as e: except Exception as e:
...@@ -387,7 +387,7 @@ class ModelPoolManager: ...@@ -387,7 +387,7 @@ class ModelPoolManager:
# 确保所有信号处理完成 # 确保所有信号处理完成
QApplication.processEvents() QApplication.processEvents()
print(f"🔍 [模型池管理器] 加载结果: _loading_success={self._loading_success}") print(f" [模型池管理器] 加载结果: _loading_success={self._loading_success}")
# 返回加载结果 # 返回加载结果
return self._loading_success return self._loading_success
...@@ -428,7 +428,7 @@ class ModelPoolManager: ...@@ -428,7 +428,7 @@ class ModelPoolManager:
self._loading_success = success and len(loaded_models) > 0 self._loading_success = success and len(loaded_models) > 0
self._loading_finished = True # 标记加载完成 self._loading_finished = True # 标记加载完成
print(f"🔍 [模型池管理器] 设置加载状态: _loading_success={self._loading_success}, _loading_finished={self._loading_finished}") print(f" [模型池管理器] 设置加载状态: _loading_success={self._loading_success}, _loading_finished={self._loading_finished}")
# 关闭进度条对话框 # 关闭进度条对话框
self._close_progress_dialog(self._loading_success) self._close_progress_dialog(self._loading_success)
...@@ -541,7 +541,7 @@ class ModelPoolManager: ...@@ -541,7 +541,7 @@ class ModelPoolManager:
# 步骤2: 加载模型文件 (20-60%) # 步骤2: 加载模型文件 (20-60%)
self._update_progress_dialog(current_idx, model_id, "正在加载模型文件到显存...", 25) self._update_progress_dialog(current_idx, model_id, "正在加载模型文件到显存...", 25)
print(f"🔍 [模型池管理器] 正在加载模型: {model_path}") print(f" [模型池管理器] 正在加载模型: {model_path}")
if not engine.load_model(model_path): if not engine.load_model(model_path):
print(f"❌ [模型池管理器] 模型加载失败: {model_path}") print(f"❌ [模型池管理器] 模型加载失败: {model_path}")
return None return None
...@@ -550,7 +550,7 @@ class ModelPoolManager: ...@@ -550,7 +550,7 @@ class ModelPoolManager:
# 步骤3: 读取标注配置 (60-75%) # 步骤3: 读取标注配置 (60-75%)
self._update_progress_dialog(current_idx, model_id, "正在读取标注配置文件...", 65) self._update_progress_dialog(current_idx, model_id, "正在读取标注配置文件...", 65)
print(f"🔍 [模型池管理器] 正在加载 {model_id} 的标注配置...") print(f" [模型池管理器] 正在加载 {model_id} 的标注配置...")
annotation_config = self._load_annotation_config_for_model(model_id) annotation_config = self._load_annotation_config_for_model(model_id)
if not annotation_config: if not annotation_config:
print(f"❌ [模型池管理器] 未找到标注配置: {model_id}") print(f"❌ [模型池管理器] 未找到标注配置: {model_id}")
......
...@@ -90,13 +90,6 @@ class SimpleScheduler: ...@@ -90,13 +90,6 @@ class SimpleScheduler:
schedule_time = time.time() - schedule_start schedule_time = time.time() - schedule_start
self.stats['schedule_times'].append(schedule_time) self.stats['schedule_times'].append(schedule_time)
# 打印调度信息(调试用)
if scheduled_batches:
batch_summary = []
for batch in scheduled_batches:
batch_summary.append(f"{batch['model_id']}({batch['batch_size']}帧)")
print(f"⚡ [简单调度器] 创建批次: {' | '.join(batch_summary)}")
return scheduled_batches return scheduled_batches
except Exception as e: except Exception as e:
......
...@@ -57,12 +57,15 @@ class CurveThread: ...@@ -57,12 +57,15 @@ class CurveThread:
# 全局统一的回调函数(所有数据都发送到这个函数,函数内部根据csv_filepath或area_name处理) # 全局统一的回调函数(所有数据都发送到这个函数,函数内部根据csv_filepath或area_name处理)
_global_callback: Optional[Callable] = None _global_callback: Optional[Callable] = None
# 🔥 进度回调函数(用于显示进度条)
_progress_callback: Optional[Callable] = None
@staticmethod @staticmethod
def run(current_mission_path: str = None, callback: Optional[Callable] = None): def run(current_mission_path: str = None, callback: Optional[Callable] = None):
"""曲线绘制线程主循环(全局单例版本) """曲线绘制线程主循环(全局单例版本)
Args: Args:
current_mission_path: 当前任务路径(从全局变量 current_mission 读取) current_mission_path: 当前任务路径(从 curvemission 下拉框获取)
callback: 统一的回调函数 callback(csv_filepath, area_name, area_idx, curve_points) callback: 统一的回调函数 callback(csv_filepath, area_name, area_idx, curve_points)
曲线线程读取到新数据后,直接调用此函数 曲线线程读取到新数据后,直接调用此函数
参数说明: 参数说明:
...@@ -79,7 +82,7 @@ class CurveThread: ...@@ -79,7 +82,7 @@ class CurveThread:
if callback: if callback:
CurveThread._global_callback = callback CurveThread._global_callback = callback
# 从全局变量 current_mission 获取曲线路径 # 从 curvemission 获取曲线路径
curve_path = current_mission_path curve_path = current_mission_path
# 从配置文件读取曲线帧率 # 从配置文件读取曲线帧率
...@@ -88,19 +91,11 @@ class CurveThread: ...@@ -88,19 +91,11 @@ class CurveThread:
# 如果curve_path为None或为"0000",说明未设置任务,曲线线程优雅退出 # 如果curve_path为None或为"0000",说明未设置任务,曲线线程优雅退出
if not curve_path or curve_path == "0000": if not curve_path or curve_path == "0000":
print(f"⚠️ [全局曲线线程] 当前任务未设置(current_mission={curve_path}),曲线线程退出")
CurveThread._global_running_flag = False CurveThread._global_running_flag = False
return return
# 调试日志:显示曲线线程使用的路径
print(f"✅ [全局曲线线程] 已启动,监控路径: {curve_path}")
# 🔥 修改:定期扫描文件夹,发现所有CSV文件(包括新增的) # 🔥 修改:定期扫描文件夹,发现所有CSV文件(包括新增的)
available_csv_files = CurveThread._list_csv_files(curve_path) available_csv_files = CurveThread._list_csv_files(curve_path)
if not available_csv_files:
print(f"⚠️ [全局曲线线程] 监控目录下当前没有CSV文件: {curve_path}")
else:
print(f"📁 [全局曲线线程] 发现 {len(available_csv_files)} 个CSV文件")
# 为每个CSV文件维护读取状态(基于文件大小监控) # 为每个CSV文件维护读取状态(基于文件大小监控)
file_states = {} file_states = {}
...@@ -133,7 +128,7 @@ class CurveThread: ...@@ -133,7 +128,7 @@ class CurveThread:
# 启动时一次性加载所有CSV到内存 # 启动时一次性加载所有CSV到内存
cached_data = CurveThread._load_csv_to_memory(csv_filepath) cached_data = CurveThread._load_csv_to_memory(csv_filepath)
except Exception as e: except Exception as e:
print(f"⚠️ [全局曲线线程] 初始化文件状态失败 {csv_filepath}: {e}") pass
file_states[csv_filepath] = { file_states[csv_filepath] = {
'area_idx': area_idx, 'area_idx': area_idx,
...@@ -141,21 +136,29 @@ class CurveThread: ...@@ -141,21 +136,29 @@ class CurveThread:
'last_size': initial_size, # 上次检查时的文件大小 'last_size': initial_size, # 上次检查时的文件大小
'cached_data': cached_data, # 已加载到内存的数据 'cached_data': cached_data, # 已加载到内存的数据
} }
print(f"✅ [全局曲线线程] 监控文件: {csv_filepath} (区域索引: {area_idx}, 初始大小: {initial_size} bytes, 已加载: {len(cached_data)} 行)")
# 初次建立文件状态 # 初次建立文件状态
for csv_filepath in available_csv_files: for csv_filepath in available_csv_files:
_ensure_file_state(csv_filepath) _ensure_file_state(csv_filepath)
# 🔥 发送所有文件的历史数据(启动时一次性发送) # 🔥 发送所有文件的历史数据(启动时一次性发送,支持进度报告)
print(f"🔥 [全局曲线线程] 开始发送历史数据,共 {len(available_csv_files)} 个CSV文件") total_files = len(available_csv_files)
for csv_filepath in available_csv_files:
for idx, csv_filepath in enumerate(available_csv_files):
state = file_states.get(csv_filepath) state = file_states.get(csv_filepath)
if state and state['cached_data']: if state and state['cached_data']:
data_count = len(state['cached_data']) data_count = len(state['cached_data'])
print(f"📤 [全局曲线线程] 发送历史数据: {csv_filepath}")
print(f" 文件名: {state['area_name']}, 区域索引: {state['area_idx']}, 数据点数: {data_count}") # 🔥 报告进度
if CurveThread._progress_callback:
try:
progress_value = int((idx + 1) / total_files * 100)
progress_text = f"正在加载曲线数据... ({idx + 1}/{total_files})"
CurveThread._progress_callback(progress_value, progress_text)
except Exception as e:
import traceback
traceback.print_exc()
if CurveThread._global_callback: if CurveThread._global_callback:
try: try:
CurveThread._global_callback( CurveThread._global_callback(
...@@ -164,13 +167,17 @@ class CurveThread: ...@@ -164,13 +167,17 @@ class CurveThread:
state['area_idx'], # 区域索引 state['area_idx'], # 区域索引
state['cached_data'] # 历史数据点列表 state['cached_data'] # 历史数据点列表
) )
print(f"✅ [全局曲线线程] 历史数据发送成功")
except Exception as e: except Exception as e:
print(f"⚠️ [全局曲线线程] 发送历史数据失败 (csv_file={csv_filepath}): {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
else:
print(f"⚠️ [全局曲线线程] 文件无数据或状态不存在: {csv_filepath}") # 🔥 加载完成,报告100%进度
if CurveThread._progress_callback:
try:
CurveThread._progress_callback(100, "加载完成")
except Exception as e:
import traceback
traceback.print_exc()
# 文件扫描计数器(每10个周期重新扫描一次文件夹) # 文件扫描计数器(每10个周期重新扫描一次文件夹)
scan_counter = 0 scan_counter = 0
...@@ -188,13 +195,11 @@ class CurveThread: ...@@ -188,13 +195,11 @@ class CurveThread:
# 发现新文件 # 发现新文件
new_files = set(current_csv_files) - set(available_csv_files) new_files = set(current_csv_files) - set(available_csv_files)
if new_files: if new_files:
print(f"🆕 [全局曲线线程] 发现新CSV文件: {len(new_files)} 个")
for new_file in new_files: for new_file in new_files:
_ensure_file_state(new_file) _ensure_file_state(new_file)
# 立即发送新文件的历史数据 # 立即发送新文件的历史数据
state = file_states.get(new_file) state = file_states.get(new_file)
if state and state['cached_data']: if state and state['cached_data']:
print(f"📤 [全局曲线线程] 发送新文件历史数据: {new_file} ({len(state['cached_data'])} 行)")
if CurveThread._global_callback: if CurveThread._global_callback:
try: try:
CurveThread._global_callback( CurveThread._global_callback(
...@@ -204,7 +209,7 @@ class CurveThread: ...@@ -204,7 +209,7 @@ class CurveThread:
state['cached_data'] state['cached_data']
) )
except Exception as e: except Exception as e:
print(f"⚠️ [全局曲线线程] 发送新文件历史数据失败: {e}") pass
# 更新文件列表 # 更新文件列表
available_csv_files = current_csv_files available_csv_files = current_csv_files
...@@ -282,13 +287,10 @@ class CurveThread: ...@@ -282,13 +287,10 @@ class CurveThread:
new_points # 数据点列表 new_points # 数据点列表
) )
except Exception as e: except Exception as e:
print(f"⚠️ [全局曲线线程] 回调函数执行失败 (csv_file={csv_filepath}): {e}") pass
else:
print(f"⚠️ [全局曲线线程] 未设置回调函数 (csv_file={csv_filepath}, area_idx={state['area_idx']})")
# 如果文件大小减小(文件被截断或重新创建),重新加载 # 如果文件大小减小(文件被截断或重新创建),重新加载
elif current_size < last_size: elif current_size < last_size:
print(f"⚠️ [全局曲线线程] 文件大小减小,重新加载: {csv_filepath}")
state['cached_data'] = CurveThread._load_csv_to_memory(csv_filepath) state['cached_data'] = CurveThread._load_csv_to_memory(csv_filepath)
state['last_size'] = current_size state['last_size'] = current_size
...@@ -296,14 +298,12 @@ class CurveThread: ...@@ -296,14 +298,12 @@ class CurveThread:
time.sleep(check_interval) time.sleep(check_interval)
except Exception as e: except Exception as e:
print(f"⚠️ [全局曲线线程] 错误: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
time.sleep(0.5) time.sleep(1.0)
# 清理资源 # 清理资源
print(f"🛑 [全局曲线线程] 正在停止...") CurveThread._global_running_flag = False
print(f"✅ [全局曲线线程] 已停止")
@staticmethod @staticmethod
def _load_csv_to_memory(csv_filepath: str) -> List[dict]: def _load_csv_to_memory(csv_filepath: str) -> List[dict]:
...@@ -526,3 +526,19 @@ class CurveThread: ...@@ -526,3 +526,19 @@ class CurveThread:
def clear_callback(): def clear_callback():
"""清除回调函数""" """清除回调函数"""
CurveThread._global_callback = None CurveThread._global_callback = None
@staticmethod
def set_progress_callback(callback: Optional[Callable]):
"""设置进度回调函数
Args:
callback: 进度回调函数 callback(value, text)
- value: 进度值 (0-100)
- text: 进度文本描述
"""
CurveThread._progress_callback = callback
@staticmethod
def clear_progress_callback():
"""清除进度回调函数"""
CurveThread._progress_callback = None
...@@ -89,17 +89,6 @@ class DisplayThread: ...@@ -89,17 +89,6 @@ class DisplayThread:
liquid_positions = last_liquid_positions liquid_positions = last_liquid_positions
is_new_data = False # 标记为历史数据(黄色) is_new_data = False # 标记为历史数据(黄色)
# 调试信息:显示线程接收数据
if liquid_positions and debug_frame_count <= 5: # 只打印前5帧
print(f"\n [显示线程-{channel_id}] 第{debug_frame_count}帧 接收液位数据:")
for idx, pos_data in liquid_positions.items():
print(f" 目标{idx}:")
print(f" - 包含的键: {pos_data.keys()}")
if 'height_cm' in pos_data:
print(f" - height_cm: {pos_data['height_cm']}")
if 'height_mm' in pos_data:
print(f" - height_mm: {pos_data['height_mm']}")
# 绘制液位线 # 绘制液位线
if liquid_positions: if liquid_positions:
if draw_detection_func: if draw_detection_func:
......
...@@ -85,8 +85,6 @@ class GlobalDetectionThread: ...@@ -85,8 +85,6 @@ class GlobalDetectionThread:
'average_batch_size': 0.0, 'average_batch_size': 0.0,
'processing_times': deque(maxlen=1000) 'processing_times': deque(maxlen=1000)
} }
print("🚀 [全局检测线程] 初始化完成")
@classmethod @classmethod
def get_instance(cls): def get_instance(cls):
...@@ -118,7 +116,6 @@ class GlobalDetectionThread: ...@@ -118,7 +116,6 @@ class GlobalDetectionThread:
def start(self) -> bool: def start(self) -> bool:
"""启动全局检测线程""" """启动全局检测线程"""
if self._running: if self._running:
print("⚠️ [全局检测线程] 已在运行中")
return True return True
try: try:
...@@ -141,12 +138,9 @@ class GlobalDetectionThread: ...@@ -141,12 +138,9 @@ class GlobalDetectionThread:
# 更新全局状态变量 # 更新全局状态变量
GlobalDetectionThread.detection_state = True GlobalDetectionThread.detection_state = True
print("✅ [全局检测线程] 启动成功")
print(f"🔄 [全局检测线程] 状态变量已更新: detection_state = {GlobalDetectionThread.detection_state}")
return True return True
except Exception as e: except Exception as e:
print(f"❌ [全局检测线程] 启动失败: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
...@@ -159,11 +153,9 @@ class GlobalDetectionThread: ...@@ -159,11 +153,9 @@ class GlobalDetectionThread:
def stop(self) -> bool: def stop(self) -> bool:
"""停止全局检测线程""" """停止全局检测线程"""
if not self._running: if not self._running:
print("⚠️ [全局检测线程] 未在运行")
return True return True
try: try:
print("🛑 [全局检测线程] 正在停止...")
# 设置停止信号 # 设置停止信号
self._stop_event.set() self._stop_event.set()
...@@ -175,20 +167,13 @@ class GlobalDetectionThread: ...@@ -175,20 +167,13 @@ class GlobalDetectionThread:
# 等待线程结束 # 等待线程结束
if self._thread and self._thread.is_alive(): if self._thread and self._thread.is_alive():
self._thread.join(timeout=5.0) self._thread.join(timeout=5.0)
if self._thread.is_alive():
print("⚠️ [全局检测线程] 线程未能在5秒内停止")
else:
print("✅ [全局检测线程] 线程已停止")
# 清理资源 # 清理资源
self._cleanup_resources() self._cleanup_resources()
print("✅ [全局检测线程] 停止完成")
print(f"🔄 [全局检测线程] 状态变量已更新: detection_state = {GlobalDetectionThread.detection_state}")
return True return True
except Exception as e: except Exception as e:
print(f"❌ [全局检测线程] 停止失败: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
...@@ -214,8 +199,6 @@ class GlobalDetectionThread: ...@@ -214,8 +199,6 @@ class GlobalDetectionThread:
# 在注册通道时,重新加载该通道的标注配置到模型 # 在注册通道时,重新加载该通道的标注配置到模型
if self.model_pool_manager: if self.model_pool_manager:
self._reload_channel_annotation_config(channel_id) self._reload_channel_annotation_config(channel_id)
print(f"📝 [全局检测线程] 注册通道: {channel_id}")
def unregister_channel(self, channel_id: str): def unregister_channel(self, channel_id: str):
"""注销通道 """注销通道
...@@ -226,8 +209,6 @@ class GlobalDetectionThread: ...@@ -226,8 +209,6 @@ class GlobalDetectionThread:
self.active_channels.discard(channel_id) self.active_channels.discard(channel_id)
self.channel_contexts.pop(channel_id, None) self.channel_contexts.pop(channel_id, None)
self.channel_callbacks.pop(channel_id, None) self.channel_callbacks.pop(channel_id, None)
print(f"📝 [全局检测线程] 注销通道: {channel_id}")
def _load_config(self): def _load_config(self):
"""加载配置文件""" """加载配置文件"""
...@@ -247,22 +228,12 @@ class GlobalDetectionThread: ...@@ -247,22 +228,12 @@ class GlobalDetectionThread:
self.default_batch_size = config.get('default_batch_size', 4) self.default_batch_size = config.get('default_batch_size', 4)
self.max_wait_time = config.get('max_batch_wait_time', 0.05) self.max_wait_time = config.get('max_batch_wait_time', 0.05)
print(f"📋 [全局检测线程] 配置加载成功:")
print(f" - 检测帧率: {self.detection_frame_rate}")
print(f" - 批处理: {self.batch_processing_enabled}")
print(f" - 批大小: {self.default_batch_size}")
print(f" - 最大等待: {self.max_wait_time}s")
else:
print(f"⚠️ [全局检测线程] 配置文件不存在,使用默认配置: {config_file}")
except Exception as e: except Exception as e:
print(f"❌ [全局检测线程] 配置加载失败: {e}") pass
def _initialize_components(self): def _initialize_components(self):
"""初始化核心组件""" """初始化核心组件"""
try: try:
print("🔧 [全局检测线程] 初始化核心组件...")
# 模型池管理器(真实实现) # 模型池管理器(真实实现)
from ..model_pool_manager import ModelPoolManager from ..model_pool_manager import ModelPoolManager
self.model_pool_manager = ModelPoolManager() self.model_pool_manager = ModelPoolManager()
...@@ -283,15 +254,11 @@ class GlobalDetectionThread: ...@@ -283,15 +254,11 @@ class GlobalDetectionThread:
from ..result_distributor import ResultDistributor from ..result_distributor import ResultDistributor
self.result_distributor = ResultDistributor() self.result_distributor = ResultDistributor()
print("✅ [全局检测线程] 核心组件初始化完成")
except Exception as e: except Exception as e:
print(f"❌ [全局检测线程] 组件初始化失败: {e}")
raise raise
def _main_loop(self): def _main_loop(self):
"""主循环 - 全局检测线程的核心逻辑""" """主循环 - 全局检测线程的核心逻辑"""
print("🔄 [全局检测线程] 主循环开始")
frame_interval = 1.0 / self.detection_frame_rate if self.detection_frame_rate > 0 else 0.2 frame_interval = 1.0 / self.detection_frame_rate if self.detection_frame_rate > 0 else 0.2
...@@ -331,12 +298,9 @@ class GlobalDetectionThread: ...@@ -331,12 +298,9 @@ class GlobalDetectionThread:
time.sleep(sleep_time) time.sleep(sleep_time)
except Exception as e: except Exception as e:
print(f"❌ [全局检测线程] 主循环异常: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
time.sleep(0.1) time.sleep(0.1)
print("🔄 [全局检测线程] 主循环结束")
def _collect_frames(self) -> Dict[str, Any]: def _collect_frames(self) -> Dict[str, Any]:
"""收集各通道的帧数据""" """收集各通道的帧数据"""
...@@ -344,15 +308,9 @@ class GlobalDetectionThread: ...@@ -344,15 +308,9 @@ class GlobalDetectionThread:
# 使用优化的帧收集方法 # 使用优化的帧收集方法
collected_data = self.frame_collector.optimize_frame_collection(self.channel_contexts) collected_data = self.frame_collector.optimize_frame_collection(self.channel_contexts)
# 如果收集到帧,打印简要信息(调试用)
if collected_data.get('total_frames', 0) > 0:
summary = self.frame_collector.get_frame_groups_summary(collected_data.get('model_groups', {}))
print(f"📥 [全局检测线程] 收集帧: {summary}")
return collected_data return collected_data
except Exception as e: except Exception as e:
print(f"❌ [全局检测线程] 帧收集失败: {e}")
return { return {
'model_groups': {}, 'model_groups': {},
'total_frames': 0, 'total_frames': 0,
...@@ -381,14 +339,8 @@ class GlobalDetectionThread: ...@@ -381,14 +339,8 @@ class GlobalDetectionThread:
self.result_distributor.distribute_results_optimized( self.result_distributor.distribute_results_optimized(
results, self.channel_contexts, self.channel_callbacks results, self.channel_contexts, self.channel_callbacks
) )
# 打印分发摘要(调试用)
if results:
summary = self.result_distributor.get_distribution_summary(results)
print(f"📤 [全局检测线程] 分发结果: {summary}")
except Exception as e: except Exception as e:
print(f"❌ [全局检测线程] 结果分发失败: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
...@@ -410,23 +362,15 @@ class GlobalDetectionThread: ...@@ -410,23 +362,15 @@ class GlobalDetectionThread:
channel_id: 通道ID channel_id: 通道ID
""" """
try: try:
print(f"🔄 [全局检测线程] 验证通道 {channel_id} 的标注配置...")
# 使用模型池管理器的方法验证配置 # 使用模型池管理器的方法验证配置
annotation_config = self.model_pool_manager.get_channel_annotation_config(channel_id) annotation_config = self.model_pool_manager.get_channel_annotation_config(channel_id)
if not annotation_config: if not annotation_config:
print(f"⚠️ [全局检测线程] 通道 {channel_id} 的标注配置不存在或加载失败")
return False return False
print(f"✅ [全局检测线程] 通道 {channel_id} 的标注配置验证通过")
print(f" - 检测区域数量: {len(annotation_config.get('boxes', []))}")
print(f" - 区域高度: {annotation_config.get('actual_heights', [])}")
return True return True
except Exception as e: except Exception as e:
print(f"❌ [全局检测线程] 验证标注配置失败: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
return False return False
...@@ -436,7 +380,6 @@ class GlobalDetectionThread: ...@@ -436,7 +380,6 @@ class GlobalDetectionThread:
try: try:
# 清理核心组件 # 清理核心组件
if self.model_pool_manager and hasattr(self.model_pool_manager, 'cleanup'): if self.model_pool_manager and hasattr(self.model_pool_manager, 'cleanup'):
print("🧹 [全局检测线程] 清理模型池管理器...")
self.model_pool_manager.cleanup() self.model_pool_manager.cleanup()
# 清理通道信息 # 清理通道信息
...@@ -444,10 +387,8 @@ class GlobalDetectionThread: ...@@ -444,10 +387,8 @@ class GlobalDetectionThread:
self.channel_contexts.clear() self.channel_contexts.clear()
self.channel_callbacks.clear() self.channel_callbacks.clear()
print("🧹 [全局检测线程] 资源清理完成")
except Exception as e: except Exception as e:
print(f"❌ [全局检测线程] 资源清理失败: {e}") pass
def get_stats(self) -> Dict[str, Any]: def get_stats(self) -> Dict[str, Any]:
"""获取性能统计信息""" """获取性能统计信息"""
......
...@@ -90,8 +90,6 @@ class StorageThread: ...@@ -90,8 +90,6 @@ class StorageThread:
csv_file = open(csv_filepath, 'a', encoding='utf-8', buffering=8192) # 8KB缓冲 csv_file = open(csv_filepath, 'a', encoding='utf-8', buffering=8192) # 8KB缓冲
csv_files[area_idx] = csv_file csv_files[area_idx] = csv_file
csv_writers[area_idx] = csv_file csv_writers[area_idx] = csv_file
print(f" - 区域{area_idx+1}({area_name}): {csv_filename}")
# 存储线程不受 save_data_rate 限制,全速消费队列,避免数据丢失 # 存储线程不受 save_data_rate 限制,全速消费队列,避免数据丢失
# save_data_rate 仅用于配置文件记录,实际存储线程会保存所有检测结果 # save_data_rate 仅用于配置文件记录,实际存储线程会保存所有检测结果
...@@ -216,7 +214,6 @@ class StorageThread: ...@@ -216,7 +214,6 @@ class StorageThread:
config_file = os.path.join(project_root, 'database', 'config', 'channel_config.yaml') config_file = os.path.join(project_root, 'database', 'config', 'channel_config.yaml')
if not os.path.exists(config_file): if not os.path.exists(config_file):
print(f"⚠️ 配置文件不存在: {config_file}")
return None return None
with open(config_file, 'r', encoding='utf-8') as f: with open(config_file, 'r', encoding='utf-8') as f:
...@@ -227,7 +224,6 @@ class StorageThread: ...@@ -227,7 +224,6 @@ class StorageThread:
return None return None
except Exception as e: except Exception as e:
print(f"❌ 加载通道配置失败: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
return None return None
...@@ -277,42 +273,38 @@ class StorageThread: ...@@ -277,42 +273,38 @@ class StorageThread:
def _get_area_names(channel_id: str, channel_config: dict): def _get_area_names(channel_id: str, channel_config: dict):
"""获取区域名称列表 """获取区域名称列表
从 annotation_result.yaml 读取区域名称
Args: Args:
channel_id: 通道ID channel_id: 通道ID
channel_config: 通道配置字典 channel_config: 通道配置字典(保留参数以保持接口兼容)
Returns: Returns:
dict: {区域索引: 区域名称} dict: {区域索引: 区域名称}
""" """
area_names = {} area_names = {}
if channel_config: try:
areas = channel_config.get('general', {}).get('areas', {}) project_root = StorageThread._get_project_root()
area_count = channel_config.get('general', {}).get('area_count', 0) annotation_file = os.path.join(project_root, 'database', 'config', 'annotation_result.yaml')
# 获取通道名称(用于生成默认区域名称)
channel_name = StorageThread._get_channel_name(channel_id, channel_config)
for i in range(area_count): if os.path.exists(annotation_file):
area_key = f'area_{i+1}' with open(annotation_file, 'r', encoding='utf-8') as f:
area_name = areas.get(area_key, '') annotation_data = yaml.safe_load(f)
# 如果没有区域名称,使用与标注界面相同的默认生成逻辑
if not area_name:
if channel_name:
area_name = f"{channel_name}_区域{i+1}"
else:
area_name = f"区域{i+1}"
area_names[i] = area_name if annotation_data and channel_id in annotation_data:
areas_config = annotation_data[channel_id].get('areas', {})
# 如果没有配置区域,使用默认值(保持向后兼容)
if not area_names: # 从标注结果读取区域名称
channel_name = StorageThread._get_channel_name(channel_id, channel_config) for area_key, area_info in areas_config.items():
if channel_name: # area_key 格式: 'area_1', 'area_2', ...
area_names = {0: f"{channel_name}_区域1"} area_idx = int(area_key.split('_')[1]) - 1 # 转换为0索引
else: area_name = area_info.get('name', '')
area_names = {0: "区域1"}
if area_name:
area_names[area_idx] = area_name
except Exception as e:
pass
return area_names return area_names
...@@ -328,7 +320,6 @@ class StorageThread: ...@@ -328,7 +320,6 @@ class StorageThread:
str: 任务路径,如果未设置则返回None str: 任务路径,如果未设置则返回None
""" """
if not main_window: if not main_window:
print(f"⚠️ [存储线程-{channel_id}] 主窗口实例为空,无法获取通道任务路径")
return None return None
try: try:
...@@ -341,25 +332,19 @@ class StorageThread: ...@@ -341,25 +332,19 @@ class StorageThread:
mission_label = getattr(main_window, mission_var_name) mission_label = getattr(main_window, mission_var_name)
task_folder_name = mission_label.text() task_folder_name = mission_label.text()
print(f"📁 [存储线程-{channel_id}] 从 {mission_var_name} 读取任务: {task_folder_name}")
# 检查任务是否有效 # 检查任务是否有效
if not task_folder_name or task_folder_name.strip() == "" or task_folder_name == "未分配任务": if not task_folder_name or task_folder_name.strip() == "" or task_folder_name == "未分配任务":
print(f"⚠️ [存储线程-{channel_id}] 通道未分配任务,跳过存储")
return None return None
# 构建完整的任务路径 # 构建完整的任务路径
project_root = StorageThread._get_project_root() project_root = StorageThread._get_project_root()
mission_path = os.path.join(project_root, 'database', 'mission_result', task_folder_name.strip()) mission_path = os.path.join(project_root, 'database', 'mission_result', task_folder_name.strip())
print(f"✅ [存储线程-{channel_id}] 任务路径: {mission_path}")
return mission_path return mission_path
else: else:
print(f"⚠️ [存储线程-{channel_id}] 主窗口中未找到变量: {mission_var_name}")
return None return None
except Exception as e: except Exception as e:
print(f"❌ [存储线程-{channel_id}] 获取通道任务路径失败: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
return None return None
......
# -*- 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)
"""
Pythonexe
SDK_internal
"""
import os
import base64
from pathlib import Path
def embed_file_to_python(file_path, output_path, var_name):
"""
Pythonbase64
Args:
file_path:
output_path: Python
var_name:
"""
with open(file_path, 'rb') as f:
data = f.read()
encoded = base64.b64encode(data).decode('utf-8')
# Python
code = f'''"""
: {file_path}
"""
import base64
import io
# Base64
_{var_name}_data = """{encoded}"""
def get_{var_name}():
""""""
return base64.b64decode(_{var_name}_data)
def get_{var_name}_path():
""""""
import tempfile
import os
data = get_{var_name}()
#
ext = os.path.splitext("{os.path.basename(file_path)}")[1]
#
fd, temp_path = tempfile.mkstemp(suffix=ext, prefix='embedded_resource_')
try:
with os.fdopen(fd, 'wb') as f:
f.write(data)
return temp_path
except:
os.close(fd)
raise
'''
with open(output_path, 'w', encoding='utf-8') as f:
f.write(code)
print(f" : {file_path} -> {output_path} (: {var_name})")
def embed_directory_to_python(dir_path, output_dir, max_size_mb=1):
"""
Python
Args:
dir_path:
output_dir:
max_size_mb: MB
"""
os.makedirs(output_dir, exist_ok=True)
embedded_files = []
skipped_files = []
for root, dirs, files in os.walk(dir_path):
for file in files:
file_path = os.path.join(root, file)
rel_path = os.path.relpath(file_path, dir_path)
#
size_mb = os.path.getsize(file_path) / (1024 * 1024)
if size_mb > max_size_mb:
skipped_files.append((rel_path, size_mb))
continue
#
var_name = rel_path.replace(os.sep, '_').replace('.', '_').replace('-', '_')
#
output_path = os.path.join(output_dir, f"embedded_{var_name}.py")
try:
embed_file_to_python(file_path, output_path, var_name)
embedded_files.append((rel_path, file_path))
except Exception as e:
print(f" : {file_path} - {e}")
skipped_files.append((rel_path, size_mb))
print(f"\n:")
print(f" : {len(embedded_files)} ")
print(f" : {len(skipped_files)} {max_size_mb}MB")
return embedded_files, skipped_files
if __name__ == '__main__':
# icons
project_root = os.path.abspath('.')
icons_dir = os.path.join(project_root, 'icons')
output_dir = os.path.join(project_root, 'hooks', 'embedded_resources')
if os.path.exists(icons_dir):
print("icons...")
embed_directory_to_python(icons_dir, output_dir, max_size_mb=0.5) # 0.5MB
else:
print(f": {icons_dir}")
# -*- coding: utf-8 -*-
"""
PyInstaller hook for encodings module
encodings encodings
"""
from PyInstaller.utils.hooks import collect_submodules
# encodings
hiddenimports = collect_submodules('encodings')
# encodings exe.spec
# encodings base_library.zip
datas = []
"""
PyArmor Hook for PyInstaller
PyInstallerPyArmor
"""
from PyInstaller.utils.hooks import collect_data_files, collect_submodules
# PyArmor
hiddenimports = [
'pyarmor',
'pyarmor.pyarmor_runtime',
'pyarmor.pyarmor_runtime_000000',
'pyarmor.pytransform',
'pytransform',
]
# pyarmor
try:
hiddenimports += collect_submodules('pyarmor')
except:
pass
# PyArmor
datas = []
try:
datas += collect_data_files('pyarmor')
except:
pass
# PyArmorhook
# PyArmor.so/.dll
binaries = []
try:
from PyInstaller.utils.hooks import collect_dynamic_libs
binaries += collect_dynamic_libs('pyarmor')
except:
pass
from PyInstaller.utils.hooks import collect_submodules, collect_dynamic_libs
#
# - torch .py _internal/torch
# - DLL/PYD Python PYZ.pyc
hiddenimports = collect_submodules('torch')
binaries = collect_dynamic_libs('torch')
datas = []
import os
import sys
import faulthandler
import traceback
from datetime import datetime
def _log_dir() -> str:
# onedir: sys.executable dist/exe/exe.exe
base_dir = os.path.dirname(getattr(sys, "executable", sys.argv[0]))
path = os.path.join(base_dir, "logs")
try:
os.makedirs(path, exist_ok=True)
except Exception:
#
path = os.path.join(os.path.abspath(os.getcwd()), "logs")
os.makedirs(path, exist_ok=True)
return path
def _open_log_file():
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
log_path = os.path.join(_log_dir(), f"app_{timestamp}.log")
#
# encodings
try:
return open(log_path, mode="a", encoding="utf-8", buffering=1)
except (LookupError, NameError):
# encodings
return open(log_path, mode="a", buffering=1)
def _install_handlers():
log_fh = _open_log_file()
#
print_fn = lambda *args: log_fh.write(" ".join(str(a) for a in args) + "\n")
print_fn("===== Application Start =====")
print_fn("Executable:", getattr(sys, "executable", sys.argv[0]))
print_fn("CWD:", os.getcwd())
print_fn("Args:", " ".join(sys.argv))
# stdout/stderr
sys.stdout = log_fh
sys.stderr = log_fh
# faulthandler
try:
faulthandler.enable(log_fh)
except Exception:
pass
#
def _excepthook(exc_type, exc, tb):
log_fh.write("===== Uncaught Exception =====\n")
traceback.print_exception(exc_type, exc, tb, file=log_fh)
log_fh.flush()
sys.excepthook = _excepthook
# import Python
# sys.modules encodings
def _delayed_install():
try:
# encodings
import encodings
_install_handlers()
except Exception:
# encodings
try:
_install_handlers()
except Exception:
#
pass
#
import sys
if hasattr(sys, '_getframe'):
# sys._getframe
try:
_delayed_install()
except:
pass
else:
#
try:
_delayed_install()
except:
pass
import os
import sys
import json
import tempfile
import shutil
# hook
# - Python
# - _internal/encrypted .bin DLL
# - /IV
AES256_KEY = b'0123456789ABCDEF0123456789ABCDEF' # exe.spec
AES_IV = b'ABCDEF0123456789' # exe.spec
PROTECT_KEYWORDS = ['torch', 'torchvision', 'torchaudio']
def _try_import_crypto():
try:
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
return Cipher, algorithms, modes, default_backend
except Exception:
return None, None, None, None
def _decrypt_bytes(data: bytes) -> bytes:
Cipher, algorithms, modes, default_backend = _try_import_crypto()
if Cipher is not None:
cipher = Cipher(algorithms.AES(AES256_KEY), modes.CTR(AES_IV), backend=default_backend())
decryptor = cipher.decryptor()
return decryptor.update(data) + decryptor.finalize()
# XOR
key = AES256_KEY
out = bytearray(len(data))
for i, b in enumerate(data):
out[i] = b ^ key[i % len(key)]
return bytes(out)
def _get_base_dir():
# PyInstaller onedir _MEIPASS _internal
# _MEIPASS
if hasattr(sys, '_MEIPASS') and sys._MEIPASS:
return sys._MEIPASS
# exe
exe_dir = os.path.dirname(sys.executable)
# dist/exe/exe.exe dist/exe/_internal
# a.datas encrypted _internal/encrypted
return os.path.join(exe_dir, '_internal')
def _load_manifest(encrypted_root):
manifest_path = os.path.join(encrypted_root, 'manifest.json')
if not os.path.exists(manifest_path):
return []
try:
with open(manifest_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception:
return []
def _prepare_temp_dir():
base = tempfile.gettempdir()
target = os.path.join(base, 'lllds_decrypt_bins')
os.makedirs(target, exist_ok=True)
return target
def _add_search_path(path):
# Windows: DLL
try:
if hasattr(os, 'add_dll_directory'):
os.add_dll_directory(path)
except Exception:
pass
if path not in os.environ.get('PATH', ''):
os.environ['PATH'] = path + os.pathsep + os.environ.get('PATH', '')
if path not in sys.path:
sys.path.insert(0, path)
def _main():
try:
base_dir = _get_base_dir()
encrypted_root = os.path.join(base_dir, 'encrypted')
if not os.path.isdir(encrypted_root):
return
manifest = _load_manifest(encrypted_root)
if not manifest:
return
out_dir = _prepare_temp_dir()
wrote_any = False
for item in manifest:
name = item.get('name')
cipher_rel = item.get('cipher_path')
if not name or not cipher_rel:
continue
#
low = name.lower()
if not any(k in low for k in PROTECT_KEYWORDS):
continue
cipher_abs = os.path.join(base_dir, cipher_rel.replace('/', os.sep))
if not os.path.exists(cipher_abs):
continue
with open(cipher_abs, 'rb') as f:
enc = f.read()
raw = _decrypt_bytes(enc)
out_path = os.path.join(out_dir, name)
with open(out_path, 'wb') as wf:
wf.write(raw)
wrote_any = True
if wrote_any:
_add_search_path(out_dir)
except Exception:
#
pass
_main()
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
PyInstaller Hook - ultralytics
ultralytics
"""
import os
import sys
import tempfile
from pathlib import Path
def setup_ultralytics_environment():
""" ultralytics """
try:
#
if getattr(sys, 'frozen', False):
#
app_dir = Path(sys.executable).parent
else:
#
app_dir = Path(__file__).parent.parent
# ultralytics
os.environ['YOLO_CONFIG_DIR'] = str(app_dir / 'database' / 'config')
# ultralytics
temp_dir = tempfile.mkdtemp(prefix='ultralytics_')
os.environ['ULTRALYTICS_CONFIG_DIR'] = temp_dir
# ultralytics
os.environ['YOLO_VERBOSE'] = 'False'
os.environ['ULTRALYTICS_ANALYTICS'] = 'False'
print(f" ultralytics : {app_dir}")
except Exception as e:
print(f" ultralytics : {e}")
#
setup_ultralytics_environment()
"""
PyArmor Runtime Hook
PyInstallerPyArmor
"""
import os
import sys
def _init_pyarmor_runtime():
"""
PyArmor
PyArmor
"""
try:
# _internalonedir
if hasattr(sys, '_MEIPASS'):
# onefile
base_dir = sys._MEIPASS
else:
# onedirexe.exe_internal
exe_dir = os.path.dirname(sys.executable)
base_dir = os.path.join(exe_dir, '_internal')
# _internalsys.path
if base_dir not in sys.path:
sys.path.insert(0, base_dir)
# PyArmorPyArmor
try:
import pyarmor.pyarmor_runtime # type: ignore #
except ImportError:
# PyArmor
pass
except Exception:
#
pass
#
_init_pyarmor_runtime()
File added
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment