Commit 13c76314 by Yuhaibo

修改

parent f6177d04
......@@ -850,55 +850,22 @@ class MainWindow(
self.videoLayoutStack.addWidget(layout_widget)
def _onCurveMissionChanged(self, mission_name):
"""曲线任务选择变化,更新显示的通道"""
"""曲线任务选择变化(基于CSV文件动态显示,不做筛选)"""
# 🔥 不再从任务配置读取通道列表,改为基于CSV文件数量动态显示
# 曲线线程会读取所有CSV文件,这里只需要确保有足够的容器显示
if not mission_name or mission_name == "请选择任务":
# 隐藏所有通道
print(f"[曲线布局] 未选择任务,隐藏所有通道容器")
self._updateCurveChannelDisplay([])
return
# 获取任务使用的通道列表
selected_channels = self._getTaskChannels(mission_name)
if selected_channels:
print(f"📊 [曲线布局] 任务 {mission_name} 使用通道: {selected_channels}")
self._updateCurveChannelDisplay(selected_channels)
else:
print(f"[曲线布局] 任务 {mission_name} 无通道配置")
self._updateCurveChannelDisplay([])
# 🔥 显示所有4个通道容器,让曲线系统自动填充
# 实际显示的曲线数量由CSV文件数量决定
all_channels = ['通道1', '通道2', '通道3', '通道4']
print(f"📊 [曲线布局] 任务 {mission_name} 已选择,显示所有通道容器,等待CSV文件加载")
self._updateCurveChannelDisplay(all_channels)
def _getTaskChannels(self, mission_name):
"""从任务配置文件获取使用的通道列表"""
import os
import yaml
try:
# 构建任务配置文件路径
if getattr(sys, 'frozen', False):
project_root = os.path.dirname(sys.executable)
else:
try:
from database.config import get_project_root
project_root = get_project_root()
except ImportError:
project_root = os.getcwd()
config_path = os.path.join(project_root, 'database', 'config', 'mission', f'{mission_name}.yaml')
if not os.path.exists(config_path):
print(f"[曲线布局] 任务配置文件不存在: {config_path}")
return []
# 读取任务配置
with open(config_path, 'r', encoding='utf-8') as f:
config = yaml.safe_load(f)
# 获取选中的通道列表
selected_channels = config.get('selected_channels', [])
return selected_channels
except Exception as e:
print(f"[曲线布局] 读取任务配置失败: {e}")
return []
# 🔥 删除 _getTaskChannels 方法,不再需要从配置文件读取通道列表
# 改为显示所有通道容器,由CSV文件动态驱动曲线显示
def _updateCurveChannelDisplay(self, selected_channels):
"""更新曲线布局中显示的通道"""
......@@ -907,10 +874,10 @@ class MainWindow(
# 通道名称到索引的映射
channel_name_to_index = {
'通道_1': 0,
'通道_2': 1,
'通道_3': 2,
'通道_4': 3
'通道1': 0,
'通道2': 1,
'通道3': 2,
'通道4': 3
}
# 首先隐藏所有通道
......
qweqwrfwadfsafa
duaisfdhuahofhaofhoa
yuhaibo
\ No newline at end of file
......@@ -601,16 +601,15 @@ class ChannelPanelHandler:
if channel_str.startswith('channel') and channel_str[7:].isdigit():
return channel_str
if channel_str.startswith('通道_'):
suffix = channel_str.split('_', 1)[1]
# 支持新格式:'通道1', '通道2'(推荐)
if channel_str.startswith('通道'):
# 移除'通道'前缀,获取数字部分
suffix = channel_str.replace('通道', '').strip()
# 如果包含下划线(旧格式'通道_1'),去掉下划线
suffix = suffix.replace('_', '').strip()
if suffix.isdigit():
return f"channel{suffix}"
if channel_str.startswith('通道'):
digits = ''.join(ch for ch in channel_str if ch.isdigit())
if digits:
return f"channel{digits}"
return None
def _getChannelConfigFromFile(self, channel_id):
......
......@@ -233,6 +233,9 @@ class CurvePanelHandler:
# - 'history':历史回放模式,加载所有数据点,不做限制
self.curve_load_mode = 'realtime'
# 🔥 历史数据加载标志(避免重复加载)
self._history_data_loaded = False
# UI组件引用
self.curve_panel = None
......@@ -559,25 +562,29 @@ class CurvePanelHandler:
folder_path = None
mission_name = None
# 更新 current_mission 全局变量(会自动同步到配置文件)
if hasattr(self, 'current_mission'):
self.current_mission = folder_path
# 🔥 同步任务信息到所有通道面板(从current_mission读取)
if hasattr(self, 'syncTaskInfoToAllPanels'):
self.syncTaskInfoToAllPanels()
# 清除当前数据
self.clearAllData()
# 加载新任务的历史数据
if folder_path and os.path.exists(folder_path):
print(f"📊 [曲线面板Handler] 开始加载任务历史数据: {folder_path}")
success = self.loadHistoricalCurveData(folder_path)
if success:
print(f"✅ [曲线面板Handler] 历史数据加载成功")
else:
print(f"⚠️ [曲线面板Handler] 历史数据加载失败或无数据")
# 🔥 只在曲线模式布局时才启动曲线线程
# 检查是否在曲线模式(_video_layout_mode == 1)
is_curve_mode = hasattr(self, '_video_layout_mode') and self._video_layout_mode == 1
if not is_curve_mode:
print(f"🔄 [曲线面板Handler] 不在曲线模式,跳过启动曲线线程")
return
# 🔥 重新启动曲线线程以监控新任务的CSV文件变化
if hasattr(self, 'thread_manager') and folder_path:
# 先停止旧的曲线线程
self.thread_manager.stop_all_curve_threads()
print(f"🔄 [曲线面板Handler] 已停止旧的曲线线程")
# 启动新的曲线线程监控新任务
import time
time.sleep(0.1) # 短暂延迟确保线程完全停止
self.thread_manager.start_all_curve_threads()
print(f"🚀 [曲线面板Handler] 已重新启动曲线线程,监控任务: {folder_path}")
print(f"📊 [曲线面板Handler] 曲线线程将自动加载历史数据并监控新数据")
else:
print(f"🔄 [曲线面板Handler] 清空曲线数据(无有效任务路径)")
......@@ -840,7 +847,7 @@ class CurvePanelHandler:
- 只需要一次性读取当前任务文件夹下的所有CSV文件并显示
Args:
data_directory: 数据目录路径(通常传入 current_mission 路径,如果为None则使用save_base_dir
data_directory: 数据目录路径(通常从 curvemission 获取
Returns:
bool: 是否成功启动加载任务
......@@ -859,22 +866,49 @@ class CurvePanelHandler:
if not csv_files:
return False
# 创建进度对话框
progress_dialog = QtWidgets.QProgressDialog(
"正在加载曲线数据...",
"取消",
0,
100 # 使用百分比进度
)
progress_dialog.setWindowTitle("曲线数据加载进度")
progress_dialog.setWindowModality(QtCore.Qt.WindowModal)
progress_dialog.setMinimumWidth(400)
progress_dialog.setWindowIcon(newIcon("动态曲线"))
progress_dialog.setWindowFlags(
progress_dialog.windowFlags() & ~QtCore.Qt.WindowContextHelpButtonHint
)
progress_dialog.setCancelButton(None)
progress_dialog.show()
# 🔥 检查是否需要显示进度条
# 触发条件:CSV文件数量 > 1 或 单个CSV文件大小 > 8MB
show_progress = False
if len(csv_files) > 1:
# 条件1:多个CSV文件
show_progress = True
print(f"📊 [进度条触发] CSV文件数量: {len(csv_files)} > 1")
else:
# 条件2:单个CSV文件大小超过8MB
csv_path = os.path.join(data_directory, csv_files[0])
file_size_mb = os.path.getsize(csv_path) / (1024 * 1024)
if file_size_mb > 8:
show_progress = True
print(f"📊 [进度条触发] 单个CSV文件大小: {file_size_mb:.2f}MB > 8MB")
else:
print(f"ℹ️ [跳过进度条] 单个CSV文件 ({file_size_mb:.2f}MB),直接加载")
# 创建进度对话框(仅在需要时)
progress_dialog = None
if show_progress:
progress_dialog = QtWidgets.QProgressDialog(
"正在加载曲线数据...",
"取消",
0,
100 # 使用百分比进度
)
progress_dialog.setWindowTitle("曲线数据加载进度")
# 🔥 使用应用程序模态,确保进度条显示在最前面
progress_dialog.setWindowModality(QtCore.Qt.ApplicationModal)
progress_dialog.setMinimumWidth(400)
# 🔥 设置最小显示时间(毫秒),避免闪烁
progress_dialog.setMinimumDuration(0) # 立即显示
progress_dialog.setWindowIcon(newIcon("动态曲线"))
progress_dialog.setWindowFlags(
progress_dialog.windowFlags() & ~QtCore.Qt.WindowContextHelpButtonHint
)
progress_dialog.setCancelButton(None)
progress_dialog.setValue(0) # 设置初始值
progress_dialog.show()
# 🔥 强制刷新UI,确保进度条立即显示
QtWidgets.QApplication.processEvents()
print(f"✅ [进度条] 已显示进度对话框")
# 创建并启动后台加载线程
self._load_thread = CurveDataLoadThread(
......@@ -931,10 +965,19 @@ class CurvePanelHandler:
def _onLoadFinished(self, progress_dialog, success, count):
"""处理加载完成"""
if progress_dialog:
progress_dialog.close()
# 🔥 设置进度为100%,确保用户看到完成状态
progress_dialog.setValue(100)
QtWidgets.QApplication.processEvents()
# 🔥 延迟关闭进度条,确保用户能看到(至少显示500ms)
from PyQt5.QtCore import QTimer
QTimer.singleShot(500, progress_dialog.close)
print(f"✅ [进度条] 将在500ms后关闭")
if success:
print(f"✅ [曲线数据加载] 成功加载 {count} 个文件")
# 🔥 设置历史数据已加载标志
self._history_data_loaded = True
else:
print(f"⚠️ [曲线数据加载] 加载失败")
......@@ -1114,6 +1157,8 @@ class CurvePanelHandler:
mode = 'realtime'
self.curve_load_mode = mode
# 🔥 切换模式时重置历史数据加载标志
self._history_data_loaded = False
print(f"✅ [曲线加载模式] 已切换到: {mode}")
def getCurveLoadMode(self) -> str:
......@@ -1124,4 +1169,12 @@ class CurvePanelHandler:
str: 当前加载模式 ('realtime' 或 'history')
"""
return self.curve_load_mode
\ No newline at end of file
def isHistoryDataLoaded(self) -> bool:
"""
检查历史数据是否已加载
Returns:
bool: True 如果已加载,False 否则
"""
return self._history_data_loaded
\ No newline at end of file
......@@ -611,9 +611,7 @@ class LiquidDetectionEngine:
masks = mission_result.masks.data.cpu().numpy() > 0.5
classes = mission_result.boxes.cls.cpu().numpy().astype(int)
confidences = mission_result.boxes.conf.cpu().numpy()
print(f"[检测-目标{idx}] YOLO检测到 {len(masks)} 个对象")
else:
print(f"[检测-目标{idx}] ⚠️ YOLO未检测到任何mask")
return None
# 收集所有mask信息
......@@ -621,7 +619,6 @@ class LiquidDetectionEngine:
for i in range(len(masks)):
class_name = self.model.names[classes[i]]
conf = confidences[i]
print(f"[检测-目标{idx}] 对象{i+1}: {class_name} (置信度: {conf:.3f})")
if confidences[i] >= 0.5:
resized_mask = cv2.resize(
......@@ -630,10 +627,7 @@ class LiquidDetectionEngine:
) > 0.5
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:
print(f"[检测-目标{idx}] ⚠️ 没有置信度>=0.5的对象,无法计算液位")
return None
# ️ 关键修复:将原图坐标转换为裁剪图像坐标
......@@ -656,11 +650,6 @@ class LiquidDetectionEngine:
idx
)
if liquid_height is None:
print(f"[检测-目标{idx}] ⚠️ _enhanced_liquid_detection返回None,无法确定液位")
else:
print(f"[检测-目标{idx}] ✅ 检测到液位: {liquid_height:.2f}mm")
return liquid_height
except Exception as e:
......@@ -684,14 +673,6 @@ class LiquidDetectionEngine:
"""
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
liquid_masks = []
foam_masks = []
......@@ -705,14 +686,8 @@ class LiquidDetectionEngine:
elif class_name == 'air':
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检测(优先)
if liquid_masks:
print(f"[液位分析-目标{idx}] 使用方法1: 直接liquid检测")
# 找到最上层的液体mask
topmost_y = float('inf')
for i, mask in enumerate(liquid_masks):
......@@ -727,27 +702,9 @@ class LiquidDetectionEngine:
liquid_height_px = container_bottom - topmost_y
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))
else:
print(f"[液位分析-目标{idx}] ⚠️ liquid mask存在但无法找到有效的顶部y坐标")
# 方法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 len(foam_masks) >= 2:
......@@ -777,7 +734,6 @@ class LiquidDetectionEngine:
liquid_height_mm = liquid_height_px / pixel_per_mm
return max(0, min(liquid_height_mm, container_height_mm))
print(f"[液位分析-目标{idx}] ⚠️ 所有方法都无法确定液位,返回None")
return None
def _apply_kalman_filter(self, observation, idx, container_height_mm):
......
......@@ -90,13 +90,6 @@ class SimpleScheduler:
schedule_time = time.time() - schedule_start
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
except Exception as e:
......
......@@ -68,7 +68,7 @@ class ChannelThreadManager:
# 曲线模式标记(由外部设置,用于检测线程启动时自动启动曲线线程)
self.is_curve_mode = False
# 主窗口引用(用于访问 current_mission)
# 主窗口引用(用于访问 curvemission)
self.main_window = None
# 应用配置(用于获取编译模式)
......@@ -337,7 +337,8 @@ class ChannelThreadManager:
return True
def start_global_curve_thread(self):
"""启动全局单例曲线线程(基于CSV文件的增量读取)"""
"""启动全局单例曲线线程(基于CSV文件的增量读取,支持进度条)"""
import os
from .threads.curve_thread import CurveThread
# 如果已经运行,直接返回
......@@ -345,15 +346,71 @@ class ChannelThreadManager:
print(f"✅ [线程管理器] 全局曲线线程已在运行")
return True
# 🔥 从主窗口获取 current_mission 路径
# 🔥 从主窗口的 curvemission 获取任务路径
current_mission_path = None
if self.main_window and hasattr(self.main_window, 'current_mission'):
current_mission_path = self.main_window.current_mission
print(f"📁 [线程管理器] 启动全局曲线线程,current_mission路径: {current_mission_path}")
if self.main_window and hasattr(self.main_window, '_getCurveMissionPath'):
current_mission_path = self.main_window._getCurveMissionPath()
print(f"📁 [线程管理器] 启动全局曲线线程,从 curvemission 获取路径: {current_mission_path}")
else:
print(f"⚠️ [线程管理器] 无法获取current_mission,main_window未设置")
print(f"⚠️ [线程管理器] 无法获取任务路径,main_window 或 _getCurveMissionPath 未设置")
return False
# 🔥 检查是否需要显示进度条
show_progress = False
progress_dialog = None
if current_mission_path and os.path.exists(current_mission_path):
# 查找所有CSV文件
csv_files = [f for f in os.listdir(current_mission_path) if f.endswith('.csv')]
if len(csv_files) > 1:
# 条件1:多个CSV文件
show_progress = True
print(f"📊 [进度条触发] CSV文件数量: {len(csv_files)} > 1")
elif len(csv_files) == 1:
# 条件2:单个CSV文件大小超过8MB
csv_path = os.path.join(current_mission_path, csv_files[0])
file_size_mb = os.path.getsize(csv_path) / (1024 * 1024)
if file_size_mb > 8:
show_progress = True
print(f"📊 [进度条触发] 单个CSV文件大小: {file_size_mb:.2f}MB > 8MB")
else:
print(f"ℹ️ [跳过进度条] 单个CSV文件 ({file_size_mb:.2f}MB),直接加载")
# 🔥 创建进度对话框(仅在需要时)
if show_progress:
try:
from qtpy import QtWidgets, QtCore
from widgets.style_manager import newIcon
progress_dialog = QtWidgets.QProgressDialog(
"正在加载曲线数据...",
"取消",
0,
100 # 使用百分比进度
)
progress_dialog.setWindowTitle("曲线数据加载进度")
progress_dialog.setWindowModality(QtCore.Qt.ApplicationModal)
progress_dialog.setMinimumWidth(400)
progress_dialog.setMinimumDuration(0) # 立即显示
progress_dialog.setWindowIcon(newIcon("动态曲线"))
progress_dialog.setWindowFlags(
progress_dialog.windowFlags() & ~QtCore.Qt.WindowContextHelpButtonHint
)
progress_dialog.setCancelButton(None)
progress_dialog.setValue(0)
progress_dialog.show()
QtWidgets.QApplication.processEvents()
print(f"✅ [进度条] 已显示进度对话框")
# 🔥 设置进度回调到全局曲线线程
CurveThread.set_progress_callback(
lambda value, text: self._onCurveLoadProgress(progress_dialog, value, text)
)
except Exception as e:
print(f"⚠️ [进度条] 创建失败: {e}")
progress_dialog = None
# 使用统一的回调函数(回调函数内部根据channel_id参数区分通道)
callback = self.on_curve_updated if self.on_curve_updated else None
......@@ -368,12 +425,41 @@ class ChannelThreadManager:
return True
def _onCurveLoadProgress(self, progress_dialog, value, text):
"""处理曲线加载进度更新(确保在主线程执行)"""
try:
if not progress_dialog:
return
# 🔥 使用 QTimer.singleShot 确保在主线程执行UI更新
from qtpy import QtCore
def update_ui():
try:
if progress_dialog:
progress_dialog.setValue(value)
progress_dialog.setLabelText(text)
# 加载完成时关闭进度条
if value >= 100:
progress_dialog.close()
progress_dialog.deleteLater()
print(f"✅ [进度条] 加载完成,已关闭进度对话框")
except Exception as e:
print(f"⚠️ [进度条] UI更新失败: {e}")
# 在主线程执行UI更新(延迟0ms,确保在主线程的事件循环中执行)
QtCore.QTimer.singleShot(0, update_ui)
except Exception as e:
print(f"⚠️ [进度条] 更新失败: {e}")
def start_storage_thread(self, channel_id: str, storage_path: str = None):
"""启动存储线程
Args:
channel_id: 通道ID
storage_path: 存储路径(已废弃,现在从 current_mission 读取)
storage_path: 存储路径(已废弃,现在从通道配置读取)
"""
context = self.get_channel_context(channel_id)
if not context:
......@@ -382,7 +468,7 @@ class ChannelThreadManager:
if context.storage_flag:
return True
# 存储路径现在从 current_mission 读取,不再使用 recordings 路径
# 存储路径现在从通道配置读取,不再使用 recordings 路径
# storage_path 参数保留但不使用,保持向后兼容
context.storage_flag = True
......@@ -480,6 +566,10 @@ class ChannelThreadManager:
def stop_global_curve_thread(self):
"""停止全局单例曲线线程"""
from .threads.curve_thread import CurveThread
# 清除进度回调
CurveThread.clear_progress_callback()
CurveThread.stop()
# 等待线程结束
......@@ -522,10 +612,8 @@ class ChannelThreadManager:
用于切换到曲线模式布局时启动全局曲线线程
"""
# 设置统一的回调函数(如果还没有设置)
if self.on_curve_updated:
from .threads.curve_thread import CurveThread
CurveThread.set_callback(self.on_curve_updated)
# 🔥 不要在这里设置回调,start_global_curve_thread 会处理
# 避免重复设置导致数据被处理两次
# 启动全局曲线线程(如果尚未启动)
return self.start_global_curve_thread()
......
......@@ -57,12 +57,15 @@ class CurveThread:
# 全局统一的回调函数(所有数据都发送到这个函数,函数内部根据csv_filepath或area_name处理)
_global_callback: Optional[Callable] = None
# 🔥 进度回调函数(用于显示进度条)
_progress_callback: Optional[Callable] = None
@staticmethod
def run(current_mission_path: str = None, callback: Optional[Callable] = None):
"""曲线绘制线程主循环(全局单例版本)
Args:
current_mission_path: 当前任务路径(从全局变量 current_mission 读取)
current_mission_path: 当前任务路径(从 curvemission 下拉框获取)
callback: 统一的回调函数 callback(csv_filepath, area_name, area_idx, curve_points)
曲线线程读取到新数据后,直接调用此函数
参数说明:
......@@ -79,7 +82,7 @@ class CurveThread:
if callback:
CurveThread._global_callback = callback
# 从全局变量 current_mission 获取曲线路径
# 从 curvemission 获取曲线路径
curve_path = current_mission_path
# 从配置文件读取曲线帧率
......@@ -148,14 +151,31 @@ class CurveThread:
for csv_filepath in available_csv_files:
_ensure_file_state(csv_filepath)
# 🔥 发送所有文件的历史数据(启动时一次性发送)
# 🔥 发送所有文件的历史数据(启动时一次性发送,支持进度报告
print(f"🔥 [全局曲线线程] 开始发送历史数据,共 {len(available_csv_files)} 个CSV文件")
for csv_filepath in available_csv_files:
total_files = len(available_csv_files)
for idx, csv_filepath in enumerate(available_csv_files):
state = file_states.get(csv_filepath)
if state and 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})"
print(f"📊 [进度回调] 调用进度更新: {progress_value}% - {progress_text}")
CurveThread._progress_callback(progress_value, progress_text)
except Exception as e:
print(f"⚠️ [进度回调] 失败: {e}")
import traceback
traceback.print_exc()
else:
print(f"⚠️ [进度回调] 回调函数未设置,跳过进度报告")
if CurveThread._global_callback:
try:
CurveThread._global_callback(
......@@ -172,6 +192,18 @@ class CurveThread:
else:
print(f"⚠️ [全局曲线线程] 文件无数据或状态不存在: {csv_filepath}")
# 🔥 加载完成,报告100%进度
if CurveThread._progress_callback:
try:
print(f"📊 [进度回调] 调用完成通知: 100%")
CurveThread._progress_callback(100, "加载完成")
except Exception as e:
print(f"⚠️ [进度回调] 完成通知失败: {e}")
import traceback
traceback.print_exc()
else:
print(f"⚠️ [进度回调] 回调函数未设置,跳过完成通知")
# 文件扫描计数器(每10个周期重新扫描一次文件夹)
scan_counter = 0
scan_interval = 10 # 每10个周期扫描一次
......@@ -526,3 +558,19 @@ class CurveThread:
def clear_callback():
"""清除回调函数"""
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
......@@ -344,11 +344,6 @@ class GlobalDetectionThread:
# 使用优化的帧收集方法
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
except Exception as e:
......@@ -381,11 +376,6 @@ class GlobalDetectionThread:
self.result_distributor.distribute_results_optimized(
results, self.channel_contexts, self.channel_callbacks
)
# 打印分发摘要(调试用)
if results:
summary = self.result_distributor.get_distribution_summary(results)
print(f"📤 [全局检测线程] 分发结果: {summary}")
except Exception as e:
print(f"❌ [全局检测线程] 结果分发失败: {e}")
......
......@@ -8,7 +8,6 @@ from .videopage import (
CurvePanel,
MissionPanel,
ModelSettingDialog,
HistoryPanel,
)
# 数据集页面组件(从 datasetpage 子模块导入)
......
......@@ -231,10 +231,11 @@ class DialogManager:
# 🔥 移除了font-size设置,改用FontManager统一管理字体
DEFAULT_STYLE = """
QMessageBox {
min-height: 100px;
min-width: 400px;
min-height: 180px;
}
QLabel {
min-height: 50px;
min-height: 60px;
qproperty-alignment: 'AlignCenter';
}
"""
......
......@@ -10,7 +10,8 @@ from .channelpanel import ChannelPanel
from .curvepanel import CurvePanel
from .missionpanel import MissionPanel
from .modelsetting_dialogue import ModelSettingDialog
from .historypanel import HistoryPanel
from .historyvideopanel import HistoryVideoPanel
from .general_set import GeneralSetPanel, GeneralSetDialog
......@@ -19,7 +20,7 @@ __all__ = [
'CurvePanel',
'MissionPanel',
'ModelSettingDialog',
'HistoryPanel',
'HistoryVideoPanel',
'RtspDialog',
'GeneralSetPanel',
'GeneralSetDialog',
......
......@@ -49,7 +49,7 @@ class ChannelPanel(QtWidgets.QWidget):
channelAdded = QtCore.Signal(dict) # 通道添加信号
channelRemoved = QtCore.Signal(str) # 通道移除信号
channelEdited = QtCore.Signal(str, dict) # 通道编辑信号
curveClicked = QtCore.Signal() # 曲线按钮点击信号
curveClicked = QtCore.Signal(str) # 曲线按钮点击信号,传递任务名称
amplifyClicked = QtCore.Signal(str) # 放大按钮点击信号,传递channel_id
channelNameChanged = QtCore.Signal(str, str) # 通道名称改变信号(channel_id, new_name)
......@@ -665,8 +665,10 @@ class ChannelPanel(QtWidgets.QWidget):
def _onCurveClicked(self):
"""曲线按钮点击"""
# 发射曲线按钮点击信号
self.curveClicked.emit()
# 获取当前面板的任务信息
task_name = self.getTaskInfo()
# 发射曲线按钮点击信号,传递任务名称
self.curveClicked.emit(task_name if task_name else "")
def _onAmplifyClicked(self):
"""放大按钮点击"""
......@@ -738,80 +740,6 @@ class ChannelPanel(QtWidgets.QWidget):
if channel_id:
self.channelNameChanged.emit(channel_id, new_name)
def setHistoryPlaybackMode(self, is_history_mode):
"""
设置历史回放模式
当曲线布局为历史回放布局时调用此方法:
- 隐藏通道名称标签(左上角)
- 隐藏任务信息标签(右上角)
- 在中间显示"历史回放"文本标签
Args:
is_history_mode: bool, True=启用历史回放模式, False=禁用历史回放模式
"""
if is_history_mode:
# 启用历史回放模式
print(f"[ChannelPanel] 启用历史回放模式: {self._title}")
# 隐藏通道名称标签(左上角)
if hasattr(self, 'nameLabel'):
self.nameLabel.hide()
# 隐藏任务信息标签(右上角)
if hasattr(self, 'taskLabel'):
self.taskLabel.hide()
# 创建或显示历史回放文本标签(中间)
if not hasattr(self, 'historyPlaybackLabel'):
self.historyPlaybackLabel = QtWidgets.QLabel(self.videoLabel)
self.historyPlaybackLabel.setText("历史回放")
self.historyPlaybackLabel.setStyleSheet("""
QLabel {
background-color: transparent;
color: white;
font-size: 16pt;
font-weight: bold;
padding: 10px 20px;
}
""")
self.historyPlaybackLabel.setAlignment(Qt.AlignCenter)
# 显示历史回放标签
self.historyPlaybackLabel.show()
self.historyPlaybackLabel.adjustSize()
# 定位到中间
self._positionHistoryPlaybackLabel()
else:
# 禁用历史回放模式(恢复正常显示)
print(f"[ChannelPanel] 禁用历史回放模式: {self._title}")
# 显示通道名称标签
if hasattr(self, 'nameLabel'):
self.nameLabel.show()
# 显示任务信息标签
if hasattr(self, 'taskLabel'):
self.taskLabel.show()
# 隐藏历史回放标签
if hasattr(self, 'historyPlaybackLabel'):
self.historyPlaybackLabel.hide()
def _positionHistoryPlaybackLabel(self):
"""定位历史回放标签到顶部居中"""
if hasattr(self, 'historyPlaybackLabel') and hasattr(self, 'videoLabel'):
video_width = self.videoLabel.width()
label_width = self.historyPlaybackLabel.width()
# 🔥 定位到顶部居中(距离顶部20px)
x = (video_width - label_width) // 2
y = 20 # 距离顶部20px
self.historyPlaybackLabel.move(x, y)
if __name__ == "__main__":
......
......@@ -236,7 +236,7 @@ class CurvePanel(QtWidgets.QWidget):
self.spn_upper_limit.setSuffix("mm")
self.spn_upper_limit.setDecimals(1)
self.spn_upper_limit.setSingleStep(0.5) # 设置步长为0.5mm
self.spn_upper_limit.setFixedWidth(scale_w(80)) # 🔥 响应式宽度
self.spn_upper_limit.setFixedWidth(scale_w(85)) # 🔥 响应式宽度
self.spn_upper_limit.setToolTip("设置安全上限")
toolbar_layout.addWidget(self.spn_upper_limit)
......@@ -250,7 +250,7 @@ class CurvePanel(QtWidgets.QWidget):
self.spn_lower_limit.setSuffix("mm")
self.spn_lower_limit.setDecimals(1)
self.spn_lower_limit.setSingleStep(0.5) # 设置步长为0.5mm
self.spn_lower_limit.setFixedWidth(scale_w(80)) # 🔥 响应式宽度
self.spn_lower_limit.setFixedWidth(scale_w(85)) # 🔥 响应式宽度
self.spn_lower_limit.setToolTip("设置安全下限")
toolbar_layout.addWidget(self.spn_lower_limit)
......@@ -506,6 +506,31 @@ class CurvePanel(QtWidgets.QWidget):
# 如果找不到,选中默认选项
self.curvemission.setCurrentIndex(0)
def setMissionFromTaskName(self, task_name):
"""
从任务名称设置 curvemission(来源1和来源2使用)
Args:
task_name: 任务名称(如 "1_1", "32_23")
"""
if not task_name or task_name == "未分配任务" or task_name == "None":
# 选中默认选项
self.curvemission.setCurrentIndex(0)
print(f"⚠️ [CurvePanel] 无效任务名称,重置为默认选项: {task_name}")
return False
# 在下拉框中查找并选中对应的任务
index = self.curvemission.findText(task_name)
if index >= 0:
self.curvemission.setCurrentIndex(index)
print(f"✅ [CurvePanel] 已设置任务: {task_name} (索引: {index})")
return True
else:
# 如果找不到,选中默认选项
self.curvemission.setCurrentIndex(0)
print(f"⚠️ [CurvePanel] 未找到任务 '{task_name}',重置为默认选项")
return False
def addChannel(self, channel_id, channel_name=None, window_name=None, color=None):
"""
添加一个通道(UI创建)
......
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