Commit 46f8dd34 by Yuhaibo

merge: 保留YHBdebug分支版本解决冲突

parents 8b8517af 9385bbba
...@@ -67,3 +67,7 @@ build/ ...@@ -67,3 +67,7 @@ build/
# 忽略配置文件中的敏感信息 # 忽略配置文件中的敏感信息
config/secrets.yaml config/secrets.yaml
config/private.yaml config/private.yaml
# 忽略特定的处理器文件(不上传)
handlers/modelpage/model_training_handler.py
handlers/videopage/detection.py
\ No newline at end of file
...@@ -785,7 +785,7 @@ class MainWindow( ...@@ -785,7 +785,7 @@ class MainWindow(
# 保存第一个面板的引用(兼容现有代码) # 保存第一个面板的引用(兼容现有代码)
self.channelPanel = self.channelPanels[0] if self.channelPanels else None self.channelPanel = self.channelPanels[0] if self.channelPanels else None
# 🔥 创建4个历史视频面板(用于曲线模式的历史回放布局) # 🔥 创建4个历史视频面板(用于曲线模式布局的历史回放子布局)
try: try:
from .widgets.videopage import HistoryVideoPanel from .widgets.videopage import HistoryVideoPanel
except ImportError: except ImportError:
...@@ -812,8 +812,8 @@ class MainWindow( ...@@ -812,8 +812,8 @@ class MainWindow(
"""创建曲线模式布局:左侧垂直排列通道 + 右侧曲线面板 """创建曲线模式布局:左侧垂直排列通道 + 右侧曲线面板
包含两个子布局: 包含两个子布局:
- 子布局0:实时检测模式(带任务选择和底部按钮) - 子布局0:同步布局(带任务选择和底部按钮)
- 子布局1:历史回放模式(无任务选择和底部按钮) - 子布局1:历史回放布局(无任务选择和底部按钮)
🔥 两个子布局共用同一个CurvePanel(右侧) 🔥 两个子布局共用同一个CurvePanel(右侧)
""" """
...@@ -844,12 +844,12 @@ class MainWindow( ...@@ -844,12 +844,12 @@ class MainWindow(
# 🔥 创建左侧子布局栈(实时检测 vs 历史回放) # 🔥 创建左侧子布局栈(实时检测 vs 历史回放)
self.curveLayoutStack = QtWidgets.QStackedWidget() self.curveLayoutStack = QtWidgets.QStackedWidget()
self.curveLayoutStack.setFixedWidth(660) self.curveLayoutStack.setFixedWidth(660)
self._curve_sub_layout_mode = 0 # 0=实时检测, 1=历史回放 self._curve_sub_layout_mode = 0 # 0=同步布局, 1=历史回放布局
# === 子布局0:实时检测模式(左侧通道列表)=== # === 子布局0:同步布局(左侧通道列表)===
self._createRealtimeCurveSubLayout() self._createRealtimeCurveSubLayout()
# === 子布局1:历史回放模式(左侧历史视频面板容器)=== # === 子布局1:历史回放布局(左侧历史视频面板容器)===
self._createHistoryCurveSubLayout() self._createHistoryCurveSubLayout()
# 布局结构:左侧子布局栈 + 右侧共用CurvePanel # 布局结构:左侧子布局栈 + 右侧共用CurvePanel
...@@ -881,7 +881,7 @@ class MainWindow( ...@@ -881,7 +881,7 @@ class MainWindow(
self.curve_channel_layout.setContentsMargins(5, 5, 5, 5) self.curve_channel_layout.setContentsMargins(5, 5, 5, 5)
self.curve_channel_layout.setSpacing(10) self.curve_channel_layout.setSpacing(10)
# 初始化通道包裹容器列表(实时检测模式 # 初始化通道包裹容器列表(同步布局
self.channel_widgets_for_curve = [] self.channel_widgets_for_curve = []
# 创建4个通道容器(初始隐藏,等待CSV文件加载) # 创建4个通道容器(初始隐藏,等待CSV文件加载)
...@@ -924,7 +924,7 @@ class MainWindow( ...@@ -924,7 +924,7 @@ class MainWindow(
self.history_channel_layout.setContentsMargins(5, 5, 5, 5) self.history_channel_layout.setContentsMargins(5, 5, 5, 5)
self.history_channel_layout.setSpacing(10) self.history_channel_layout.setSpacing(10)
# 初始化历史视频包裹容器列表(历史回放模式 # 初始化历史视频包裹容器列表(历史回放布局
self.history_channel_widgets_for_curve = [] self.history_channel_widgets_for_curve = []
# 创建4个历史视频容器(初始隐藏,等待CSV文件加载) # 创建4个历史视频容器(初始隐藏,等待CSV文件加载)
...@@ -962,24 +962,22 @@ class MainWindow( ...@@ -962,24 +962,22 @@ class MainWindow(
def _onCurveMissionChanged(self, mission_name): def _onCurveMissionChanged(self, mission_name):
"""曲线任务选择变化(基于CSV文件动态显示)""" """曲线任务选择变化(基于CSV文件动态显示)"""
if not mission_name or mission_name == "请选择任务": if not mission_name or mission_name == "请选择任务":
print(f"[曲线布局] 未选择任务,隐藏所有通道容器")
self._updateCurveChannelDisplay([]) self._updateCurveChannelDisplay([])
return return
# 🔥 根据 curve_load_mode 决定显示逻辑 # 🔥 重新检查检测状态并切换布局
if hasattr(self, 'curvePanelHandler'): detection_running = False
curve_mode = self.curvePanelHandler.getCurveLoadMode() if hasattr(self, '_switchCurveSubLayout') and hasattr(self, '_getCurrentDetectionState'):
else: detection_running = self._getCurrentDetectionState()
curve_mode = 'realtime' self._switchCurveSubLayout(detection_running)
if curve_mode == 'realtime': # 🔥 根据检测状态决定显示逻辑(而不是依赖 getCurveLoadMode)
# 🔥 实时模式:只显示任务配置中使用的通道 if detection_running:
# 🔥 同步布局:只显示任务配置中使用的通道
selected_channels = self._getTaskChannels(mission_name) selected_channels = self._getTaskChannels(mission_name)
print(f"📊 [曲线布局-实时模式] 任务 {mission_name},显示任务使用的通道: {selected_channels}")
else: else:
# 🔥 历史模式:显示所有通道容器 # 🔥 历史回放布局:显示所有通道容器
selected_channels = ['通道1', '通道2', '通道3', '通道4'] selected_channels = ['通道1', '通道2', '通道3', '通道4']
print(f"📊 [曲线布局-历史模式] 任务 {mission_name},显示所有通道容器")
self._updateCurveChannelDisplay(selected_channels) self._updateCurveChannelDisplay(selected_channels)
...@@ -997,21 +995,15 @@ class MainWindow( ...@@ -997,21 +995,15 @@ class MainWindow(
import yaml import yaml
try: try:
# 构建任务文件夹路径 # 构建任务配置文件路径(在 database/config/mission/ 目录下)
try: try:
from database.config import get_project_root from database.config import get_project_root
project_root = get_project_root() project_root = get_project_root()
except ImportError: except ImportError:
project_root = os.getcwd() project_root = os.getcwd()
mission_path = os.path.join(project_root, 'database', 'mission_result', mission_name) # 任务配置文件在 database/config/mission/ 目录下
config_file = os.path.join(project_root, 'database', 'config', 'mission', f"{mission_name}.yaml")
if not os.path.exists(mission_path):
print(f"⚠️ [通道筛选] 任务文件夹不存在: {mission_path}")
return []
# 查找任务配置文件(.yaml文件,文件名与任务名相同)
config_file = os.path.join(mission_path, f"{mission_name}.yaml")
if not os.path.exists(config_file): if not os.path.exists(config_file):
print(f"⚠️ [通道筛选] 任务配置文件不存在: {config_file}") print(f"⚠️ [通道筛选] 任务配置文件不存在: {config_file}")
...@@ -1027,7 +1019,7 @@ class MainWindow( ...@@ -1027,7 +1019,7 @@ class MainWindow(
return [] return []
# 调试:打印配置文件的所有键 # 调试:打印配置文件的所有键
print(f" [通道筛选] 配置文件键: {list(task_config.keys())}")
# 从配置中提取使用的通道 # 从配置中提取使用的通道
# 配置格式可能是:selected_channels: ['通道2', '通道3'] 或 channels: ['channel1', 'channel2'] # 配置格式可能是:selected_channels: ['通道2', '通道3'] 或 channels: ['channel1', 'channel2']
...@@ -1039,7 +1031,6 @@ class MainWindow( ...@@ -1039,7 +1031,6 @@ class MainWindow(
channel_list = task_config['selected_channels'] channel_list = task_config['selected_channels']
if isinstance(channel_list, list): if isinstance(channel_list, list):
used_channels = [ch for ch in channel_list if isinstance(ch, str) and '通道' in ch] used_channels = [ch for ch in channel_list if isinstance(ch, str) and '通道' in ch]
print(f" [通道筛选] 从 selected_channels 读取: {used_channels}")
# 尝试其他可能的配置键名 # 尝试其他可能的配置键名
elif 'channels' in task_config: elif 'channels' in task_config:
...@@ -1104,11 +1095,11 @@ class MainWindow( ...@@ -1104,11 +1095,11 @@ class MainWindow(
# 🔥 根据当前曲线子布局模式选择要操作的容器 # 🔥 根据当前曲线子布局模式选择要操作的容器
if hasattr(self, '_curve_sub_layout_mode'): if hasattr(self, '_curve_sub_layout_mode'):
if self._curve_sub_layout_mode == 0: if self._curve_sub_layout_mode == 0:
# 实时检测模式:操作channel_widgets_for_curve # 同步布局:操作channel_widgets_for_curve
target_widgets = self.channel_widgets_for_curve if hasattr(self, 'channel_widgets_for_curve') else [] target_widgets = self.channel_widgets_for_curve if hasattr(self, 'channel_widgets_for_curve') else []
target_container = self.curve_channel_container if hasattr(self, 'curve_channel_container') else None target_container = self.curve_channel_container if hasattr(self, 'curve_channel_container') else None
else: else:
# 历史回放模式:操作history_channel_widgets_for_curve # 历史回放布局:操作history_channel_widgets_for_curve
target_widgets = self.history_channel_widgets_for_curve if hasattr(self, 'history_channel_widgets_for_curve') else [] target_widgets = self.history_channel_widgets_for_curve if hasattr(self, 'history_channel_widgets_for_curve') else []
target_container = self.history_channel_container if hasattr(self, 'history_channel_container') else None target_container = self.history_channel_container if hasattr(self, 'history_channel_container') else None
else: else:
...@@ -1148,7 +1139,6 @@ class MainWindow( ...@@ -1148,7 +1139,6 @@ class MainWindow(
if target_container: if target_container:
target_container.setFixedSize(640, total_height) target_container.setFixedSize(640, total_height)
print(f"[曲线布局] 已更新通道显示,显示 {visible_count} 个通道")
def _createModelPage(self): def _createModelPage(self):
"""创建模型管理页面""" """创建模型管理页面"""
...@@ -1493,12 +1483,17 @@ class MainWindow( ...@@ -1493,12 +1483,17 @@ class MainWindow(
更新任务面板中通道列的颜色 更新任务面板中通道列的颜色
当通道的检测状态改变时调用此方法,更新任务面板中对应通道列的背景色 当通道的检测状态改变时调用此方法,更新任务面板中对应通道列的背景色
调用 MissionPanelHandler 的方法(MainWindow 继承了 MissionPanelHandler)
""" """
print(f"🎯 [app._updateChannelColumnColor] 方法被调用")
try: try:
if hasattr(self, '_updateChannelCellColors'): # MainWindow 继承了 MissionPanelHandler,直接调用父类方法
self._updateChannelCellColors() from handlers.videopage import MissionPanelHandler
MissionPanelHandler._updateChannelColumnColor(self)
except Exception as e: except Exception as e:
print(f"⚠️ [更新通道列颜色] 失败: {e}") print(f"⚠️ [更新通道列颜色] 失败: {e}")
import traceback
traceback.print_exc()
def closeEvent(self, event): def closeEvent(self, event):
"""窗口关闭事件""" """窗口关闭事件"""
......
...@@ -5,59 +5,59 @@ channel1: ...@@ -5,59 +5,59 @@ channel1:
height: 20mm height: 20mm
name: 通道1_区域1 name: 通道1_区域1
boxes: boxes:
- - 617 - - 855
- 415 - 737
- 192 - 192
fixed_bottoms: fixed_bottoms:
- 482 - 813
fixed_tops: fixed_tops:
- 387 - 660
last_updated: '2025-11-27 15:55:10' last_updated: '2025-11-29 17:01:13'
channel2: channel2:
annotation_count: 1 annotation_count: 1
areas: areas:
area_1: area_1:
height: 20mm height: 20mm
name: 我去饿_区域1 name: 通道2_区域1
boxes: boxes:
- - 643 - - 640
- 558 - 641
- 160 - 256
fixed_bottoms: fixed_bottoms:
- 616 - 743
fixed_tops: fixed_tops:
- 534 - 538
last_updated: '2025-11-26 20:09:26' last_updated: '2025-11-29 12:49:18'
channel3: channel3:
annotation_count: 1 annotation_count: 1
areas: areas:
area_1: area_1:
height: 20mm height: 20mm
name: 3_区域1 name: 通道3_区域1
boxes: boxes:
- - 1365 - - 1306
- 915 - 762
- 128 - 224
fixed_bottoms: fixed_bottoms:
- 939 - 835
fixed_tops: fixed_tops:
- 886 - 739
last_updated: '2025-11-26 20:09:35' last_updated: '2025-11-29 12:32:57'
channel4: channel4:
annotation_count: 1 annotation_count: 1
areas: areas:
area_1: area_1:
height: 20mm height: 20mm
name: asfdhuu_区域1 name: 通道4_区域1
boxes: boxes:
- - 1689 - - 1700
- 884 - 760
- 96 - 192
fixed_bottoms: fixed_bottoms:
- 908 - 819
fixed_tops: fixed_tops:
- 860 - 729
last_updated: '2025-11-26 20:02:17' last_updated: '2025-11-29 12:30:19'
通道1: 通道1:
annotation_count: 2 annotation_count: 2
areas: areas:
......
...@@ -17,23 +17,23 @@ channels: ...@@ -17,23 +17,23 @@ channels:
name: '4' name: '4'
channel2: channel2:
general: general:
task_id: 2恶趣味 task_id: '123'
task_name: q'we task_name: '21'
save_liquid_data_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\2恶趣味_q'we save_liquid_data_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\123_21
channel3: channel3:
general: general:
task_id: '21' task_id: '123'
task_name: '321' task_name: '21'
save_liquid_data_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\21_321 save_liquid_data_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\123_21
channel4: channel4:
general: general:
task_id: '21' task_id: '1'
task_name: '321' task_name: '1'
save_liquid_data_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\21_321 save_liquid_data_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\1_1
channel1: channel1:
general: general:
task_id: '21' task_id: '1'
task_name: '321' task_name: '1'
area_count: 0 area_count: 0
safe_low: 2.0mm safe_low: 2.0mm
safe_high: 10.0mm safe_high: 10.0mm
...@@ -41,13 +41,11 @@ channel1: ...@@ -41,13 +41,11 @@ channel1:
video_format: AVI video_format: AVI
push_address: '' push_address: ''
video_path: '' video_path: ''
save_liquid_data_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\21_321 save_liquid_data_path: d:\restructure\liquid_level_line_detection_system\database\mission_result\1_1
areas: areas:
area_1: 通道1_区域1 area_1: 通道1_区域1
area_2: 通道1_区域2
area_heights: area_heights:
area_1: 20mm area_1: 20mm
area_2: 20mm
model: model:
model_path: d:\restructure\liquid_level_line_detection_system\database\model\detection_model\5\best.dat model_path: d:\restructure\liquid_level_line_detection_system\database\model\detection_model\5\best.dat
channel_1: channel_1:
......
task_id: '123' task_id: '123'
task_name: '21' task_name: '21'
status: 待配置 status: 未启动
selected_channels: selected_channels:
- 通道2 - 通道2
- 通道3 - 通道3
......
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_id: '1'
task_name: '2' task_name: '2'
status: 待配置 status: 未启动
selected_channels: selected_channels:
- 通道1 - 通道1
- 通道2 - 通道2
......
task_id: '1' task_id: '1'
task_name: '222' task_name: '222'
status: 待配置 status: 未启动
selected_channels: selected_channels:
- 通道1 - 通道1
- 通道2 - 通道2
......
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_id: '21'
task_name: '321' task_name: '321'
status: 待配置 status: 未启动
selected_channels: selected_channels:
- 通道1 - 通道1
- 通道2 - 通道2
......
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_id: 2恶趣味 task_id: 2恶趣味
task_name: q'we task_name: q'we
status: 待配置 status: 未启动
selected_channels: selected_channels:
- 通道2 - 通道2
created_time: '2025-11-26 14:56:26' created_time: '2025-11-26 14:56:26'
......
task_id: test
task_name: test
status: 未启动
selected_channels:
- 通道1
created_time: '2025-11-29 15:52:05'
mission_result_folder_path: D:\restructure\liquid_level_line_detection_system\database\mission_result\test_test
task_id: 任务
task_id: 任务
task_name: 若是
status: 未启动
selected_channels:
- 通道1
created_time: '2025-11-29 13:39:21'
mission_result_folder_path: D:\restructure\liquid_level_line_detection_system\database\mission_result\任务_若是
task_id: 企鹅
task_id: 企鹅
task_name: 1额
status: 未启动
selected_channels:
- 通道1
created_time: '2025-11-29 13:34:35'
mission_result_folder_path: D:\restructure\liquid_level_line_detection_system\database\mission_result\企鹅_1额
task_id: 去v
task_id: 去v
task_name: 去v
status: 未启动
selected_channels:
- 通道1
created_time: '2025-11-29 13:34:18'
mission_result_folder_path: D:\restructure\liquid_level_line_detection_system\database\mission_result\去v_去v
task_id: 去人
task_id: 去人
task_name: '2314'
status: 未启动
selected_channels:
- 通道1
created_time: '2025-11-29 13:22:23'
mission_result_folder_path: D:\restructure\liquid_level_line_detection_system\database\mission_result\去人_2314
task_id: 去问驱蚊器恶气
task_id: 去问驱蚊器恶气
task_name: 企鹅去而且
status: 未启动
selected_channels:
- 通道1
created_time: '2025-11-29 12:25:58'
mission_result_folder_path: D:\restructure\liquid_level_line_detection_system\database\mission_result\去问驱蚊器恶气_企鹅去而且
task_id: 吃个海鲜 task_id: 吃个海鲜
task_id: 吃个海鲜 task_id: 吃个海鲜
task_name: 显示提醒他 task_name: 显示提醒他
status: 待配置 status: 未启动
selected_channels: selected_channels:
- 通道4 - 通道4
created_time: '2025-11-27 11:06:03' created_time: '2025-11-27 11:06:03'
......
task_id: 大润发给 task_id: 大润发给
task_id: 大润发给 task_id: 大润发给
task_name: 上方 task_name: 上方
status: 待配置 status: 未启动
selected_channels: selected_channels:
- 通道1 - 通道1
- 通道2 - 通道2
......
task_id: 文档
task_id: 文档
task_name: 啊啊
status: 未启动
selected_channels:
- 通道1
created_time: '2025-11-29 13:18:35'
mission_result_folder_path: D:\restructure\liquid_level_line_detection_system\database\mission_result\文档_啊啊
task_id: 的使得发 task_id: 的使得发
task_id: 的使得发 task_id: 的使得发
task_name: 如图微软 task_name: 如图微软
status: 待配置 status: 未启动
selected_channels: selected_channels:
- 通道1 - 通道1
- 通道3 - 通道3
......
task_id: 的啊 task_id: 的啊
task_id: 的啊 task_id: 的啊
task_name: 而突然 task_name: 而突然
status: 待配置 status: 未启动
selected_channels: selected_channels:
- 通道1 - 通道1
- 通道2 - 通道2
......
This source diff could not be displayed because it is too large. You can view the blob instead.
2025-11-29-13:26:33.445 0.0
2025-11-29-13:26:33.445 0.0
2025-11-29-13:26:33.478 0.0
2025-11-29-13:26:33.688 0.0
2025-11-29-13:26:33.888 0.0
2025-11-29-13:26:34.086 0.0
2025-11-29-13:26:34.294 0.0
2025-11-29-13:26:34.493 0.0
2025-11-29-13:26:34.695 0.0
2025-11-29-13:26:34.904 0.0
2025-11-29-13:26:35.107 0.0
2025-11-29-13:26:35.363 15.0
2025-11-29-13:26:35.524 15.3
2025-11-29-13:26:35.711 0.0
2025-11-29-13:26:35.918 0.0
2025-11-29-13:26:36.114 0.0
2025-11-29-13:26:36.314 0.0
2025-11-29-13:26:36.521 16.1
2025-11-29-13:26:36.710 0.0
2025-11-29-13:26:36.912 0.0
2025-11-29-13:26:37.121 0.0
2025-11-29-13:26:37.318 0.0
2025-11-29-13:26:37.589 15.6
2025-11-29-13:26:37.725 0.0
2025-11-29-13:26:37.929 0.0
2025-11-29-13:26:38.156 0.0
2025-11-29-13:26:38.367 0.0
2025-11-29-13:26:38.549 0.0
2025-11-29-13:26:38.747 0.0
2025-11-29-13:26:38.956 0.0
2025-11-29-13:26:39.163 0.0
2025-11-29-13:26:39.374 0.0
2025-11-29-13:26:39.572 0.0
2025-11-29-13:26:39.792 15.5
2025-11-29-13:26:39.993 0.0
2025-11-29-13:26:40.213 15.5
2025-11-29-13:26:40.410 16.0
2025-11-29-13:26:40.599 15.8
2025-11-29-13:26:40.821 16.0
2025-11-29-13:26:41.033 15.6
2025-11-29-13:26:41.246 15.3
2025-11-29-13:26:41.459 14.8
2025-11-29-13:26:41.654 14.7
2025-11-29-13:26:41.853 14.8
2025-11-29-13:26:42.060 0.0
2025-11-29-13:26:42.265 0.0
2025-11-29-13:26:42.488 0.0
2025-11-29-13:26:42.670 0.0
2025-11-29-13:26:42.882 0.0
2025-11-29-13:26:43.124 0.0
2025-11-29-13:26:43.314 0.0
2025-11-29-13:26:43.539 0.0
2025-11-29-13:26:43.756 0.0
2025-11-29-13:26:43.959 0.0
2025-11-29-13:26:44.185 0.0
2025-11-29-13:26:44.388 15.3
2025-11-29-13:26:44.585 15.3
2025-11-29-13:26:44.797 15.3
2025-11-29-13:26:45.001 0.0
2025-11-29-13:26:45.201 15.5
2025-11-29-13:26:45.403 15.5
2025-11-29-13:26:45.607 0.0
2025-11-29-13:26:45.808 0.0
2025-11-29-13:26:46.038 0.0
2025-11-29-13:26:46.238 0.0
2025-11-29-13:26:46.447 17.2
2025-11-29-13:26:46.663 0.0
2025-11-29-13:26:46.851 0.0
2025-11-29-13:26:47.066 0.0
2025-11-29-13:26:47.284 0.0
2025-11-29-13:26:47.496 0.0
2025-11-29-13:26:47.675 0.0
2025-11-29-13:26:47.893 0.0
2025-11-29-13:26:48.080 0.0
2025-11-29-13:26:48.285 0.0
2025-11-29-13:26:48.513 0.0
2025-11-29-13:26:48.700 0.0
2025-11-29-13:26:48.901 0.0
2025-11-29-13:26:49.106 0.0
2025-11-29-13:26:49.301 14.5
2025-11-29-13:26:49.510 0.0
2025-11-29-13:26:49.748 15.2
2025-11-29-13:26:49.941 0.0
2025-11-29-13:26:50.154 15.6
2025-11-29-13:26:50.358 0.0
2025-11-29-13:26:50.566 15.3
2025-11-29-13:26:50.781 15.5
2025-11-29-13:26:50.946 15.0
2025-11-29-13:26:51.157 0.0
2025-11-29-13:26:51.371 15.6
2025-11-29-13:26:51.586 15.5
2025-11-29-13:26:51.809 15.0
2025-11-29-13:26:52.010 15.3
2025-11-29-13:26:52.191 0.0
2025-11-29-13:26:52.395 0.0
2025-11-29-13:26:52.613 0.0
2025-11-29-13:26:52.799 0.0
2025-11-29-13:26:53.003 0.0
2025-11-29-13:26:53.218 15.0
2025-11-29-13:26:53.411 0.0
2025-11-29-13:26:53.619 0.0
2025-11-29-13:26:53.835 0.0
2025-11-29-13:26:54.058 15.3
2025-11-29-13:26:54.255 15.0
2025-11-29-13:26:54.484 15.0
2025-11-29-13:26:54.679 15.6
2025-11-29-13:26:54.863 15.6
2025-11-29-13:26:55.058 0.0
2025-11-29-13:26:55.261 0.0
2025-11-29-13:26:55.482 0.0
2025-11-29-13:26:55.685 0.0
2025-11-29-13:26:55.891 0.0
2025-11-29-13:26:56.107 0.0
2025-11-29-13:26:56.315 0.0
2025-11-29-13:26:56.521 15.0
2025-11-29-13:26:56.742 15.2
2025-11-29-13:26:56.926 0.0
2025-11-29-13:26:57.125 0.0
2025-11-29-13:26:57.335 0.0
2025-11-29-13:26:57.532 0.0
2025-11-29-13:26:57.744 0.0
2025-11-29-13:26:57.946 0.0
2025-11-29-13:26:58.153 0.0
2025-11-29-13:26:58.359 0.0
2025-11-29-13:26:58.561 0.0
2025-11-29-13:26:58.776 0.0
2025-11-29-13:26:58.984 15.5
2025-11-29-13:26:59.171 14.7
2025-11-29-13:26:59.374 14.8
2025-11-29-13:26:59.588 0.0
2025-11-29-13:26:59.781 0.0
2025-11-29-13:26:59.989 15.6
2025-11-29-13:27:00.194 15.5
2025-11-29-13:27:00.393 0.0
2025-11-29-13:27:00.611 0.0
2025-11-29-13:27:00.809 0.0
2025-11-29-13:27:01.025 0.0
2025-11-29-13:27:01.228 0.0
2025-11-29-13:27:01.435 0.0
2025-11-29-13:27:01.639 0.0
2025-11-29-13:27:01.851 15.5
2025-11-29-13:27:02.058 0.0
2025-11-29-13:27:02.263 0.0
2025-11-29-13:27:02.459 0.0
2025-11-29-13:27:02.678 0.0
2025-11-29-13:27:02.870 0.0
2025-11-29-13:27:03.071 0.0
2025-11-29-13:27:03.274 0.0
2025-11-29-13:27:03.498 0.0
2025-11-29-13:27:03.678 0.0
2025-11-29-13:27:03.897 0.0
2025-11-29-13:27:04.105 0.0
2025-11-29-13:27:04.318 0.0
2025-11-29-13:27:04.540 15.5
2025-11-29-13:27:04.746 15.5
2025-11-29-13:27:05.004 0.0
2025-11-29-13:27:05.113 0.0
2025-11-29-13:27:05.282 0.0
2025-11-29-13:27:05.494 0.0
2025-11-29-13:27:05.690 0.0
2025-11-29-13:27:05.896 15.6
2025-11-29-13:27:06.108 0.0
2025-11-29-13:27:06.305 0.0
2025-11-29-13:27:06.514 0.0
2025-11-29-13:27:06.703 0.0
2025-11-29-13:27:06.925 0.0
2025-11-29-13:27:07.142 0.0
2025-11-29-13:27:07.339 0.0
2025-11-29-13:27:07.540 0.0
2025-11-29-13:27:07.750 15.0
2025-11-29-13:27:07.963 15.0
2025-11-29-13:27:08.167 14.5
2025-11-29-13:27:08.371 14.7
2025-11-29-13:27:08.573 0.0
2025-11-29-13:27:08.775 0.0
2025-11-29-13:27:08.983 0.0
2025-11-29-13:27:09.185 0.0
2025-11-29-13:27:09.396 0.0
2025-11-29-13:27:09.602 0.0
2025-11-29-13:27:09.810 0.0
2025-11-29-13:27:10.016 0.0
2025-11-29-13:27:10.236 0.0
2025-11-29-13:27:10.450 0.0
2025-11-29-13:27:10.650 0.0
2025-11-29-13:27:10.847 0.0
2025-11-29-13:27:11.047 0.0
2025-11-29-13:27:11.254 0.0
2025-11-29-13:27:11.529 0.0
2025-11-29-13:27:11.679 0.0
2025-11-29-13:27:11.816 0.0
2025-11-29-13:27:12.022 0.0
2025-11-29-13:27:12.227 0.0
2025-11-29-13:27:12.460 15.0
2025-11-29-13:27:12.643 15.2
2025-11-29-13:27:12.836 15.3
2025-11-29-13:27:13.048 0.0
2025-11-29-13:27:13.261 15.3
2025-11-29-13:27:13.450 0.0
2025-11-29-13:27:13.663 0.0
2025-11-29-13:27:13.878 15.0
2025-11-29-13:27:14.101 0.0
2025-11-29-13:27:14.311 0.0
2025-11-29-13:27:14.517 16.3
2025-11-29-13:27:14.720 16.0
2025-11-29-13:27:14.938 15.5
2025-11-29-13:27:15.158 15.2
2025-11-29-13:27:15.343 0.0
2025-11-29-13:27:15.569 0.0
2025-11-29-13:27:15.789 15.5
2025-11-29-13:27:15.976 0.0
2025-11-29-13:27:16.197 0.0
2025-11-29-13:27:16.392 0.0
2025-11-29-13:27:16.605 15.2
2025-11-29-13:27:16.809 0.0
2025-11-29-13:27:17.001 0.0
2025-11-29-13:27:17.211 0.0
2025-11-29-13:27:17.414 0.0
2025-11-29-13:27:17.629 0.0
2025-11-29-13:27:17.837 0.0
2025-11-29-13:27:18.032 0.0
2025-11-29-13:27:18.261 0.0
2025-11-29-13:27:18.444 0.0
2025-11-29-13:27:18.680 0.0
2025-11-29-13:27:18.871 0.0
2025-11-29-13:27:19.081 0.0
2025-11-29-13:27:19.296 0.0
2025-11-29-13:27:19.490 0.0
2025-11-29-13:27:19.682 0.0
2025-11-29-13:27:19.875 0.0
2025-11-29-13:27:20.091 0.0
2025-11-29-13:27:20.297 0.0
2025-11-29-13:27:20.486 0.0
2025-11-29-13:27:20.689 0.0
2025-11-29-13:27:20.894 0.0
2025-11-29-13:27:21.107 0.0
2025-11-29-13:27:21.329 0.0
2025-11-29-13:27:21.508 0.0
2025-11-29-13:27:21.723 0.0
2025-11-29-13:27:21.918 0.0
2025-11-29-13:27:22.124 0.0
2025-11-29-13:27:22.328 0.0
2025-11-29-13:27:22.524 0.0
2025-11-29-13:27:22.755 0.0
2025-11-29-13:27:22.935 0.0
2025-11-29-13:27:23.135 0.0
2025-11-29-13:27:23.344 0.0
2025-11-29-13:27:23.544 0.0
2025-11-29-13:27:23.755 0.0
2025-11-29-13:27:23.954 0.0
2025-11-29-13:27:24.156 0.0
2025-11-29-13:27:24.359 0.0
2025-11-29-13:27:24.582 0.0
2025-11-29-13:27:24.787 0.0
2025-11-29-13:27:24.987 0.0
2025-11-29-13:27:25.206 0.0
2025-11-29-13:27:25.392 0.0
2025-11-29-13:27:25.596 0.0
2025-11-29-13:27:25.809 0.0
2025-11-29-13:27:26.013 0.0
2025-11-29-13:27:26.213 0.0
2025-11-29-13:27:26.425 0.0
2025-11-29-13:27:26.620 0.0
2025-11-29-13:27:26.823 0.0
2025-11-29-13:27:27.031 0.0
2025-11-29-13:27:27.238 15.5
2025-11-29-13:27:27.452 15.6
2025-11-29-13:27:27.656 15.2
2025-11-29-13:27:27.845 15.0
2025-11-29-13:27:28.043 15.2
2025-11-29-13:27:28.269 0.0
2025-11-29-13:27:28.453 0.0
2025-11-29-13:27:28.672 14.5
2025-11-29-13:27:28.860 0.0
2025-11-29-13:27:29.080 0.0
2025-11-29-13:27:29.307 0.0
2025-11-29-13:27:29.497 0.0
2025-11-29-13:27:29.713 0.0
2025-11-29-13:27:29.921 15.2
2025-11-29-13:27:30.128 15.6
2025-11-29-13:27:30.334 0.0
2025-11-29-13:27:30.549 15.8
2025-11-29-13:27:30.748 15.5
2025-11-29-13:27:30.937 15.3
2025-11-29-13:27:31.153 0.0
2025-11-29-13:27:31.363 0.0
2025-11-29-13:27:31.570 0.0
2025-11-29-13:27:31.772 15.6
2025-11-29-13:27:31.974 15.5
2025-11-29-13:27:32.176 0.0
2025-11-29-13:27:32.387 0.0
2025-11-29-13:27:32.602 16.4
2025-11-29-13:27:32.794 0.0
2025-11-29-13:27:32.995 0.0
2025-11-29-13:27:33.228 0.0
2025-11-29-13:27:33.405 0.0
2025-11-29-13:27:33.611 15.2
2025-11-29-13:27:33.813 0.0
2025-11-29-13:27:34.018 0.0
2025-11-29-13:27:34.218 0.0
2025-11-29-13:27:34.451 0.0
2025-11-29-13:27:34.657 0.0
2025-11-29-13:27:34.840 0.0
2025-11-29-13:27:35.063 15.8
2025-11-29-13:27:35.260 17.1
2025-11-29-13:27:35.463 0.0
2025-11-29-13:27:35.664 0.0
2025-11-29-13:27:35.885 0.0
2025-11-29-13:27:36.067 0.0
2025-11-29-13:27:36.274 0.0
2025-11-29-13:27:36.487 0.0
2025-11-29-13:27:36.689 0.0
2025-11-29-13:27:36.880 0.0
2025-11-29-13:27:37.100 0.0
2025-11-29-13:27:37.317 0.0
2025-11-29-13:27:37.503 0.0
2025-11-29-13:27:37.718 15.3
2025-11-29-13:27:37.936 15.3
2025-11-29-13:27:38.121 0.0
2025-11-29-13:27:38.325 0.0
2025-11-29-13:27:38.532 0.0
2025-11-29-13:27:38.738 0.0
2025-11-29-13:27:38.944 0.0
2025-11-29-13:27:39.129 0.0
2025-11-29-13:27:39.328 0.0
2025-11-29-13:27:39.544 0.0
2025-11-29-13:27:39.756 0.0
2025-11-29-13:27:39.956 15.5
2025-11-29-13:27:40.160 0.0
2025-11-29-13:27:40.349 0.0
2025-11-29-13:27:40.568 0.0
2025-11-29-13:27:40.768 0.0
2025-11-29-13:27:40.963 0.0
2025-11-29-13:27:41.171 0.0
2025-11-29-13:27:41.379 0.0
2025-11-29-13:27:41.582 0.0
2025-11-29-13:27:41.788 0.0
2025-11-29-13:27:41.985 0.0
2025-11-29-13:27:42.196 0.0
2025-11-29-13:27:42.449 0.0
2025-11-29-13:27:42.620 0.0
2025-11-29-13:27:42.847 14.8
2025-11-29-13:27:43.009 15.0
2025-11-29-13:27:43.216 15.5
2025-11-29-13:27:43.434 0.0
2025-11-29-13:27:43.670 16.1
2025-11-29-13:27:43.827 0.0
2025-11-29-13:27:44.032 0.0
2025-11-29-13:27:44.223 15.8
2025-11-29-13:27:44.434 0.0
2025-11-29-13:27:44.635 15.6
2025-11-29-13:27:44.836 0.0
2025-11-29-13:27:45.026 0.0
2025-11-29-13:27:45.282 0.0
2025-11-29-13:27:45.435 0.0
2025-11-29-13:27:45.721 0.0
2025-11-29-13:27:45.857 0.0
2025-11-29-13:27:46.060 0.0
2025-11-29-13:27:46.276 0.0
2025-11-29-13:27:46.421 0.0
2025-11-29-13:27:46.619 0.0
2025-11-29-13:27:46.841 0.0
2025-11-29-13:27:47.050 0.0
2025-11-29-13:27:47.247 0.0
2025-11-29-13:27:47.453 0.0
2025-11-29-13:27:47.651 0.0
2025-11-29-13:27:47.880 0.0
2025-11-29-13:27:48.099 0.0
2025-11-29-13:27:48.286 0.0
2025-11-29-13:27:48.482 0.0
2025-11-29-13:27:48.691 15.8
2025-11-29-13:27:48.892 0.0
2025-11-29-13:27:49.097 0.0
2025-11-29-13:27:49.307 0.0
2025-11-29-13:27:49.511 0.0
2025-11-29-13:27:49.729 0.0
2025-11-29-13:27:49.941 0.0
2025-11-29-13:27:50.147 0.0
2025-11-29-13:27:50.336 0.0
2025-11-29-13:27:50.547 0.0
2025-11-29-13:27:50.758 0.0
2025-11-29-13:27:50.979 0.0
2025-11-29-13:27:51.173 0.0
2025-11-29-13:27:51.394 15.0
2025-11-29-13:27:51.598 0.0
2025-11-29-13:27:51.804 0.0
2025-11-29-13:27:52.008 0.0
2025-11-29-13:27:52.231 0.0
2025-11-29-13:27:52.434 0.0
2025-11-29-13:27:52.629 0.0
2025-11-29-13:27:52.846 0.0
2025-11-29-13:27:53.046 0.0
2025-11-29-13:27:53.250 0.0
2025-11-29-13:27:53.465 0.0
2025-11-29-13:27:53.660 0.0
2025-11-29-13:27:53.887 0.0
2025-11-29-13:27:54.104 0.0
2025-11-29-13:27:54.299 0.0
2025-11-29-13:27:54.482 0.0
2025-11-29-13:27:54.679 16.6
2025-11-29-13:27:54.953 17.9
2025-11-29-13:27:55.116 15.8
2025-11-29-13:27:55.315 16.0
2025-11-29-13:27:55.588 15.8
2025-11-29-13:27:55.700 15.3
2025-11-29-13:27:55.838 0.0
2025-11-29-13:27:56.079 0.0
2025-11-29-13:27:56.260 0.0
2025-11-29-13:27:56.463 16.0
2025-11-29-13:27:56.669 16.6
2025-11-29-13:27:56.907 16.4
2025-11-29-13:27:57.089 16.4
2025-11-29-13:27:57.269 0.0
2025-11-29-13:27:57.493 17.1
2025-11-29-13:27:57.711 17.9
2025-11-29-13:27:57.891 0.0
2025-11-29-13:27:58.095 0.0
2025-11-29-13:27:58.301 0.0
2025-11-29-13:27:58.513 0.0
2025-11-29-13:27:58.715 0.0
2025-11-29-13:27:58.935 0.0
2025-11-29-13:27:59.163 0.0
2025-11-29-13:27:59.361 0.0
2025-11-29-13:27:59.566 0.0
2025-11-29-13:27:59.765 0.0
2025-11-29-13:27:59.969 0.0
2025-11-29-13:28:00.175 0.0
2025-11-29-13:28:00.389 0.0
2025-11-29-13:28:00.595 0.0
2025-11-29-13:28:00.812 0.0
2025-11-29-13:28:01.013 0.0
2025-11-29-13:28:01.218 0.0
2025-11-29-13:28:01.434 0.0
2025-11-29-13:28:01.631 0.0
2025-11-29-13:28:01.847 0.0
2025-11-29-13:28:02.052 0.0
2025-11-29-13:28:02.263 0.0
2025-11-29-13:28:02.497 0.0
2025-11-29-13:28:02.676 15.3
2025-11-29-13:28:02.904 16.3
2025-11-29-13:28:03.088 16.1
2025-11-29-13:28:03.293 16.6
2025-11-29-13:28:03.550 17.1
2025-11-29-13:28:03.718 16.3
2025-11-29-16:41:31.498 0.0
2025-11-29-16:41:31.526 0.0
2025-11-29-16:41:31.858 0.0
2025-11-29-16:41:31.933 0.0
2025-11-29-16:41:32.136 0.0
2025-11-29-16:41:32.334 0.0
2025-11-29-16:41:32.548 0.0
2025-11-29-16:41:32.746 0.0
2025-11-29-16:41:32.943 0.0
2025-11-29-16:41:33.147 0.0
2025-11-29-16:41:33.348 0.0
2025-11-29-16:41:33.540 0.0
2025-11-29-16:41:33.750 0.0
2025-11-29-16:41:33.953 0.0
2025-11-29-16:41:34.146 0.0
2025-11-29-16:41:34.361 0.0
2025-11-29-16:41:34.554 0.0
2025-11-29-17:26:08.241 0.0
2025-11-29-17:26:08.278 0.0
2025-11-29-17:26:08.481 0.0
2025-11-29-17:26:08.684 0.0
2025-11-29-17:26:08.883 0.0
2025-11-29-17:26:09.085 0.0
2025-11-29-17:26:09.287 0.0
2025-11-29-17:26:09.484 0.0
2025-11-29-17:26:09.679 0.0
2025-11-29-17:26:09.998 0.0
2025-11-29-17:26:10.233 0.0
2025-11-29-17:26:10.298 0.0
2025-11-29-17:26:10.504 0.0
2025-11-29-17:26:10.708 0.0
2025-11-29-17:26:10.890 0.0
2025-11-29-17:26:11.101 0.0
2025-11-29-17:26:11.295 0.0
2025-11-29-17:26:11.496 0.0
2025-11-29-17:26:11.704 0.0
2025-11-29-11:16:22.428 0.0
2025-11-29-11:16:22.428 0.0
2025-11-29-11:16:22.461 0.0
2025-11-29-11:16:22.662 0.0
2025-11-29-11:16:22.875 0.0
2025-11-29-11:16:23.067 0.0
2025-11-29-11:16:23.269 0.0
2025-11-29-11:16:23.468 0.0
2025-11-29-11:16:23.666 0.0
2025-11-29-11:16:23.868 0.0
2025-11-29-11:16:24.072 0.0
2025-11-29-11:16:24.280 0.0
2025-11-29-11:16:24.483 0.0
2025-11-29-11:16:24.685 0.0
2025-11-29-11:16:24.887 0.0
2025-11-29-11:16:25.086 0.0
2025-11-29-11:16:25.289 0.0
2025-11-29-11:16:25.491 0.0
2025-11-29-11:16:25.833 0.0
2025-11-29-11:16:26.449 0.0
2025-11-29-11:16:26.604 0.0
2025-11-29-11:16:26.903 0.0
2025-11-29-11:16:26.955 0.0
2025-11-29-11:16:27.315 0.0
2025-11-29-11:16:27.479 0.0
2025-11-29-11:16:27.813 0.0
2025-11-29-11:16:27.941 0.0
2025-11-29-11:16:28.109 0.0
2025-11-29-11:16:28.264 0.0
2025-11-29-11:16:28.466 0.0
2025-11-29-11:16:28.669 0.0
2025-11-29-11:16:28.871 0.0
2025-11-29-11:16:29.075 0.0
2025-11-29-11:16:29.273 0.0
2025-11-29-11:16:29.482 0.0
2025-11-29-11:16:29.691 0.0
2025-11-29-11:16:29.925 0.0
2025-11-29-11:16:30.126 0.0
2025-11-29-11:16:30.321 0.0
2025-11-29-11:16:30.575 0.0
2025-11-29-11:16:30.714 0.0
2025-11-29-11:16:30.913 0.0
2025-11-29-11:16:31.113 0.0
2025-11-29-11:16:31.315 0.0
2025-11-29-11:16:31.525 0.0
2025-11-29-11:16:31.731 0.0
2025-11-29-11:16:31.919 0.0
2025-11-29-11:16:32.132 0.0
2025-11-29-11:16:32.324 0.0
2025-11-29-11:16:32.508 0.0
2025-11-29-11:16:32.706 0.0
2025-11-29-11:16:32.909 0.0
2025-11-29-11:16:33.116 0.0
2025-11-29-11:16:33.316 0.0
2025-11-29-11:16:33.514 0.0
2025-11-29-11:16:33.716 0.0
2025-11-29-11:16:33.931 0.0
2025-11-29-11:16:34.124 0.0
2025-11-29-11:16:34.373 0.0
2025-11-29-11:16:34.568 0.0
2025-11-29-11:16:34.759 0.0
2025-11-29-11:16:34.992 0.0
2025-11-29-11:16:35.171 0.0
2025-11-29-11:16:35.376 0.0
2025-11-29-11:16:35.586 0.0
2025-11-29-11:16:35.785 0.0
2025-11-29-11:16:35.978 0.0
2025-11-29-11:16:36.187 0.0
2025-11-29-11:16:36.387 0.0
2025-11-29-11:16:36.586 0.0
2025-11-29-11:16:36.797 0.0
2025-11-29-11:16:37.005 0.0
2025-11-29-11:16:37.189 0.0
2025-11-29-11:16:37.399 0.0
2025-11-29-11:16:37.601 0.0
2025-11-29-11:16:37.805 0.0
2025-11-29-11:16:38.018 0.0
2025-11-29-11:16:38.203 0.0
2025-11-29-11:16:38.410 0.0
2025-11-29-11:16:38.615 0.0
2025-11-29-11:16:38.818 0.0
2025-11-29-11:16:39.017 0.0
2025-11-29-11:16:39.222 0.0
2025-11-29-11:16:39.491 0.0
2025-11-29-11:16:39.626 0.0
2025-11-29-11:16:39.774 0.0
2025-11-29-11:16:39.973 0.0
2025-11-29-11:16:40.180 0.0
2025-11-29-11:16:40.378 0.0
2025-11-29-11:16:40.582 0.0
2025-11-29-11:16:40.787 0.0
2025-11-29-11:16:40.992 0.0
2025-11-29-11:16:41.188 0.0
2025-11-29-11:16:41.390 0.0
2025-11-29-11:16:41.638 0.0
2025-11-29-11:16:41.843 0.0
2025-11-29-11:16:42.066 0.0
2025-11-29-11:16:42.264 0.0
2025-11-29-11:16:42.463 0.0
2025-11-29-11:16:42.656 0.0
2025-11-29-11:16:42.864 0.0
2025-11-29-11:16:43.074 0.0
2025-11-29-11:16:43.188 0.0
2025-11-29-11:16:43.388 0.0
2025-11-29-11:16:43.595 0.0
2025-11-29-11:16:43.792 0.0
2025-11-29-11:16:43.997 0.0
2025-11-29-11:16:44.198 0.0
2025-11-29-11:16:44.401 0.0
2025-11-29-11:16:44.615 0.0
2025-11-29-11:16:44.851 0.0
2025-11-29-11:16:45.044 0.0
2025-11-29-11:16:45.241 0.0
2025-11-29-11:16:45.441 0.0
2025-11-29-11:16:45.666 0.0
2025-11-29-11:16:45.855 0.0
2025-11-29-11:16:46.054 0.0
2025-11-29-11:16:46.282 0.0
2025-11-29-11:16:46.466 0.0
2025-11-29-11:16:46.668 0.0
2025-11-29-11:16:46.873 0.0
2025-11-29-11:16:47.082 0.0
2025-11-29-11:16:47.285 0.0
2025-11-29-11:16:47.492 0.0
2025-11-29-11:16:47.703 0.0
2025-11-29-11:16:47.908 0.0
2025-11-29-11:16:48.178 0.0
2025-11-29-11:16:48.312 0.0
2025-11-29-11:16:48.518 0.0
2025-11-29-11:16:48.734 0.0
2025-11-29-11:16:48.934 0.0
2025-11-29-11:16:49.146 0.0
2025-11-29-11:16:49.344 0.0
2025-11-29-11:16:49.556 0.0
2025-11-29-11:16:49.764 0.0
2025-11-29-11:16:49.988 0.0
2025-11-29-11:16:50.194 0.0
2025-11-29-11:16:50.399 0.0
2025-11-29-11:16:50.610 0.0
2025-11-29-11:16:50.821 0.0
2025-11-29-11:16:50.984 0.0
2025-11-29-11:16:51.190 0.0
2025-11-29-11:16:51.394 0.0
2025-11-29-11:16:51.611 0.0
2025-11-29-11:16:51.801 0.0
2025-11-29-11:16:52.006 0.0
2025-11-29-11:16:52.234 0.0
2025-11-29-11:16:52.448 0.0
2025-11-29-11:16:52.652 0.0
2025-11-29-11:16:52.841 0.0
2025-11-29-11:16:53.066 0.0
2025-11-29-11:16:53.254 0.0
2025-11-29-11:16:53.464 0.0
2025-11-29-11:16:53.678 0.0
2025-11-29-11:16:53.853 0.0
2025-11-29-11:16:54.088 0.0
2025-11-29-11:16:54.289 0.0
2025-11-29-11:16:54.507 0.0
2025-11-29-11:16:54.691 0.0
2025-11-29-11:16:54.903 0.0
2025-11-29-11:16:55.101 0.0
2025-11-29-11:16:55.316 0.0
2025-11-29-11:16:55.523 0.0
2025-11-29-11:16:55.726 0.0
2025-11-29-11:16:55.941 0.0
2025-11-29-11:16:56.144 0.0
2025-11-29-11:16:56.375 0.0
2025-11-29-11:16:56.550 0.0
2025-11-29-11:16:56.753 0.0
2025-11-29-11:16:56.966 0.0
2025-11-29-11:16:57.207 0.0
2025-11-29-11:16:57.400 0.0
2025-11-29-11:16:57.566 0.0
2025-11-29-11:16:57.758 0.0
2025-11-29-11:16:57.969 0.0
2025-11-29-11:16:58.163 0.0
2025-11-29-11:16:58.369 0.0
2025-11-29-11:16:58.578 0.0
2025-11-29-11:16:58.771 0.0
2025-11-29-11:16:58.975 0.0
2025-11-29-11:16:59.173 0.0
2025-11-29-11:16:59.423 0.0
2025-11-29-11:16:59.621 0.0
2025-11-29-11:16:59.810 0.0
2025-11-29-11:17:00.023 0.0
2025-11-29-11:17:00.229 0.0
2025-11-29-11:17:00.491 0.0
2025-11-29-11:17:00.616 0.0
2025-11-29-11:17:00.808 0.0
2025-11-29-11:17:01.013 0.0
2025-11-29-11:17:01.214 0.0
2025-11-29-11:17:01.419 0.0
2025-11-29-11:17:01.618 0.0
2025-11-29-11:17:01.820 0.0
2025-11-29-11:17:02.022 0.0
2025-11-29-11:17:02.222 0.0
2025-11-29-11:17:02.434 0.0
2025-11-29-11:17:02.646 0.0
2025-11-29-11:17:02.924 20.0
2025-11-29-11:17:03.048 0.0
2025-11-29-11:17:03.258 0.0
2025-11-29-11:17:03.459 0.0
2025-11-29-11:17:03.666 0.0
2025-11-29-11:17:03.875 20.0
2025-11-29-11:17:04.111 20.0
2025-11-29-11:17:04.269 20.0
2025-11-29-11:17:04.413 0.0
2025-11-29-11:17:04.612 0.0
2025-11-29-11:17:04.821 0.0
2025-11-29-11:17:05.007 0.0
2025-11-29-11:17:05.208 0.0
2025-11-29-11:17:05.436 0.0
2025-11-29-11:17:05.616 0.0
2025-11-29-11:17:05.825 0.0
2025-11-29-11:17:06.029 0.0
2025-11-29-11:17:06.221 0.0
2025-11-29-11:17:06.418 0.0
2025-11-29-11:17:06.623 0.0
2025-11-29-11:17:06.822 0.0
2025-11-29-11:17:07.029 0.0
2025-11-29-11:17:07.231 0.0
2025-11-29-11:17:07.435 0.0
2025-11-29-11:17:07.679 0.0
2025-11-29-11:17:07.852 0.0
2025-11-29-11:17:08.061 0.0
2025-11-29-11:17:08.241 0.0
2025-11-29-11:17:08.439 0.0
2025-11-29-11:17:08.647 0.0
2025-11-29-11:17:08.860 0.0
2025-11-29-11:17:09.062 0.0
2025-11-29-11:17:09.266 0.0
2025-11-29-11:17:09.469 0.0
2025-11-29-11:17:09.668 0.0
2025-11-29-11:17:09.879 0.0
2025-11-29-11:17:10.079 0.0
2025-11-29-11:17:10.284 0.0
2025-11-29-11:17:10.490 0.0
2025-11-29-11:17:10.690 0.0
2025-11-29-11:17:10.894 0.0
2025-11-29-11:17:11.097 0.0
2025-11-29-11:17:11.304 0.0
2025-11-29-11:17:11.501 0.0
2025-11-29-11:17:11.712 0.0
2025-11-29-11:17:11.915 0.0
2025-11-29-11:17:12.118 0.0
2025-11-29-11:17:12.364 0.0
2025-11-29-11:17:12.524 0.0
2025-11-29-11:17:12.731 0.0
2025-11-29-11:17:12.938 0.0
2025-11-29-11:17:13.146 0.0
2025-11-29-11:17:13.344 0.0
2025-11-29-11:17:13.547 0.0
2025-11-29-11:17:13.756 0.0
2025-11-29-11:17:13.967 0.0
2025-11-29-11:17:14.140 0.0
2025-11-29-11:17:14.386 0.0
2025-11-29-11:17:14.581 0.0
2025-11-29-11:17:14.797 0.0
2025-11-29-11:17:14.997 0.0
2025-11-29-11:17:15.207 0.0
2025-11-29-11:17:15.464 0.0
2025-11-29-11:17:15.528 0.0
2025-11-29-11:17:15.732 0.0
2025-11-29-11:17:15.932 0.0
2025-11-29-11:17:16.135 0.0
2025-11-29-11:17:16.336 0.0
2025-11-29-11:17:16.539 0.0
2025-11-29-11:17:16.739 0.0
2025-11-29-11:17:16.941 0.0
2025-11-29-11:17:17.143 0.0
2025-11-29-11:17:17.350 0.0
2025-11-29-11:17:17.572 0.0
2025-11-29-11:17:17.759 0.0
2025-11-29-11:17:17.960 0.0
2025-11-29-11:17:18.168 0.0
2025-11-29-11:17:18.361 0.0
2025-11-29-11:17:18.564 0.0
2025-11-29-11:17:18.764 0.0
2025-11-29-11:17:18.973 0.0
2025-11-29-11:17:19.177 0.0
2025-11-29-11:17:19.375 0.0
2025-11-29-11:17:19.581 0.0
2025-11-29-11:17:19.782 0.0
2025-11-29-11:17:19.990 0.0
2025-11-29-11:17:20.187 0.0
2025-11-29-11:17:20.401 0.0
2025-11-29-11:17:20.604 0.0
2025-11-29-11:17:20.806 0.0
2025-11-29-11:17:21.011 0.0
2025-11-29-11:17:21.207 0.0
2025-11-29-11:17:21.431 0.0
2025-11-29-11:17:21.637 0.0
2025-11-29-11:17:21.869 0.0
2025-11-29-11:17:22.069 0.0
2025-11-29-11:17:22.270 0.0
2025-11-29-11:17:22.464 0.0
2025-11-29-11:17:22.666 0.0
2025-11-29-11:17:22.949 0.0
2025-11-29-11:17:23.060 0.0
2025-11-29-11:17:23.221 0.0
2025-11-29-11:17:23.405 0.0
2025-11-29-11:17:23.612 0.0
2025-11-29-11:17:23.820 0.0
2025-11-29-11:17:24.034 0.0
2025-11-29-11:17:24.232 0.0
2025-11-29-11:17:24.446 0.0
2025-11-29-11:17:24.654 0.0
2025-11-29-11:17:24.876 0.0
2025-11-29-11:17:25.035 0.0
2025-11-29-11:17:25.244 0.0
2025-11-29-11:17:25.454 0.0
2025-11-29-11:17:25.638 0.0
2025-11-29-11:17:25.843 0.0
2025-11-29-11:17:26.048 0.0
2025-11-29-11:17:26.245 0.0
2025-11-29-11:17:26.447 0.0
2025-11-29-11:17:26.679 0.0
2025-11-29-11:17:26.877 0.0
2025-11-29-11:17:27.098 0.0
2025-11-29-11:17:27.301 0.0
2025-11-29-11:17:27.503 0.0
2025-11-29-11:17:27.726 0.0
2025-11-29-11:17:27.918 0.0
2025-11-29-11:17:28.151 0.0
2025-11-29-11:17:28.372 0.0
2025-11-29-11:17:28.578 0.0
2025-11-29-11:17:28.780 0.0
2025-11-29-11:17:28.986 0.0
2025-11-29-11:17:29.191 0.0
2025-11-29-11:17:29.405 0.0
2025-11-29-11:17:29.621 0.0
2025-11-29-11:17:29.829 0.0
2025-11-29-11:17:30.021 0.0
2025-11-29-11:17:30.240 0.0
2025-11-29-11:17:30.444 0.0
2025-11-29-11:17:30.641 0.0
2025-11-29-11:17:30.866 0.0
2025-11-29-11:17:31.068 0.0
2025-11-29-11:17:31.291 0.0
2025-11-29-11:17:31.500 0.0
2025-11-29-11:17:31.713 0.0
2025-11-29-11:17:31.910 0.0
2025-11-29-11:17:32.113 0.0
2025-11-29-11:17:32.318 0.0
2025-11-29-11:17:32.521 0.0
2025-11-29-11:17:32.732 0.0
2025-11-29-11:17:32.934 0.0
2025-11-29-11:17:33.120 0.0
2025-11-29-11:17:33.339 0.0
2025-11-29-11:17:33.530 0.0
2025-11-29-11:17:33.739 0.0
2025-11-29-11:17:33.947 0.0
2025-11-29-11:17:34.155 0.0
2025-11-29-11:17:34.361 0.0
2025-11-29-11:17:34.570 0.0
2025-11-29-11:17:34.785 0.0
2025-11-29-11:17:34.991 0.0
2025-11-29-11:17:35.197 0.0
2025-11-29-11:17:35.404 0.0
2025-11-29-11:17:35.617 0.0
2025-11-29-11:17:35.810 0.0
2025-11-29-11:17:36.013 0.0
2025-11-29-11:17:36.233 0.0
2025-11-29-11:17:36.436 0.0
2025-11-29-11:17:36.638 0.0
2025-11-29-11:17:36.849 0.0
2025-11-29-11:17:37.053 0.0
2025-11-29-11:17:37.277 0.0
2025-11-29-11:17:37.468 0.0
2025-11-29-11:17:37.682 0.0
2025-11-29-11:17:37.883 0.0
2025-11-29-11:17:38.088 0.0
2025-11-29-11:17:38.297 0.0
2025-11-29-11:17:38.499 0.0
2025-11-29-11:17:38.716 0.0
2025-11-29-11:17:38.924 0.0
2025-11-29-11:17:39.126 0.0
2025-11-29-11:17:39.347 0.0
2025-11-29-11:17:39.554 0.0
2025-11-29-11:17:39.747 0.0
2025-11-29-11:17:39.960 0.0
2025-11-29-11:17:40.165 0.0
2025-11-29-11:17:40.388 0.0
2025-11-29-11:17:40.566 0.0
2025-11-29-11:17:40.763 0.0
2025-11-29-11:17:40.987 0.0
2025-11-29-11:17:41.187 0.0
2025-11-29-11:17:41.370 0.0
2025-11-29-11:17:41.581 0.0
2025-11-29-11:17:41.786 0.0
2025-11-29-11:17:42.008 0.0
2025-11-29-11:17:42.191 0.0
2025-11-29-11:17:42.403 0.0
2025-11-29-11:17:42.602 0.0
2025-11-29-11:17:42.832 0.0
2025-11-29-11:17:43.036 0.0
2025-11-29-11:17:43.229 0.0
2025-11-29-11:17:43.420 0.0
2025-11-29-11:17:43.629 0.0
2025-11-29-11:17:43.838 0.0
2025-11-29-11:17:44.048 0.0
2025-11-29-11:17:44.264 0.0
2025-11-29-11:17:44.455 0.0
2025-11-29-11:17:44.671 0.0
2025-11-29-11:17:44.885 20.0
2025-11-29-11:17:45.085 0.0
2025-11-29-11:17:45.320 20.0
2025-11-29-11:17:45.521 20.0
2025-11-29-11:17:45.696 0.0
2025-11-29-11:17:45.914 0.0
2025-11-29-11:17:46.125 0.0
2025-11-29-11:17:46.337 0.0
2025-11-29-11:17:46.544 0.0
2025-11-29-11:17:46.756 0.0
2025-11-29-11:17:46.963 0.0
2025-11-29-11:17:47.161 0.0
2025-11-29-11:17:47.368 0.0
2025-11-29-11:17:47.584 0.0
2025-11-29-11:17:47.775 0.0
2025-11-29-11:17:47.975 0.0
2025-11-29-11:17:48.185 0.0
2025-11-29-11:17:48.386 0.0
2025-11-29-11:17:48.618 0.0
2025-11-29-11:17:48.816 0.0
2025-11-29-11:17:49.018 0.0
2025-11-29-11:17:49.198 0.0
2025-11-29-11:17:49.422 0.0
2025-11-29-11:17:49.643 0.0
2025-11-29-11:17:49.834 0.0
2025-11-29-11:17:50.021 0.0
2025-11-29-11:17:50.235 0.0
2025-11-29-11:17:50.436 0.0
2025-11-29-11:17:50.638 0.0
2025-11-29-11:17:50.839 0.0
2025-11-29-11:17:51.070 0.0
2025-11-29-11:17:51.267 0.0
2025-11-29-11:17:51.480 0.0
2025-11-29-11:17:51.670 0.0
2025-11-29-11:17:51.880 0.0
2025-11-29-11:17:52.077 0.0
2025-11-29-11:17:52.288 0.0
2025-11-29-11:17:52.490 0.0
2025-11-29-11:17:52.699 0.0
2025-11-29-11:17:52.918 0.0
2025-11-29-11:17:53.119 0.0
2025-11-29-11:17:53.335 0.0
2025-11-29-11:17:53.532 0.0
2025-11-29-11:17:53.731 0.0
2025-11-29-11:17:53.965 0.0
2025-11-29-11:17:54.123 0.0
2025-11-29-11:17:54.320 0.0
2025-11-29-11:17:54.531 0.0
2025-11-29-11:17:54.732 0.0
2025-11-29-11:17:54.950 0.0
2025-11-29-11:17:55.159 0.0
2025-11-29-11:17:55.355 0.0
2025-11-29-11:17:55.571 0.0
2025-11-29-11:17:55.763 0.0
2025-11-29-11:17:55.976 0.0
2025-11-29-11:17:56.186 0.0
2025-11-29-11:17:56.386 0.0
2025-11-29-11:17:56.582 0.0
2025-11-29-11:17:56.803 0.0
2025-11-29-11:17:57.007 0.0
2025-11-29-11:17:57.193 0.0
2025-11-29-11:17:57.440 0.0
2025-11-29-11:17:57.604 0.0
2025-11-29-11:17:57.825 0.0
2025-11-29-11:17:58.008 0.0
2025-11-29-11:17:58.232 0.0
2025-11-29-11:17:58.449 0.0
2025-11-29-11:17:58.635 0.0
2025-11-29-11:17:58.836 0.0
2025-11-29-11:17:59.083 0.0
2025-11-29-11:17:59.269 0.0
2025-11-29-11:17:59.455 0.0
2025-11-29-11:17:59.668 0.0
2025-11-29-11:17:59.871 0.0
2025-11-29-11:18:00.080 0.0
2025-11-29-11:18:00.281 0.0
2025-11-29-11:18:00.486 0.0
2025-11-29-11:18:00.712 0.0
2025-11-29-11:18:00.924 0.0
2025-11-29-11:18:01.119 0.0
2025-11-29-11:18:01.330 0.0
2025-11-29-11:18:01.522 0.0
2025-11-29-11:18:01.735 0.0
2025-11-29-11:18:01.947 0.0
2025-11-29-11:18:02.136 0.0
2025-11-29-11:18:02.353 0.0
2025-11-29-11:18:02.555 0.0
2025-11-29-11:18:02.758 0.0
2025-11-29-11:18:02.971 0.0
2025-11-29-11:18:03.171 0.0
2025-11-29-11:18:03.377 0.0
2025-11-29-11:18:03.596 0.0
2025-11-29-11:18:03.730 0.0
2025-11-29-11:18:03.956 0.0
2025-11-29-11:18:04.137 0.0
2025-11-29-11:18:04.342 0.0
2025-11-29-11:18:04.569 0.0
2025-11-29-11:18:04.778 0.0
2025-11-29-11:18:04.996 0.0
2025-11-29-11:18:05.185 0.0
2025-11-29-11:18:05.385 0.0
2025-11-29-11:18:05.605 0.0
2025-11-29-11:18:05.819 0.0
2025-11-29-11:18:06.020 0.0
2025-11-29-11:18:06.221 0.0
2025-11-29-11:18:06.423 0.0
2025-11-29-11:18:06.632 0.0
2025-11-29-11:18:06.834 0.0
2025-11-29-11:18:07.038 0.0
2025-11-29-11:18:07.247 0.0
2025-11-29-11:18:07.446 0.0
2025-11-29-11:18:07.670 0.0
2025-11-29-11:18:07.875 0.0
2025-11-29-11:18:08.082 0.0
2025-11-29-11:18:08.298 0.0
2025-11-29-11:18:08.505 0.0
2025-11-29-11:18:08.695 0.0
2025-11-29-11:18:08.922 0.0
2025-11-29-11:18:09.177 0.0
2025-11-29-11:18:09.309 0.0
2025-11-29-11:18:09.504 0.0
2025-11-29-11:18:09.703 0.0
2025-11-29-11:18:09.915 0.0
2025-11-29-11:18:10.130 0.0
2025-11-29-11:18:10.354 0.0
2025-11-29-11:18:10.541 0.0
2025-11-29-11:18:10.756 0.0
2025-11-29-11:18:10.946 0.0
2025-11-29-11:18:11.173 0.0
2025-11-29-11:18:11.369 0.0
2025-11-29-11:18:11.577 0.0
2025-11-29-11:18:11.804 0.0
2025-11-29-11:18:12.001 0.0
2025-11-29-11:18:12.213 0.0
2025-11-29-11:18:12.419 0.0
2025-11-29-11:18:12.620 0.0
2025-11-29-11:18:12.828 0.0
2025-11-29-11:18:13.032 0.0
2025-11-29-11:18:13.243 0.0
2025-11-29-11:18:13.456 0.0
2025-11-29-11:18:13.665 0.0
2025-11-29-11:18:13.859 20.0
2025-11-29-11:18:14.084 20.0
2025-11-29-11:18:14.275 20.0
2025-11-29-11:18:14.482 20.0
2025-11-29-11:18:14.679 0.0
2025-11-29-11:18:14.882 0.0
2025-11-29-11:18:15.106 0.0
2025-11-29-11:18:15.342 0.0
2025-11-29-11:18:15.558 0.0
2025-11-29-11:18:15.789 0.0
2025-11-29-11:18:15.979 0.0
2025-11-29-11:18:16.224 0.0
2025-11-29-11:18:16.386 0.0
2025-11-29-11:18:16.613 0.0
2025-11-29-11:18:16.792 0.0
2025-11-29-11:18:17.027 0.0
2025-11-29-11:18:17.214 0.0
2025-11-29-11:18:17.429 0.0
2025-11-29-11:18:17.621 0.0
2025-11-29-11:18:17.826 0.0
2025-11-29-11:18:18.047 0.0
2025-11-29-11:18:18.241 0.0
2025-11-29-11:18:18.475 0.0
2025-11-29-11:18:18.679 0.0
2025-11-29-11:18:18.880 0.0
2025-11-29-11:18:19.084 0.0
2025-11-29-11:18:19.288 0.0
2025-11-29-11:18:19.504 0.0
2025-11-29-11:18:19.721 0.0
2025-11-29-11:18:19.913 0.0
2025-11-29-11:18:20.127 0.0
2025-11-29-11:18:20.317 0.0
2025-11-29-11:18:20.471 0.0
2025-11-29-11:18:20.658 0.0
2025-11-29-11:18:20.878 0.0
2025-11-29-11:18:21.077 0.0
2025-11-29-11:18:21.271 0.0
2025-11-29-11:18:21.483 0.0
2025-11-29-11:18:21.697 0.0
2025-11-29-11:18:21.905 0.0
2025-11-29-11:18:22.108 0.0
2025-11-29-11:18:22.304 0.0
2025-11-29-11:18:22.529 0.0
2025-11-29-11:18:22.747 0.0
2025-11-29-11:18:22.946 0.0
2025-11-29-11:18:23.148 0.0
2025-11-29-11:18:23.345 0.0
2025-11-29-11:18:23.547 0.0
2025-11-29-11:18:23.771 0.0
2025-11-29-11:18:23.979 0.0
2025-11-29-11:18:24.192 0.0
2025-11-29-11:18:24.398 0.0
2025-11-29-11:18:24.601 0.0
2025-11-29-11:18:24.808 0.0
2025-11-29-11:18:25.001 0.0
2025-11-29-11:18:25.235 0.0
2025-11-29-11:18:25.418 0.0
2025-11-29-11:18:25.628 0.0
2025-11-29-11:18:25.832 0.0
2025-11-29-11:18:26.028 0.0
2025-11-29-11:18:26.246 0.0
2025-11-29-11:18:26.488 0.0
2025-11-29-11:18:26.687 0.0
2025-11-29-11:18:26.888 0.0
2025-11-29-11:18:27.120 0.0
2025-11-29-11:18:27.334 0.0
2025-11-29-11:18:27.547 0.0
2025-11-29-11:18:27.743 0.0
2025-11-29-11:18:27.939 0.0
2025-11-29-11:18:28.142 0.0
2025-11-29-11:18:28.374 0.0
2025-11-29-11:18:28.580 0.0
2025-11-29-11:18:28.786 0.0
2025-11-29-11:18:28.988 0.0
2025-11-29-11:18:29.175 0.0
2025-11-29-11:18:29.385 0.0
2025-11-29-11:18:29.606 0.0
2025-11-29-11:18:29.844 0.0
2025-11-29-11:18:30.020 0.0
2025-11-29-11:18:30.216 0.0
2025-11-29-11:18:30.425 0.0
2025-11-29-11:18:30.630 0.0
2025-11-29-11:18:30.845 0.0
2025-11-29-11:18:31.041 0.0
2025-11-29-11:18:31.252 0.0
2025-11-29-11:18:31.444 0.0
2025-11-29-11:18:31.642 0.0
2025-11-29-11:18:31.869 0.0
2025-11-29-11:18:32.057 0.0
2025-11-29-11:18:32.273 0.0
2025-11-29-11:18:32.465 0.0
2025-11-29-11:18:32.676 0.0
2025-11-29-11:18:32.889 0.0
2025-11-29-11:18:33.097 0.0
2025-11-29-11:18:33.314 0.0
2025-11-29-11:18:33.507 0.0
2025-11-29-11:18:33.708 0.0
2025-11-29-11:18:33.923 0.0
2025-11-29-11:18:34.120 0.0
2025-11-29-11:18:34.339 0.0
2025-11-29-11:18:34.545 0.0
2025-11-29-11:18:34.746 0.0
2025-11-29-11:18:34.954 0.0
2025-11-29-11:18:35.158 0.0
2025-11-29-11:18:35.367 0.0
2025-11-29-11:18:35.580 0.0
2025-11-29-11:18:35.771 0.0
2025-11-29-11:18:35.986 0.0
2025-11-29-11:18:36.188 0.0
2025-11-29-11:18:36.374 0.0
2025-11-29-11:18:36.582 0.0
2025-11-29-11:18:36.784 0.0
2025-11-29-11:18:36.998 0.0
2025-11-29-11:18:37.185 0.0
2025-11-29-11:18:37.399 0.0
2025-11-29-11:18:37.622 0.0
2025-11-29-11:18:37.816 0.0
2025-11-29-11:18:38.021 0.0
2025-11-29-11:18:38.315 0.0
2025-11-29-11:18:38.446 0.0
2025-11-29-11:18:38.635 0.0
2025-11-29-11:18:38.843 0.0
2025-11-29-11:18:39.089 0.0
2025-11-29-11:18:39.253 0.0
2025-11-29-11:18:39.460 0.0
2025-11-29-11:18:39.659 0.0
2025-11-29-11:18:39.934 0.0
2025-11-29-11:18:40.079 0.0
2025-11-29-11:18:40.275 0.0
2025-11-29-11:18:40.491 0.0
2025-11-29-11:18:40.685 0.0
2025-11-29-11:18:40.901 0.0
2025-11-29-11:18:41.088 0.0
2025-11-29-11:18:41.326 0.0
2025-11-29-11:18:41.522 0.0
2025-11-29-11:18:41.737 0.0
2025-11-29-11:18:41.997 0.0
2025-11-29-11:18:42.121 0.0
2025-11-29-11:18:42.294 0.0
2025-11-29-11:18:42.503 0.0
2025-11-29-11:18:42.710 0.0
2025-11-29-11:18:42.936 0.0
2025-11-29-11:18:43.159 0.0
2025-11-29-11:18:43.341 0.0
2025-11-29-11:18:43.542 0.0
2025-11-29-11:18:43.763 0.0
2025-11-29-11:18:43.952 0.0
2025-11-29-11:18:44.148 0.0
2025-11-29-11:18:44.355 0.0
2025-11-29-11:18:44.560 0.0
2025-11-29-11:18:44.804 0.0
2025-11-29-11:18:44.997 0.0
2025-11-29-11:18:45.201 0.0
2025-11-29-11:18:45.429 0.0
2025-11-29-11:18:45.620 0.0
2025-11-29-11:18:45.843 0.0
2025-11-29-11:18:46.055 0.0
2025-11-29-11:18:46.273 0.0
2025-11-29-11:18:46.479 0.0
2025-11-29-11:18:46.688 0.0
2025-11-29-11:18:46.891 0.0
2025-11-29-11:18:47.099 0.0
2025-11-29-11:18:47.294 0.0
2025-11-29-11:18:47.512 0.0
2025-11-29-11:18:47.754 0.0
2025-11-29-11:18:47.914 0.0
2025-11-29-11:18:48.121 0.0
2025-11-29-11:18:48.336 0.0
2025-11-29-11:18:48.543 0.0
2025-11-29-11:18:48.766 0.0
2025-11-29-11:18:48.955 0.0
2025-11-29-11:18:49.172 0.0
2025-11-29-11:18:49.356 0.0
2025-11-29-11:18:49.557 0.0
2025-11-29-11:18:49.779 0.0
2025-11-29-11:18:49.969 0.0
2025-11-29-11:18:50.183 0.0
2025-11-29-11:18:50.397 0.0
2025-11-29-11:18:50.594 0.0
2025-11-29-11:18:50.804 0.0
2025-11-29-11:18:50.988 0.0
2025-11-29-11:18:51.197 0.0
2025-11-29-11:18:51.392 0.0
2025-11-29-11:18:51.604 0.0
2025-11-29-11:18:51.832 0.0
2025-11-29-11:18:52.010 0.0
2025-11-29-11:18:52.252 0.0
2025-11-29-11:18:52.411 0.0
2025-11-29-11:18:52.646 0.0
2025-11-29-11:18:52.838 0.0
2025-11-29-11:18:53.059 0.0
2025-11-29-11:18:53.253 0.0
2025-11-29-11:18:53.454 0.0
2025-11-29-11:18:53.671 0.0
2025-11-29-11:18:53.865 0.0
2025-11-29-11:18:54.082 0.0
2025-11-29-11:18:54.290 0.0
2025-11-29-11:18:54.507 0.0
2025-11-29-11:18:54.704 0.0
2025-11-29-11:18:54.909 0.0
2025-11-29-11:18:55.130 0.0
2025-11-29-11:18:55.334 0.0
2025-11-29-11:18:55.541 0.0
2025-11-29-11:18:55.736 0.0
2025-11-29-11:18:55.971 0.0
2025-11-29-11:18:56.187 0.0
2025-11-29-11:18:56.391 0.0
2025-11-29-11:18:56.589 0.0
2025-11-29-11:18:56.796 0.0
2025-11-29-11:18:57.017 0.0
2025-11-29-11:18:57.196 0.0
2025-11-29-11:18:57.419 0.0
2025-11-29-11:18:57.605 0.0
2025-11-29-11:18:57.823 0.0
2025-11-29-11:18:58.021 0.0
2025-11-29-11:18:58.262 0.0
2025-11-29-11:18:58.447 0.0
2025-11-29-11:18:58.633 0.0
2025-11-29-11:18:58.851 0.0
2025-11-29-11:18:59.051 0.0
2025-11-29-11:18:59.257 0.0
2025-11-29-11:18:59.472 0.0
2025-11-29-11:18:59.687 0.0
2025-11-29-11:18:59.892 0.0
2025-11-29-11:19:00.101 0.0
2025-11-29-11:19:00.303 0.0
2025-11-29-11:19:00.513 0.0
2025-11-29-11:19:00.702 0.0
2025-11-29-11:19:00.918 0.0
2025-11-29-11:19:01.131 0.0
2025-11-29-11:19:01.332 0.0
2025-11-29-11:19:01.548 0.0
2025-11-29-11:19:01.741 0.0
2025-11-29-11:19:01.948 0.0
2025-11-29-11:19:02.157 0.0
2025-11-29-11:19:02.383 0.0
2025-11-29-11:19:02.571 0.0
2025-11-29-11:19:02.821 0.0
2025-11-29-11:19:02.982 0.0
2025-11-29-11:19:03.189 0.0
2025-11-29-11:19:03.408 0.0
2025-11-29-11:19:03.599 0.0
2025-11-29-11:19:03.816 0.0
2025-11-29-11:19:04.009 0.0
2025-11-29-11:19:04.201 0.0
2025-11-29-11:19:04.444 0.0
2025-11-29-11:19:04.630 0.0
2025-11-29-11:19:04.833 0.0
2025-11-29-11:19:05.039 0.0
2025-11-29-11:19:05.254 0.0
2025-11-29-11:19:05.466 0.0
2025-11-29-11:19:05.681 0.0
2025-11-29-11:19:05.872 0.0
2025-11-29-11:19:06.092 0.0
2025-11-29-11:19:06.317 0.0
2025-11-29-11:19:06.505 0.0
2025-11-29-11:19:06.713 0.0
2025-11-29-11:19:06.937 0.0
2025-11-29-11:19:07.132 0.0
2025-11-29-11:19:07.321 0.0
2025-11-29-11:19:07.550 0.0
2025-11-29-11:19:07.751 0.0
2025-11-29-11:19:07.974 0.0
2025-11-29-11:19:08.191 0.0
2025-11-29-11:19:08.406 0.0
2025-11-29-11:19:08.606 0.0
2025-11-29-11:19:08.802 0.0
2025-11-29-11:19:09.005 0.0
2025-11-29-11:19:09.221 0.0
2025-11-29-11:19:09.438 0.0
2025-11-29-11:19:09.636 0.0
2025-11-29-11:19:09.837 0.0
2025-11-29-11:19:10.058 0.0
2025-11-29-11:19:10.241 0.0
2025-11-29-11:19:10.449 0.0
2025-11-29-11:19:10.669 0.0
2025-11-29-11:19:10.867 0.0
2025-11-29-11:19:11.099 0.0
2025-11-29-11:19:11.308 0.0
2025-11-29-11:19:11.497 0.0
2025-11-29-11:19:11.718 0.0
2025-11-29-11:19:11.910 0.0
2025-11-29-11:19:12.130 0.0
2025-11-29-11:19:12.333 0.0
2025-11-29-11:19:12.548 0.0
2025-11-29-11:19:12.742 0.0
2025-11-29-11:19:12.943 0.0
2025-11-29-11:19:13.137 0.0
2025-11-29-11:19:13.350 0.0
2025-11-29-11:19:13.550 0.0
2025-11-29-11:19:13.751 0.0
2025-11-29-11:19:13.942 0.0
2025-11-29-11:19:14.149 0.0
2025-11-29-11:19:14.373 0.0
2025-11-29-11:19:14.557 0.0
2025-11-29-11:19:14.775 0.0
2025-11-29-11:19:14.972 0.0
2025-11-29-11:19:15.194 0.0
2025-11-29-11:19:15.397 0.0
2025-11-29-11:19:15.600 0.0
2025-11-29-11:19:15.798 0.0
2025-11-29-11:19:16.025 0.0
2025-11-29-11:19:16.219 0.0
2025-11-29-11:19:16.423 0.0
2025-11-29-11:19:16.622 0.0
2025-11-29-11:19:16.835 0.0
2025-11-29-11:19:17.047 0.0
2025-11-29-11:19:17.242 0.0
2025-11-29-11:19:17.442 0.0
2025-11-29-11:19:17.654 0.0
2025-11-29-11:19:17.854 0.0
2025-11-29-11:19:18.055 0.0
2025-11-29-11:19:18.259 0.0
2025-11-29-11:19:18.466 0.0
2025-11-29-11:19:18.766 0.0
2025-11-29-11:19:18.875 0.0
2025-11-29-11:19:19.086 0.0
2025-11-29-11:19:19.307 0.0
2025-11-29-11:19:19.494 0.0
2025-11-29-11:19:19.709 0.0
2025-11-29-11:19:19.939 0.0
2025-11-29-11:19:20.125 0.0
2025-11-29-11:19:20.346 0.0
2025-11-29-11:19:20.598 0.0
2025-11-29-11:19:20.702 0.0
2025-11-29-11:19:20.921 0.0
2025-11-29-11:19:21.109 0.0
2025-11-29-11:19:21.319 0.0
2025-11-29-11:19:21.561 0.0
2025-11-29-11:19:21.735 0.0
2025-11-29-11:19:21.920 0.0
2025-11-29-11:19:22.149 0.0
2025-11-29-11:19:22.356 0.0
2025-11-29-11:19:22.545 0.0
2025-11-29-11:19:22.774 0.0
2025-11-29-11:19:22.971 0.0
2025-11-29-11:19:23.156 0.0
2025-11-29-11:19:23.385 0.0
2025-11-29-11:19:23.597 0.0
2025-11-29-11:19:23.785 0.0
2025-11-29-11:19:23.996 0.0
2025-11-29-11:19:24.186 0.0
2025-11-29-11:19:24.410 0.0
2025-11-29-11:19:24.641 0.0
2025-11-29-11:19:24.820 0.0
2025-11-29-11:19:25.077 0.0
2025-11-29-11:19:25.283 0.0
2025-11-29-11:19:25.525 0.0
2025-11-29-11:19:25.654 0.0
2025-11-29-11:19:25.836 0.0
2025-11-29-11:19:26.047 0.0
2025-11-29-11:19:26.250 0.0
2025-11-29-11:19:26.424 0.0
2025-11-29-11:19:26.632 0.0
2025-11-29-11:19:26.844 0.0
2025-11-29-11:19:27.073 0.0
2025-11-29-11:19:27.273 0.0
2025-11-29-11:19:27.487 0.0
2025-11-29-11:19:27.694 0.0
2025-11-29-11:19:27.899 0.0
2025-11-29-11:19:28.090 0.0
2025-11-29-11:19:28.296 0.0
2025-11-29-11:19:28.518 0.0
2025-11-29-11:19:28.747 0.0
2025-11-29-11:19:28.965 0.0
2025-11-29-11:19:29.136 0.0
2025-11-29-11:19:29.353 0.0
2025-11-29-11:19:29.553 0.0
2025-11-29-11:19:29.772 0.0
2025-11-29-11:19:29.989 0.0
2025-11-29-11:19:30.203 0.0
2025-11-29-11:19:30.416 0.0
2025-11-29-11:19:30.622 0.0
2025-11-29-11:19:30.835 0.0
2025-11-29-11:19:31.029 0.0
2025-11-29-11:19:31.238 0.0
2025-11-29-11:19:31.432 0.0
2025-11-29-11:19:31.644 0.0
2025-11-29-11:19:31.834 0.0
2025-11-29-11:19:32.070 0.0
2025-11-29-11:19:32.247 0.0
2025-11-29-11:19:32.463 0.0
2025-11-29-11:19:32.683 0.0
2025-11-29-11:19:32.896 0.0
2025-11-29-11:19:33.098 0.0
2025-11-29-11:19:33.323 0.0
2025-11-29-11:19:33.530 0.0
2025-11-29-11:19:33.775 0.0
2025-11-29-11:19:33.886 0.0
2025-11-29-11:19:34.086 0.0
2025-11-29-11:19:34.282 0.0
2025-11-29-11:19:34.484 0.0
2025-11-29-11:19:34.689 0.0
2025-11-29-13:33:03.834 0.0
2025-11-29-13:33:03.882 0.0
2025-11-29-13:33:04.068 0.0
2025-11-29-13:33:04.285 0.0
2025-11-29-13:33:04.491 0.0
2025-11-29-13:33:04.708 0.0
2025-11-29-13:33:04.902 0.0
2025-11-29-13:33:05.102 0.0
2025-11-29-13:33:05.309 0.0
2025-11-29-13:33:05.514 0.0
2025-11-29-13:33:05.719 0.0
2025-11-29-13:33:05.910 0.0
2025-11-29-13:33:06.118 0.0
2025-11-29-13:33:06.328 0.0
2025-11-29-13:33:06.526 0.0
2025-11-29-13:33:06.741 0.0
2025-11-29-13:33:06.971 0.0
2025-11-29-13:33:07.177 0.0
2025-11-29-13:33:07.374 0.0
2025-11-29-13:33:07.572 0.0
2025-11-29-13:33:07.768 0.0
2025-11-29-13:33:07.982 0.0
2025-11-29-13:33:08.189 0.0
2025-11-29-13:33:08.399 0.0
2025-11-29-13:33:08.610 0.0
2025-11-29-13:33:08.813 0.0
2025-11-29-13:33:09.029 0.0
2025-11-29-13:33:09.241 0.0
2025-11-29-13:33:09.437 0.0
2025-11-29-13:33:09.642 0.0
2025-11-29-13:33:09.847 0.0
2025-11-29-13:33:10.071 0.0
2025-11-29-13:33:10.279 0.0
2025-11-29-13:33:10.489 0.0
2025-11-29-13:33:10.722 0.0
2025-11-29-13:33:10.898 0.0
2025-11-29-13:33:11.112 0.0
2025-11-29-13:33:11.351 0.0
2025-11-29-13:33:11.553 0.0
2025-11-29-13:33:11.751 0.0
2025-11-29-13:33:11.943 0.0
2025-11-29-13:33:12.151 0.0
2025-11-29-13:33:12.357 0.0
2025-11-29-13:33:12.574 0.0
2025-11-29-13:33:12.803 0.0
2025-11-29-13:33:12.999 0.0
2025-11-29-13:33:13.182 0.0
2025-11-29-13:33:13.418 0.0
2025-11-29-13:33:13.619 0.0
2025-11-29-13:33:13.813 0.0
2025-11-29-13:33:14.003 0.0
2025-11-29-13:33:14.203 0.0
2025-11-29-13:33:14.412 0.0
2025-11-29-13:33:14.641 0.0
2025-11-29-13:33:14.847 0.0
2025-11-29-13:33:15.062 0.0
2025-11-29-13:33:15.255 0.0
2025-11-29-13:33:15.498 0.0
2025-11-29-13:33:15.669 0.0
2025-11-29-13:33:15.906 0.0
2025-11-29-13:33:16.098 0.0
2025-11-29-13:33:16.282 0.0
2025-11-29-13:33:16.510 0.0
2025-11-29-13:33:16.691 0.0
2025-11-29-13:33:16.891 0.0
2025-11-29-13:33:17.113 0.0
2025-11-29-13:33:17.296 0.0
2025-11-29-13:33:17.541 0.0
2025-11-29-13:33:17.729 0.0
2025-11-29-13:33:17.926 0.0
2025-11-29-13:33:18.163 0.0
2025-11-29-13:33:18.365 0.0
2025-11-29-13:33:18.547 0.0
2025-11-29-13:33:18.748 0.0
2025-11-29-13:33:18.955 0.0
2025-11-29-13:33:19.160 0.0
2025-11-29-13:33:19.365 0.0
2025-11-29-13:33:19.568 0.0
2025-11-29-13:33:19.770 0.0
2025-11-29-13:33:19.982 0.0
2025-11-29-13:33:20.181 0.0
2025-11-29-13:33:20.384 0.0
2025-11-29-13:33:20.604 0.0
2025-11-29-13:33:20.797 0.0
2025-11-29-13:33:21.011 0.0
2025-11-29-13:33:21.192 0.0
2025-11-29-13:33:21.420 0.0
2025-11-29-13:33:21.608 0.0
2025-11-29-13:33:21.832 0.0
2025-11-29-13:33:22.018 0.0
2025-11-29-13:33:22.233 0.0
2025-11-29-13:33:22.437 0.0
2025-11-29-13:33:22.629 0.0
2025-11-29-13:33:22.843 0.0
2025-11-29-13:33:23.057 0.0
2025-11-29-13:33:23.200 0.0
2025-11-29-13:33:23.429 0.0
2025-11-29-13:33:23.617 0.0
2025-11-29-13:33:23.793 0.0
2025-11-29-13:33:23.993 0.0
2025-11-29-13:33:24.204 0.0
2025-11-29-13:33:24.400 0.0
2025-11-29-13:33:24.607 0.0
2025-11-29-13:33:24.809 0.0
2025-11-29-13:33:25.003 0.0
2025-11-29-13:33:25.210 0.0
2025-11-29-13:33:25.411 0.0
2025-11-29-13:33:25.607 0.0
2025-11-29-13:33:25.808 0.0
2025-11-29-13:33:26.020 0.0
2025-11-29-13:33:26.215 0.0
2025-11-29-13:33:26.424 0.0
2025-11-29-13:33:26.633 0.0
2025-11-29-13:33:26.838 0.0
2025-11-29-13:33:27.033 0.0
2025-11-29-13:33:27.238 0.0
2025-11-29-13:33:27.444 0.0
2025-11-29-13:33:27.644 0.0
2025-11-29-13:33:27.846 0.0
2025-11-29-13:33:28.053 0.0
2025-11-29-13:33:28.254 0.0
2025-11-29-13:33:28.506 0.0
2025-11-29-13:33:28.672 0.0
2025-11-29-13:33:28.889 0.0
2025-11-29-13:33:29.100 0.0
2025-11-29-13:33:29.307 0.0
2025-11-29-13:33:29.530 0.0
2025-11-29-13:33:29.748 0.0
2025-11-29-13:33:29.943 0.0
2025-11-29-13:33:30.161 0.0
2025-11-29-13:33:30.367 0.0
2025-11-29-13:33:30.566 0.0
2025-11-29-13:33:30.788 0.0
2025-11-29-13:33:30.989 0.0
2025-11-29-13:33:31.206 0.0
2025-11-29-13:33:31.405 0.0
2025-11-29-13:33:31.606 0.0
2025-11-29-13:33:31.812 0.0
2025-11-29-13:33:32.027 0.0
2025-11-29-13:33:32.233 0.0
2025-11-29-13:33:32.449 0.0
2025-11-29-13:33:32.637 0.0
2025-11-29-13:33:32.844 0.0
2025-11-29-13:33:33.048 0.0
2025-11-29-13:33:33.245 0.0
2025-11-29-13:33:33.463 0.0
2025-11-29-13:33:33.675 0.0
2025-11-29-13:33:33.858 0.0
2025-11-29-13:33:34.077 0.0
2025-11-29-13:33:34.280 0.0
2025-11-29-13:33:34.484 0.0
2025-11-29-13:33:34.713 0.0
2025-11-29-13:33:34.880 0.0
2025-11-29-13:33:35.088 0.0
2025-11-29-13:33:35.300 0.0
2025-11-29-13:33:35.491 0.0
2025-11-29-13:33:35.692 0.0
2025-11-29-13:33:35.896 0.0
2025-11-29-13:33:36.114 0.0
2025-11-29-13:33:36.308 0.0
2025-11-29-13:33:36.510 0.0
2025-11-29-13:33:36.727 0.0
2025-11-29-13:33:36.935 0.0
2025-11-29-13:33:37.147 0.0
2025-11-29-13:33:37.343 0.0
2025-11-29-13:33:37.559 0.0
2025-11-29-13:33:37.777 0.0
2025-11-29-13:33:37.978 0.0
2025-11-29-13:33:38.179 0.0
2025-11-29-13:33:38.375 0.0
2025-11-29-13:33:38.603 0.0
2025-11-29-13:33:38.792 0.0
2025-11-29-13:33:39.009 0.0
2025-11-29-13:33:39.215 0.0
2025-11-29-13:33:39.404 0.0
2025-11-29-13:33:39.629 0.0
2025-11-29-13:33:39.810 0.0
2025-11-29-13:33:40.020 0.0
2025-11-29-13:33:40.221 0.0
2025-11-29-13:33:40.421 0.0
2025-11-29-13:33:40.632 0.0
2025-11-29-13:33:40.844 0.0
2025-11-29-13:33:41.048 0.0
2025-11-29-13:33:41.251 0.0
2025-11-29-13:33:41.451 0.0
2025-11-29-13:33:41.667 0.0
2025-11-29-13:33:41.861 0.0
2025-11-29-13:33:42.081 0.0
2025-11-29-13:33:42.268 0.0
2025-11-29-13:33:42.479 0.0
2025-11-29-13:33:42.683 0.0
2025-11-29-13:33:42.879 0.0
2025-11-29-13:33:43.125 0.0
2025-11-29-13:33:43.309 0.0
2025-11-29-13:33:43.447 0.0
2025-11-29-13:33:43.644 0.0
2025-11-29-13:33:43.859 0.0
2025-11-29-13:33:44.050 0.0
2025-11-29-13:33:44.281 0.0
2025-11-29-13:33:44.480 0.0
2025-11-29-13:33:44.701 0.0
2025-11-29-13:33:44.896 0.0
2025-11-29-13:33:45.103 0.0
2025-11-29-13:33:45.319 0.0
2025-11-29-13:33:45.545 0.0
2025-11-29-13:33:45.725 0.0
2025-11-29-13:33:45.921 0.0
2025-11-29-13:33:46.120 0.0
2025-11-29-13:33:46.340 0.0
2025-11-29-13:33:46.554 0.0
2025-11-29-13:33:46.765 0.0
2025-11-29-13:33:46.990 0.0
2025-11-29-13:33:47.189 0.0
2025-11-29-13:33:47.397 0.0
2025-11-29-13:33:47.604 0.0
2025-11-29-13:33:47.800 0.0
2025-11-29-13:33:48.022 0.0
2025-11-29-13:33:48.229 0.0
2025-11-29-13:33:48.432 0.0
2025-11-29-13:33:48.672 0.0
2025-11-29-13:33:48.854 0.0
2025-11-29-13:33:49.048 0.0
2025-11-29-13:33:49.265 0.0
2025-11-29-13:33:49.481 0.0
2025-11-29-13:33:49.679 0.0
2025-11-29-13:33:49.868 0.0
2025-11-29-13:33:50.069 0.0
2025-11-29-13:33:50.305 0.0
2025-11-29-13:33:50.494 0.0
2025-11-29-13:33:50.693 0.0
2025-11-29-13:33:50.918 0.0
2025-11-29-13:33:51.089 0.0
2025-11-29-13:33:51.310 0.0
2025-11-29-13:33:51.514 0.0
2025-11-29-13:33:51.726 0.0
2025-11-29-13:33:51.943 0.0
2025-11-29-13:33:52.165 0.0
2025-11-29-13:33:52.351 0.0
2025-11-29-13:33:52.573 0.0
2025-11-29-13:33:52.782 0.0
2025-11-29-13:33:52.963 0.0
2025-11-29-13:33:53.165 0.0
2025-11-29-13:33:53.368 0.0
2025-11-29-13:33:53.584 0.0
2025-11-29-13:33:53.798 0.0
2025-11-29-13:33:54.033 0.0
2025-11-29-13:33:54.146 0.0
2025-11-29-13:33:54.349 0.0
2025-11-29-13:33:54.546 0.0
2025-11-29-13:33:54.760 0.0
2025-11-29-13:33:54.943 0.0
2025-11-29-13:33:55.167 0.0
2025-11-29-13:33:55.365 0.0
2025-11-29-13:33:55.547 0.0
2025-11-29-13:33:55.753 0.0
2025-11-29-13:33:55.956 0.0
2025-11-29-13:33:56.160 0.0
2025-11-29-13:33:56.385 0.0
2025-11-29-13:33:56.589 0.0
2025-11-29-13:33:56.821 0.0
2025-11-29-13:33:56.996 0.0
2025-11-29-13:33:57.202 0.0
2025-11-29-13:33:57.409 0.0
2025-11-29-13:33:57.627 0.0
2025-11-29-13:33:57.820 0.0
2025-11-29-13:33:58.020 0.0
2025-11-29-13:33:58.244 0.0
2025-11-29-13:33:58.446 0.0
2025-11-29-13:33:58.651 0.0
2025-11-29-13:33:58.861 0.0
2025-11-29-13:33:59.064 0.0
2025-11-29-13:33:59.266 0.0
2025-11-29-13:33:59.479 0.0
2025-11-29-13:33:59.679 0.0
2025-11-29-13:33:59.894 0.0
2025-11-29-13:34:00.149 0.0
2025-11-29-13:34:00.332 0.0
2025-11-29-13:34:00.541 0.0
2025-11-29-13:34:00.731 0.0
2025-11-29-13:34:00.960 0.0
2025-11-29-13:34:01.179 0.0
2025-11-29-13:34:01.386 0.0
2025-11-29-13:34:01.570 0.0
2025-11-29-13:34:01.818 0.0
2025-11-29-13:34:02.012 0.0
2025-11-29-13:34:02.199 0.0
2025-11-29-13:34:02.404 0.0
2025-11-29-13:34:02.582 0.0
2025-11-29-13:34:02.757 0.0
2025-11-29-13:34:02.966 0.0
2025-11-29-13:34:03.163 0.0
2025-11-29-13:34:03.367 0.0
2025-11-29-13:34:03.568 0.0
2025-11-29-13:34:03.767 0.0
2025-11-29-13:34:03.973 0.0
2025-11-29-13:34:04.177 0.0
2025-11-29-13:34:04.394 0.0
2025-11-29-13:34:04.608 0.0
2025-11-29-13:34:04.816 0.0
2025-11-29-13:34:05.015 0.0
2025-11-29-13:34:05.216 0.0
2025-11-29-13:34:05.423 0.0
2025-11-29-13:34:05.624 0.0
2025-11-29-13:34:05.833 0.0
2025-11-29-13:34:06.027 0.0
2025-11-29-13:34:06.234 0.0
2025-11-29-13:34:06.434 0.0
2025-11-29-13:34:06.635 0.0
2025-11-29-13:34:06.841 0.0
2025-11-29-13:34:07.040 0.0
2025-11-29-13:34:07.244 0.0
2025-11-29-13:34:07.447 0.0
2025-11-29-13:34:07.644 0.0
2025-11-29-13:34:07.847 0.0
2025-11-29-13:34:08.045 0.0
2025-11-29-13:34:08.250 0.0
2025-11-29-13:34:08.451 0.0
2025-11-29-13:49:57.633 0.0
2025-11-29-13:49:57.657 0.0
2025-11-29-13:49:57.927 15.1
2025-11-29-13:49:58.066 0.0
2025-11-29-13:49:58.262 0.0
2025-11-29-13:49:58.471 0.0
2025-11-29-13:49:58.674 15.7
2025-11-29-13:49:58.869 0.0
2025-11-29-13:49:59.079 0.0
2025-11-29-13:49:59.279 16.6
2025-11-29-13:49:59.485 16.2
2025-11-29-13:49:59.719 16.3
2025-11-29-13:49:59.893 0.0
2025-11-29-13:50:00.104 0.0
2025-11-29-13:50:00.291 0.0
2025-11-29-13:50:00.500 0.0
2025-11-29-13:50:00.707 15.4
2025-11-29-13:50:00.903 0.0
2025-11-29-13:50:01.109 0.0
2025-11-29-13:50:01.333 0.0
2025-11-29-13:50:01.511 0.0
2025-11-29-13:50:01.729 0.0
2025-11-29-13:50:01.931 0.0
2025-11-29-13:50:02.135 0.0
2025-11-29-13:50:02.332 0.0
2025-11-29-13:50:02.567 16.5
2025-11-29-13:50:02.763 16.2
2025-11-29-13:50:02.947 16.2
2025-11-29-13:50:03.145 16.3
2025-11-29-13:50:03.358 0.0
2025-11-29-13:50:03.572 0.0
2025-11-29-13:50:03.769 0.0
2025-11-29-13:50:03.996 0.0
2025-11-29-13:50:04.192 0.0
2025-11-29-13:50:04.389 0.0
2025-11-29-13:50:04.604 0.0
2025-11-29-13:50:04.809 0.0
2025-11-29-13:50:05.027 0.0
2025-11-29-13:50:05.244 0.0
2025-11-29-13:50:05.437 0.0
2025-11-29-13:50:05.651 15.2
2025-11-29-13:50:05.810 15.7
2025-11-29-13:50:06.018 16.0
2025-11-29-13:50:06.219 0.0
2025-11-29-13:50:06.421 0.0
2025-11-29-13:50:06.626 0.0
2025-11-29-13:50:06.830 0.0
2025-11-29-13:50:07.045 0.0
2025-11-29-13:50:07.247 15.5
2025-11-29-13:50:07.457 16.3
2025-11-29-13:50:07.660 0.0
2025-11-29-13:50:07.857 15.1
2025-11-29-13:50:08.062 15.5
2025-11-29-13:50:08.281 16.2
2025-11-29-13:50:08.484 16.3
2025-11-29-13:50:08.680 16.0
2025-11-29-13:50:08.890 16.2
2025-11-29-13:50:09.094 16.3
2025-11-29-13:50:09.297 0.0
2025-11-29-13:50:09.503 0.0
2025-11-29-13:50:09.710 0.0
2025-11-29-13:50:09.924 0.0
2025-11-29-13:50:10.132 16.6
2025-11-29-13:50:10.334 0.0
2025-11-29-13:50:10.543 0.0
2025-11-29-13:50:10.755 16.9
2025-11-29-13:50:10.969 16.6
2025-11-29-13:50:11.199 0.0
2025-11-29-13:50:11.396 17.1
2025-11-29-13:50:11.603 17.1
2025-11-29-13:50:11.805 17.4
2025-11-29-13:50:12.008 17.4
2025-11-29-13:50:12.213 16.9
2025-11-29-13:50:12.416 17.1
2025-11-29-13:50:12.633 16.9
2025-11-29-13:50:12.828 16.9
2025-11-29-13:50:13.054 16.9
2025-11-29-13:50:13.255 16.6
2025-11-29-13:50:13.468 16.5
2025-11-29-13:50:13.675 16.5
2025-11-29-13:50:13.881 16.6
2025-11-29-13:50:14.069 15.7
2025-11-29-13:50:14.266 16.0
2025-11-29-13:50:14.466 16.3
2025-11-29-13:50:14.687 16.5
2025-11-29-13:50:14.907 16.3
2025-11-29-13:50:15.141 16.6
2025-11-29-13:50:15.324 15.1
2025-11-29-13:50:15.538 14.9
2025-11-29-13:50:15.730 15.5
2025-11-29-13:50:15.940 16.0
2025-11-29-13:50:16.154 16.0
2025-11-29-13:50:16.398 0.0
2025-11-29-13:50:16.564 16.0
2025-11-29-13:50:16.774 16.6
2025-11-29-13:50:16.971 16.9
2025-11-29-13:50:17.189 16.9
2025-11-29-13:50:17.374 0.0
2025-11-29-13:50:17.578 0.0
2025-11-29-13:50:17.784 0.0
2025-11-29-13:50:17.993 0.0
2025-11-29-13:50:18.189 0.0
2025-11-29-13:50:18.401 15.9
2025-11-29-13:50:18.605 16.0
2025-11-29-13:50:18.810 15.4
2025-11-29-13:50:19.018 0.0
2025-11-29-13:50:19.223 0.0
2025-11-29-13:50:19.438 0.0
2025-11-29-13:50:19.643 0.0
2025-11-29-13:50:19.856 0.0
2025-11-29-13:50:20.056 0.0
2025-11-29-13:50:20.264 0.0
2025-11-29-13:50:20.475 0.0
2025-11-29-13:50:20.668 0.0
2025-11-29-13:50:20.874 0.0
2025-11-29-13:50:21.093 0.0
2025-11-29-13:50:21.299 0.0
2025-11-29-13:50:21.487 0.0
2025-11-29-13:50:21.699 0.0
2025-11-29-13:50:21.918 0.0
2025-11-29-13:50:22.105 0.0
2025-11-29-13:50:22.333 15.5
2025-11-29-13:50:22.524 0.0
2025-11-29-13:50:22.719 16.5
2025-11-29-13:50:22.918 0.0
2025-11-29-13:50:23.130 0.0
2025-11-29-13:50:23.342 16.3
2025-11-29-13:50:23.544 16.2
2025-11-29-13:50:23.760 16.0
2025-11-29-13:50:23.944 16.5
2025-11-29-13:50:24.143 16.8
2025-11-29-13:50:24.356 17.1
2025-11-29-13:50:24.588 16.8
2025-11-29-13:50:24.773 16.0
2025-11-29-13:50:24.960 0.0
2025-11-29-13:50:25.158 0.0
2025-11-29-13:50:25.369 17.1
2025-11-29-13:50:25.574 16.9
2025-11-29-13:50:25.783 16.9
2025-11-29-13:50:25.999 16.0
2025-11-29-13:50:26.205 0.0
2025-11-29-13:50:26.410 16.5
2025-11-29-13:50:26.610 0.0
2025-11-29-13:50:26.819 0.0
2025-11-29-13:50:27.023 16.3
2025-11-29-13:50:27.245 16.8
2025-11-29-13:50:27.515 16.8
2025-11-29-13:50:27.577 0.0
2025-11-29-13:50:27.785 0.0
2025-11-29-13:50:27.980 0.0
2025-11-29-13:50:28.188 16.0
2025-11-29-13:50:28.389 16.0
2025-11-29-13:50:28.594 16.2
2025-11-29-13:50:28.810 15.9
2025-11-29-13:50:29.004 15.7
2025-11-29-13:50:29.204 17.1
2025-11-29-13:50:29.419 17.1
2025-11-29-13:50:29.614 16.2
2025-11-29-13:50:29.834 16.5
2025-11-29-13:50:30.018 16.3
2025-11-29-13:50:30.236 16.6
2025-11-29-13:50:30.416 0.0
2025-11-29-13:50:30.637 0.0
2025-11-29-13:50:30.822 0.0
2025-11-29-13:50:31.044 17.4
2025-11-29-13:50:31.237 15.7
2025-11-29-13:50:31.439 0.0
2025-11-29-13:50:31.654 15.4
2025-11-29-13:50:31.871 14.9
2025-11-29-13:50:32.080 15.5
2025-11-29-13:50:32.294 0.0
2025-11-29-13:50:32.508 0.0
2025-11-29-13:50:32.700 0.0
2025-11-29-13:50:32.899 0.0
2025-11-29-13:50:33.116 0.0
2025-11-29-13:50:33.309 0.0
2025-11-29-13:50:33.513 0.0
2025-11-29-13:50:33.725 16.6
2025-11-29-13:50:33.947 16.6
2025-11-29-13:50:34.154 0.0
2025-11-29-13:50:34.353 16.3
2025-11-29-13:50:34.577 16.5
2025-11-29-13:50:34.770 16.5
2025-11-29-13:50:34.967 0.0
2025-11-29-13:50:35.168 0.0
2025-11-29-13:50:35.398 0.0
2025-11-29-13:50:35.601 0.0
2025-11-29-13:50:35.808 16.5
2025-11-29-13:50:36.001 16.3
2025-11-29-13:50:36.206 15.9
2025-11-29-13:50:36.419 16.2
2025-11-29-13:50:36.621 0.0
2025-11-29-13:50:36.833 0.0
2025-11-29-13:50:37.111 16.9
2025-11-29-13:50:37.249 0.0
2025-11-29-13:50:37.425 0.0
2025-11-29-13:50:37.628 0.0
2025-11-29-13:50:37.851 16.5
2025-11-29-13:50:38.051 16.0
2025-11-29-13:50:38.257 16.0
2025-11-29-13:50:38.451 16.2
2025-11-29-13:50:38.659 15.9
2025-11-29-13:50:38.867 0.0
2025-11-29-13:50:39.108 15.7
2025-11-29-13:50:39.308 0.0
2025-11-29-13:50:39.510 16.0
2025-11-29-13:50:39.697 0.0
2025-11-29-13:50:39.904 0.0
2025-11-29-13:50:40.121 0.0
2025-11-29-13:50:40.321 0.0
2025-11-29-13:50:40.517 16.3
2025-11-29-13:50:40.722 16.6
2025-11-29-13:50:40.935 16.2
2025-11-29-13:50:41.155 16.6
2025-11-29-13:50:41.344 0.0
2025-11-29-13:50:41.544 0.0
2025-11-29-13:50:41.761 0.0
2025-11-29-13:50:41.989 0.0
2025-11-29-13:50:42.210 16.2
2025-11-29-13:50:42.401 16.2
2025-11-29-13:50:42.630 16.3
2025-11-29-13:50:42.856 16.3
2025-11-29-13:50:43.049 16.2
2025-11-29-13:50:43.247 0.0
2025-11-29-13:50:43.473 16.5
2025-11-29-13:50:43.697 16.6
2025-11-29-13:50:43.903 16.5
2025-11-29-13:50:44.091 16.6
2025-11-29-13:50:44.329 16.6
2025-11-29-13:50:44.507 16.6
2025-11-29-13:50:44.731 16.5
2025-11-29-13:50:44.971 16.3
2025-11-29-13:50:45.142 16.5
2025-11-29-13:50:45.353 0.0
2025-11-29-13:50:45.550 0.0
2025-11-29-13:50:45.769 0.0
2025-11-29-13:50:45.961 0.0
2025-11-29-13:50:46.211 0.0
2025-11-29-13:50:46.366 0.0
2025-11-29-13:50:46.557 0.0
2025-11-29-13:50:46.786 0.0
2025-11-29-13:50:46.996 16.9
2025-11-29-13:50:47.198 0.0
2025-11-29-13:50:47.405 0.0
2025-11-29-13:50:47.621 0.0
2025-11-29-13:50:47.758 16.9
2025-11-29-13:50:47.953 0.0
2025-11-29-13:50:48.163 0.0
2025-11-29-13:50:48.371 0.0
2025-11-29-13:50:48.589 16.5
2025-11-29-13:50:48.801 16.6
2025-11-29-13:50:48.989 16.8
2025-11-29-13:50:49.194 17.5
2025-11-29-13:50:49.427 17.2
2025-11-29-13:50:49.617 17.2
2025-11-29-13:50:49.819 17.2
2025-11-29-13:50:50.029 16.9
2025-11-29-13:50:50.225 16.5
2025-11-29-13:50:50.433 16.8
2025-11-29-13:50:50.637 16.8
2025-11-29-13:50:50.829 0.0
2025-11-29-13:50:51.062 16.8
2025-11-29-13:50:51.251 0.0
2025-11-29-13:50:51.479 0.0
2025-11-29-13:50:51.675 0.0
2025-11-29-13:50:51.885 0.0
2025-11-29-13:50:52.089 0.0
2025-11-29-13:50:52.315 0.0
2025-11-29-13:50:52.523 0.0
2025-11-29-13:50:52.727 0.0
2025-11-29-13:50:52.933 0.0
2025-11-29-13:50:53.152 0.0
2025-11-29-13:50:53.371 0.0
2025-11-29-13:50:53.584 0.0
2025-11-29-13:50:53.759 0.0
2025-11-29-13:50:54.009 16.6
2025-11-29-13:50:54.193 0.0
2025-11-29-13:50:54.408 0.0
2025-11-29-13:50:54.607 0.0
2025-11-29-13:50:54.799 0.0
2025-11-29-13:50:55.029 16.6
2025-11-29-13:50:55.209 16.8
2025-11-29-13:50:55.434 0.0
2025-11-29-13:50:55.634 0.0
2025-11-29-13:50:55.834 0.0
2025-11-29-13:50:56.038 16.2
2025-11-29-13:50:56.263 16.0
2025-11-29-13:50:56.472 15.9
2025-11-29-13:50:56.671 0.0
2025-11-29-13:50:56.896 0.0
2025-11-29-13:50:57.110 16.0
2025-11-29-13:50:57.303 0.0
2025-11-29-13:50:57.514 15.9
2025-11-29-13:50:57.713 16.0
2025-11-29-13:50:57.920 16.0
2025-11-29-13:50:58.125 16.2
2025-11-29-13:50:58.334 15.9
2025-11-29-13:50:58.516 0.0
2025-11-29-13:50:58.732 0.0
2025-11-29-13:50:58.951 0.0
2025-11-29-13:50:59.145 0.0
2025-11-29-13:50:59.347 17.4
2025-11-29-13:50:59.580 16.6
2025-11-29-13:50:59.772 16.3
2025-11-29-13:50:59.999 0.0
2025-11-29-13:51:00.182 0.0
2025-11-29-13:51:00.395 16.3
2025-11-29-13:51:00.623 16.3
2025-11-29-13:51:00.819 16.2
2025-11-29-13:51:01.029 0.0
2025-11-29-13:51:01.279 16.5
2025-11-29-13:51:01.428 16.0
2025-11-29-13:51:01.621 16.8
2025-11-29-13:51:01.816 16.2
2025-11-29-13:51:02.016 16.0
2025-11-29-13:51:02.232 16.3
2025-11-29-13:51:02.446 0.0
2025-11-29-13:51:02.642 0.0
2025-11-29-13:51:02.838 0.0
2025-11-29-13:51:03.044 16.0
2025-11-29-13:51:03.252 16.5
2025-11-29-13:51:03.459 16.3
2025-11-29-13:51:03.667 16.5
2025-11-29-13:51:03.870 16.2
2025-11-29-13:51:04.072 0.0
2025-11-29-13:51:04.280 16.5
2025-11-29-13:51:04.503 16.6
2025-11-29-13:51:04.724 16.8
2025-11-29-13:51:04.945 16.3
2025-11-29-13:51:05.132 16.5
2025-11-29-13:51:05.322 0.0
2025-11-29-13:51:05.576 0.0
2025-11-29-13:51:05.730 0.0
2025-11-29-13:51:05.913 0.0
2025-11-29-13:51:06.140 0.0
2025-11-29-13:51:06.346 16.8
2025-11-29-13:51:06.534 0.0
2025-11-29-13:51:06.739 0.0
2025-11-29-13:51:06.938 0.0
2025-11-29-13:51:07.145 0.0
2025-11-29-13:51:07.348 0.0
2025-11-29-13:51:07.568 0.0
2025-11-29-13:51:07.788 0.0
2025-11-29-13:51:07.971 0.0
2025-11-29-13:51:08.175 0.0
2025-11-29-13:51:08.382 0.0
2025-11-29-13:51:08.589 0.0
2025-11-29-13:51:08.793 0.0
2025-11-29-13:51:08.988 0.0
2025-11-29-13:51:09.210 0.0
2025-11-29-13:51:09.401 0.0
2025-11-29-13:51:09.605 0.0
2025-11-29-13:51:09.808 0.0
2025-11-29-13:51:10.034 0.0
2025-11-29-13:51:10.236 0.0
2025-11-29-13:51:10.426 0.0
2025-11-29-13:51:10.627 0.0
2025-11-29-13:51:10.843 0.0
2025-11-29-13:51:11.053 0.0
2025-11-29-13:51:11.260 0.0
2025-11-29-13:51:11.471 16.0
2025-11-29-13:51:11.663 16.0
2025-11-29-13:51:11.876 16.6
2025-11-29-13:51:12.077 16.3
2025-11-29-13:51:12.283 16.2
2025-11-29-13:51:12.479 0.0
2025-11-29-13:51:12.696 16.2
2025-11-29-13:51:12.891 0.0
2025-11-29-13:51:13.094 0.0
2025-11-29-13:51:13.289 16.8
2025-11-29-13:51:13.503 16.3
2025-11-29-13:51:13.758 16.2
2025-11-29-13:51:13.925 16.5
2025-11-29-13:51:14.121 16.3
2025-11-29-13:51:14.335 16.3
2025-11-29-13:51:14.531 16.8
2025-11-29-13:51:14.745 16.3
2025-11-29-13:51:14.957 16.6
2025-11-29-13:51:15.139 16.6
2025-11-29-13:51:15.351 0.0
2025-11-29-13:51:15.569 0.0
2025-11-29-13:51:15.759 0.0
2025-11-29-13:51:15.968 0.0
2025-11-29-13:51:16.159 0.0
2025-11-29-13:51:16.367 0.0
2025-11-29-13:51:16.571 0.0
2025-11-29-13:51:16.768 0.0
2025-11-29-13:51:16.978 0.0
2025-11-29-13:51:17.185 0.0
2025-11-29-13:51:17.385 16.8
2025-11-29-13:51:17.601 16.6
2025-11-29-13:51:17.787 0.0
2025-11-29-13:51:17.999 0.0
2025-11-29-13:51:18.191 0.0
2025-11-29-13:51:18.404 0.0
2025-11-29-13:51:18.637 0.0
2025-11-29-13:51:18.829 16.2
2025-11-29-13:51:19.016 16.0
2025-11-29-13:51:19.238 16.6
2025-11-29-13:51:19.428 0.0
2025-11-29-13:51:19.616 0.0
2025-11-29-13:51:19.835 0.0
2025-11-29-13:51:20.042 0.0
2025-11-29-13:51:20.230 0.0
2025-11-29-13:51:20.425 0.0
2025-11-29-13:51:20.626 0.0
2025-11-29-13:51:20.830 0.0
2025-11-29-13:51:21.055 0.0
2025-11-29-13:51:21.278 16.5
2025-11-29-13:51:21.495 16.6
2025-11-29-13:51:21.686 16.9
2025-11-29-13:51:21.871 16.9
2025-11-29-13:51:22.077 16.0
2025-11-29-13:51:22.289 17.2
2025-11-29-13:51:22.493 17.1
2025-11-29-13:51:22.693 0.0
2025-11-29-13:51:22.975 0.0
2025-11-29-13:51:23.091 0.0
2025-11-29-13:51:23.304 0.0
2025-11-29-13:51:23.484 0.0
2025-11-29-13:51:23.682 0.0
2025-11-29-13:51:23.905 0.0
2025-11-29-13:51:24.106 0.0
2025-11-29-13:51:24.305 0.0
2025-11-29-13:51:24.557 0.0
2025-11-29-13:51:24.722 0.0
2025-11-29-13:51:24.908 0.0
2025-11-29-13:51:25.120 0.0
2025-11-29-13:51:25.325 0.0
2025-11-29-13:51:25.523 0.0
2025-11-29-13:51:25.730 16.0
2025-11-29-13:51:25.949 16.3
2025-11-29-13:51:26.144 0.0
2025-11-29-13:51:26.341 0.0
2025-11-29-13:51:26.573 16.6
2025-11-29-13:51:26.760 16.3
2025-11-29-13:51:26.960 0.0
2025-11-29-13:51:27.165 0.0
2025-11-29-13:51:27.368 0.0
2025-11-29-13:51:27.575 0.0
2025-11-29-13:51:27.772 0.0
2025-11-29-13:51:28.119 0.0
2025-11-29-13:51:28.154 0.0
2025-11-29-13:51:28.369 16.3
2025-11-29-13:51:28.580 16.2
2025-11-29-13:51:28.777 0.0
2025-11-29-13:51:28.970 0.0
2025-11-29-13:51:29.169 0.0
2025-11-29-13:51:29.380 0.0
2025-11-29-13:51:29.585 0.0
2025-11-29-13:51:29.771 0.0
2025-11-29-13:51:29.982 0.0
2025-11-29-13:51:30.194 16.8
2025-11-29-13:51:30.377 0.0
2025-11-29-13:51:30.582 0.0
2025-11-29-13:51:30.788 0.0
2025-11-29-13:51:30.985 0.0
2025-11-29-13:51:31.195 0.0
2025-11-29-16:14:35.854 0.0
2025-11-29-16:14:35.891 0.0
2025-11-29-16:14:36.123 0.0
2025-11-29-16:14:36.313 0.0
2025-11-29-16:14:36.518 0.0
2025-11-29-16:14:36.735 0.0
2025-11-29-16:14:36.956 0.0
2025-11-29-16:14:37.137 0.0
2025-11-29-16:14:37.337 0.0
2025-11-29-16:14:37.552 0.0
2025-11-29-16:14:37.765 0.0
2025-11-29-16:14:37.957 0.0
2025-11-29-16:14:38.163 0.0
2025-11-29-16:14:38.369 0.0
2025-11-29-16:14:38.577 0.0
2025-11-29-16:14:38.790 0.0
2025-11-29-16:14:38.979 0.0
2025-11-29-16:14:39.181 0.0
2025-11-29-16:14:39.378 0.0
2025-11-29-16:14:39.609 0.0
2025-11-29-16:14:39.808 0.0
2025-11-29-16:14:40.003 0.0
2025-11-29-16:14:40.221 0.0
2025-11-29-16:14:40.496 18.2
2025-11-29-16:14:40.626 0.0
2025-11-29-16:14:40.828 0.0
2025-11-29-16:14:41.026 0.0
2025-11-29-16:14:41.228 0.0
2025-11-29-16:14:41.427 0.0
2025-11-29-16:14:41.628 0.0
2025-11-29-16:14:41.837 0.0
2025-11-29-16:14:42.036 0.0
2025-11-29-16:14:42.241 0.0
2025-11-29-16:14:42.443 0.0
2025-11-29-16:14:42.661 0.0
2025-11-29-16:14:42.874 0.0
2025-11-29-16:14:43.076 0.0
2025-11-29-16:14:43.290 0.0
2025-11-29-16:14:43.491 0.0
2025-11-29-16:14:43.717 0.0
2025-11-29-16:14:43.908 0.0
2025-11-29-16:14:44.111 0.0
2025-11-29-16:14:44.336 0.0
2025-11-29-16:14:44.505 0.0
2025-11-29-16:14:44.719 0.0
2025-11-29-16:14:44.919 0.0
2025-11-29-16:14:45.180 0.0
2025-11-29-16:14:45.309 0.0
2025-11-29-16:14:45.519 0.0
2025-11-29-16:14:45.715 0.0
2025-11-29-16:14:45.929 0.0
2025-11-29-16:14:46.148 0.0
2025-11-29-16:14:46.335 0.0
2025-11-29-16:14:46.525 0.0
2025-11-29-16:14:46.724 0.0
2025-11-29-16:14:46.925 0.0
2025-11-29-16:14:47.116 0.0
2025-11-29-16:14:47.330 0.0
2025-11-29-16:14:47.540 0.0
2025-11-29-16:14:47.724 0.0
2025-11-29-16:14:47.929 0.0
2025-11-29-16:14:48.143 0.0
2025-11-29-16:14:48.346 0.0
2025-11-29-16:14:48.533 0.0
2025-11-29-16:14:48.744 0.0
2025-11-29-16:14:48.950 0.0
2025-11-29-16:14:49.159 0.0
2025-11-29-16:14:49.352 0.0
2025-11-29-16:14:49.552 0.0
2025-11-29-16:14:49.746 0.0
2025-11-29-16:14:49.933 0.0
2025-11-29-16:14:50.105 0.0
2025-11-29-16:14:50.316 0.0
2025-11-29-16:14:50.519 0.0
2025-11-29-16:14:50.722 0.0
2025-11-29-16:14:50.924 0.0
2025-11-29-16:14:51.137 0.0
2025-11-29-16:14:51.346 0.0
2025-11-29-16:14:51.538 0.0
2025-11-29-16:14:51.738 0.0
2025-11-29-16:14:51.943 0.0
2025-11-29-16:14:52.147 0.0
2025-11-29-16:14:52.364 0.0
2025-11-29-16:14:52.573 0.0
2025-11-29-16:14:52.771 0.0
2025-11-29-16:14:52.980 0.0
2025-11-29-16:14:53.182 0.0
2025-11-29-16:14:53.395 0.0
2025-11-29-16:14:53.595 0.0
2025-11-29-16:14:53.791 0.0
2025-11-29-16:14:53.999 0.0
2025-11-29-16:14:54.207 0.0
2025-11-29-16:14:54.415 0.0
2025-11-29-16:14:54.628 0.0
2025-11-29-16:14:54.850 0.0
2025-11-29-16:14:55.027 0.0
2025-11-29-16:14:55.234 0.0
2025-11-29-16:14:55.427 0.0
2025-11-29-16:14:55.641 0.0
2025-11-29-16:14:55.851 0.0
2025-11-29-16:14:56.033 0.0
2025-11-29-16:14:56.244 0.0
2025-11-29-16:14:56.445 0.0
2025-11-29-16:14:56.647 0.0
2025-11-29-16:14:56.851 0.0
2025-11-29-16:14:57.049 0.0
2025-11-29-16:14:57.254 0.0
2025-11-29-16:14:57.453 0.0
2025-11-29-16:14:57.655 0.0
2025-11-29-16:14:57.855 0.0
2025-11-29-16:14:58.054 0.0
2025-11-29-16:14:58.262 0.0
2025-11-29-16:14:58.458 0.0
2025-11-29-16:14:58.661 0.0
2025-11-29-16:14:58.864 0.0
2025-11-29-16:14:59.066 0.0
2025-11-29-16:14:59.269 0.0
2025-11-29-16:14:59.469 0.0
2025-11-29-16:14:59.675 0.0
2025-11-29-16:14:59.876 0.0
2025-11-29-16:15:00.082 0.0
2025-11-29-16:15:00.288 19.1
2025-11-29-16:15:00.508 0.0
2025-11-29-16:15:00.681 0.0
2025-11-29-16:15:00.901 0.0
2025-11-29-16:15:01.095 0.0
2025-11-29-16:15:01.302 19.2
2025-11-29-16:15:01.526 18.7
2025-11-29-16:15:01.695 0.0
2025-11-29-16:15:01.926 18.7
2025-11-29-16:15:02.125 18.3
2025-11-29-16:15:02.309 18.4
2025-11-29-16:15:02.509 0.0
2025-11-29-16:15:02.715 18.4
2025-11-29-16:15:02.940 18.7
2025-11-29-16:15:03.121 0.0
2025-11-29-16:15:03.318 0.0
2025-11-29-16:15:03.534 18.3
2025-11-29-16:15:03.719 0.0
2025-11-29-16:15:03.952 0.0
2025-11-29-16:15:04.117 0.0
2025-11-29-16:15:04.375 18.4
2025-11-29-16:15:04.554 18.3
2025-11-29-16:15:04.740 18.3
2025-11-29-16:15:04.944 0.0
2025-11-29-16:15:05.148 0.0
2025-11-29-16:15:05.356 0.0
2025-11-29-16:15:05.543 0.0
2025-11-29-16:15:05.826 0.0
2025-11-29-16:15:05.955 0.0
2025-11-29-16:15:06.113 0.0
2025-11-29-16:15:06.316 0.0
2025-11-29-16:15:06.521 0.0
2025-11-29-16:15:06.726 0.0
2025-11-29-16:15:06.914 0.0
2025-11-29-16:15:07.123 18.3
2025-11-29-16:15:07.318 0.0
2025-11-29-16:15:07.520 0.0
2025-11-29-16:15:07.725 0.0
2025-11-29-16:15:07.984 0.0
2025-11-29-16:15:08.186 0.0
2025-11-29-16:15:08.389 18.3
2025-11-29-16:14:45.309 0.0
2025-11-29-16:14:45.309 0.0
2025-11-29-16:14:45.519 0.0
2025-11-29-16:14:45.715 0.0
2025-11-29-16:14:45.929 0.0
2025-11-29-16:14:46.148 0.0
2025-11-29-16:14:46.335 0.0
2025-11-29-16:14:46.525 0.0
2025-11-29-16:14:46.724 0.0
2025-11-29-16:14:46.925 0.0
2025-11-29-16:14:47.116 0.0
2025-11-29-16:14:47.330 0.0
2025-11-29-16:14:47.540 0.0
2025-11-29-16:14:47.724 0.0
2025-11-29-16:14:47.929 0.0
2025-11-29-16:14:48.143 0.0
2025-11-29-16:14:48.346 0.0
2025-11-29-16:14:48.533 0.0
2025-11-29-16:14:48.744 0.0
2025-11-29-16:14:48.950 0.0
2025-11-29-16:14:49.159 0.0
2025-11-29-16:14:49.352 0.0
2025-11-29-16:14:49.552 0.0
2025-11-29-16:00:04.407 15.4
2025-11-29-16:00:04.407 15.4
2025-11-29-16:00:04.445 16.0
2025-11-29-16:00:04.640 16.2
2025-11-29-16:00:04.837 16.0
2025-11-29-16:00:05.049 16.8
2025-11-29-16:00:05.251 16.5
2025-11-29-16:00:05.449 15.1
2025-11-29-16:00:05.644 15.1
2025-11-29-16:00:05.846 15.2
2025-11-29-16:00:06.054 15.4
2025-11-29-16:00:06.257 15.7
2025-11-29-16:00:06.449 15.9
2025-11-29-16:00:06.658 15.7
2025-11-29-16:00:06.860 16.0
2025-11-29-16:00:07.054 15.5
2025-11-29-16:00:07.266 17.0
2025-11-29-16:00:07.463 17.1
2025-11-29-16:00:07.660 16.8
2025-11-29-16:00:07.870 17.0
2025-11-29-16:00:08.073 16.5
2025-11-29-16:00:08.270 16.6
This source diff could not be displayed because it is too large. You can view the blob instead.
2025-11-29-15:48:08.468 0.0
2025-11-29-15:48:08.468 0.0
2025-11-29-15:48:08.885 17.1
2025-11-29-15:48:09.053 15.5
2025-11-29-15:48:09.321 15.4
2025-11-29-15:48:09.465 0.0
2025-11-29-15:48:09.653 0.0
2025-11-29-15:48:09.876 0.0
2025-11-29-15:48:10.058 0.0
2025-11-29-15:48:10.326 0.0
2025-11-29-15:48:10.472 0.0
2025-11-29-15:48:10.653 0.0
2025-11-29-15:48:10.905 0.0
2025-11-29-15:48:11.121 0.0
2025-11-29-15:48:11.351 0.0
2025-11-29-15:48:11.495 0.0
2025-11-29-15:48:11.671 0.0
2025-11-29-15:48:11.854 0.0
2025-11-29-15:48:12.104 0.0
2025-11-29-15:48:12.308 0.0
2025-11-29-15:48:12.474 0.0
2025-11-29-15:48:12.872 0.0
2025-11-29-15:48:13.043 0.0
2025-11-29-15:48:13.221 0.0
2025-11-29-15:48:13.428 0.0
2025-11-29-15:48:13.618 0.0
2025-11-29-15:48:13.867 0.0
2025-11-29-15:48:14.050 0.0
2025-11-29-15:48:14.279 0.0
2025-11-29-15:48:14.626 0.0
2025-11-29-15:48:14.787 0.0
2025-11-29-15:48:15.016 0.0
2025-11-29-15:48:15.210 0.0
2025-11-29-15:48:15.335 0.0
2025-11-29-15:48:15.539 0.0
2025-11-29-15:48:15.715 0.0
2025-11-29-15:48:15.982 0.0
2025-11-29-15:48:16.192 0.0
2025-11-29-15:48:16.368 0.0
2025-11-29-15:48:16.575 0.0
2025-11-29-15:48:16.766 0.0
2025-11-29-15:48:16.957 0.0
2025-11-29-15:48:17.176 0.0
2025-11-29-15:48:17.370 15.9
2025-11-29-15:48:17.585 0.0
2025-11-29-15:48:17.764 15.5
2025-11-29-15:48:17.976 15.4
2025-11-29-15:48:18.209 0.0
2025-11-29-15:48:18.383 0.0
2025-11-29-15:48:18.648 0.0
2025-11-29-15:48:18.782 0.0
2025-11-29-15:48:18.974 0.0
2025-11-29-15:48:19.224 17.7
2025-11-29-15:48:19.379 0.0
2025-11-29-15:48:19.610 0.0
2025-11-29-15:48:19.788 0.0
2025-11-29-15:48:20.004 0.0
2025-11-29-15:48:20.211 0.0
2025-11-29-15:48:20.404 0.0
2025-11-29-15:48:20.597 0.0
2025-11-29-15:48:20.877 0.0
2025-11-29-15:48:21.010 0.0
2025-11-29-15:48:21.222 0.0
2025-11-29-15:48:21.422 15.5
2025-11-29-15:48:21.622 0.0
2025-11-29-15:48:21.846 0.0
2025-11-29-15:48:22.040 0.0
2025-11-29-15:48:22.261 0.0
2025-11-29-15:48:22.439 0.0
2025-11-29-15:48:22.682 0.0
2025-11-29-15:48:22.986 0.0
2025-11-29-15:48:23.180 0.0
2025-11-29-15:48:23.330 0.0
2025-11-29-15:48:23.484 0.0
2025-11-29-15:48:23.687 0.0
2025-11-29-15:48:23.907 0.0
2025-11-29-15:48:24.080 0.0
2025-11-29-15:48:24.308 0.0
2025-11-29-15:48:24.511 0.0
2025-11-29-15:48:24.700 0.0
2025-11-29-15:48:24.959 0.0
2025-11-29-15:48:25.151 0.0
2025-11-29-15:48:25.317 0.0
2025-11-29-15:48:25.532 0.0
2025-11-29-15:48:25.710 0.0
2025-11-29-15:48:25.930 17.7
2025-11-29-15:48:26.139 18.0
2025-11-29-15:48:26.449 16.7
2025-11-29-15:48:26.639 0.0
2025-11-29-15:48:26.848 0.0
2025-11-29-15:48:27.027 0.0
2025-11-29-15:48:27.263 0.0
2025-11-29-15:48:27.450 0.0
2025-11-29-15:48:27.637 0.0
2025-11-29-15:48:27.834 0.0
2025-11-29-15:48:28.034 0.0
2025-11-29-15:48:28.248 0.0
2025-11-29-15:48:28.436 0.0
2025-11-29-15:48:28.660 0.0
2025-11-29-15:48:28.909 0.0
2025-11-29-15:48:29.223 0.0
2025-11-29-15:48:29.411 0.0
2025-11-29-15:48:29.610 0.0
2025-11-29-15:48:29.849 0.0
2025-11-29-15:48:30.017 0.0
2025-11-29-15:48:30.215 0.0
2025-11-29-15:48:30.447 0.0
2025-11-29-15:48:30.648 0.0
2025-11-29-15:48:30.865 0.0
2025-11-29-15:48:31.054 0.0
2025-11-29-15:48:31.386 0.0
2025-11-29-15:48:31.569 0.0
2025-11-29-15:48:31.788 0.0
2025-11-29-15:48:31.993 0.0
2025-11-29-15:48:32.170 0.0
2025-11-29-15:48:32.395 0.0
2025-11-29-15:48:32.594 0.0
2025-11-29-15:48:32.775 0.0
2025-11-29-15:48:32.992 0.0
2025-11-29-15:48:33.233 0.0
2025-11-29-15:48:33.409 0.0
2025-11-29-15:48:33.621 0.0
2025-11-29-15:48:33.842 0.0
2025-11-29-15:48:34.046 0.0
2025-11-29-15:48:34.215 0.0
2025-11-29-15:48:34.423 0.0
2025-11-29-15:48:34.671 0.0
2025-11-29-15:48:34.857 0.0
2025-11-29-15:48:35.066 0.0
2025-11-29-15:48:35.325 0.0
2025-11-29-15:48:35.526 0.0
2025-11-29-15:48:35.724 0.0
2025-11-29-15:48:35.925 0.0
2025-11-29-15:48:36.124 0.0
2025-11-29-15:48:36.345 0.0
2025-11-29-15:48:36.538 15.7
2025-11-29-15:48:36.755 0.0
2025-11-29-15:48:36.959 0.0
2025-11-29-15:48:37.190 0.0
2025-11-29-15:48:37.408 0.0
2025-11-29-15:48:37.631 0.0
2025-11-29-15:48:37.855 0.0
2025-11-29-15:48:38.048 0.0
2025-11-29-15:48:38.261 0.0
2025-11-29-15:48:38.451 0.0
2025-11-29-15:48:38.886 0.0
2025-11-29-15:48:39.098 0.0
2025-11-29-15:48:39.332 0.0
2025-11-29-15:48:39.543 0.0
2025-11-29-15:48:39.741 0.0
2025-11-29-15:48:39.963 0.0
2025-11-29-15:48:40.146 0.0
2025-11-29-15:48:40.379 0.0
2025-11-29-15:48:40.600 0.0
2025-11-29-15:48:40.845 0.0
2025-11-29-15:48:41.032 0.0
2025-11-29-15:48:41.264 0.0
2025-11-29-15:48:41.639 0.0
2025-11-29-15:48:41.855 0.0
2025-11-29-15:48:42.048 0.0
2025-11-29-15:48:42.253 0.0
2025-11-29-15:48:42.448 0.0
2025-11-29-15:48:42.644 0.0
2025-11-29-15:48:42.849 0.0
2025-11-29-15:48:43.044 0.0
2025-11-29-15:48:43.286 17.6
2025-11-29-15:48:43.491 0.0
2025-11-29-15:48:43.688 0.0
2025-11-29-15:48:43.894 0.0
2025-11-29-15:48:44.110 0.0
2025-11-29-15:48:44.338 15.9
2025-11-29-15:48:44.549 17.6
2025-11-29-15:48:44.754 15.1
2025-11-29-15:48:44.986 0.0
2025-11-29-15:48:45.190 0.0
2025-11-29-15:48:45.385 0.0
2025-11-29-15:48:45.623 0.0
2025-11-29-15:48:45.861 0.0
2025-11-29-15:48:46.063 0.0
2025-11-29-15:48:46.279 0.0
2025-11-29-15:48:46.506 0.0
2025-11-29-15:48:46.894 0.0
2025-11-29-15:48:47.125 0.0
2025-11-29-15:48:47.323 0.0
2025-11-29-15:48:47.519 0.0
2025-11-29-15:48:47.720 0.0
2025-11-29-15:48:47.919 0.0
2025-11-29-15:48:48.121 0.0
2025-11-29-15:48:48.333 0.0
2025-11-29-15:48:48.534 0.0
2025-11-29-15:48:48.823 0.0
2025-11-29-15:48:49.073 0.0
2025-11-29-15:48:49.313 0.0
2025-11-29-15:48:49.482 0.0
2025-11-29-15:48:49.769 0.0
2025-11-29-15:48:49.971 0.0
2025-11-29-15:48:50.189 0.0
2025-11-29-15:48:50.432 0.0
2025-11-29-15:48:50.658 0.0
2025-11-29-15:48:50.896 0.0
2025-11-29-15:48:51.198 0.0
2025-11-29-15:48:51.550 0.0
2025-11-29-15:48:51.779 0.0
2025-11-29-15:48:52.003 0.0
2025-11-29-15:48:52.186 0.0
2025-11-29-15:48:52.414 0.0
2025-11-29-15:48:52.701 0.0
2025-11-29-15:48:52.918 0.0
2025-11-29-15:48:53.125 0.0
2025-11-29-15:48:53.335 0.0
2025-11-29-15:48:53.543 0.0
2025-11-29-15:48:53.770 0.0
2025-11-29-15:48:53.981 0.0
2025-11-29-15:48:54.182 0.0
2025-11-29-15:48:54.395 0.0
2025-11-29-15:48:54.620 0.0
2025-11-29-15:48:54.816 0.0
2025-11-29-15:48:55.079 0.0
2025-11-29-15:48:55.301 0.0
2025-11-29-15:48:55.635 0.0
2025-11-29-15:48:56.009 0.0
2025-11-29-15:48:56.241 0.0
2025-11-29-15:48:56.448 0.0
2025-11-29-15:48:56.663 0.0
2025-11-29-15:48:56.904 0.0
2025-11-29-15:48:57.136 0.0
2025-11-29-15:48:57.357 0.0
2025-11-29-15:48:57.611 0.0
2025-11-29-15:48:57.879 0.0
2025-11-29-15:48:58.124 15.4
2025-11-29-15:48:58.365 0.0
2025-11-29-15:48:58.643 0.0
2025-11-29-15:48:59.010 0.0
2025-11-29-15:48:59.397 0.0
2025-11-29-15:48:59.915 0.0
2025-11-29-15:49:00.425 0.0
2025-11-29-15:49:00.891 0.0
2025-11-29-15:49:01.444 0.0
2025-11-29-15:49:02.002 0.0
2025-11-29-15:49:02.568 0.0
2025-11-29-15:49:03.130 0.0
2025-11-29-15:49:03.615 0.0
2025-11-29-15:49:04.168 15.4
2025-11-29-15:49:04.880 0.0
2025-11-29-15:49:05.693 0.0
2025-11-29-15:49:06.530 0.0
2025-11-29-15:49:07.323 0.0
2025-11-29-15:49:07.844 0.0
2025-11-29-15:49:08.416 0.0
2025-11-29-12:49:02.930 0.0
2025-11-29-12:49:02.930 0.0
2025-11-29-12:49:02.962 0.0
2025-11-29-12:49:03.158 0.0
2025-11-29-12:49:03.368 0.0
2025-11-29-12:49:03.565 0.0
2025-11-29-12:49:03.775 0.0
2025-11-29-12:49:03.974 0.0
2025-11-29-12:49:04.182 0.0
2025-11-29-12:49:04.381 0.0
2025-11-29-12:49:04.575 0.0
2025-11-29-12:49:04.783 0.0
2025-11-29-12:49:04.977 0.0
2025-11-29-12:49:05.188 0.0
2025-11-29-12:49:05.386 0.0
2025-11-29-12:49:05.596 0.0
2025-11-29-12:49:05.791 0.0
2025-11-29-12:49:06.001 0.0
2025-11-29-12:49:06.202 0.0
2025-11-29-12:49:06.402 0.0
2025-11-29-12:49:06.610 0.0
2025-11-29-12:49:06.810 0.0
2025-11-29-12:49:07.005 0.0
2025-11-29-12:49:07.223 0.0
2025-11-29-12:49:07.431 0.0
2025-11-29-12:49:07.611 0.0
2025-11-29-12:49:07.819 0.0
2025-11-29-12:49:08.021 0.0
2025-11-29-12:49:08.230 0.0
2025-11-29-12:49:08.434 0.0
2025-11-29-12:49:08.638 0.0
2025-11-29-12:49:08.835 0.0
2025-11-29-12:49:09.044 0.0
2025-11-29-12:49:09.246 0.0
2025-11-29-12:49:09.446 0.0
2025-11-29-12:49:09.658 0.0
2025-11-29-12:49:09.854 0.0
2025-11-29-12:49:10.064 0.0
2025-11-29-12:49:10.285 0.0
2025-11-29-12:49:10.488 0.0
2025-11-29-12:49:10.691 0.0
2025-11-29-12:49:10.903 0.0
2025-11-29-12:49:11.106 0.0
2025-11-29-12:49:11.301 0.0
2025-11-29-12:49:11.519 0.0
2025-11-29-12:49:11.710 0.0
2025-11-29-12:49:11.924 0.0
2025-11-29-12:49:12.132 0.0
2025-11-29-12:49:12.330 0.0
2025-11-29-12:49:12.533 0.0
2025-11-29-12:49:12.755 0.0
2025-11-29-12:49:12.929 0.0
2025-11-29-12:49:13.144 0.0
2025-11-29-12:49:13.369 0.0
2025-11-29-12:49:13.597 0.0
2025-11-29-12:49:13.746 0.0
2025-11-29-12:49:13.923 0.0
2025-11-29-12:49:14.129 0.0
2025-11-29-12:49:14.333 0.0
2025-11-29-12:49:14.527 0.0
2025-11-29-12:49:14.734 0.0
2025-11-29-12:49:14.942 0.0
2025-11-29-12:49:15.130 0.0
2025-11-29-12:49:15.341 0.0
2025-11-29-12:49:15.549 0.0
2025-11-29-12:49:15.762 0.0
2025-11-29-12:49:15.955 0.0
2025-11-29-12:49:16.170 0.0
2025-11-29-12:49:16.374 0.0
2025-11-29-12:49:16.561 0.0
2025-11-29-12:49:16.789 0.0
2025-11-29-12:49:16.971 0.0
2025-11-29-12:49:17.166 0.0
2025-11-29-12:49:17.374 0.0
2025-11-29-12:49:17.587 0.0
2025-11-29-12:49:17.786 0.0
2025-11-29-12:49:17.999 0.0
2025-11-29-12:49:18.202 0.0
2025-11-29-12:49:18.397 0.0
2025-11-29-12:49:18.592 0.0
2025-11-29-12:49:18.793 0.0
2025-11-29-12:49:19.023 0.0
2025-11-29-12:49:19.154 0.0
2025-11-29-12:49:19.356 0.0
2025-11-29-12:49:19.552 0.0
2025-11-29-12:49:19.753 0.0
2025-11-29-12:49:19.943 0.0
2025-11-29-12:49:20.157 0.0
2025-11-29-12:49:20.360 0.0
2025-11-29-12:49:20.566 0.0
2025-11-29-12:49:20.753 0.0
2025-11-29-12:49:20.945 0.0
2025-11-29-12:49:21.155 0.0
2025-11-29-12:49:21.363 0.0
2025-11-29-12:49:21.569 0.0
2025-11-29-12:49:21.778 0.0
2025-11-29-12:49:21.970 0.0
2025-11-29-12:49:22.172 0.0
2025-11-29-12:49:22.365 0.0
2025-11-29-12:49:22.578 0.0
2025-11-29-12:49:22.801 0.0
2025-11-29-12:49:22.988 0.0
2025-11-29-12:49:23.193 0.0
2025-11-29-12:49:23.388 0.0
2025-11-29-12:49:23.594 0.0
2025-11-29-12:49:23.801 0.0
2025-11-29-12:49:24.015 0.0
2025-11-29-12:49:24.213 0.0
2025-11-29-12:49:24.435 0.0
2025-11-29-12:49:24.644 0.0
2025-11-29-12:49:24.834 0.0
2025-11-29-12:49:25.031 0.0
2025-11-29-12:49:25.255 0.0
2025-11-29-12:49:25.439 0.0
2025-11-29-12:49:25.638 0.0
2025-11-29-12:49:25.846 0.0
2025-11-29-12:49:26.125 17.7
2025-11-29-12:49:26.245 0.0
2025-11-29-12:49:26.455 0.0
2025-11-29-12:49:26.655 0.0
2025-11-29-12:49:26.882 0.0
2025-11-29-12:49:27.075 0.0
2025-11-29-12:49:27.275 0.0
2025-11-29-12:49:27.480 0.0
2025-11-29-12:49:27.679 0.0
2025-11-29-12:49:27.876 0.0
2025-11-29-12:49:28.085 0.0
2025-11-29-12:49:28.295 18.1
2025-11-29-12:49:28.490 0.0
2025-11-29-12:49:28.681 0.0
2025-11-29-12:49:28.879 0.0
2025-11-29-12:49:29.091 0.0
2025-11-29-12:49:29.293 0.0
2025-11-29-12:49:29.498 0.0
2025-11-29-12:49:29.705 0.0
2025-11-29-12:49:29.916 0.0
2025-11-29-12:49:30.102 0.0
2025-11-29-12:49:30.282 0.0
2025-11-29-12:49:30.512 0.0
2025-11-29-12:49:30.704 0.0
2025-11-29-12:49:30.910 0.0
2025-11-29-12:49:31.107 0.0
2025-11-29-12:49:31.297 0.0
2025-11-29-12:49:31.519 0.0
2025-11-29-12:49:31.703 0.0
2025-11-29-12:49:31.918 0.0
2025-11-29-12:49:32.114 0.0
2025-11-29-12:49:32.315 0.0
2025-11-29-12:49:32.522 0.0
2025-11-29-12:49:32.771 0.0
2025-11-29-12:49:32.913 0.0
2025-11-29-12:49:33.128 0.0
2025-11-29-12:49:33.344 0.0
2025-11-29-12:49:33.521 0.0
2025-11-29-12:49:33.729 0.0
2025-11-29-12:49:33.924 0.0
2025-11-29-12:49:34.150 0.0
2025-11-29-12:49:34.364 0.0
2025-11-29-12:49:34.550 0.0
2025-11-29-15:48:26.639 0.0
2025-11-29-15:48:26.848 0.0
2025-11-29-15:48:27.027 0.0
2025-11-29-15:48:27.263 0.0
2025-11-29-15:48:27.450 0.0
2025-11-29-15:48:27.637 0.0
2025-11-29-15:48:27.832 0.0
2025-11-29-15:48:28.036 0.0
2025-11-29-15:48:28.248 0.0
2025-11-29-15:48:28.436 0.0
2025-11-29-15:48:28.660 0.0
2025-11-29-15:48:28.909 0.0
2025-11-29-15:48:29.223 0.0
2025-11-29-15:48:29.411 0.0
2025-11-29-15:48:29.610 0.0
2025-11-29-15:48:29.860 0.0
2025-11-29-15:48:30.017 0.0
2025-11-29-15:48:30.215 0.0
2025-11-29-15:48:30.447 0.0
2025-11-29-15:48:30.648 0.0
2025-11-29-15:48:30.865 0.0
2025-11-29-15:48:31.055 0.0
2025-11-29-15:48:31.386 0.0
2025-11-29-15:48:31.569 0.0
2025-11-29-15:48:31.788 0.0
2025-11-29-15:48:31.994 0.0
2025-11-29-15:48:32.170 0.0
2025-11-29-15:48:32.395 0.0
2025-11-29-15:48:32.594 0.0
2025-11-29-15:48:32.775 0.0
2025-11-29-15:48:32.992 0.0
2025-11-29-15:48:33.233 0.0
2025-11-29-15:48:33.409 0.0
2025-11-29-15:48:33.621 0.0
2025-11-29-15:48:33.842 0.0
2025-11-29-15:48:34.046 0.0
2025-11-29-15:48:34.215 0.0
2025-11-29-15:48:34.423 0.0
2025-11-29-15:48:34.671 0.0
2025-11-29-15:48:34.857 0.0
2025-11-29-15:48:35.066 0.0
2025-11-29-15:48:35.325 0.0
2025-11-29-15:48:35.526 0.0
2025-11-29-15:48:35.724 0.0
2025-11-29-15:48:35.925 0.0
2025-11-29-15:48:36.124 0.0
2025-11-29-15:48:36.345 0.0
2025-11-29-15:48:36.538 0.0
2025-11-29-15:48:36.755 0.0
2025-11-29-15:48:36.959 0.0
2025-11-29-15:48:37.190 0.0
2025-11-29-15:48:37.407 0.0
2025-11-29-15:48:37.632 0.0
2025-11-29-15:48:37.855 0.0
2025-11-29-15:48:38.050 0.0
2025-11-29-15:48:38.261 0.0
2025-11-29-15:48:38.451 0.0
2025-11-29-15:48:38.886 0.0
2025-11-29-15:48:39.098 0.0
2025-11-29-15:48:39.333 0.0
2025-11-29-15:48:39.543 0.0
2025-11-29-15:48:39.741 0.0
2025-11-29-15:48:39.963 0.0
2025-11-29-15:48:40.146 0.0
2025-11-29-15:48:40.379 0.0
2025-11-29-15:48:40.601 0.0
2025-11-29-15:48:40.845 0.0
2025-11-29-15:48:41.032 0.0
2025-11-29-15:48:41.264 0.0
2025-11-29-15:48:41.641 0.0
2025-11-29-15:48:41.855 0.0
2025-11-29-15:48:42.048 0.0
2025-11-29-15:48:42.253 0.0
2025-11-29-15:48:42.448 0.0
2025-11-29-15:48:42.643 0.0
2025-11-29-15:48:42.849 0.0
2025-11-29-15:48:43.044 0.0
2025-11-29-15:48:43.286 0.0
2025-11-29-15:48:43.491 0.0
2025-11-29-15:48:43.688 0.0
2025-11-29-15:48:43.894 0.0
2025-11-29-15:48:44.110 0.0
2025-11-29-15:48:44.338 0.0
2025-11-29-15:48:44.549 0.0
2025-11-29-15:48:44.754 0.0
2025-11-29-15:48:44.986 0.0
2025-11-29-15:48:45.191 0.0
2025-11-29-15:48:45.385 0.0
2025-11-29-15:48:45.624 0.0
2025-11-29-15:48:45.861 0.0
2025-11-29-15:48:46.063 0.0
2025-11-29-15:48:46.279 0.0
2025-11-29-15:48:46.506 0.0
2025-11-29-15:48:46.895 0.0
2025-11-29-15:48:47.125 0.0
2025-11-29-15:48:47.323 0.0
2025-11-29-15:48:47.519 0.0
2025-11-29-15:48:47.720 0.0
2025-11-29-15:48:47.919 0.0
2025-11-29-15:48:48.121 0.0
2025-11-29-15:48:48.333 0.0
2025-11-29-15:48:48.535 0.0
2025-11-29-15:48:48.823 0.0
2025-11-29-15:48:49.073 0.0
2025-11-29-15:48:49.313 0.0
2025-11-29-15:48:49.482 0.0
2025-11-29-15:48:49.769 0.0
2025-11-29-15:48:49.971 0.0
2025-11-29-15:48:50.189 0.0
2025-11-29-15:48:50.432 0.0
2025-11-29-15:48:50.658 0.0
2025-11-29-15:48:50.896 0.0
2025-11-29-15:48:51.201 0.0
2025-11-29-15:48:51.550 0.0
2025-11-29-15:48:51.780 0.0
2025-11-29-15:48:52.003 0.0
2025-11-29-15:48:52.186 0.0
2025-11-29-15:48:52.414 0.0
2025-11-29-15:48:52.703 0.0
2025-11-29-15:48:52.918 0.0
2025-11-29-15:48:53.125 0.0
2025-11-29-15:48:53.335 0.0
2025-11-29-15:48:53.543 0.0
2025-11-29-15:48:53.770 0.0
2025-11-29-15:48:53.981 0.0
2025-11-29-15:48:54.182 0.0
2025-11-29-15:48:54.392 0.0
2025-11-29-15:48:54.621 0.0
2025-11-29-15:48:54.816 0.0
2025-11-29-15:48:55.081 0.0
2025-11-29-15:48:55.301 0.0
2025-11-29-15:48:55.635 0.0
2025-11-29-15:48:56.009 0.0
2025-11-29-15:48:56.241 0.0
2025-11-29-15:48:56.448 0.0
2025-11-29-15:48:56.665 0.0
2025-11-29-15:48:56.904 0.0
2025-11-29-15:48:57.136 0.0
2025-11-29-15:48:57.357 0.0
2025-11-29-15:48:57.612 0.0
2025-11-29-15:48:57.879 0.0
2025-11-29-15:48:58.124 0.0
2025-11-29-15:48:58.365 0.0
2025-11-29-15:48:58.643 0.0
2025-11-29-15:48:59.010 0.0
2025-11-29-15:48:59.397 0.0
2025-11-29-15:48:59.915 0.0
2025-11-29-15:49:00.425 0.0
2025-11-29-15:49:00.891 0.0
2025-11-29-15:49:01.444 0.0
2025-11-29-15:49:02.002 0.0
2025-11-29-15:49:02.571 0.0
2025-11-29-15:49:03.131 0.0
2025-11-29-15:49:03.615 0.0
2025-11-29-15:49:04.169 0.0
2025-11-29-15:49:04.880 0.0
2025-11-29-15:49:05.693 0.0
2025-11-29-15:49:06.530 0.0
2025-11-29-15:49:07.323 0.0
2025-11-29-15:49:07.844 0.0
2025-11-29-15:49:08.416 0.0
2025-11-29-16:16:55.866 0.0
2025-11-29-16:16:56.435 0.0
2025-11-29-16:16:57.084 0.0
2025-11-29-16:16:57.981 0.0
2025-11-29-16:16:58.641 0.0
2025-11-29-16:16:59.614 0.0
2025-11-29-16:17:00.740 0.0
2025-11-29-16:17:01.434 0.0
2025-11-29-16:17:01.711 0.0
2025-11-29-16:17:01.974 0.0
2025-11-29-16:17:02.249 0.0
2025-11-29-16:17:02.487 0.0
2025-11-29-16:17:02.683 0.0
2025-11-29-16:17:33.877 0.0
2025-11-29-16:17:34.285 0.0
2025-11-29-16:17:34.707 0.0
2025-11-29-16:17:34.989 0.0
2025-11-29-16:17:35.474 0.0
2025-11-29-16:17:35.846 0.0
2025-11-29-16:17:36.138 0.0
2025-11-29-16:17:36.596 0.0
2025-11-29-16:17:37.087 0.0
2025-11-29-16:17:37.601 0.0
2025-11-29-16:17:38.152 0.0
2025-11-29-16:17:38.675 0.0
2025-11-29-16:17:39.166 0.0
2025-11-29-16:17:39.598 0.0
2025-11-29-16:17:39.935 0.0
2025-11-29-16:17:40.111 0.0
2025-11-29-16:17:40.326 0.0
2025-11-29-16:17:40.516 0.0
2025-11-29-16:17:40.732 0.0
2025-11-29-16:17:40.815 0.0
2025-11-29-16:17:41.026 0.0
2025-11-29-16:17:41.215 0.0
2025-11-29-16:17:41.428 0.0
2025-11-29-16:17:41.634 0.0
2025-11-29-15:48:49.971 0.0
2025-11-29-15:48:49.971 0.0
2025-11-29-15:48:50.189 0.0
2025-11-29-15:48:50.432 0.0
2025-11-29-15:48:50.658 0.0
2025-11-29-15:48:50.896 0.0
2025-11-29-15:48:51.198 0.0
2025-11-29-15:48:51.550 0.0
2025-11-29-15:48:51.779 0.0
2025-11-29-15:48:52.003 0.0
2025-11-29-15:48:52.186 0.0
2025-11-29-15:48:52.415 0.0
2025-11-29-15:48:52.701 0.0
2025-11-29-15:48:52.918 0.0
2025-11-29-15:48:53.125 0.0
2025-11-29-15:48:53.335 0.0
2025-11-29-15:48:53.543 0.0
2025-11-29-15:48:53.770 0.0
2025-11-29-15:48:53.981 0.0
2025-11-29-15:48:54.182 0.0
2025-11-29-15:48:54.394 0.0
2025-11-29-15:48:54.621 0.0
2025-11-29-15:48:54.816 0.0
2025-11-29-15:48:55.079 0.0
2025-11-29-15:48:55.301 0.0
2025-11-29-15:48:55.635 0.0
2025-11-29-15:48:56.009 0.0
2025-11-29-15:48:56.241 0.0
2025-11-29-15:48:56.448 0.0
2025-11-29-15:48:56.665 0.0
2025-11-29-15:48:56.904 0.0
2025-11-29-15:48:57.136 0.0
2025-11-29-15:48:57.358 0.0
2025-11-29-15:48:57.611 0.0
2025-11-29-15:48:57.879 0.0
2025-11-29-15:48:58.124 0.0
2025-11-29-15:48:58.366 0.0
2025-11-29-15:48:58.643 0.0
2025-11-29-15:48:59.010 0.0
2025-11-29-15:48:59.397 0.0
2025-11-29-15:48:59.915 0.0
2025-11-29-15:49:00.425 0.0
2025-11-29-15:49:00.891 0.0
2025-11-29-15:49:01.444 0.0
2025-11-29-15:49:02.002 0.0
2025-11-29-15:49:02.571 0.0
2025-11-29-15:49:03.131 0.0
2025-11-29-15:49:03.615 0.0
2025-11-29-15:49:04.168 0.0
2025-11-29-15:49:04.882 0.0
2025-11-29-15:49:05.693 0.0
2025-11-29-15:49:06.530 0.0
2025-11-29-15:49:07.323 0.0
2025-11-29-15:49:07.850 0.0
2025-11-29-15:49:08.419 0.0
2025-11-29-16:16:55.866 0.0
2025-11-29-16:16:56.435 0.0
2025-11-29-16:16:57.090 0.0
2025-11-29-16:16:57.981 0.0
2025-11-29-16:16:58.648 0.0
2025-11-29-16:16:59.614 0.0
2025-11-29-16:17:00.741 0.0
2025-11-29-16:17:01.434 0.0
2025-11-29-16:17:01.711 0.0
2025-11-29-16:17:01.974 0.0
2025-11-29-16:17:02.249 0.0
2025-11-29-16:17:02.487 0.0
2025-11-29-16:17:02.683 0.0
2025-11-29-16:17:02.871 0.0
2025-11-29-16:17:03.038 0.0
2025-11-29-16:17:03.197 0.0
2025-11-29-16:17:03.453 0.0
2025-11-29-16:17:03.642 0.0
2025-11-29-16:17:03.803 0.0
2025-11-29-16:17:04.008 0.0
2025-11-29-16:17:04.216 0.0
2025-11-29-16:17:30.725 0.0
2025-11-29-16:17:30.929 0.0
2025-11-29-16:17:31.273 0.0
2025-11-29-16:17:31.550 0.0
2025-11-29-16:17:31.856 0.0
2025-11-29-16:17:32.096 0.0
2025-11-29-16:17:32.411 0.0
2025-11-29-16:17:32.695 0.0
2025-11-29-16:17:32.941 0.0
2025-11-29-16:17:33.181 0.0
2025-11-29-16:17:33.477 0.0
2025-11-29-16:17:33.878 0.0
2025-11-29-16:17:34.285 0.0
2025-11-29-16:17:34.708 0.0
2025-11-29-16:17:34.989 0.0
2025-11-29-16:17:35.474 0.0
2025-11-29-16:17:35.838 20.0
2025-11-29-16:17:36.138 0.0
2025-11-29-16:17:36.596 0.0
2025-11-29-16:17:37.087 0.0
2025-11-29-16:17:37.601 0.0
2025-11-29-16:17:38.152 0.0
2025-11-29-16:17:38.675 0.0
2025-11-29-16:17:39.166 0.0
2025-11-29-16:17:39.599 0.0
2025-11-29-16:17:39.935 0.0
2025-11-29-16:17:40.111 0.0
2025-11-29-16:17:40.326 0.0
2025-11-29-16:17:40.516 0.0
2025-11-29-15:49:00.891 0.0
2025-11-29-15:49:00.891 0.0
2025-11-29-15:49:01.444 0.0
2025-11-29-15:49:02.002 0.0
2025-11-29-15:49:02.563 0.0
2025-11-29-15:49:03.131 0.0
2025-11-29-15:49:03.615 0.0
2025-11-29-15:49:04.168 0.0
2025-11-29-15:49:04.882 0.0
2025-11-29-15:49:05.693 0.0
2025-11-29-15:49:06.530 0.0
2025-11-29-15:49:07.323 0.0
2025-11-29-15:49:07.844 0.0
2025-11-29-15:49:08.416 0.0
2025-11-29-16:16:59.614 0.0
2025-11-29-16:17:00.741 20.0
2025-11-29-16:17:01.434 0.0
2025-11-29-16:17:01.711 0.0
2025-11-29-16:17:01.974 0.0
2025-11-29-16:17:02.249 0.0
2025-11-29-16:17:02.487 0.0
2025-11-29-16:17:02.683 0.0
2025-11-29-16:17:02.871 0.0
2025-11-29-16:17:03.038 20.0
2025-11-29-16:17:03.197 0.0
2025-11-29-16:17:03.453 0.0
2025-11-29-16:17:03.642 0.0
2025-11-29-16:17:03.803 0.0
2025-11-29-16:17:04.008 0.0
2025-11-29-16:17:04.216 0.0
2025-11-29-16:17:04.361 0.0
2025-11-29-16:17:04.556 0.0
2025-11-29-16:17:04.756 0.0
2025-11-29-16:17:04.963 0.0
2025-11-29-16:17:05.169 0.0
2025-11-29-16:17:05.368 0.0
2025-11-29-16:17:26.350 0.0
2025-11-29-16:17:26.478 0.0
2025-11-29-16:17:26.664 0.0
2025-11-29-16:17:26.852 0.0
2025-11-29-16:17:27.169 0.0
2025-11-29-16:17:27.260 0.0
2025-11-29-16:17:27.453 0.0
2025-11-29-16:17:27.729 0.0
2025-11-29-16:17:27.921 20.0
2025-11-29-16:17:28.099 0.0
2025-11-29-16:17:28.323 0.0
2025-11-29-16:17:28.482 0.0
2025-11-29-16:17:28.678 0.0
2025-11-29-16:17:28.888 0.0
2025-11-29-16:17:29.116 0.0
2025-11-29-16:17:29.340 0.0
2025-11-29-16:17:29.494 0.0
2025-11-29-16:17:29.831 0.0
2025-11-29-16:17:29.935 0.0
2025-11-29-16:17:30.166 0.0
2025-11-29-16:17:30.408 0.0
2025-11-29-16:17:30.725 0.0
2025-11-29-16:17:30.929 0.0
2025-11-29-16:17:31.273 0.0
2025-11-29-16:17:31.549 0.0
2025-11-29-16:17:31.855 0.0
2025-11-29-16:17:32.096 0.0
2025-11-29-16:17:32.411 0.0
2025-11-29-16:17:32.691 0.0
2025-11-29-16:17:32.941 0.0
2025-11-29-16:17:33.180 0.0
2025-11-29-16:17:33.477 0.0
2025-11-29-16:17:33.877 0.0
2025-11-29-16:17:34.285 0.0
2025-11-29-16:17:34.707 0.0
2025-11-29-16:17:34.989 0.0
2025-11-29-16:17:35.474 20.0
2025-11-29-16:17:35.846 0.0
2025-11-29-16:17:36.138 0.0
2025-11-29-16:17:36.596 0.0
2025-11-29-16:17:37.087 0.0
2025-11-29-16:17:37.601 0.0
2025-11-29-16:17:38.152 0.0
2025-11-29-16:17:38.675 0.0
2025-11-29-16:17:39.166 0.0
2025-11-29-16:17:39.598 0.0
2025-11-29-13:18:28.484 0.0
2025-11-29-13:18:28.484 0.0
2025-11-29-13:18:28.510 0.0
2025-11-29-13:18:28.718 0.0
2025-11-29-13:18:28.921 0.0
2025-11-29-13:18:29.118 0.0
2025-11-29-13:18:29.327 0.0
2025-11-29-13:18:29.527 0.0
2025-11-29-13:18:29.729 0.0
2025-11-29-13:18:29.928 0.0
2025-11-29-13:18:30.131 0.0
2025-11-29-13:18:30.339 0.0
2025-11-29-13:18:30.539 0.0
2025-11-29-13:18:30.743 0.0
2025-11-29-13:18:30.940 0.0
2025-11-29-13:18:31.147 0.0
2025-11-29-13:18:31.347 0.0
2025-11-29-13:18:31.547 0.0
2025-11-29-13:18:31.756 0.0
2025-11-29-13:18:31.958 0.0
2025-11-29-13:18:32.154 0.0
2025-11-29-13:18:32.372 0.0
2025-11-29-13:18:32.585 0.0
2025-11-29-13:18:32.765 0.0
2025-11-29-13:18:32.962 0.0
2025-11-29-13:18:33.157 0.0
2025-11-29-13:18:33.369 0.0
2025-11-29-13:18:33.563 0.0
2025-11-29-13:18:33.774 0.0
2025-11-29-13:18:33.971 0.0
2025-11-29-13:18:34.189 0.0
2025-11-29-13:18:34.380 0.0
2025-11-29-13:18:34.578 0.0
2025-11-29-13:18:34.786 0.0
2025-11-29-13:18:34.979 0.0
2025-11-29-13:18:35.186 0.0
2025-11-29-13:18:35.400 0.0
2025-11-29-13:18:35.584 0.0
2025-11-29-13:18:35.853 16.0
2025-11-29-13:18:35.994 0.0
2025-11-29-13:18:36.205 16.0
2025-11-29-13:18:36.420 15.9
2025-11-29-13:18:36.613 0.0
2025-11-29-13:18:36.821 0.0
2025-11-29-13:18:37.019 0.0
2025-11-29-13:18:37.222 0.0
2025-11-29-13:18:37.431 0.0
2025-11-29-13:18:37.631 0.0
2025-11-29-13:18:37.832 0.0
2025-11-29-13:18:38.054 0.0
2025-11-29-13:18:38.255 0.0
2025-11-29-13:18:38.495 0.0
2025-11-29-13:18:38.692 0.0
2025-11-29-13:18:38.884 16.0
2025-11-29-13:18:39.098 0.0
2025-11-29-13:18:39.332 0.0
2025-11-29-13:18:39.523 0.0
2025-11-29-13:18:39.723 0.0
2025-11-29-13:18:39.921 0.0
2025-11-29-13:18:40.129 0.0
2025-11-29-13:18:40.337 0.0
2025-11-29-13:18:40.543 0.0
2025-11-29-13:18:40.736 0.0
2025-11-29-13:18:40.959 15.9
2025-11-29-13:18:41.151 15.7
2025-11-29-13:18:41.365 15.9
2025-11-29-13:18:41.555 0.0
2025-11-29-13:18:41.759 0.0
2025-11-29-13:18:41.972 0.0
2025-11-29-13:18:42.196 0.0
2025-11-29-13:18:42.395 0.0
2025-11-29-13:18:42.595 15.5
2025-11-29-13:18:42.809 0.0
2025-11-29-13:18:43.005 0.0
2025-11-29-13:18:43.220 0.0
2025-11-29-13:18:43.410 0.0
2025-11-29-13:18:43.642 0.0
2025-11-29-13:18:43.833 0.0
2025-11-29-13:18:44.040 0.0
2025-11-29-13:18:44.251 0.0
2025-11-29-13:18:44.463 0.0
2025-11-29-13:18:44.671 0.0
2025-11-29-13:18:44.876 0.0
2025-11-29-13:18:45.082 0.0
2025-11-29-13:18:45.318 16.2
2025-11-29-13:18:45.504 0.0
2025-11-29-13:18:45.712 0.0
2025-11-29-13:18:45.898 0.0
2025-11-29-13:18:46.138 0.0
2025-11-29-13:18:46.381 0.0
2025-11-29-13:18:46.509 0.0
2025-11-29-13:18:46.732 0.0
2025-11-29-13:18:46.923 0.0
2025-11-29-13:18:47.146 0.0
2025-11-29-13:18:47.293 0.0
2025-11-29-13:18:47.479 0.0
2025-11-29-13:18:47.688 0.0
2025-11-29-13:18:47.888 0.0
2025-11-29-13:18:48.097 0.0
2025-11-29-13:18:48.297 0.0
2025-11-29-13:18:48.505 0.0
2025-11-29-13:18:48.722 0.0
2025-11-29-13:18:48.929 16.8
2025-11-29-13:18:49.134 17.2
2025-11-29-13:18:49.341 0.0
2025-11-29-13:18:49.568 17.7
2025-11-29-13:18:49.776 17.4
2025-11-29-13:18:49.972 0.0
2025-11-29-13:18:50.179 0.0
2025-11-29-13:18:50.389 18.0
2025-11-29-13:18:50.581 0.0
2025-11-29-13:18:50.794 15.9
2025-11-29-13:18:51.003 15.9
2025-11-29-13:18:51.215 15.7
2025-11-29-13:18:51.415 15.9
2025-11-29-13:18:51.627 15.7
2025-11-29-13:18:51.819 0.0
2025-11-29-13:18:52.030 16.4
2025-11-29-13:18:52.244 0.0
2025-11-29-13:18:52.447 0.0
2025-11-29-13:18:52.671 16.2
2025-11-29-13:18:52.872 0.0
2025-11-29-13:18:53.099 0.0
2025-11-29-13:18:53.302 0.0
2025-11-29-13:18:53.510 16.4
2025-11-29-13:18:53.730 0.0
2025-11-29-13:18:53.904 0.0
2025-11-29-13:18:54.137 0.0
2025-11-29-13:18:54.355 0.0
2025-11-29-13:18:54.528 0.0
2025-11-29-13:18:54.738 0.0
2025-11-29-13:18:54.946 15.7
2025-11-29-13:18:55.142 16.0
2025-11-29-13:18:55.355 0.0
2025-11-29-13:18:55.556 15.9
2025-11-29-13:18:55.759 15.9
2025-11-29-13:18:55.959 15.9
2025-11-29-13:18:56.171 0.0
2025-11-29-13:18:56.363 0.0
2025-11-29-13:18:56.595 15.7
2025-11-29-13:18:56.789 15.7
2025-11-29-13:18:56.985 0.0
2025-11-29-13:18:57.196 0.0
2025-11-29-13:18:57.400 0.0
2025-11-29-13:18:57.606 0.0
2025-11-29-13:18:57.807 0.0
2025-11-29-13:18:58.015 0.0
2025-11-29-13:18:58.207 0.0
2025-11-29-13:18:58.409 0.0
2025-11-29-13:18:58.629 0.0
2025-11-29-13:18:58.827 0.0
2025-11-29-13:18:59.026 0.0
2025-11-29-13:18:59.229 0.0
2025-11-29-13:18:59.456 0.0
2025-11-29-13:18:59.659 0.0
2025-11-29-13:18:59.864 0.0
2025-11-29-13:19:00.057 0.0
2025-11-29-13:19:00.286 0.0
2025-11-29-13:19:00.486 0.0
2025-11-29-13:19:00.688 0.0
2025-11-29-13:19:00.885 16.2
2025-11-29-13:19:01.094 16.4
2025-11-29-13:19:01.291 16.3
2025-11-29-13:19:01.512 16.4
2025-11-29-13:19:01.708 16.2
2025-11-29-13:19:01.916 16.4
2025-11-29-13:19:02.116 0.0
2025-11-29-13:19:02.323 0.0
2025-11-29-13:19:02.534 16.4
2025-11-29-13:19:02.728 16.8
2025-11-29-13:19:02.953 17.9
2025-11-29-13:19:03.138 0.0
2025-11-29-13:19:03.345 17.2
2025-11-29-13:19:03.543 0.0
2025-11-29-13:19:03.751 16.7
2025-11-29-13:19:03.961 16.0
2025-11-29-13:19:04.159 0.0
2025-11-29-13:19:04.363 0.0
2025-11-29-13:19:04.573 16.4
2025-11-29-13:19:04.773 16.7
2025-11-29-13:19:04.975 18.0
2025-11-29-13:19:05.206 16.3
2025-11-29-13:19:05.396 16.4
2025-11-29-13:19:05.591 0.0
2025-11-29-13:19:05.823 0.0
2025-11-29-13:19:06.025 16.0
2025-11-29-13:19:06.234 16.2
2025-11-29-13:19:06.441 16.0
2025-11-29-13:19:06.629 15.9
2025-11-29-13:19:06.847 17.4
2025-11-29-13:19:07.039 18.0
2025-11-29-13:19:07.237 17.6
2025-11-29-13:19:07.445 17.7
2025-11-29-13:19:07.640 16.8
2025-11-29-13:19:07.851 0.0
2025-11-29-13:19:08.070 0.0
2025-11-29-13:19:08.261 0.0
2025-11-29-13:19:08.488 0.0
2025-11-29-13:19:08.674 0.0
2025-11-29-13:19:08.885 16.7
2025-11-29-13:19:09.089 16.9
2025-11-29-13:19:09.294 16.7
2025-11-29-13:19:09.501 16.9
2025-11-29-13:19:09.703 0.0
2025-11-29-13:19:09.932 0.0
2025-11-29-13:19:10.136 0.0
2025-11-29-13:19:10.327 0.0
2025-11-29-13:19:10.538 16.9
2025-11-29-13:19:10.725 0.0
2025-11-29-13:19:10.951 16.7
2025-11-29-13:19:11.134 0.0
2025-11-29-13:19:11.356 0.0
2025-11-29-13:19:11.553 0.0
2025-11-29-13:19:11.756 0.0
2025-11-29-13:19:11.978 0.0
2025-11-29-13:19:12.169 0.0
2025-11-29-13:19:12.393 0.0
2025-11-29-13:19:12.579 0.0
2025-11-29-13:19:12.797 0.0
2025-11-29-13:19:13.005 0.0
2025-11-29-13:19:13.214 0.0
2025-11-29-13:19:13.448 16.0
2025-11-29-13:19:13.627 0.0
2025-11-29-13:19:13.839 16.3
2025-11-29-13:19:14.047 0.0
2025-11-29-13:19:14.248 0.0
2025-11-29-13:19:14.469 0.0
2025-11-29-13:19:14.675 0.0
2025-11-29-13:19:14.879 16.2
2025-11-29-13:19:15.100 15.7
2025-11-29-13:19:15.296 15.7
2025-11-29-13:19:15.499 15.7
2025-11-29-13:19:15.712 15.9
2025-11-29-13:19:15.912 15.9
2025-11-29-13:19:16.107 15.9
2025-11-29-13:19:16.307 16.4
2025-11-29-13:19:16.533 16.3
2025-11-29-13:19:16.720 15.9
2025-11-29-13:19:16.915 0.0
2025-11-29-13:19:17.119 0.0
2025-11-29-13:19:17.334 0.0
2025-11-29-13:19:17.551 17.7
2025-11-29-13:19:17.766 17.9
2025-11-29-13:19:17.949 16.7
2025-11-29-13:19:18.160 16.2
2025-11-29-13:19:18.369 0.0
2025-11-29-13:19:18.577 0.0
2025-11-29-13:19:18.775 16.4
2025-11-29-13:19:18.972 16.8
2025-11-29-13:19:19.177 0.0
2025-11-29-13:19:19.399 0.0
2025-11-29-13:19:19.609 0.0
2025-11-29-13:19:19.791 0.0
2025-11-29-13:19:19.995 0.0
2025-11-29-13:19:20.214 16.0
2025-11-29-13:19:20.409 16.3
2025-11-29-13:19:20.601 16.0
2025-11-29-13:19:20.806 16.7
2025-11-29-13:19:21.029 16.4
2025-11-29-13:19:21.223 16.3
2025-11-29-13:19:21.454 16.2
2025-11-29-13:19:21.650 16.2
2025-11-29-13:19:21.849 0.0
2025-11-29-13:19:22.065 16.7
2025-11-29-13:19:22.269 16.4
2025-11-29-13:19:22.475 17.7
2025-11-29-13:19:22.665 0.0
2025-11-29-13:19:22.876 18.2
2025-11-29-13:19:23.068 18.4
2025-11-29-13:19:23.284 18.4
2025-11-29-13:19:23.481 17.2
2025-11-29-13:19:23.681 17.6
2025-11-29-13:19:23.899 17.2
2025-11-29-13:19:24.095 16.9
2025-11-29-13:19:24.287 0.0
2025-11-29-13:19:24.497 0.0
2025-11-29-13:19:24.708 0.0
2025-11-29-13:19:24.926 16.3
2025-11-29-13:19:25.141 16.3
2025-11-29-13:19:25.337 16.4
2025-11-29-13:19:25.580 16.4
2025-11-29-13:19:25.755 0.0
2025-11-29-13:19:25.963 16.4
2025-11-29-13:19:26.158 16.3
2025-11-29-13:19:26.368 16.4
2025-11-29-13:19:26.576 16.3
2025-11-29-13:19:26.775 16.3
2025-11-29-13:19:26.987 0.0
2025-11-29-13:19:27.173 0.0
2025-11-29-13:19:27.385 0.0
2025-11-29-13:19:27.586 0.0
2025-11-29-13:19:27.787 0.0
2025-11-29-13:19:27.998 0.0
2025-11-29-13:19:28.193 0.0
2025-11-29-13:19:28.412 0.0
2025-11-29-13:19:28.636 16.7
2025-11-29-13:19:28.840 0.0
2025-11-29-13:19:29.045 0.0
2025-11-29-13:19:29.264 0.0
2025-11-29-13:19:29.489 0.0
2025-11-29-13:19:29.694 0.0
2025-11-29-13:19:29.920 0.0
2025-11-29-13:19:30.112 0.0
2025-11-29-13:19:30.320 0.0
2025-11-29-13:19:30.521 0.0
2025-11-29-13:19:30.725 0.0
2025-11-29-13:19:30.930 0.0
2025-11-29-13:19:31.130 0.0
2025-11-29-13:19:31.350 15.5
2025-11-29-13:19:31.538 15.9
2025-11-29-13:19:31.751 16.2
2025-11-29-13:19:31.971 16.2
2025-11-29-13:19:32.172 0.0
2025-11-29-13:19:32.405 16.8
2025-11-29-13:19:32.608 16.8
2025-11-29-13:19:32.813 16.4
2025-11-29-13:19:33.063 0.0
2025-11-29-13:19:33.277 0.0
2025-11-29-13:19:33.435 0.0
2025-11-29-13:19:33.637 0.0
2025-11-29-13:19:33.835 0.0
2025-11-29-13:19:34.051 0.0
2025-11-29-13:19:34.244 0.0
2025-11-29-13:19:34.451 0.0
2025-11-29-13:19:34.664 0.0
2025-11-29-13:19:34.868 0.0
2025-11-29-13:19:35.066 0.0
2025-11-29-13:19:35.272 0.0
2025-11-29-13:19:35.471 16.2
2025-11-29-13:19:35.688 16.0
2025-11-29-13:19:35.886 0.0
2025-11-29-13:19:36.117 0.0
2025-11-29-13:19:36.305 0.0
2025-11-29-13:19:36.529 0.0
2025-11-29-13:19:36.729 0.0
2025-11-29-13:19:36.943 16.7
2025-11-29-13:19:37.146 0.0
2025-11-29-13:19:37.363 16.8
2025-11-29-13:19:37.558 16.2
2025-11-29-13:19:37.764 16.2
2025-11-29-13:19:37.974 16.3
2025-11-29-13:19:38.169 16.4
2025-11-29-13:19:38.369 16.3
2025-11-29-13:19:38.579 16.0
2025-11-29-13:19:38.784 16.0
2025-11-29-13:19:38.985 0.0
2025-11-29-13:19:39.180 0.0
2025-11-29-13:19:39.391 0.0
2025-11-29-13:19:39.636 0.0
2025-11-29-13:19:39.805 0.0
2025-11-29-13:19:40.001 0.0
2025-11-29-13:19:40.208 0.0
2025-11-29-13:19:40.418 0.0
2025-11-29-13:19:40.639 0.0
2025-11-29-13:19:40.847 0.0
2025-11-29-13:19:41.048 0.0
2025-11-29-13:19:41.267 0.0
2025-11-29-13:19:41.463 0.0
2025-11-29-13:19:41.683 0.0
2025-11-29-13:19:41.878 0.0
2025-11-29-13:19:42.101 0.0
2025-11-29-13:19:42.287 16.9
2025-11-29-13:19:42.497 0.0
2025-11-29-13:19:42.691 16.2
2025-11-29-13:19:42.908 0.0
2025-11-29-13:19:43.103 0.0
2025-11-29-13:19:43.301 0.0
2025-11-29-13:19:43.512 0.0
2025-11-29-13:19:43.723 16.8
2025-11-29-13:19:43.935 0.0
2025-11-29-13:19:44.065 0.0
2025-11-29-13:19:44.267 0.0
2025-11-29-13:19:44.484 0.0
2025-11-29-13:19:44.669 0.0
2025-11-29-13:19:44.888 15.9
2025-11-29-13:19:45.104 15.9
2025-11-29-13:19:45.298 15.9
2025-11-29-13:19:45.509 0.0
2025-11-29-13:19:45.713 0.0
2025-11-29-13:19:45.926 15.9
2025-11-29-13:19:46.119 15.9
2025-11-29-13:19:46.334 16.0
2025-11-29-13:19:46.533 15.9
2025-11-29-13:19:46.744 15.9
2025-11-29-13:19:46.930 0.0
2025-11-29-13:19:47.130 0.0
2025-11-29-13:19:47.355 16.2
2025-11-29-13:19:47.548 0.0
2025-11-29-13:19:47.771 0.0
2025-11-29-13:19:47.971 16.3
2025-11-29-13:19:48.201 16.4
2025-11-29-13:19:48.406 0.0
2025-11-29-13:19:48.625 16.3
2025-11-29-13:19:48.824 0.0
2025-11-29-13:19:49.050 16.4
2025-11-29-13:19:49.246 0.0
2025-11-29-13:19:49.471 16.4
2025-11-29-13:19:49.686 16.3
2025-11-29-13:19:49.926 16.2
2025-11-29-13:19:50.105 16.2
2025-11-29-13:19:50.301 15.9
2025-11-29-13:19:50.516 15.9
2025-11-29-13:19:50.718 15.9
2025-11-29-13:19:50.925 16.4
2025-11-29-13:19:51.129 16.4
2025-11-29-13:19:51.344 16.0
2025-11-29-13:19:51.529 0.0
2025-11-29-13:19:51.754 0.0
2025-11-29-13:19:51.950 0.0
2025-11-29-13:19:52.176 0.0
2025-11-29-13:19:52.383 0.0
2025-11-29-13:19:52.577 0.0
2025-11-29-13:19:52.776 0.0
2025-11-29-13:19:52.988 0.0
2025-11-29-13:19:53.177 15.7
2025-11-29-13:19:53.378 0.0
2025-11-29-13:19:53.592 0.0
2025-11-29-13:19:53.814 15.9
2025-11-29-13:19:54.001 15.9
2025-11-29-13:19:54.213 15.7
2025-11-29-13:19:54.423 0.0
2025-11-29-13:19:54.611 15.7
2025-11-29-13:19:54.817 0.0
2025-11-29-13:19:55.018 0.0
2025-11-29-13:19:55.223 0.0
2025-11-29-13:19:55.433 0.0
2025-11-29-13:19:55.646 0.0
2025-11-29-13:19:55.851 0.0
2025-11-29-13:19:56.056 0.0
2025-11-29-13:19:56.254 0.0
2025-11-29-13:19:56.453 0.0
2025-11-29-13:19:56.663 0.0
2025-11-29-13:19:56.868 0.0
2025-11-29-13:19:57.071 0.0
2025-11-29-13:19:57.291 0.0
2025-11-29-13:19:57.467 0.0
2025-11-29-13:19:57.671 0.0
2025-11-29-13:19:57.905 0.0
2025-11-29-13:19:58.076 0.0
2025-11-29-13:19:58.278 0.0
2025-11-29-13:19:58.511 0.0
2025-11-29-13:19:58.708 0.0
2025-11-29-13:19:58.908 16.4
2025-11-29-13:19:59.111 0.0
2025-11-29-13:19:59.321 0.0
2025-11-29-13:19:59.526 0.0
2025-11-29-13:19:59.727 0.0
2025-11-29-13:19:59.921 0.0
2025-11-29-13:20:00.125 0.0
2025-11-29-13:20:00.335 0.0
2025-11-29-13:20:00.542 16.0
2025-11-29-13:20:00.760 0.0
2025-11-29-13:20:00.963 0.0
2025-11-29-13:20:01.180 0.0
2025-11-29-13:20:01.363 0.0
2025-11-29-13:20:01.571 0.0
2025-11-29-13:20:01.796 0.0
2025-11-29-13:20:01.996 0.0
2025-11-29-13:20:02.215 16.2
2025-11-29-13:20:02.404 0.0
2025-11-29-13:20:02.620 0.0
2025-11-29-13:20:02.816 0.0
2025-11-29-13:20:03.026 0.0
2025-11-29-13:20:03.227 0.0
2025-11-29-13:20:03.445 0.0
2025-11-29-13:20:03.646 0.0
2025-11-29-13:20:03.839 0.0
2025-11-29-13:20:04.056 0.0
2025-11-29-13:20:04.264 0.0
2025-11-29-13:20:04.471 0.0
2025-11-29-13:20:04.675 0.0
2025-11-29-13:20:04.886 0.0
2025-11-29-13:20:05.087 0.0
2025-11-29-13:20:05.282 0.0
2025-11-29-13:20:05.497 0.0
2025-11-29-13:20:05.710 0.0
2025-11-29-13:20:05.926 0.0
2025-11-29-13:20:06.123 0.0
2025-11-29-13:20:06.321 0.0
2025-11-29-13:20:06.510 0.0
2025-11-29-13:20:06.711 0.0
2025-11-29-13:20:06.942 0.0
2025-11-29-13:20:07.141 0.0
2025-11-29-13:20:07.347 0.0
2025-11-29-13:20:07.552 0.0
2025-11-29-13:20:07.754 0.0
2025-11-29-13:20:07.957 0.0
2025-11-29-13:20:08.149 0.0
2025-11-29-13:20:08.371 0.0
2025-11-29-13:20:08.568 0.0
2025-11-29-13:20:08.768 0.0
2025-11-29-13:20:08.971 16.2
2025-11-29-13:20:09.179 0.0
2025-11-29-13:20:09.374 0.0
2025-11-29-13:20:09.579 0.0
2025-11-29-13:20:09.826 16.0
2025-11-29-13:20:10.012 0.0
2025-11-29-13:20:10.187 0.0
2025-11-29-13:20:10.405 0.0
2025-11-29-13:20:10.609 0.0
2025-11-29-13:20:10.808 0.0
2025-11-29-13:20:11.042 16.2
2025-11-29-13:20:11.257 16.2
2025-11-29-13:20:11.457 16.2
2025-11-29-13:20:11.679 16.2
2025-11-29-13:20:11.884 0.0
2025-11-29-13:20:12.071 0.0
2025-11-29-13:20:12.271 16.0
2025-11-29-13:20:12.490 16.2
2025-11-29-13:20:12.698 16.2
2025-11-29-13:20:12.906 0.0
2025-11-29-13:20:13.105 15.9
2025-11-29-13:20:13.317 0.0
2025-11-29-13:20:13.528 0.0
2025-11-29-13:20:13.730 0.0
2025-11-29-13:20:13.934 0.0
2025-11-29-13:20:14.134 0.0
2025-11-29-13:20:14.334 0.0
2025-11-29-13:20:14.562 0.0
2025-11-29-13:20:14.744 0.0
2025-11-29-13:20:14.961 0.0
2025-11-29-13:20:15.171 0.0
2025-11-29-13:20:15.385 0.0
2025-11-29-13:20:15.601 0.0
2025-11-29-13:20:15.818 0.0
2025-11-29-13:20:16.030 0.0
2025-11-29-13:20:16.235 0.0
2025-11-29-13:20:16.445 15.7
2025-11-29-13:20:16.656 15.5
2025-11-29-13:20:16.888 16.2
2025-11-29-13:20:17.090 15.9
2025-11-29-13:20:17.307 16.2
2025-11-29-13:20:17.500 15.9
2025-11-29-13:20:17.699 15.9
2025-11-29-13:20:17.903 16.2
2025-11-29-13:20:18.123 15.9
2025-11-29-13:20:18.321 16.0
2025-11-29-13:20:18.522 0.0
2025-11-29-13:20:18.733 15.1
2025-11-29-13:20:18.956 15.9
2025-11-29-13:20:19.168 16.3
2025-11-29-13:20:19.357 16.0
2025-11-29-13:20:19.555 0.0
2025-11-29-13:20:19.752 0.0
2025-11-29-13:20:19.961 0.0
2025-11-29-13:20:20.160 0.0
2025-11-29-13:20:20.368 14.6
2025-11-29-13:20:20.569 0.0
2025-11-29-13:20:20.787 0.0
2025-11-29-13:20:20.984 0.0
2025-11-29-13:20:21.185 0.0
2025-11-29-13:20:21.381 0.0
2025-11-29-13:20:21.593 15.1
2025-11-29-13:20:21.791 15.1
2025-11-29-13:20:22.000 15.1
2025-11-29-13:20:22.209 15.7
2025-11-29-13:20:22.421 15.7
2025-11-29-13:20:22.644 15.7
2025-11-29-13:20:22.853 0.0
2025-11-29-13:20:23.075 0.0
2025-11-29-13:20:23.271 0.0
2025-11-29-13:20:23.465 0.0
2025-11-29-13:20:23.671 0.0
2025-11-29-13:20:23.883 0.0
2025-11-29-13:20:24.089 15.1
2025-11-29-13:20:24.287 0.0
2025-11-29-13:20:24.486 0.0
2025-11-29-13:20:24.699 0.0
2025-11-29-13:20:24.921 16.8
2025-11-29-13:20:25.108 16.4
2025-11-29-13:20:25.318 16.8
2025-11-29-13:20:25.520 17.1
2025-11-29-13:20:25.716 17.1
2025-11-29-13:20:25.944 16.0
2025-11-29-13:20:26.135 16.0
2025-11-29-13:20:26.351 16.8
2025-11-29-13:20:26.559 17.1
2025-11-29-13:20:26.755 17.2
2025-11-29-13:20:26.948 0.0
2025-11-29-13:20:27.146 0.0
2025-11-29-13:20:27.364 16.4
2025-11-29-13:20:27.561 0.0
2025-11-29-13:20:27.776 0.0
2025-11-29-13:20:27.971 0.0
2025-11-29-13:20:28.178 0.0
2025-11-29-13:20:28.391 17.6
2025-11-29-13:20:28.590 17.4
2025-11-29-13:20:28.787 0.0
2025-11-29-13:20:29.003 16.2
2025-11-29-13:20:29.216 16.4
2025-11-29-13:20:29.397 0.0
2025-11-29-13:20:29.600 0.0
2025-11-29-13:20:29.816 15.4
2025-11-29-13:20:30.007 15.2
2025-11-29-13:20:30.202 0.0
2025-11-29-13:20:30.416 15.7
2025-11-29-13:20:30.642 16.0
2025-11-29-13:20:30.845 0.0
2025-11-29-13:20:31.051 16.3
2025-11-29-13:20:31.271 0.0
2025-11-29-13:20:31.455 0.0
2025-11-29-13:20:31.662 0.0
2025-11-29-13:20:31.862 0.0
2025-11-29-13:20:32.059 0.0
2025-11-29-13:20:32.269 0.0
2025-11-29-13:20:32.468 15.9
2025-11-29-13:20:32.719 0.0
2025-11-29-13:20:32.908 17.4
2025-11-29-13:20:33.111 16.4
2025-11-29-13:20:33.311 0.0
2025-11-29-13:20:33.509 0.0
2025-11-29-13:20:33.698 0.0
2025-11-29-13:20:33.911 0.0
2025-11-29-13:20:34.121 0.0
2025-11-29-13:20:34.320 0.0
2025-11-29-13:20:34.521 0.0
2025-11-29-13:20:34.738 0.0
2025-11-29-13:20:34.959 19.6
2025-11-29-13:20:35.170 17.6
2025-11-29-13:20:35.363 0.0
2025-11-29-13:20:35.595 0.0
2025-11-29-13:20:35.773 0.0
2025-11-29-13:20:35.983 0.0
2025-11-29-13:20:36.183 0.0
2025-11-29-13:20:36.391 17.2
2025-11-29-13:20:36.591 0.0
2025-11-29-13:20:36.784 0.0
2025-11-29-13:20:36.980 0.0
2025-11-29-13:20:37.183 0.0
2025-11-29-13:20:37.401 0.0
2025-11-29-13:20:37.595 0.0
2025-11-29-13:20:37.796 0.0
2025-11-29-13:20:38.023 0.0
2025-11-29-13:20:38.236 0.0
2025-11-29-13:20:38.445 0.0
2025-11-29-13:20:38.661 0.0
2025-11-29-13:20:38.893 0.0
2025-11-29-13:20:39.085 0.0
2025-11-29-13:20:39.238 0.0
2025-11-29-13:20:39.434 0.0
2025-11-29-13:20:39.631 0.0
2025-11-29-13:20:39.837 0.0
2025-11-29-13:20:40.043 0.0
2025-11-29-13:20:40.244 0.0
2025-11-29-13:20:40.451 0.0
2025-11-29-13:20:40.640 0.0
2025-11-29-13:20:40.854 0.0
2025-11-29-13:20:41.060 0.0
2025-11-29-13:20:41.262 0.0
2025-11-29-13:20:41.456 0.0
2025-11-29-13:20:41.658 0.0
2025-11-29-13:20:41.903 0.0
2025-11-29-13:20:42.095 0.0
2025-11-29-13:20:42.304 0.0
2025-11-29-13:20:42.502 0.0
2025-11-29-13:20:42.719 0.0
2025-11-29-13:20:42.918 0.0
2025-11-29-13:20:43.120 0.0
2025-11-29-13:20:43.324 0.0
2025-11-29-13:20:43.556 0.0
2025-11-29-13:20:43.763 0.0
2025-11-29-13:20:43.992 0.0
2025-11-29-13:20:44.189 0.0
2025-11-29-13:20:44.387 0.0
2025-11-29-13:20:44.590 0.0
2025-11-29-13:20:44.797 0.0
2025-11-29-13:20:45.006 0.0
2025-11-29-13:20:45.216 0.0
2025-11-29-13:20:45.419 0.0
2025-11-29-13:20:45.633 0.0
2025-11-29-13:20:45.854 0.0
2025-11-29-13:20:46.074 0.0
2025-11-29-13:20:46.284 0.0
2025-11-29-13:20:46.485 0.0
2025-11-29-13:20:46.697 0.0
2025-11-29-13:20:46.904 0.0
2025-11-29-13:20:47.106 0.0
2025-11-29-13:20:47.301 0.0
2025-11-29-13:20:47.518 0.0
2025-11-29-13:20:47.719 0.0
2025-11-29-13:20:47.937 0.0
2025-11-29-13:20:48.161 0.0
2025-11-29-13:20:48.373 0.0
2025-11-29-13:20:48.579 0.0
2025-11-29-13:20:48.794 0.0
2025-11-29-13:20:49.001 0.0
2025-11-29-13:20:49.197 0.0
2025-11-29-13:20:49.404 0.0
2025-11-29-13:20:49.610 0.0
2025-11-29-13:20:49.828 0.0
2025-11-29-13:20:50.035 0.0
2025-11-29-13:20:50.262 0.0
2025-11-29-13:20:50.455 0.0
2025-11-29-13:20:50.655 0.0
2025-11-29-13:20:50.860 0.0
2025-11-29-13:20:51.062 0.0
2025-11-29-13:20:51.263 0.0
2025-11-29-13:20:51.486 0.0
2025-11-29-13:20:51.686 0.0
2025-11-29-13:20:51.913 0.0
2025-11-29-13:20:52.128 0.0
2025-11-29-13:20:52.328 0.0
2025-11-29-13:20:52.547 0.0
2025-11-29-13:20:52.755 0.0
2025-11-29-13:20:52.994 0.0
2025-11-29-13:20:53.183 0.0
2025-11-29-13:20:53.369 0.0
2025-11-29-13:20:53.604 0.0
2025-11-29-13:20:53.780 0.0
2025-11-29-13:20:54.002 0.0
2025-11-29-13:20:54.182 0.0
2025-11-29-13:20:54.386 0.0
2025-11-29-13:20:54.590 0.0
2025-11-29-13:20:54.798 0.0
2025-11-29-13:20:54.995 0.0
2025-11-29-13:20:55.211 0.0
2025-11-29-13:20:55.413 0.0
2025-11-29-13:20:55.606 0.0
2025-11-29-13:20:55.813 0.0
2025-11-29-13:20:56.018 0.0
2025-11-29-13:20:56.223 0.0
2025-11-29-13:20:56.430 0.0
2025-11-29-13:20:56.634 0.0
2025-11-29-13:20:56.848 0.0
2025-11-29-13:20:57.056 0.0
2025-11-29-13:20:57.261 0.0
2025-11-29-13:20:57.471 0.0
2025-11-29-13:20:57.668 0.0
2025-11-29-13:20:57.870 0.0
2025-11-29-13:20:58.090 0.0
2025-11-29-13:20:58.301 0.0
2025-11-29-13:20:58.534 0.0
2025-11-29-13:20:58.736 0.0
2025-11-29-13:20:58.953 0.0
2025-11-29-13:20:59.151 0.0
2025-11-29-13:20:59.352 0.0
2025-11-29-13:20:59.566 0.0
2025-11-29-13:20:59.766 0.0
2025-11-29-13:20:59.966 0.0
2025-11-29-13:21:00.214 0.0
2025-11-29-13:21:00.394 0.0
2025-11-29-13:21:00.597 0.0
2025-11-29-13:21:00.800 0.0
2025-11-29-13:21:01.004 0.0
2025-11-29-13:21:01.206 0.0
2025-11-29-13:21:01.421 0.0
2025-11-29-13:21:01.663 0.0
2025-11-29-13:21:01.851 0.0
2025-11-29-13:21:02.006 0.0
2025-11-29-13:21:02.217 0.0
2025-11-29-13:21:02.430 0.0
2025-11-29-13:21:02.636 0.0
2025-11-29-13:21:02.864 0.0
2025-11-29-13:21:03.054 0.0
2025-11-29-13:21:03.250 0.0
2025-11-29-13:21:03.453 0.0
2025-11-29-13:21:03.662 0.0
2025-11-29-13:21:03.877 0.0
2025-11-29-13:21:04.084 0.0
2025-11-29-13:21:04.330 0.0
2025-11-29-13:21:04.505 0.0
2025-11-29-13:21:04.710 0.0
2025-11-29-13:21:04.921 0.0
2025-11-29-13:21:05.120 0.0
2025-11-29-13:21:05.327 0.0
2025-11-29-13:21:05.530 0.0
2025-11-29-13:21:05.752 0.0
2025-11-29-13:21:05.954 0.0
2025-11-29-13:21:06.157 0.0
2025-11-29-13:21:06.356 0.0
2025-11-29-13:21:06.583 0.0
2025-11-29-13:21:06.781 0.0
2025-11-29-13:21:06.986 0.0
2025-11-29-13:21:07.205 0.0
2025-11-29-13:21:07.444 0.0
2025-11-29-13:21:07.600 0.0
2025-11-29-13:21:07.825 0.0
2025-11-29-13:21:08.047 0.0
2025-11-29-13:21:08.291 0.0
2025-11-29-13:21:08.486 0.0
2025-11-29-13:21:08.655 0.0
2025-11-29-13:21:08.868 0.0
2025-11-29-13:21:09.068 0.0
2025-11-29-13:21:09.269 0.0
2025-11-29-13:21:09.461 0.0
2025-11-29-13:21:09.702 0.0
2025-11-29-13:21:09.900 0.0
2025-11-29-13:21:10.098 0.0
2025-11-29-13:21:10.301 0.0
2025-11-29-13:21:10.526 0.0
2025-11-29-13:21:10.739 0.0
2025-11-29-13:21:10.946 0.0
2025-11-29-13:21:11.134 0.0
2025-11-29-13:21:11.331 0.0
2025-11-29-13:21:11.463 0.0
2025-11-29-13:21:11.671 0.0
2025-11-29-13:21:11.878 0.0
2025-11-29-13:21:12.072 0.0
2025-11-29-13:21:12.280 0.0
2025-11-29-13:21:12.478 0.0
2025-11-29-13:21:12.677 0.0
2025-11-29-13:21:12.886 0.0
2025-11-29-13:21:13.088 0.0
2025-11-29-13:21:13.285 0.0
2025-11-29-13:21:13.482 0.0
2025-11-29-13:21:13.707 0.0
2025-11-29-13:21:13.896 0.0
2025-11-29-13:21:14.093 0.0
2025-11-29-13:21:14.301 0.0
2025-11-29-13:21:14.504 0.0
2025-11-29-13:21:14.706 0.0
2025-11-29-13:21:14.910 0.0
2025-11-29-13:21:15.114 0.0
2025-11-29-13:22:15.899 0.0
2025-11-29-13:22:15.925 0.0
2025-11-29-13:22:16.126 0.0
2025-11-29-13:22:16.333 0.0
2025-11-29-13:22:16.534 0.0
2025-11-29-13:22:16.738 0.0
2025-11-29-13:22:16.933 0.0
2025-11-29-13:22:17.144 0.0
2025-11-29-13:22:17.339 0.0
2025-11-29-13:22:17.537 0.0
2025-11-29-13:22:17.749 0.0
2025-11-29-13:22:17.951 0.0
2025-11-29-13:22:18.147 0.0
2025-11-29-13:22:18.347 0.0
2025-11-29-13:22:18.554 0.0
2025-11-29-13:22:18.754 0.0
2025-11-29-13:22:18.947 0.0
2025-11-29-13:22:19.160 0.0
2025-11-29-13:22:19.356 0.0
2025-11-29-13:22:19.560 0.0
2025-11-29-13:22:19.769 0.0
2025-11-29-13:22:19.969 0.0
2025-11-29-13:22:20.176 0.0
2025-11-29-13:22:20.394 0.0
2025-11-29-13:22:20.585 0.0
2025-11-29-13:22:20.782 0.0
2025-11-29-13:22:20.990 0.0
2025-11-29-13:22:21.188 0.0
2025-11-29-13:22:21.401 0.0
2025-11-29-13:22:21.605 0.0
2025-11-29-13:22:21.808 0.0
2025-11-29-13:22:22.009 0.0
2025-11-29-13:22:22.210 0.0
2025-11-29-13:22:22.414 0.0
2025-11-29-13:22:22.616 0.0
2025-11-29-13:22:22.829 0.0
2025-11-29-13:22:23.000 0.0
2025-11-29-13:22:23.197 0.0
2025-11-29-13:22:23.406 0.0
2025-11-29-13:22:23.607 0.0
2025-11-29-13:22:23.806 0.0
2025-11-29-13:22:24.008 0.0
2025-11-29-13:22:24.214 0.0
2025-11-29-13:22:24.414 0.0
2025-11-29-13:22:24.610 0.0
2025-11-29-13:22:24.821 0.0
2025-11-29-13:22:25.018 0.0
2025-11-29-13:22:25.213 0.0
2025-11-29-13:22:25.422 0.0
2025-11-29-13:22:25.621 0.0
2025-11-29-13:22:25.818 0.0
2025-11-29-13:22:26.024 0.0
2025-11-29-13:22:26.225 0.0
2025-11-29-13:22:26.436 0.0
2025-11-29-13:22:26.637 0.0
2025-11-29-13:22:26.846 0.0
2025-11-29-13:22:27.043 0.0
2025-11-29-13:22:27.246 0.0
2025-11-29-13:22:27.455 0.0
2025-11-29-13:22:27.671 0.0
2025-11-29-13:22:27.864 0.0
2025-11-29-13:22:28.063 0.0
2025-11-29-13:22:28.264 0.0
2025-11-29-13:22:28.466 0.0
2025-11-29-13:22:28.700 0.0
2025-11-29-13:22:28.895 0.0
2025-11-29-13:22:29.120 0.0
2025-11-29-13:22:29.310 0.0
2025-11-29-13:22:29.521 0.0
2025-11-29-13:22:29.719 0.0
2025-11-29-13:22:29.926 0.0
2025-11-29-13:22:30.127 0.0
2025-11-29-13:22:30.333 0.0
2025-11-29-13:22:30.568 0.0
2025-11-29-13:22:30.747 0.0
2025-11-29-13:22:30.913 0.0
2025-11-29-13:22:31.125 0.0
2025-11-29-13:22:31.333 0.0
2025-11-29-13:22:31.527 0.0
2025-11-29-13:22:31.739 0.0
2025-11-29-13:22:31.960 0.0
2025-11-29-13:22:32.134 0.0
2025-11-29-13:22:32.368 0.0
2025-11-29-13:22:32.550 0.0
2025-11-29-13:22:32.740 0.0
2025-11-29-13:22:32.937 0.0
2025-11-29-13:22:33.159 0.0
2025-11-29-13:22:33.362 0.0
2025-11-29-13:22:33.571 0.0
2025-11-29-13:22:33.820 0.0
2025-11-29-13:22:33.989 0.0
2025-11-29-13:22:34.143 0.0
2025-11-29-13:22:34.350 0.0
2025-11-29-13:22:34.538 0.0
2025-11-29-13:22:34.747 0.0
2025-11-29-13:22:34.953 0.0
2025-11-29-13:22:35.171 0.0
2025-11-29-13:22:35.362 0.0
2025-11-29-13:22:35.584 0.0
2025-11-29-13:22:35.770 0.0
2025-11-29-13:22:36.032 0.0
2025-11-29-13:22:36.234 0.0
2025-11-29-13:22:36.446 0.0
2025-11-29-13:22:36.705 0.0
2025-11-29-13:22:36.896 0.0
2025-11-29-13:22:37.102 0.0
2025-11-29-13:22:37.271 0.0
2025-11-29-13:22:37.485 0.0
2025-11-29-13:22:37.694 0.0
2025-11-29-13:22:37.897 0.0
2025-11-29-13:22:38.109 0.0
2025-11-29-13:22:38.332 0.0
2025-11-29-13:22:38.520 0.0
2025-11-29-13:22:38.730 0.0
2025-11-29-13:22:38.934 0.0
2025-11-29-13:22:39.187 0.0
2025-11-29-13:22:39.388 0.0
2025-11-29-13:22:39.545 0.0
2025-11-29-13:22:39.736 0.0
2025-11-29-13:22:39.935 0.0
2025-11-29-13:22:40.132 0.0
2025-11-29-13:22:40.335 0.0
2025-11-29-13:22:40.551 0.0
2025-11-29-13:22:40.753 0.0
2025-11-29-13:22:40.958 0.0
2025-11-29-13:22:41.159 0.0
2025-11-29-13:22:41.368 0.0
2025-11-29-13:22:41.594 0.0
2025-11-29-13:22:41.785 0.0
2025-11-29-13:22:42.000 0.0
2025-11-29-13:22:42.220 0.0
2025-11-29-13:22:42.425 0.0
2025-11-29-13:22:42.620 0.0
2025-11-29-13:22:42.816 0.0
2025-11-29-13:22:43.033 0.0
2025-11-29-13:22:43.246 0.0
2025-11-29-13:22:43.456 0.0
2025-11-29-13:22:43.661 0.0
2025-11-29-13:22:43.873 0.0
2025-11-29-13:22:44.067 0.0
2025-11-29-13:22:44.268 0.0
2025-11-29-13:22:44.473 0.0
2025-11-29-13:22:44.676 0.0
2025-11-29-13:22:44.894 0.0
2025-11-29-13:22:45.084 0.0
2025-11-29-13:22:45.293 0.0
2025-11-29-13:22:45.512 0.0
2025-11-29-13:22:45.716 0.0
2025-11-29-13:22:45.918 0.0
2025-11-29-13:22:46.116 0.0
2025-11-29-13:22:46.331 0.0
2025-11-29-13:22:46.527 0.0
2025-11-29-13:22:46.731 0.0
2025-11-29-13:22:46.946 0.0
2025-11-29-13:22:47.156 0.0
2025-11-29-13:22:47.364 0.0
2025-11-29-13:22:47.573 0.0
2025-11-29-13:22:47.792 0.0
2025-11-29-13:22:47.984 0.0
2025-11-29-13:22:48.193 0.0
2025-11-29-13:22:48.398 0.0
2025-11-29-13:22:48.628 0.0
2025-11-29-13:22:48.811 0.0
2025-11-29-13:22:49.011 0.0
2025-11-29-13:22:49.218 0.0
2025-11-29-13:22:49.420 0.0
2025-11-29-13:22:49.642 0.0
2025-11-29-13:22:49.871 0.0
2025-11-29-13:22:50.043 0.0
2025-11-29-13:22:50.297 0.0
2025-11-29-13:22:50.471 0.0
2025-11-29-13:22:50.651 0.0
2025-11-29-13:22:50.857 0.0
2025-11-29-13:22:51.072 0.0
2025-11-29-13:22:51.260 0.0
2025-11-29-13:22:51.477 0.0
2025-11-29-13:22:51.682 0.0
2025-11-29-13:22:51.900 0.0
2025-11-29-13:22:52.089 0.0
2025-11-29-13:22:52.296 0.0
2025-11-29-13:22:52.510 0.0
2025-11-29-13:22:52.703 0.0
2025-11-29-13:22:52.916 0.0
2025-11-29-13:22:53.181 0.0
2025-11-29-13:22:53.341 0.0
2025-11-29-13:22:53.560 0.0
2025-11-29-13:22:53.897 0.0
2025-11-29-13:22:53.998 0.0
2025-11-29-13:22:54.197 0.0
2025-11-29-13:22:54.419 0.0
2025-11-29-13:22:54.620 0.0
2025-11-29-13:22:54.822 0.0
2025-11-29-13:22:55.047 0.0
2025-11-29-13:22:55.279 0.0
2025-11-29-13:22:55.589 16.4
2025-11-29-13:22:55.647 16.8
2025-11-29-13:22:55.882 16.7
2025-11-29-13:22:56.076 0.0
2025-11-29-13:22:56.279 0.0
2025-11-29-13:22:56.483 0.0
2025-11-29-13:22:56.715 0.0
2025-11-29-13:22:56.927 0.0
2025-11-29-13:22:57.105 0.0
2025-11-29-13:22:57.301 0.0
2025-11-29-13:22:57.589 0.0
2025-11-29-13:22:57.784 0.0
2025-11-29-13:22:57.969 0.0
2025-11-29-13:22:58.182 0.0
2025-11-29-13:22:58.379 0.0
2025-11-29-13:22:58.598 0.0
2025-11-29-13:22:58.791 0.0
2025-11-29-13:22:59.004 0.0
2025-11-29-13:22:59.240 0.0
2025-11-29-13:22:59.452 0.0
2025-11-29-13:22:59.635 0.0
2025-11-29-13:22:59.834 0.0
2025-11-29-13:23:00.060 0.0
2025-11-29-13:23:00.264 0.0
2025-11-29-13:23:00.468 0.0
2025-11-29-13:23:00.694 0.0
2025-11-29-13:23:00.945 0.0
2025-11-29-13:23:01.122 0.0
2025-11-29-13:23:01.297 0.0
2025-11-29-13:23:01.513 0.0
2025-11-29-13:23:01.734 0.0
2025-11-29-13:23:01.938 0.0
2025-11-29-13:23:02.144 0.0
2025-11-29-13:23:02.318 0.0
2025-11-29-13:23:02.521 0.0
2025-11-29-13:23:02.743 0.0
2025-11-29-13:23:02.948 0.0
2025-11-29-13:23:03.190 0.0
2025-11-29-13:23:03.377 0.0
2025-11-29-13:23:03.605 0.0
2025-11-29-13:23:03.794 0.0
2025-11-29-13:23:03.958 0.0
2025-11-29-13:23:04.163 0.0
2025-11-29-13:23:04.393 0.0
2025-11-29-13:23:04.582 0.0
2025-11-29-13:23:04.827 0.0
2025-11-29-13:23:05.005 0.0
2025-11-29-13:23:05.169 0.0
2025-11-29-13:23:05.392 0.0
2025-11-29-13:23:05.565 0.0
2025-11-29-13:23:05.786 0.0
2025-11-29-13:23:05.989 0.0
2025-11-29-13:23:06.176 0.0
2025-11-29-13:23:06.386 0.0
2025-11-29-13:23:06.576 0.0
2025-11-29-13:23:06.795 0.0
2025-11-29-13:23:07.019 0.0
2025-11-29-13:23:07.201 0.0
2025-11-29-13:23:07.413 0.0
2025-11-29-13:23:07.619 0.0
2025-11-29-13:23:07.819 0.0
2025-11-29-13:23:08.019 0.0
2025-11-29-13:23:08.221 0.0
2025-11-29-13:23:08.465 0.0
2025-11-29-13:23:08.672 0.0
2025-11-29-13:23:08.886 0.0
2025-11-29-13:23:09.102 0.0
2025-11-29-13:23:09.301 0.0
2025-11-29-13:23:09.515 0.0
2025-11-29-13:23:09.729 0.0
2025-11-29-13:23:09.930 0.0
2025-11-29-13:23:10.121 0.0
2025-11-29-13:23:10.346 0.0
2025-11-29-13:23:10.575 0.0
2025-11-29-13:23:10.746 0.0
2025-11-29-13:23:11.049 0.0
2025-11-29-13:23:11.188 0.0
2025-11-29-13:23:11.336 0.0
2025-11-29-13:23:11.550 0.0
2025-11-29-13:23:11.739 0.0
2025-11-29-13:23:11.945 0.0
2025-11-29-13:23:12.166 0.0
2025-11-29-13:23:12.382 0.0
2025-11-29-13:23:12.569 0.0
2025-11-29-13:23:12.792 0.0
2025-11-29-13:23:13.070 0.0
2025-11-29-13:23:13.269 0.0
2025-11-29-13:23:13.467 0.0
2025-11-29-13:23:13.681 0.0
2025-11-29-13:23:13.864 0.0
2025-11-29-13:23:14.169 0.0
2025-11-29-13:23:14.214 0.0
2025-11-29-13:23:14.436 0.0
2025-11-29-13:23:14.626 0.0
2025-11-29-13:23:14.830 0.0
2025-11-29-13:23:15.033 0.0
2025-11-29-13:23:15.238 0.0
2025-11-29-13:23:15.497 0.0
2025-11-29-13:23:15.674 0.0
2025-11-29-13:23:15.865 0.0
2025-11-29-13:23:16.088 0.0
2025-11-29-13:23:16.298 0.0
2025-11-29-13:23:16.507 0.0
2025-11-29-13:23:16.720 0.0
2025-11-29-13:23:16.935 0.0
2025-11-29-13:23:17.137 0.0
2025-11-29-13:23:17.366 0.0
2025-11-29-13:23:17.568 0.0
2025-11-29-13:23:17.768 0.0
2025-11-29-13:23:17.955 0.0
2025-11-29-13:23:18.162 0.0
2025-11-29-13:23:18.403 0.0
2025-11-29-13:23:18.583 0.0
2025-11-29-13:23:18.784 0.0
2025-11-29-13:23:19.001 0.0
2025-11-29-13:23:19.231 0.0
2025-11-29-13:23:19.426 0.0
2025-11-29-13:23:19.632 0.0
2025-11-29-13:23:19.838 0.0
2025-11-29-13:23:20.055 0.0
2025-11-29-13:23:20.270 0.0
2025-11-29-13:23:20.469 0.0
2025-11-29-13:23:20.673 0.0
2025-11-29-13:23:20.877 0.0
2025-11-29-13:23:21.096 0.0
2025-11-29-13:23:21.315 0.0
2025-11-29-13:23:21.522 0.0
2025-11-29-13:23:21.716 0.0
2025-11-29-13:23:21.928 0.0
2025-11-29-13:23:22.151 0.0
2025-11-29-13:23:22.339 0.0
2025-11-29-13:23:22.572 0.0
2025-11-29-13:23:22.763 16.7
2025-11-29-13:23:22.979 0.0
2025-11-29-13:23:23.174 16.7
2025-11-29-13:23:23.354 0.0
2025-11-29-13:23:23.564 0.0
2025-11-29-13:23:23.772 0.0
2025-11-29-13:23:24.005 0.0
2025-11-29-13:23:24.234 0.0
2025-11-29-13:23:24.404 0.0
2025-11-29-13:23:24.604 0.0
2025-11-29-13:23:24.839 0.0
2025-11-29-13:23:25.041 0.0
2025-11-29-13:23:25.229 0.0
2025-11-29-13:23:25.428 0.0
2025-11-29-13:23:25.621 0.0
2025-11-29-13:23:25.838 0.0
2025-11-29-13:23:26.036 0.0
2025-11-29-13:23:26.255 0.0
2025-11-29-13:23:26.456 0.0
2025-11-29-13:23:26.669 0.0
2025-11-29-13:23:26.864 0.0
2025-11-29-13:23:27.072 0.0
2025-11-29-13:23:27.305 0.0
2025-11-29-13:23:27.501 0.0
2025-11-29-13:23:27.714 0.0
2025-11-29-13:23:27.915 0.0
2025-11-29-13:23:28.115 0.0
2025-11-29-13:23:28.303 0.0
2025-11-29-13:23:28.532 0.0
2025-11-29-13:23:28.715 0.0
2025-11-29-13:23:28.921 0.0
2025-11-29-13:23:29.123 0.0
2025-11-29-13:23:29.339 0.0
2025-11-29-13:23:29.539 0.0
2025-11-29-13:23:29.736 0.0
2025-11-29-13:23:29.952 0.0
2025-11-29-13:23:30.167 0.0
2025-11-29-13:23:30.378 0.0
2025-11-29-13:23:30.575 0.0
2025-11-29-13:23:30.783 0.0
2025-11-29-13:23:30.979 0.0
2025-11-29-13:23:31.201 0.0
2025-11-29-13:23:31.405 0.0
2025-11-29-13:23:31.611 0.0
2025-11-29-13:23:31.814 0.0
2025-11-29-13:23:32.026 0.0
2025-11-29-13:23:32.229 0.0
2025-11-29-13:23:32.468 0.0
2025-11-29-13:23:32.638 0.0
2025-11-29-13:23:32.838 0.0
2025-11-29-13:23:33.069 0.0
2025-11-29-13:23:33.245 0.0
2025-11-29-13:23:33.462 0.0
2025-11-29-13:23:33.655 0.0
2025-11-29-13:23:33.857 0.0
2025-11-29-13:23:34.089 0.0
2025-11-29-13:23:34.300 0.0
2025-11-29-13:23:34.494 0.0
2025-11-29-13:23:34.688 0.0
2025-11-29-13:23:34.889 0.0
2025-11-29-13:23:35.091 0.0
2025-11-29-13:23:35.303 0.0
2025-11-29-13:23:35.525 0.0
2025-11-29-13:23:35.749 0.0
2025-11-29-13:23:35.942 0.0
2025-11-29-13:23:36.155 0.0
2025-11-29-13:23:36.353 0.0
2025-11-29-13:23:36.572 0.0
2025-11-29-13:23:36.758 0.0
2025-11-29-13:23:36.968 0.0
2025-11-29-13:23:37.171 0.0
2025-11-29-13:23:37.386 16.3
2025-11-29-13:23:37.621 0.0
2025-11-29-13:23:37.793 0.0
2025-11-29-13:23:37.997 0.0
2025-11-29-13:23:38.213 0.0
2025-11-29-13:23:38.425 0.0
2025-11-29-13:23:38.605 0.0
2025-11-29-13:23:38.818 0.0
2025-11-29-13:23:39.018 0.0
2025-11-29-13:23:39.235 0.0
2025-11-29-13:23:39.448 0.0
2025-11-29-13:23:39.663 0.0
2025-11-29-13:23:39.844 0.0
2025-11-29-13:23:40.063 0.0
2025-11-29-13:23:40.267 0.0
2025-11-29-13:23:40.487 0.0
2025-11-29-13:23:40.659 0.0
2025-11-29-13:23:40.884 0.0
2025-11-29-13:23:41.080 0.0
2025-11-29-13:23:41.298 0.0
2025-11-29-13:23:41.497 0.0
2025-11-29-13:23:41.712 0.0
2025-11-29-13:23:41.930 0.0
2025-11-29-13:23:42.136 0.0
2025-11-29-13:23:42.337 0.0
2025-11-29-13:23:42.528 0.0
2025-11-29-13:23:42.888 0.0
2025-11-29-13:23:42.978 0.0
2025-11-29-13:23:43.168 0.0
2025-11-29-13:23:43.385 0.0
2025-11-29-13:23:43.558 0.0
2025-11-29-13:23:43.754 0.0
2025-11-29-13:23:43.990 0.0
2025-11-29-13:23:44.181 0.0
2025-11-29-13:23:44.393 0.0
2025-11-29-13:23:44.568 0.0
2025-11-29-13:23:44.755 0.0
2025-11-29-13:23:44.971 0.0
2025-11-29-13:23:45.218 16.2
2025-11-29-13:23:45.388 16.2
2025-11-29-13:23:45.579 0.0
2025-11-29-13:23:45.790 0.0
2025-11-29-13:23:46.003 0.0
2025-11-29-13:23:46.201 0.0
2025-11-29-13:23:46.408 0.0
2025-11-29-13:23:46.614 0.0
2025-11-29-13:23:46.834 0.0
2025-11-29-13:23:47.033 0.0
2025-11-29-13:23:47.230 0.0
2025-11-29-13:23:47.444 0.0
2025-11-29-13:23:47.647 0.0
2025-11-29-13:23:47.855 0.0
2025-11-29-13:23:48.064 0.0
2025-11-29-13:23:48.264 0.0
2025-11-29-13:23:48.475 0.0
2025-11-29-13:23:48.689 0.0
2025-11-29-13:23:48.897 0.0
2025-11-29-13:23:49.144 0.0
2025-11-29-13:23:49.318 0.0
2025-11-29-13:23:49.529 0.0
2025-11-29-13:23:49.718 0.0
2025-11-29-13:23:49.936 0.0
2025-11-29-13:23:50.144 0.0
2025-11-29-13:23:50.349 0.0
2025-11-29-13:23:50.554 0.0
2025-11-29-13:23:50.752 0.0
2025-11-29-13:23:50.987 0.0
2025-11-29-13:23:51.182 0.0
2025-11-29-13:23:51.379 0.0
2025-11-29-13:23:51.571 0.0
2025-11-29-13:23:51.781 0.0
2025-11-29-13:23:51.991 0.0
2025-11-29-13:23:52.185 0.0
2025-11-29-13:23:52.394 0.0
2025-11-29-13:23:52.588 0.0
2025-11-29-13:23:52.807 0.0
2025-11-29-13:23:53.024 0.0
2025-11-29-13:23:53.207 0.0
2025-11-29-13:23:53.411 0.0
2025-11-29-13:23:53.611 0.0
2025-11-29-13:23:53.821 0.0
2025-11-29-13:23:54.036 0.0
2025-11-29-13:23:54.224 0.0
2025-11-29-13:23:54.426 0.0
2025-11-29-13:23:54.644 0.0
2025-11-29-13:23:54.844 0.0
2025-11-29-13:23:55.052 0.0
2025-11-29-13:23:55.251 0.0
2025-11-29-13:23:55.455 0.0
2025-11-29-13:23:55.659 0.0
2025-11-29-13:23:55.894 0.0
2025-11-29-13:23:56.104 0.0
2025-11-29-13:23:56.316 0.0
2025-11-29-13:23:56.529 0.0
2025-11-29-13:23:56.726 0.0
2025-11-29-13:23:56.925 0.0
2025-11-29-13:23:57.134 0.0
2025-11-29-13:23:57.349 0.0
2025-11-29-13:23:57.525 0.0
2025-11-29-13:23:57.736 0.0
2025-11-29-13:23:57.936 0.0
2025-11-29-13:23:58.146 0.0
2025-11-29-13:23:58.397 0.0
2025-11-29-13:23:58.511 0.0
2025-11-29-13:23:58.700 0.0
2025-11-29-13:23:58.902 0.0
2025-11-29-13:23:59.121 0.0
2025-11-29-13:23:59.317 0.0
2025-11-29-13:23:59.542 0.0
2025-11-29-13:23:59.728 0.0
2025-11-29-13:23:59.934 0.0
2025-11-29-13:24:00.179 0.0
2025-11-29-13:24:00.355 0.0
2025-11-29-13:24:00.538 0.0
2025-11-29-13:24:00.739 0.0
2025-11-29-13:24:00.963 0.0
2025-11-29-13:24:01.154 0.0
2025-11-29-13:24:01.353 0.0
2025-11-29-13:24:01.573 0.0
2025-11-29-13:24:01.763 0.0
2025-11-29-13:24:01.958 0.0
2025-11-29-13:24:02.170 0.0
2025-11-29-13:24:02.376 0.0
2025-11-29-13:24:02.582 0.0
2025-11-29-13:24:02.786 0.0
2025-11-29-13:24:02.990 0.0
2025-11-29-13:24:03.187 0.0
2025-11-29-13:24:03.396 0.0
2025-11-29-13:24:03.593 0.0
2025-11-29-13:24:03.797 0.0
2025-11-29-14:32:52.819 0.0
2025-11-29-14:32:52.845 0.0
2025-11-29-14:32:53.108 16.0
2025-11-29-14:32:53.253 0.0
2025-11-29-14:32:53.451 0.0
2025-11-29-14:32:53.662 0.0
2025-11-29-14:32:53.853 0.0
2025-11-29-14:32:54.063 0.0
2025-11-29-14:32:54.263 0.0
2025-11-29-14:32:54.478 0.0
2025-11-29-14:32:54.691 0.0
2025-11-29-14:32:54.891 0.0
2025-11-29-14:32:55.101 0.0
2025-11-29-14:32:55.313 0.0
2025-11-29-14:32:55.518 0.0
2025-11-29-14:32:55.722 0.0
2025-11-29-14:32:55.931 15.9
2025-11-29-14:32:56.130 0.0
2025-11-29-14:32:56.332 0.0
2025-11-29-14:32:56.544 0.0
2025-11-29-14:32:56.753 0.0
2025-11-29-14:32:56.957 0.0
2025-11-29-14:32:57.177 0.0
2025-11-29-14:32:57.375 0.0
2025-11-29-14:32:57.607 0.0
2025-11-29-14:32:57.803 0.0
2025-11-29-14:32:58.030 0.0
2025-11-29-14:32:58.215 0.0
2025-11-29-14:32:58.416 0.0
2025-11-29-14:32:58.629 0.0
2025-11-29-14:32:58.841 16.5
2025-11-29-14:32:59.048 16.3
2025-11-29-14:32:59.273 16.5
2025-11-29-14:32:59.469 0.0
2025-11-29-14:32:59.674 0.0
2025-11-29-14:32:59.901 0.0
2025-11-29-14:33:00.084 0.0
2025-11-29-14:33:00.279 0.0
2025-11-29-14:33:00.501 0.0
2025-11-29-14:33:00.697 0.0
2025-11-29-14:33:00.896 0.0
2025-11-29-14:33:01.096 0.0
2025-11-29-14:33:01.317 0.0
2025-11-29-14:33:01.508 0.0
2025-11-29-14:33:01.716 0.0
2025-11-29-14:33:01.907 0.0
2025-11-29-14:33:02.131 0.0
2025-11-29-14:33:02.321 0.0
2025-11-29-14:33:02.547 16.5
2025-11-29-14:33:02.751 0.0
2025-11-29-14:33:02.960 0.0
2025-11-29-14:33:03.170 0.0
2025-11-29-14:33:03.355 0.0
2025-11-29-14:33:03.572 0.0
2025-11-29-14:33:03.780 0.0
2025-11-29-14:33:04.023 0.0
2025-11-29-14:33:04.228 0.0
2025-11-29-14:33:04.446 0.0
2025-11-29-14:33:04.626 0.0
2025-11-29-14:33:04.816 15.9
2025-11-29-14:33:05.030 0.0
2025-11-29-14:33:05.235 0.0
2025-11-29-14:33:05.421 0.0
2025-11-29-14:33:05.650 0.0
2025-11-29-14:33:05.853 16.3
2025-11-29-14:33:06.054 0.0
2025-11-29-14:33:06.251 0.0
2025-11-29-14:33:06.463 0.0
2025-11-29-14:33:06.668 0.0
2025-11-29-14:33:06.866 0.0
2025-11-29-14:33:07.072 0.0
2025-11-29-14:33:07.289 0.0
2025-11-29-14:33:07.504 0.0
2025-11-29-14:33:07.716 0.0
2025-11-29-14:33:07.909 0.0
2025-11-29-14:33:08.106 0.0
2025-11-29-14:33:08.338 0.0
2025-11-29-14:33:08.517 0.0
2025-11-29-14:33:08.722 0.0
2025-11-29-14:33:08.919 0.0
2025-11-29-14:33:09.113 0.0
2025-11-29-14:33:09.337 0.0
2025-11-29-14:33:09.550 0.0
2025-11-29-14:33:09.743 0.0
2025-11-29-14:33:09.955 0.0
2025-11-29-14:33:10.155 0.0
2025-11-29-14:33:10.355 0.0
2025-11-29-14:33:10.555 0.0
2025-11-29-14:33:10.771 0.0
2025-11-29-14:33:10.972 0.0
2025-11-29-14:33:11.184 0.0
2025-11-29-14:33:11.385 0.0
2025-11-29-14:33:11.585 0.0
2025-11-29-14:33:11.814 0.0
2025-11-29-14:33:12.034 0.0
2025-11-29-14:33:12.221 0.0
2025-11-29-14:33:12.420 0.0
2025-11-29-14:33:12.625 0.0
2025-11-29-14:33:12.832 0.0
2025-11-29-14:33:13.048 0.0
2025-11-29-14:33:13.253 0.0
2025-11-29-14:33:13.468 0.0
2025-11-29-14:33:13.671 0.0
2025-11-29-14:33:13.882 0.0
2025-11-29-14:33:14.081 0.0
2025-11-29-14:33:14.300 0.0
2025-11-29-14:33:14.510 0.0
2025-11-29-14:33:14.726 0.0
2025-11-29-14:33:14.924 0.0
2025-11-29-14:33:15.120 0.0
2025-11-29-14:33:15.331 0.0
2025-11-29-14:33:15.532 0.0
2025-11-29-14:33:15.737 0.0
2025-11-29-14:33:15.954 0.0
2025-11-29-14:33:16.154 0.0
2025-11-29-14:33:16.386 0.0
2025-11-29-14:33:16.589 0.0
2025-11-29-14:33:16.788 0.0
2025-11-29-14:33:16.999 0.0
2025-11-29-14:33:17.199 0.0
2025-11-29-14:33:17.418 0.0
2025-11-29-14:33:17.621 0.0
2025-11-29-14:33:17.821 0.0
2025-11-29-14:33:18.025 0.0
2025-11-29-14:33:18.229 0.0
2025-11-29-14:33:18.436 0.0
2025-11-29-14:33:18.654 0.0
2025-11-29-14:33:18.853 0.0
2025-11-29-14:33:19.036 0.0
2025-11-29-14:33:19.260 0.0
2025-11-29-14:33:19.455 0.0
2025-11-29-14:33:19.655 0.0
2025-11-29-14:33:19.859 0.0
2025-11-29-14:33:20.071 0.0
2025-11-29-14:33:20.298 0.0
2025-11-29-14:33:20.488 0.0
2025-11-29-14:33:20.682 0.0
2025-11-29-14:33:20.886 0.0
2025-11-29-14:33:21.090 0.0
2025-11-29-14:33:21.297 0.0
2025-11-29-14:33:21.522 0.0
2025-11-29-14:33:21.727 0.0
2025-11-29-14:33:21.953 0.0
2025-11-29-14:33:22.152 0.0
2025-11-29-14:33:22.359 0.0
2025-11-29-14:33:22.568 0.0
2025-11-29-14:33:22.778 0.0
2025-11-29-14:33:22.969 0.0
2025-11-29-14:33:23.175 0.0
2025-11-29-14:33:23.376 0.0
2025-11-29-14:33:23.586 0.0
2025-11-29-14:33:23.812 0.0
2025-11-29-14:33:24.015 0.0
2025-11-29-14:33:24.219 0.0
2025-11-29-14:33:24.440 0.0
2025-11-29-14:33:24.667 0.0
2025-11-29-14:33:24.848 0.0
2025-11-29-14:33:25.057 16.3
2025-11-29-14:33:25.237 0.0
2025-11-29-14:33:25.454 0.0
2025-11-29-14:33:25.663 0.0
2025-11-29-14:33:25.851 0.0
2025-11-29-14:33:26.071 0.0
2025-11-29-14:33:26.288 0.0
2025-11-29-14:33:26.499 0.0
2025-11-29-14:33:26.705 15.9
2025-11-29-14:33:26.903 0.0
2025-11-29-14:33:27.103 0.0
2025-11-29-14:33:27.305 0.0
2025-11-29-14:33:27.522 0.0
2025-11-29-14:33:27.737 0.0
2025-11-29-14:33:27.932 0.0
2025-11-29-14:33:28.149 0.0
2025-11-29-14:33:28.352 0.0
2025-11-29-14:33:28.556 0.0
2025-11-29-14:33:28.775 0.0
2025-11-29-14:33:28.995 0.0
2025-11-29-14:33:29.196 0.0
2025-11-29-14:33:29.426 16.3
2025-11-29-14:33:29.625 16.3
2025-11-29-14:33:29.842 16.3
2025-11-29-14:33:30.054 16.3
2025-11-29-14:33:30.234 0.0
2025-11-29-14:33:30.438 0.0
2025-11-29-14:33:30.636 0.0
2025-11-29-14:33:30.866 16.0
2025-11-29-14:33:31.058 15.9
2025-11-29-14:33:31.261 15.9
2025-11-29-14:33:31.475 15.9
2025-11-29-14:33:31.692 15.9
2025-11-29-14:33:31.913 15.9
2025-11-29-14:33:32.118 15.9
2025-11-29-14:33:32.294 0.0
2025-11-29-14:33:32.486 0.0
2025-11-29-14:33:32.704 0.0
2025-11-29-14:33:32.921 0.0
2025-11-29-14:33:33.129 0.0
2025-11-29-14:33:33.335 0.0
2025-11-29-14:33:33.552 0.0
2025-11-29-14:33:33.745 0.0
2025-11-29-14:33:33.971 0.0
2025-11-29-14:33:34.173 0.0
2025-11-29-14:33:34.375 0.0
2025-11-29-14:33:34.594 16.0
2025-11-29-14:33:34.882 0.0
2025-11-29-14:33:35.036 16.0
2025-11-29-14:33:35.185 0.0
2025-11-29-14:33:35.380 0.0
2025-11-29-14:33:35.601 0.0
2025-11-29-14:33:35.818 0.0
2025-11-29-14:33:36.023 0.0
2025-11-29-14:33:36.221 0.0
2025-11-29-14:33:36.439 0.0
2025-11-29-14:33:36.640 0.0
2025-11-29-14:33:36.851 0.0
2025-11-29-14:33:37.054 0.0
2025-11-29-14:33:37.270 0.0
2025-11-29-14:33:37.471 0.0
2025-11-29-14:33:37.682 0.0
2025-11-29-14:33:37.904 0.0
2025-11-29-14:33:38.086 0.0
2025-11-29-14:33:38.297 0.0
2025-11-29-14:33:38.492 0.0
2025-11-29-14:33:38.700 0.0
2025-11-29-14:33:38.922 0.0
2025-11-29-14:33:39.119 0.0
2025-11-29-14:33:39.308 0.0
2025-11-29-14:33:39.516 0.0
2025-11-29-14:33:39.716 0.0
2025-11-29-14:33:39.932 0.0
2025-11-29-14:33:40.138 0.0
2025-11-29-14:33:40.357 0.0
2025-11-29-14:33:40.573 0.0
2025-11-29-14:33:40.791 0.0
2025-11-29-14:33:41.001 0.0
2025-11-29-14:33:41.208 0.0
2025-11-29-14:33:41.414 0.0
2025-11-29-14:33:41.631 0.0
2025-11-29-14:33:41.837 0.0
2025-11-29-14:33:42.051 0.0
2025-11-29-14:33:42.249 0.0
2025-11-29-14:33:42.445 0.0
2025-11-29-14:33:42.655 0.0
2025-11-29-14:33:42.852 0.0
2025-11-29-14:33:43.070 0.0
2025-11-29-14:33:43.301 0.0
2025-11-29-14:33:43.482 0.0
2025-11-29-14:33:43.703 0.0
2025-11-29-14:33:43.968 0.0
2025-11-29-14:33:44.063 0.0
2025-11-29-14:33:44.232 0.0
2025-11-29-14:33:44.445 0.0
2025-11-29-14:33:44.680 16.3
2025-11-29-14:33:44.872 0.0
2025-11-29-14:33:45.059 0.0
2025-11-29-14:33:45.254 0.0
2025-11-29-14:33:45.474 16.0
2025-11-29-14:33:45.686 15.9
2025-11-29-14:33:45.904 16.3
2025-11-29-14:33:46.097 16.0
2025-11-29-14:33:46.314 0.0
2025-11-29-14:33:46.499 0.0
2025-11-29-14:33:46.715 0.0
2025-11-29-14:33:46.914 0.0
2025-11-29-14:33:47.119 0.0
2025-11-29-14:33:47.338 16.0
2025-11-29-14:33:47.543 16.0
2025-11-29-14:33:47.754 16.0
2025-11-29-14:33:47.962 16.0
2025-11-29-14:33:48.173 16.3
2025-11-29-14:33:48.363 16.3
2025-11-29-14:33:48.574 16.0
2025-11-29-14:33:48.772 0.0
2025-11-29-14:33:48.982 0.0
2025-11-29-14:33:49.205 0.0
2025-11-29-14:33:49.434 0.0
2025-11-29-14:33:49.639 0.0
2025-11-29-14:33:49.868 0.0
2025-11-29-14:33:50.068 0.0
2025-11-29-14:33:50.279 0.0
2025-11-29-14:33:50.480 0.0
2025-11-29-14:33:50.742 0.0
2025-11-29-14:33:50.881 0.0
2025-11-29-14:33:51.101 0.0
2025-11-29-14:33:51.295 0.0
2025-11-29-14:33:51.489 0.0
2025-11-29-14:33:51.677 0.0
2025-11-29-14:33:51.861 0.0
2025-11-29-14:33:52.097 0.0
2025-11-29-14:33:52.278 0.0
2025-11-29-14:33:52.479 0.0
2025-11-29-14:33:52.687 0.0
2025-11-29-14:33:52.888 0.0
2025-11-29-14:33:53.084 0.0
2025-11-29-14:33:53.298 0.0
2025-11-29-14:33:53.530 0.0
2025-11-29-14:33:53.718 0.0
2025-11-29-14:33:53.930 0.0
2025-11-29-14:33:54.109 0.0
2025-11-29-14:33:54.337 0.0
2025-11-29-14:33:54.534 0.0
2025-11-29-14:33:54.743 0.0
2025-11-29-14:33:54.963 0.0
2025-11-29-14:33:55.145 0.0
2025-11-29-14:33:55.359 15.9
2025-11-29-14:33:55.551 0.0
2025-11-29-14:33:55.759 0.0
2025-11-29-14:33:55.964 0.0
2025-11-29-14:33:56.168 0.0
2025-11-29-14:33:56.376 0.0
2025-11-29-14:33:56.586 0.0
2025-11-29-14:33:56.781 0.0
2025-11-29-14:33:56.993 0.0
2025-11-29-14:33:57.237 0.0
2025-11-29-14:33:57.443 0.0
2025-11-29-14:33:57.668 0.0
2025-11-29-14:33:57.893 0.0
2025-11-29-14:33:58.076 0.0
2025-11-29-14:33:58.267 0.0
2025-11-29-14:33:58.470 0.0
2025-11-29-14:33:58.743 0.0
2025-11-29-14:33:58.879 0.0
2025-11-29-14:33:59.096 16.0
2025-11-29-14:33:59.302 16.0
2025-11-29-14:33:59.599 0.0
2025-11-29-14:33:59.644 0.0
2025-11-29-14:33:59.854 0.0
2025-11-29-14:34:00.096 0.0
2025-11-29-14:34:00.259 0.0
2025-11-29-14:34:00.473 0.0
2025-11-29-14:34:00.689 0.0
2025-11-29-14:34:00.887 0.0
2025-11-29-14:34:01.099 0.0
2025-11-29-14:34:01.288 0.0
2025-11-29-14:34:01.513 0.0
2025-11-29-14:34:01.708 0.0
2025-11-29-14:34:01.908 0.0
2025-11-29-14:34:02.116 0.0
2025-11-29-14:34:02.329 0.0
2025-11-29-14:34:02.529 0.0
2025-11-29-14:34:02.741 0.0
2025-11-29-14:34:02.939 0.0
2025-11-29-14:34:03.144 0.0
2025-11-29-14:34:03.351 0.0
2025-11-29-14:34:03.560 0.0
2025-11-29-14:34:03.782 0.0
2025-11-29-14:34:04.023 0.0
2025-11-29-14:34:04.198 0.0
2025-11-29-14:34:04.411 0.0
2025-11-29-14:34:04.616 0.0
2025-11-29-14:34:04.824 0.0
2025-11-29-14:34:05.026 0.0
2025-11-29-14:34:05.230 0.0
2025-11-29-14:34:05.454 0.0
2025-11-29-14:34:05.670 0.0
2025-11-29-14:34:05.865 0.0
2025-11-29-14:34:06.126 0.0
2025-11-29-14:34:06.272 0.0
2025-11-29-14:34:06.487 0.0
2025-11-29-14:34:06.698 0.0
2025-11-29-14:34:06.904 0.0
2025-11-29-14:34:07.120 0.0
2025-11-29-14:34:07.305 0.0
2025-11-29-14:34:07.516 0.0
2025-11-29-14:34:07.747 0.0
2025-11-29-14:34:07.926 0.0
2025-11-29-14:34:08.134 0.0
2025-11-29-14:34:08.332 0.0
2025-11-29-14:34:08.532 0.0
2025-11-29-14:34:08.735 0.0
2025-11-29-14:34:08.938 0.0
2025-11-29-14:34:09.151 0.0
2025-11-29-14:34:09.343 0.0
2025-11-29-14:34:09.551 0.0
2025-11-29-14:34:09.771 0.0
2025-11-29-14:34:09.966 0.0
2025-11-29-14:34:10.164 0.0
2025-11-29-14:34:10.384 0.0
2025-11-29-14:34:10.608 0.0
2025-11-29-14:34:10.795 0.0
2025-11-29-14:34:10.988 0.0
2025-11-29-14:34:11.194 0.0
2025-11-29-14:34:11.406 0.0
2025-11-29-14:34:11.605 0.0
2025-11-29-14:34:11.799 0.0
2025-11-29-14:34:12.018 0.0
2025-11-29-14:34:12.224 0.0
2025-11-29-14:34:12.443 0.0
2025-11-29-14:34:12.644 0.0
2025-11-29-14:34:12.854 0.0
2025-11-29-14:34:13.050 0.0
2025-11-29-14:34:13.254 0.0
2025-11-29-14:34:13.465 0.0
2025-11-29-14:34:13.668 0.0
2025-11-29-14:34:13.879 0.0
2025-11-29-14:34:14.064 0.0
2025-11-29-14:34:14.280 0.0
2025-11-29-14:34:14.483 0.0
2025-11-29-14:34:14.679 0.0
2025-11-29-14:34:14.905 0.0
2025-11-29-14:34:15.097 0.0
2025-11-29-14:34:15.291 0.0
2025-11-29-14:34:15.508 0.0
2025-11-29-14:34:15.721 0.0
2025-11-29-14:34:15.927 0.0
2025-11-29-14:34:16.133 0.0
2025-11-29-14:34:16.329 0.0
2025-11-29-14:34:16.538 0.0
2025-11-29-14:34:16.735 0.0
2025-11-29-14:34:16.949 0.0
2025-11-29-14:34:17.149 0.0
2025-11-29-14:34:17.355 0.0
2025-11-29-14:34:17.566 0.0
2025-11-29-14:34:17.757 0.0
2025-11-29-14:34:17.979 0.0
2025-11-29-14:34:18.171 0.0
2025-11-29-14:34:18.393 0.0
2025-11-29-14:34:18.604 0.0
2025-11-29-14:34:18.791 0.0
2025-11-29-14:34:19.001 0.0
2025-11-29-14:34:19.193 0.0
2025-11-29-14:34:19.407 0.0
2025-11-29-14:34:19.617 0.0
2025-11-29-14:34:19.814 0.0
2025-11-29-14:34:20.026 0.0
2025-11-29-14:34:20.229 16.5
2025-11-29-14:34:20.432 16.5
2025-11-29-14:34:20.648 16.5
2025-11-29-14:34:20.855 16.5
2025-11-29-14:34:21.051 16.3
2025-11-29-14:34:21.280 16.5
2025-11-29-14:34:21.449 16.5
2025-11-29-14:34:21.659 16.5
2025-11-29-14:34:21.859 16.3
2025-11-29-14:34:22.078 16.3
2025-11-29-14:34:22.278 16.3
2025-11-29-14:34:22.476 16.3
2025-11-29-14:34:22.668 16.3
2025-11-29-14:34:22.887 0.0
2025-11-29-14:34:23.093 16.3
2025-11-29-14:34:23.294 16.3
2025-11-29-14:34:23.485 16.3
2025-11-29-14:34:23.692 16.3
2025-11-29-14:34:23.897 16.3
2025-11-29-14:34:24.102 16.2
2025-11-29-14:34:24.312 16.3
2025-11-29-14:34:24.530 16.3
2025-11-29-14:34:24.725 0.0
2025-11-29-14:34:24.930 0.0
2025-11-29-14:34:25.132 0.0
2025-11-29-14:34:25.333 0.0
2025-11-29-14:34:25.547 0.0
2025-11-29-14:34:25.741 0.0
2025-11-29-14:34:25.960 0.0
2025-11-29-14:34:26.161 0.0
2025-11-29-14:34:26.354 0.0
2025-11-29-14:34:26.562 0.0
2025-11-29-14:34:26.764 0.0
2025-11-29-14:34:26.975 0.0
2025-11-29-14:34:27.184 15.7
2025-11-29-14:34:27.379 0.0
2025-11-29-14:34:27.602 16.3
2025-11-29-14:34:27.804 0.0
2025-11-29-14:34:28.021 0.0
2025-11-29-14:34:28.234 15.9
2025-11-29-14:34:28.420 0.0
2025-11-29-14:34:28.691 16.0
2025-11-29-14:34:28.779 0.0
2025-11-29-14:34:29.005 16.3
2025-11-29-14:34:29.202 0.0
2025-11-29-14:34:29.418 16.3
2025-11-29-14:34:29.625 0.0
2025-11-29-14:34:29.840 15.7
2025-11-29-14:34:30.015 0.0
2025-11-29-14:34:30.231 0.0
2025-11-29-14:34:30.446 0.0
2025-11-29-14:34:30.631 0.0
2025-11-29-14:34:30.843 15.9
2025-11-29-14:34:31.055 16.3
2025-11-29-14:34:31.254 16.3
2025-11-29-14:34:31.464 0.0
2025-11-29-14:34:31.691 15.9
2025-11-29-14:34:31.888 0.0
2025-11-29-14:34:32.092 0.0
2025-11-29-14:34:32.309 0.0
2025-11-29-14:34:32.493 15.9
2025-11-29-14:34:32.702 0.0
2025-11-29-14:34:32.927 0.0
2025-11-29-14:34:33.133 0.0
2025-11-29-14:34:33.339 0.0
2025-11-29-14:34:33.547 0.0
2025-11-29-14:34:33.755 0.0
2025-11-29-14:34:33.955 0.0
2025-11-29-14:34:34.168 0.0
2025-11-29-14:34:34.374 0.0
2025-11-29-14:34:34.576 0.0
2025-11-29-14:34:34.790 0.0
2025-11-29-14:34:35.016 15.9
2025-11-29-14:34:35.236 15.9
2025-11-29-14:34:35.431 15.9
2025-11-29-14:34:35.653 0.0
2025-11-29-14:34:35.850 15.9
2025-11-29-14:34:36.056 15.9
2025-11-29-14:34:36.251 15.9
2025-11-29-14:34:36.448 0.0
2025-11-29-14:34:36.657 0.0
2025-11-29-14:34:36.868 16.3
2025-11-29-14:34:37.066 16.3
2025-11-29-14:34:37.277 0.0
2025-11-29-14:34:37.487 16.5
2025-11-29-14:34:37.696 16.5
2025-11-29-14:34:37.896 0.0
2025-11-29-14:34:38.121 0.0
2025-11-29-14:34:38.304 0.0
2025-11-29-14:34:38.505 0.0
2025-11-29-14:34:38.718 0.0
2025-11-29-14:34:38.913 16.0
2025-11-29-14:34:39.139 16.3
2025-11-29-14:34:39.335 16.0
2025-11-29-14:34:39.537 16.0
2025-11-29-14:34:39.742 16.0
2025-11-29-14:34:39.947 16.0
2025-11-29-14:34:40.144 16.0
2025-11-29-14:34:40.355 0.0
2025-11-29-14:34:40.554 0.0
2025-11-29-14:34:40.753 0.0
2025-11-29-14:34:40.955 0.0
2025-11-29-14:34:41.263 0.0
2025-11-29-14:34:41.375 0.0
2025-11-29-14:34:41.516 0.0
2025-11-29-14:34:41.764 0.0
2025-11-29-14:34:41.947 0.0
2025-11-29-14:34:42.150 0.0
2025-11-29-14:34:42.331 15.9
2025-11-29-14:34:42.537 0.0
2025-11-29-14:34:42.746 16.0
2025-11-29-14:34:42.953 0.0
2025-11-29-14:34:43.154 0.0
2025-11-29-14:34:43.367 0.0
2025-11-29-14:34:43.582 0.0
2025-11-29-14:34:43.775 16.0
2025-11-29-14:34:43.985 0.0
2025-11-29-14:34:44.192 16.3
2025-11-29-14:34:44.394 16.3
2025-11-29-14:34:44.599 0.0
2025-11-29-14:34:44.806 0.0
2025-11-29-14:34:45.034 0.0
2025-11-29-14:34:45.242 0.0
2025-11-29-14:34:45.445 0.0
2025-11-29-14:34:45.660 0.0
2025-11-29-14:34:45.857 0.0
2025-11-29-14:34:46.066 0.0
2025-11-29-14:34:46.287 0.0
2025-11-29-14:34:46.494 0.0
2025-11-29-14:34:46.688 0.0
2025-11-29-14:34:46.897 0.0
2025-11-29-14:34:47.101 16.0
2025-11-29-14:34:47.318 0.0
2025-11-29-14:34:47.505 0.0
2025-11-29-14:34:47.705 0.0
2025-11-29-14:34:47.926 0.0
2025-11-29-14:34:48.118 0.0
2025-11-29-14:34:48.342 0.0
2025-11-29-14:34:48.538 0.0
2025-11-29-14:34:48.746 16.3
2025-11-29-14:34:48.960 16.3
2025-11-29-14:34:49.159 16.3
2025-11-29-14:34:49.371 0.0
2025-11-29-14:34:49.574 0.0
2025-11-29-14:34:49.863 0.0
2025-11-29-14:34:49.980 0.0
2025-11-29-14:34:50.114 0.0
2025-11-29-14:34:50.321 0.0
2025-11-29-14:34:50.524 0.0
2025-11-29-14:34:50.720 0.0
2025-11-29-14:34:50.927 0.0
2025-11-29-14:34:51.131 16.0
2025-11-29-14:34:51.324 0.0
2025-11-29-14:34:51.541 0.0
2025-11-29-14:34:51.738 0.0
2025-11-29-14:34:51.950 15.9
2025-11-29-14:34:52.152 0.0
2025-11-29-14:34:52.358 0.0
2025-11-29-14:34:52.560 0.0
2025-11-29-14:34:52.774 0.0
2025-11-29-14:34:52.982 0.0
2025-11-29-14:34:53.187 15.9
2025-11-29-14:34:53.388 0.0
2025-11-29-14:34:53.589 0.0
2025-11-29-14:34:53.794 15.9
2025-11-29-14:34:53.986 16.0
2025-11-29-14:34:54.192 16.0
2025-11-29-14:34:54.386 16.0
2025-11-29-14:34:54.591 16.0
2025-11-29-14:34:54.808 15.9
2025-11-29-14:34:55.008 15.9
2025-11-29-14:34:55.202 0.0
2025-11-29-14:34:55.439 15.9
2025-11-29-14:34:55.620 16.0
2025-11-29-14:34:55.823 16.0
2025-11-29-14:34:56.030 16.0
2025-11-29-14:34:56.220 16.0
2025-11-29-14:34:56.426 16.0
2025-11-29-14:34:56.629 16.5
2025-11-29-14:34:56.855 0.0
2025-11-29-14:34:57.082 16.5
2025-11-29-14:34:57.264 16.3
2025-11-29-14:34:57.474 0.0
2025-11-29-14:34:57.672 0.0
2025-11-29-14:34:57.882 0.0
2025-11-29-14:34:58.096 0.0
2025-11-29-14:34:58.293 0.0
2025-11-29-14:34:58.501 0.0
2025-11-29-14:34:58.703 0.0
2025-11-29-14:34:58.924 0.0
2025-11-29-14:34:59.129 0.0
2025-11-29-14:34:59.337 0.0
2025-11-29-14:34:59.546 0.0
2025-11-29-14:34:59.766 0.0
2025-11-29-14:34:59.960 0.0
2025-11-29-14:35:00.184 0.0
2025-11-29-14:35:00.394 0.0
2025-11-29-14:35:00.597 0.0
2025-11-29-14:35:00.807 15.9
2025-11-29-14:35:01.040 15.9
2025-11-29-14:35:01.239 15.9
2025-11-29-14:35:01.436 16.0
2025-11-29-14:35:01.649 16.0
2025-11-29-14:35:01.851 0.0
2025-11-29-14:35:02.069 0.0
2025-11-29-14:35:02.263 0.0
2025-11-29-14:35:02.457 0.0
2025-11-29-14:35:02.663 0.0
2025-11-29-14:35:02.941 16.3
2025-11-29-14:35:03.004 16.3
2025-11-29-14:35:03.211 16.3
2025-11-29-14:35:03.410 16.3
2025-11-29-14:35:03.618 16.3
2025-11-29-14:35:03.814 16.3
2025-11-29-14:35:04.010 16.3
2025-11-29-14:35:04.221 16.3
2025-11-29-14:35:04.419 16.0
2025-11-29-14:35:04.632 16.3
2025-11-29-14:35:04.841 0.0
2025-11-29-14:35:05.036 16.0
2025-11-29-14:35:05.242 0.0
2025-11-29-14:35:05.450 15.9
2025-11-29-14:35:05.649 0.0
2025-11-29-14:35:05.854 0.0
2025-11-29-14:35:06.060 0.0
2025-11-29-14:35:06.260 0.0
2025-11-29-14:35:06.467 0.0
2025-11-29-14:35:06.668 0.0
2025-11-29-14:35:06.891 15.9
2025-11-29-14:35:07.094 16.0
2025-11-29-14:35:07.298 15.9
2025-11-29-16:02:29.435 0.0
2025-11-29-16:02:29.462 0.0
2025-11-29-16:02:29.676 0.0
2025-11-29-16:02:29.870 0.0
2025-11-29-16:02:30.067 0.0
2025-11-29-16:02:30.282 0.0
2025-11-29-16:02:30.486 0.0
2025-11-29-16:02:30.758 18.0
2025-11-29-16:02:30.900 0.0
2025-11-29-16:02:31.087 0.0
2025-11-29-16:02:31.280 0.0
2025-11-29-16:02:31.490 0.0
2025-11-29-16:02:31.713 0.0
2025-11-29-16:02:31.918 0.0
2025-11-29-16:02:32.109 0.0
2025-11-29-16:02:32.312 0.0
2025-11-29-16:02:32.527 0.0
2025-11-29-16:02:32.732 0.0
2025-11-29-16:02:33.087 0.0
2025-11-29-16:02:33.230 0.0
2025-11-29-16:02:33.386 0.0
2025-11-29-16:02:33.571 0.0
2025-11-29-16:02:33.784 0.0
2025-11-29-16:02:34.003 0.0
2025-11-29-16:02:34.207 0.0
2025-11-29-16:02:34.381 0.0
2025-11-29-16:02:34.790 0.0
2025-11-29-16:02:35.083 0.0
2025-11-29-16:02:35.431 0.0
2025-11-29-16:02:35.773 0.0
2025-11-29-16:02:36.035 0.0
2025-11-29-16:02:36.302 0.0
2025-11-29-16:02:36.611 0.0
2025-11-29-16:02:36.921 0.0
2025-11-29-16:02:37.332 0.0
2025-11-29-16:02:37.585 0.0
2025-11-29-16:02:37.793 0.0
2025-11-29-16:02:38.141 0.0
2025-11-29-16:02:38.443 0.0
2025-11-29-16:02:39.099 0.0
2025-11-29-16:02:39.837 18.0
2025-11-29-16:02:40.329 0.0
2025-11-29-16:02:40.786 0.0
2025-11-29-16:02:41.395 18.0
2025-11-29-16:02:41.998 0.0
2025-11-29-16:02:42.642 0.0
2025-11-29-16:02:43.173 0.0
2025-11-29-16:02:43.899 0.0
2025-11-29-16:02:44.552 0.0
2025-11-29-16:02:44.950 0.0
2025-11-29-16:02:45.120 0.0
2025-11-29-16:02:45.334 0.0
2025-11-29-16:02:45.539 0.0
2025-11-29-16:02:45.783 0.0
2025-11-29-16:02:45.960 0.0
2025-11-29-16:02:46.181 0.0
2025-11-29-16:02:46.368 0.0
2025-11-29-16:02:46.508 0.0
2025-11-29-16:02:46.684 0.0
2025-11-29-16:02:46.892 0.0
2025-11-29-16:02:47.101 0.0
2025-11-29-16:02:47.306 0.0
2025-11-29-16:02:47.494 0.0
2025-11-29-16:02:47.698 0.0
2025-11-29-16:02:47.891 0.0
2025-11-29-16:02:48.105 0.0
2025-11-29-16:02:48.304 0.0
2025-11-29-16:02:48.511 0.0
2025-11-29-16:02:48.705 0.0
2025-11-29-16:02:48.913 0.0
2025-11-29-16:02:49.111 0.0
2025-11-29-16:02:49.334 0.0
2025-11-29-16:02:49.538 0.0
2025-11-29-16:02:49.737 0.0
2025-11-29-16:02:49.936 0.0
2025-11-29-16:02:50.142 0.0
2025-11-29-16:02:50.338 0.0
2025-11-29-16:02:50.545 0.0
2025-11-29-16:02:50.752 0.0
2025-11-29-16:02:50.946 0.0
2025-11-29-16:02:51.154 0.0
2025-11-29-16:02:51.422 0.0
2025-11-29-16:02:51.563 0.0
2025-11-29-16:02:51.760 0.0
2025-11-29-16:02:51.971 0.0
2025-11-29-16:02:52.163 0.0
2025-11-29-16:02:52.367 0.0
2025-11-29-16:02:52.654 0.0
2025-11-29-16:02:52.770 18.2
2025-11-29-16:02:52.914 18.2
2025-11-29-16:02:53.110 0.0
2025-11-29-16:02:53.313 0.0
2025-11-29-16:02:53.498 0.0
2025-11-29-16:02:53.698 0.0
2025-11-29-16:02:53.901 0.0
2025-11-29-16:02:54.100 0.0
2025-11-29-16:02:54.295 0.0
2025-11-29-16:02:54.505 0.0
2025-11-29-16:02:54.699 0.0
2025-11-29-16:02:54.910 0.0
2025-11-29-16:02:55.109 0.0
2025-11-29-16:02:55.305 0.0
2025-11-29-16:02:55.514 0.0
2025-11-29-16:02:55.714 0.0
2025-11-29-16:02:55.920 0.0
2025-11-29-16:02:56.118 0.0
2025-11-29-16:02:56.325 0.0
2025-11-29-16:02:56.527 0.0
2025-11-29-16:02:56.725 18.0
2025-11-29-16:02:56.934 17.5
2025-11-29-16:02:57.153 0.0
2025-11-29-16:02:57.343 0.0
2025-11-29-16:02:57.550 0.0
2025-11-29-16:02:57.751 0.0
2025-11-29-16:02:57.952 0.0
2025-11-29-16:02:58.163 0.0
2025-11-29-16:02:58.365 0.0
2025-11-29-16:02:58.561 0.0
2025-11-29-16:02:58.782 0.0
2025-11-29-16:02:58.973 0.0
2025-11-29-16:02:59.170 0.0
2025-11-29-16:02:59.401 0.0
2025-11-29-16:02:59.594 0.0
2025-11-29-16:02:59.809 0.0
2025-11-29-16:03:00.000 0.0
2025-11-29-16:03:00.212 0.0
2025-11-29-16:03:00.408 0.0
2025-11-29-16:03:00.617 0.0
2025-11-29-16:03:00.828 0.0
2025-11-29-16:03:01.015 0.0
2025-11-29-16:03:01.228 0.0
2025-11-29-16:03:01.446 0.0
2025-11-29-16:03:01.649 0.0
2025-11-29-16:03:01.863 0.0
2025-11-29-16:03:02.044 0.0
2025-11-29-16:03:02.249 0.0
2025-11-29-16:03:02.457 0.0
2025-11-29-16:03:02.665 0.0
2025-11-29-16:03:02.859 0.0
2025-11-29-16:03:03.058 0.0
2025-11-29-16:03:03.263 0.0
2025-11-29-16:03:03.545 0.0
2025-11-29-16:03:03.651 0.0
2025-11-29-16:03:03.798 0.0
2025-11-29-16:03:03.999 0.0
2025-11-29-16:03:04.197 0.0
2025-11-29-16:03:04.394 0.0
2025-11-29-16:03:04.602 0.0
2025-11-29-16:03:04.799 0.0
2025-11-29-16:03:05.005 0.0
2025-11-29-16:03:05.204 0.0
2025-11-29-16:03:05.401 0.0
2025-11-29-16:03:05.632 0.0
2025-11-29-16:03:05.833 0.0
2025-11-29-16:03:06.020 0.0
2025-11-29-16:03:06.224 0.0
2025-11-29-16:03:06.433 0.0
2025-11-29-16:03:06.632 0.0
2025-11-29-16:03:06.840 0.0
2025-11-29-16:03:07.043 0.0
2025-11-29-16:03:07.247 0.0
2025-11-29-16:03:07.467 0.0
2025-11-29-16:03:07.676 0.0
2025-11-29-16:03:07.893 0.0
2025-11-29-16:03:08.104 17.6
2025-11-29-16:03:08.298 0.0
2025-11-29-16:03:08.520 0.0
2025-11-29-16:03:08.711 0.0
2025-11-29-16:03:08.917 0.0
2025-11-29-16:03:09.114 0.0
2025-11-29-16:03:09.320 0.0
2025-11-29-16:03:09.518 0.0
2025-11-29-16:03:09.718 0.0
2025-11-29-16:03:09.939 0.0
2025-11-29-16:03:10.119 0.0
2025-11-29-16:03:10.322 0.0
2025-11-29-16:03:10.538 0.0
2025-11-29-16:03:10.716 0.0
2025-11-29-16:03:10.935 0.0
2025-11-29-16:03:11.132 0.0
2025-11-29-16:02:34.790 0.0
2025-11-29-16:02:34.790 0.0
2025-11-29-16:02:35.090 0.0
2025-11-29-16:02:35.440 0.0
2025-11-29-16:02:35.773 0.0
2025-11-29-16:02:36.035 0.0
2025-11-29-16:02:36.302 0.0
2025-11-29-16:02:36.611 0.0
2025-11-29-16:02:36.921 0.0
2025-11-29-16:02:37.332 0.0
2025-11-29-16:02:37.585 0.0
2025-11-29-16:02:37.793 0.0
2025-11-29-16:02:38.141 0.0
2025-11-29-16:02:38.443 0.0
2025-11-29-16:02:39.099 0.0
2025-11-29-16:02:39.844 0.0
2025-11-29-16:02:40.330 0.0
2025-11-29-16:02:40.786 0.0
2025-11-29-16:02:41.395 0.0
2025-11-29-16:02:41.995 0.0
2025-11-29-16:02:42.642 0.0
2025-11-29-16:02:43.173 0.0
2025-11-29-16:02:43.899 0.0
2025-11-29-16:02:44.552 0.0
2025-11-29-16:02:38.443 0.0
2025-11-29-16:02:38.443 0.0
2025-11-29-16:02:39.099 0.0
2025-11-29-16:02:39.844 20.0
2025-11-29-16:02:40.330 0.0
2025-11-29-16:02:40.786 0.0
2025-11-29-16:02:41.395 0.0
2025-11-29-16:02:41.995 20.0
2025-11-29-16:02:42.642 0.0
2025-11-29-16:02:43.173 20.0
2025-11-29-16:02:43.899 0.0
2025-11-29-16:02:44.552 20.0
2025-11-29-16:02:44.950 0.0
2025-11-29-16:02:45.120 0.0
2025-11-29-16:02:45.334 0.0
2025-11-29-16:02:45.539 0.0
2025-11-29-16:02:45.783 20.0
2025-11-29-16:02:45.960 20.0
2025-11-29-16:02:46.181 20.0
2025-11-29-16:02:46.368 20.0
2025-11-29-16:02:46.508 20.0
2025-11-29-16:02:46.684 0.0
2025-11-29-16:02:46.892 20.0
2025-11-29-16:02:47.101 20.0
2025-11-29-16:02:47.306 20.0
2025-11-29-16:02:47.494 20.0
2025-11-29-16:02:47.698 20.0
2025-11-29-16:02:47.891 0.0
2025-11-29-16:02:48.105 20.0
2025-11-29-16:02:48.304 20.0
2025-11-29-16:02:48.511 0.0
2025-11-29-16:02:48.705 0.0
2025-11-29-16:02:48.913 0.0
2025-11-29-16:02:49.111 20.0
2025-11-29-16:02:49.334 0.0
2025-11-29-16:02:49.539 20.0
2025-11-29-16:02:49.737 0.0
2025-11-29-16:02:49.936 0.0
2025-11-29-16:02:50.142 0.0
2025-11-29-16:02:50.338 20.0
2025-11-29-16:02:50.545 0.0
2025-11-29-16:02:50.752 0.0
2025-11-29-16:02:50.946 0.0
2025-11-29-16:02:51.154 0.0
2025-11-29-16:02:51.422 0.0
2025-11-29-16:02:51.563 0.0
2025-11-29-16:02:51.760 0.0
2025-11-29-16:02:51.971 0.0
2025-11-29-16:02:52.163 0.0
2025-11-29-16:02:52.367 0.0
2025-11-29-16:02:52.654 0.0
2025-11-29-16:02:41.995 0.0
2025-11-29-16:02:41.995 0.0
2025-11-29-16:02:42.643 0.0
2025-11-29-16:02:43.174 0.0
2025-11-29-16:02:43.899 0.0
2025-11-29-16:02:44.552 0.0
2025-11-29-16:02:44.950 20.0
2025-11-29-16:02:45.120 0.0
2025-11-29-16:02:45.334 0.0
2025-11-29-16:02:45.540 0.0
2025-11-29-16:02:45.783 0.0
2025-11-29-16:02:45.961 0.0
2025-11-29-16:02:46.181 0.0
2025-11-29-16:02:46.368 0.0
task_id: 2恶趣味 task_id: 2恶趣味
task_id: 2恶趣味 task_id: 2恶趣味
task_name: q'we task_name: q'we
status: 待配置 status: 未启动
selected_channels: selected_channels:
- 通道2 - 通道2
created_time: '2025-11-26 14:56:26' created_time: '2025-11-26 14:56:26'
......
task_id: test
task_name: test
status: 未启动
selected_channels:
- 通道1
created_time: '2025-11-29 15:52:05'
mission_result_folder_path: D:\restructure\liquid_level_line_detection_system\database\mission_result\test_test
2025-11-29-15:54:49.838 17.4
2025-11-29-15:54:49.838 17.4
2025-11-29-15:54:49.904 16.6
2025-11-29-15:54:50.120 16.5
2025-11-29-15:54:50.323 16.2
2025-11-29-15:54:50.537 16.0
2025-11-29-15:54:50.737 16.2
2025-11-29-15:54:51.036 16.5
2025-11-29-15:54:51.174 0.0
2025-11-29-15:54:51.311 0.0
2025-11-29-15:54:51.601 0.0
2025-11-29-15:54:51.758 17.1
2025-11-29-15:54:52.010 0.0
2025-11-29-15:54:52.199 16.6
2025-11-29-15:54:52.375 16.3
2025-11-29-15:54:52.620 17.0
2025-11-29-15:54:52.831 15.9
2025-11-29-15:54:53.131 16.2
2025-11-29-15:54:53.288 16.3
2025-11-29-15:54:53.486 17.1
2025-11-29-15:54:53.663 17.0
2025-11-29-15:54:53.905 17.3
2025-11-29-15:54:54.086 16.8
2025-11-29-15:54:54.268 16.6
2025-11-29-15:54:54.509 17.0
2025-11-29-15:54:54.711 16.2
2025-11-29-15:54:54.921 17.0
2025-11-29-15:54:55.146 17.1
2025-11-29-15:54:55.363 17.0
2025-11-29-15:54:55.585 17.3
2025-11-29-15:54:55.778 17.3
2025-11-29-15:54:55.993 17.3
2025-11-29-15:54:56.210 16.8
2025-11-29-15:54:56.409 16.3
2025-11-29-15:54:56.605 17.3
2025-11-29-15:54:56.791 16.5
2025-11-29-15:54:56.968 16.8
2025-11-29-15:54:57.216 16.8
2025-11-29-15:54:57.384 0.0
2025-11-29-15:54:57.608 16.6
2025-11-29-15:54:57.830 16.8
2025-11-29-15:54:58.022 0.0
2025-11-29-15:54:58.210 17.1
2025-11-29-15:54:58.404 16.6
2025-11-29-15:54:58.660 16.2
2025-11-29-15:54:58.826 16.5
2025-11-29-15:54:59.015 16.5
2025-11-29-15:54:59.212 16.5
2025-11-29-15:54:59.430 14.8
2025-11-29-15:52:34.752 0.0
2025-11-29-15:52:34.752 0.0
2025-11-29-15:52:34.788 0.0
2025-11-29-15:52:35.013 0.0
2025-11-29-15:52:35.205 0.0
2025-11-29-15:52:35.436 0.0
2025-11-29-15:52:35.645 0.0
2025-11-29-15:52:35.920 19.1
2025-11-29-15:52:36.032 19.1
2025-11-29-15:52:36.270 19.1
2025-11-29-15:52:36.457 19.0
2025-11-29-15:52:36.689 19.1
2025-11-29-15:52:36.876 19.1
2025-11-29-15:52:37.094 19.1
2025-11-29-15:52:37.286 19.1
2025-11-29-15:52:37.476 19.1
2025-11-29-15:52:37.729 19.1
2025-11-29-15:52:37.897 19.1
2025-11-29-15:52:38.118 0.0
2025-11-29-15:52:38.324 18.7
2025-11-29-15:52:38.544 18.4
2025-11-29-15:52:38.748 0.0
2025-11-29-15:52:38.938 0.0
2025-11-29-15:52:39.149 0.0
2025-11-29-15:52:39.382 0.0
2025-11-29-15:52:39.568 0.0
2025-11-29-15:52:39.766 0.0
2025-11-29-15:52:39.985 0.0
2025-11-29-15:52:40.216 19.1
2025-11-29-15:52:40.428 19.1
2025-11-29-15:52:40.625 19.1
2025-11-29-15:52:40.830 0.0
2025-11-29-15:52:41.031 0.0
2025-11-29-15:52:41.244 0.0
2025-11-29-15:52:41.454 0.0
2025-11-29-15:52:41.691 0.0
2025-11-29-15:52:41.878 0.0
2025-11-29-15:52:42.112 0.0
2025-11-29-15:52:42.314 19.1
2025-11-29-15:52:42.521 19.1
2025-11-29-15:52:42.718 19.2
2025-11-29-15:52:42.903 19.2
2025-11-29-15:52:43.235 19.1
2025-11-29-15:52:43.370 19.1
2025-11-29-15:52:43.520 19.1
2025-11-29-15:52:43.708 19.0
2025-11-29-15:52:43.916 19.0
2025-11-29-15:52:44.129 19.0
2025-11-29-15:52:44.341 19.1
2025-11-29-15:52:44.546 19.1
2025-11-29-15:52:44.768 19.1
2025-11-29-15:52:44.969 19.1
2025-11-29-15:52:45.159 19.0
2025-11-29-15:52:45.353 19.0
2025-11-29-15:52:45.557 19.0
2025-11-29-15:52:45.763 19.0
2025-11-29-15:52:45.981 19.0
2025-11-29-15:52:46.182 19.1
2025-11-29-15:52:46.375 19.1
2025-11-29-15:52:46.580 19.1
2025-11-29-15:52:46.788 19.1
2025-11-29-15:52:46.989 19.1
2025-11-29-15:52:47.200 19.0
2025-11-29-15:52:47.403 19.1
2025-11-29-15:52:47.618 19.1
2025-11-29-15:52:47.821 19.1
2025-11-29-15:52:48.017 0.0
2025-11-29-15:52:48.224 0.0
2025-11-29-15:52:48.454 0.0
2025-11-29-15:52:48.647 0.0
2025-11-29-15:52:48.850 0.0
2025-11-29-15:52:49.044 19.2
2025-11-29-15:52:49.274 19.2
2025-11-29-15:52:49.474 0.0
2025-11-29-15:52:49.672 0.0
2025-11-29-15:52:49.876 19.2
2025-11-29-15:52:50.060 19.1
2025-11-29-15:52:50.266 0.0
2025-11-29-15:52:50.471 0.0
2025-11-29-15:52:50.663 0.0
2025-11-29-15:52:50.901 19.1
2025-11-29-15:52:51.079 19.1
2025-11-29-15:52:51.277 19.1
2025-11-29-15:52:51.474 19.1
2025-11-29-15:52:51.670 19.1
2025-11-29-15:52:51.881 19.2
2025-11-29-15:52:52.086 19.1
2025-11-29-15:52:52.347 19.2
2025-11-29-15:52:52.479 19.0
2025-11-29-15:52:52.738 19.2
2025-11-29-15:52:52.866 19.2
2025-11-29-15:52:53.058 19.2
2025-11-29-15:52:53.246 19.2
2025-11-29-15:52:53.470 19.1
2025-11-29-15:52:53.659 19.2
2025-11-29-15:52:53.849 0.0
2025-11-29-15:52:54.076 19.1
2025-11-29-15:52:54.260 19.1
2025-11-29-15:52:54.467 19.1
2025-11-29-15:52:54.744 19.2
2025-11-29-15:52:54.921 19.2
2025-11-29-15:52:55.150 19.2
2025-11-29-15:52:55.323 19.2
2025-11-29-15:52:55.511 19.2
2025-11-29-15:52:55.703 0.0
2025-11-29-15:52:55.933 19.2
2025-11-29-15:52:56.121 19.2
2025-11-29-15:52:56.341 19.1
2025-11-29-15:52:56.524 19.2
2025-11-29-15:52:56.718 19.1
2025-11-29-15:52:56.914 19.2
2025-11-29-15:52:57.114 0.0
2025-11-29-15:52:57.357 0.0
2025-11-29-15:52:57.533 0.0
2025-11-29-15:52:57.736 0.0
2025-11-29-15:52:57.956 19.1
2025-11-29-15:52:58.136 0.0
2025-11-29-15:52:58.345 0.0
2025-11-29-15:52:58.556 0.0
2025-11-29-15:52:58.744 0.0
2025-11-29-15:52:58.953 0.0
2025-11-29-15:52:59.149 0.0
2025-11-29-15:52:59.357 0.0
2025-11-29-15:52:59.551 0.0
2025-11-29-15:52:59.758 0.0
2025-11-29-15:52:59.963 0.0
2025-11-29-15:53:00.170 19.1
2025-11-29-15:53:00.363 19.1
2025-11-29-15:53:00.585 19.1
2025-11-29-15:53:00.769 19.1
2025-11-29-15:53:00.972 19.1
2025-11-29-15:53:01.175 19.0
2025-11-29-15:53:01.380 19.1
2025-11-29-15:53:01.598 0.0
2025-11-29-15:53:01.788 0.0
2025-11-29-15:53:01.979 19.2
2025-11-29-15:53:02.186 0.0
2025-11-29-15:53:02.432 0.0
2025-11-29-15:53:02.592 0.0
2025-11-29-15:53:02.781 0.0
2025-11-29-15:53:03.091 0.0
2025-11-29-15:53:03.147 19.1
2025-11-29-15:53:03.346 19.1
2025-11-29-15:53:03.558 19.1
2025-11-29-15:53:03.762 19.1
2025-11-29-15:53:03.964 19.1
2025-11-29-15:53:04.169 19.1
2025-11-29-15:53:04.367 19.1
2025-11-29-15:53:04.576 19.1
2025-11-29-15:53:04.773 19.2
2025-11-29-15:53:04.981 19.1
2025-11-29-15:53:05.174 19.2
2025-11-29-15:53:05.371 19.1
2025-11-29-15:53:05.572 19.1
2025-11-29-15:53:05.788 19.2
2025-11-29-15:53:05.992 19.1
2025-11-29-15:53:06.197 19.2
2025-11-29-15:53:06.412 19.2
2025-11-29-15:53:06.619 19.2
2025-11-29-15:53:06.828 19.2
2025-11-29-15:53:07.037 19.1
2025-11-29-15:53:07.261 19.1
2025-11-29-15:53:07.449 19.1
2025-11-29-15:53:07.656 19.1
2025-11-29-15:53:07.863 19.1
2025-11-29-15:53:08.087 19.2
2025-11-29-15:53:08.280 0.0
2025-11-29-15:53:08.493 19.2
2025-11-29-15:53:08.698 19.2
2025-11-29-15:53:08.914 0.0
2025-11-29-15:53:09.101 19.1
2025-11-29-15:53:09.302 0.0
2025-11-29-15:53:09.508 0.0
2025-11-29-15:53:09.714 18.4
2025-11-29-15:53:09.921 0.0
2025-11-29-15:53:10.126 19.0
2025-11-29-15:53:10.342 19.1
2025-11-29-15:53:10.537 19.1
2025-11-29-15:53:10.741 19.0
2025-11-29-15:53:10.947 0.0
2025-11-29-15:53:11.143 18.4
2025-11-29-15:53:11.347 19.0
2025-11-29-15:53:11.552 19.0
2025-11-29-15:53:40.333 0.0
2025-11-29-15:53:40.377 0.0
2025-11-29-15:53:40.578 0.0
2025-11-29-15:53:40.780 0.0
2025-11-29-15:53:40.995 0.0
2025-11-29-15:53:41.203 0.0
2025-11-29-15:53:41.373 0.0
2025-11-29-15:53:41.585 0.0
2025-11-29-15:53:41.795 0.0
2025-11-29-15:53:42.005 0.0
2025-11-29-15:53:42.223 0.0
2025-11-29-15:53:42.432 0.0
2025-11-29-15:53:42.615 0.0
2025-11-29-15:53:42.823 0.0
2025-11-29-15:53:43.015 0.0
2025-11-29-15:53:43.234 0.0
2025-11-29-15:53:43.440 0.0
2025-11-29-15:53:43.653 0.0
2025-11-29-15:53:43.852 0.0
2025-11-29-15:53:44.059 0.0
2025-11-29-15:53:44.276 0.0
2025-11-29-15:53:44.479 0.0
2025-11-29-15:53:44.684 0.0
2025-11-29-15:53:44.891 0.0
2025-11-29-15:53:45.111 0.0
2025-11-29-15:53:45.347 0.0
2025-11-29-15:53:45.577 0.0
2025-11-29-15:53:45.755 0.0
2025-11-29-15:53:45.980 0.0
2025-11-29-15:53:46.177 0.0
2025-11-29-15:53:46.357 0.0
2025-11-29-15:53:46.563 0.0
2025-11-29-15:53:46.799 0.0
2025-11-29-15:53:47.007 0.0
2025-11-29-15:53:47.199 0.0
2025-11-29-15:53:47.381 0.0
2025-11-29-15:53:47.598 0.0
2025-11-29-15:53:47.819 0.0
2025-11-29-15:53:48.012 0.0
2025-11-29-15:53:48.214 0.0
2025-11-29-15:53:48.426 0.0
2025-11-29-15:53:48.634 0.0
2025-11-29-15:53:48.802 0.0
2025-11-29-15:53:49.049 0.0
2025-11-29-15:53:49.216 0.0
2025-11-29-15:53:49.413 0.0
2025-11-29-15:53:49.610 0.0
2025-11-29-15:53:49.818 0.0
2025-11-29-15:53:50.013 0.0
2025-11-29-15:53:50.221 0.0
2025-11-29-15:53:50.432 0.0
2025-11-29-15:53:50.614 0.0
2025-11-29-15:53:50.837 0.0
2025-11-29-15:53:51.047 0.0
2025-11-29-15:53:51.247 0.0
2025-11-29-15:53:51.461 0.0
2025-11-29-15:53:51.643 0.0
2025-11-29-15:53:51.857 0.0
2025-11-29-15:53:52.058 0.0
2025-11-29-15:53:52.258 0.0
2025-11-29-15:53:52.467 0.0
2025-11-29-15:53:52.660 0.0
2025-11-29-15:53:52.865 0.0
2025-11-29-15:53:53.060 0.0
2025-11-29-15:53:53.267 0.0
2025-11-29-15:53:53.487 0.0
2025-11-29-15:53:53.669 0.0
2025-11-29-15:53:53.876 0.0
2025-11-29-15:53:54.084 0.0
2025-11-29-15:53:54.286 0.0
2025-11-29-15:53:54.480 0.0
2025-11-29-15:53:54.687 0.0
2025-11-29-15:53:54.897 0.0
2025-11-29-15:53:55.089 0.0
2025-11-29-15:53:55.307 0.0
2025-11-29-15:53:55.489 0.0
2025-11-29-15:53:55.699 0.0
2025-11-29-15:53:55.892 0.0
2025-11-29-15:53:56.101 0.0
2025-11-29-15:53:56.305 0.0
2025-11-29-15:53:56.512 0.0
2025-11-29-15:53:56.725 0.0
2025-11-29-15:53:56.904 0.0
2025-11-29-15:53:57.111 0.0
2025-11-29-15:53:57.319 0.0
2025-11-29-15:53:57.519 0.0
2025-11-29-15:53:57.714 0.0
2025-11-29-15:53:57.913 0.0
2025-11-29-15:53:58.126 0.0
2025-11-29-15:53:58.316 0.0
2025-11-29-15:53:58.524 0.0
2025-11-29-15:53:58.722 0.0
2025-11-29-15:53:58.922 0.0
2025-11-29-15:53:59.134 0.0
2025-11-29-15:53:59.329 0.0
2025-11-29-15:53:59.542 0.0
2025-11-29-15:53:59.739 0.0
2025-11-29-15:53:59.946 0.0
2025-11-29-15:54:00.145 0.0
2025-11-29-15:54:00.365 0.0
2025-11-29-15:54:00.563 0.0
2025-11-29-15:54:00.754 0.0
2025-11-29-15:54:00.964 0.0
2025-11-29-15:54:01.167 0.0
2025-11-29-15:54:01.368 0.0
2025-11-29-15:54:01.568 0.0
2025-11-29-15:54:01.766 0.0
2025-11-29-15:54:01.962 0.0
2025-11-29-15:54:02.167 0.0
2025-11-29-15:54:02.371 0.0
2025-11-29-15:54:02.581 0.0
2025-11-29-15:54:02.811 0.0
2025-11-29-15:54:03.016 0.0
2025-11-29-15:54:03.212 0.0
2025-11-29-15:54:03.417 0.0
2025-11-29-15:54:03.557 0.0
2025-11-29-15:54:03.743 0.0
2025-11-29-15:54:03.972 0.0
2025-11-29-15:54:04.158 0.0
2025-11-29-15:54:04.384 0.0
2025-11-29-15:54:04.557 0.0
2025-11-29-15:54:04.764 0.0
2025-11-29-15:54:04.960 0.0
2025-11-29-15:54:05.177 0.0
2025-11-29-15:54:05.434 0.0
2025-11-29-15:54:05.638 0.0
2025-11-29-15:54:05.829 0.0
2025-11-29-15:54:06.041 0.0
2025-11-29-15:54:06.184 0.0
2025-11-29-15:54:06.411 0.0
2025-11-29-15:54:06.639 0.0
2025-11-29-15:54:06.848 0.0
task_id: 任务
task_id: 任务
task_name: 若是
status: 未启动
selected_channels:
- 通道1
created_time: '2025-11-29 13:39:21'
mission_result_folder_path: D:\restructure\liquid_level_line_detection_system\database\mission_result\任务_若是
2025-11-29-13:39:40.946 0.0
2025-11-29-13:39:40.946 0.0
2025-11-29-13:39:40.993 0.0
2025-11-29-13:39:41.186 0.0
2025-11-29-13:39:41.387 0.0
2025-11-29-13:39:41.588 0.0
2025-11-29-13:39:41.785 0.0
2025-11-29-13:39:41.977 0.0
2025-11-29-13:39:42.180 0.0
2025-11-29-13:39:42.392 0.0
2025-11-29-13:39:42.588 0.0
2025-11-29-13:39:42.893 16.3
task_id: 企鹅
task_id: 企鹅
task_name: 1额
status: 未启动
selected_channels:
- 通道1
created_time: '2025-11-29 13:34:35'
mission_result_folder_path: D:\restructure\liquid_level_line_detection_system\database\mission_result\企鹅_1额
task_id: 去v
task_id: 去v
task_name: 去v
status: 未启动
selected_channels:
- 通道1
created_time: '2025-11-29 13:34:18'
mission_result_folder_path: D:\restructure\liquid_level_line_detection_system\database\mission_result\去v_去v
task_id: 去人
task_id: 去人
task_name: '2314'
status: 未启动
selected_channels:
- 通道1
created_time: '2025-11-29 13:22:23'
mission_result_folder_path: D:\restructure\liquid_level_line_detection_system\database\mission_result\去人_2314
task_id: 去问驱蚊器恶气
task_id: 去问驱蚊器恶气
task_name: 企鹅去而且
status: 未启动
selected_channels:
- 通道1
created_time: '2025-11-29 12:25:58'
mission_result_folder_path: D:\restructure\liquid_level_line_detection_system\database\mission_result\去问驱蚊器恶气_企鹅去而且
task_id: 文档
task_id: 文档
task_name: 啊啊
status: 未启动
selected_channels:
- 通道1
created_time: '2025-11-29 13:18:35'
mission_result_folder_path: D:\restructure\liquid_level_line_detection_system\database\mission_result\文档_啊啊
这是综合模型,经过了多种场景的训练与测试,适应性强,准确率高。
这是综合模型,经过了多种场景的训练与测试,适应性强,准确率高。
\ 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 |
"""
调试日志模块
使用logging模块替代print进行调试输出
日志文件保存在: D:\restructure\liquid_level_line_detection_system\database\log\debuglog
"""
import logging
import os
import sys
from pathlib import Path
from datetime import datetime
from typing import Optional
class DebugLogger:
"""调试日志管理器"""
_instance = None
_loggers = {}
def __new__(cls):
"""单例模式"""
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
"""初始化日志管理器"""
if not hasattr(self, '_initialized'):
# 获取项目根目录(支持打包后的环境)
if getattr(sys, 'frozen', False):
# 打包后的环境
base_dir = Path(sys.executable).parent
else:
# 开发环境
base_dir = Path(__file__).parent.parent
self.log_dir = base_dir / "database" / "log" / "debuglog"
self.log_dir.mkdir(parents=True, exist_ok=True)
self._initialized = True
def get_logger(
self,
name: str,
level: int = logging.DEBUG,
console_output: bool = True,
file_output: bool = True
) -> logging.Logger:
"""
获取或创建logger
Args:
name: logger名称(通常使用模块名)
level: 日志级别 (DEBUG, INFO, WARNING, ERROR, CRITICAL)
console_output: 是否输出到控制台
file_output: 是否输出到文件
Returns:
logging.Logger: 配置好的logger实例
"""
# 如果logger已存在,直接返回
if name in self._loggers:
return self._loggers[name]
# 创建logger
logger = logging.getLogger(name)
logger.setLevel(level)
# 清除已有的handlers(避免重复添加)
logger.handlers.clear()
# 定义日志格式
# 详细格式:时间 - 名称 - 级别 - 文件:行号 - 消息
detailed_formatter = logging.Formatter(
fmt='%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# 简洁格式:时间 - 级别 - 消息
simple_formatter = logging.Formatter(
fmt='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# 控制台输出(使用简洁格式)
if console_output:
console_handler = logging.StreamHandler()
console_handler.setLevel(level)
console_handler.setFormatter(simple_formatter)
logger.addHandler(console_handler)
# 文件输出(使用详细格式)
if file_output:
# 按日期创建日志文件
today = datetime.now().strftime('%Y%m%d')
log_file = self.log_dir / f"{name}_{today}.log"
file_handler = logging.FileHandler(
log_file,
mode='a',
encoding='utf-8'
)
file_handler.setLevel(level)
file_handler.setFormatter(detailed_formatter)
logger.addHandler(file_handler)
# 缓存logger
self._loggers[name] = logger
return logger
def create_module_logger(
self,
module_name: str,
level: int = logging.DEBUG
) -> logging.Logger:
"""
为模块创建专用logger(快捷方法)
Args:
module_name: 模块名称
level: 日志级别
Returns:
logging.Logger: 配置好的logger
"""
return self.get_logger(
name=module_name,
level=level,
console_output=True,
file_output=True
)
def clear_old_logs(self, days: int = 7):
"""
清理旧日志文件
Args:
days: 保留最近多少天的日志
"""
from datetime import timedelta
cutoff_date = datetime.now() - timedelta(days=days)
for log_file in self.log_dir.glob("*.log"):
try:
file_time = datetime.fromtimestamp(log_file.stat().st_mtime)
if file_time < cutoff_date:
log_file.unlink()
print(f"已删除旧日志: {log_file.name}")
except Exception as e:
print(f"删除日志文件失败 {log_file.name}: {e}")
# 全局单例实例
_debug_logger_instance = DebugLogger()
def get_logger(
name: str,
level: int = logging.DEBUG,
console_output: bool = True,
file_output: bool = True
) -> logging.Logger:
"""
获取logger的便捷函数
使用示例:
from handlers.debuglog import get_logger
logger = get_logger(__name__)
logger.debug("调试信息")
logger.info("普通信息")
logger.warning("警告信息")
logger.error("错误信息")
logger.critical("严重错误")
Args:
name: logger名称(建议使用 __name__)
level: 日志级别
console_output: 是否输出到控制台
file_output: 是否输出到文件
Returns:
logging.Logger: 配置好的logger
"""
return _debug_logger_instance.get_logger(name, level, console_output, file_output)
def clear_old_logs(days: int = 7):
"""
清理旧日志文件的便捷函数
Args:
days: 保留最近多少天的日志
"""
_debug_logger_instance.clear_old_logs(days)
# 预定义的日志级别常量
DEBUG = logging.DEBUG # 10 - 详细的调试信息
INFO = logging.INFO # 20 - 一般信息
WARNING = logging.WARNING # 30 - 警告信息
ERROR = logging.ERROR # 40 - 错误信息
CRITICAL = logging.CRITICAL # 50 - 严重错误
if __name__ == "__main__":
"""测试代码"""
# 测试1: 基本使用
print("="*80)
print("测试1: 基本日志功能")
print("="*80)
logger = get_logger("test_module")
logger.debug("这是调试信息")
logger.info("这是普通信息")
logger.warning("这是警告信息")
logger.error("这是错误信息")
logger.critical("这是严重错误信息")
# 测试2: 不同模块的logger
print("\n" + "="*80)
print("测试2: 多个模块的logger")
print("="*80)
logger1 = get_logger("module1")
logger2 = get_logger("module2")
logger1.info("来自模块1的消息")
logger2.info("来自模块2的消息")
# 测试3: 只输出到文件
print("\n" + "="*80)
print("测试3: 只输出到文件(控制台看不到)")
print("="*80)
file_only_logger = get_logger(
"file_only",
console_output=False,
file_output=True
)
file_only_logger.info("这条消息只会出现在日志文件中")
print("(上面的消息已写入文件,但不会显示在控制台)")
# 测试4: 不同日志级别
print("\n" + "="*80)
print("测试4: 设置不同的日志级别")
print("="*80)
info_logger = get_logger("info_level", level=INFO)
info_logger.debug("这条DEBUG消息不会显示(级别太低)")
info_logger.info("这条INFO消息会显示")
info_logger.warning("这条WARNING消息会显示")
# 测试5: 异常日志
print("\n" + "="*80)
print("测试5: 记录异常信息")
print("="*80)
error_logger = get_logger("error_test")
try:
result = 1 / 0
except Exception as e:
error_logger.error(f"发生异常: {e}", exc_info=True)
print("\n" + "="*80)
print("测试完成!")
print(f"日志文件保存在: {_debug_logger_instance.log_dir}")
print("="*80)
...@@ -54,7 +54,7 @@ class ModelSetHandler: ...@@ -54,7 +54,7 @@ class ModelSetHandler:
type_layout = QtWidgets.QHBoxLayout() type_layout = QtWidgets.QHBoxLayout()
type_layout.addWidget(QtWidgets.QLabel("模型类型:")) type_layout.addWidget(QtWidgets.QLabel("模型类型:"))
type_combo = QtWidgets.QComboBox() type_combo = QtWidgets.QComboBox()
type_combo.addItems(["YOLOv8", "YOLOv11", "Faster R-CNN", "SSD", "RetinaNet", "自定义"])
type_layout.addWidget(type_combo) type_layout.addWidget(type_combo)
layout.addLayout(type_layout) layout.addLayout(type_layout)
......
...@@ -162,39 +162,14 @@ class ModelSettingsHandler: ...@@ -162,39 +162,14 @@ class ModelSettingsHandler:
# 按钮 # 按钮
button_layout = QtWidgets.QHBoxLayout() button_layout = QtWidgets.QHBoxLayout()
save_btn = QtWidgets.QPushButton("保存设置")
cancel_btn = QtWidgets.QPushButton("取消") cancel_btn = QtWidgets.QPushButton("取消")
reset_btn = QtWidgets.QPushButton("重置") reset_btn = QtWidgets.QPushButton("重置")
button_layout.addWidget(save_btn)
button_layout.addWidget(reset_btn) button_layout.addWidget(reset_btn)
button_layout.addWidget(cancel_btn) button_layout.addWidget(cancel_btn)
main_layout.addLayout(button_layout) main_layout.addLayout(button_layout)
# 连接按钮事件 # 连接按钮事件
def save_settings():
# 更新模型参数
updated_params = model_params.copy()
updated_params.update({
'type': type_edit.text(),
'confidence': confidence_spin.value(),
'iou': iou_spin.value(),
'classes': classes_spin.value(),
'input': input_edit.text(),
'device': device_combo.currentText(),
'batch_size': batch_spin.value(),
'workers': workers_spin.value(),
'epochs': epochs_spin.value(),
'blur_training': blur_spin.value(),
'description': desc_edit.toPlainText()
})
# 保存到模型集页面
self.modelSetPage._model_params[model_name] = updated_params
QtWidgets.QMessageBox.information(dialog, "保存成功", f"模型 '{model_name}' 的设置已保存")
dialog.accept()
def reset_settings(): def reset_settings():
# 重置所有设置到原始值 # 重置所有设置到原始值
type_edit.setText(model_params.get('type', '')) type_edit.setText(model_params.get('type', ''))
...@@ -209,7 +184,6 @@ class ModelSettingsHandler: ...@@ -209,7 +184,6 @@ class ModelSettingsHandler:
blur_spin.setValue(model_params.get('blur_training', 100)) blur_spin.setValue(model_params.get('blur_training', 100))
desc_edit.setPlainText(model_params.get('description', '')) desc_edit.setPlainText(model_params.get('description', ''))
save_btn.clicked.connect(save_settings)
reset_btn.clicked.connect(reset_settings) reset_btn.clicked.connect(reset_settings)
cancel_btn.clicked.connect(dialog.reject) cancel_btn.clicked.connect(dialog.reject)
......
This source diff could not be displayed because it is too large. You can view the blob instead.
from ast import main
123131441
fsafasa
main
\ No newline at end of file
# 自动标点功能模块使用说明
## 功能概述
`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 坐标填充到标注点位置
```
...@@ -751,5 +751,953 @@ def test_auto_dot(): ...@@ -751,5 +751,953 @@ def test_auto_dot():
print(f"{'='*80}") print(f"{'='*80}")
class AutoAnnotationDetector:
"""
自动标注检测器(整合标框和标点功能)
功能:
1. 输入图像
2. 使用YOLO分割模型检测液体、空气、泡沫区域
3. 可输出box位置数据(标框)或点位置数据(标点)
4. 根据位置信息绘制框或点
"""
def __init__(self, model_path: str = None, device: str = 'cuda'):
"""
初始化自动标注检测器
Args:
model_path: YOLO模型路径(.pt 或 .dat 文件)
device: 计算设备 ('cuda' 或 'cpu')
"""
self.model = None
self.model_path = model_path
self.device = self._validate_device(device)
if model_path:
self.load_model(model_path)
def _validate_device(self, device: str) -> str:
"""验证并选择可用的设备"""
try:
import torch
if device in ['cuda', '0'] or device.startswith('cuda:'):
if torch.cuda.is_available():
return 'cuda' if device in ['cuda', '0'] else device
else:
print("⚠️ CUDA不可用,切换到CPU")
return 'cpu'
return device
except Exception:
return 'cpu'
def load_model(self, model_path: str) -> bool:
"""
加载YOLO模型
Args:
model_path: 模型文件路径
Returns:
bool: 加载是否成功
"""
try:
import os
if not os.path.exists(model_path):
print(f"❌ 模型文件不存在: {model_path}")
return False
# 如果是 .dat 文件,先解码
if model_path.endswith('.dat'):
decoded_path = self._decode_dat_model(model_path)
if decoded_path is None:
print(f"❌ .dat 文件解码失败: {model_path}")
return False
model_path = decoded_path
# 设置离线模式
os.environ['YOLO_VERBOSE'] = 'False'
os.environ['YOLO_OFFLINE'] = '1'
os.environ['ULTRALYTICS_OFFLINE'] = 'True'
from ultralytics import YOLO
print(f"🔄 正在加载模型: {model_path}")
self.model = YOLO(model_path)
self.model.to(self.device)
self.model_path = model_path
print(f"✅ 模型加载成功: {os.path.basename(model_path)}")
return True
except Exception as e:
print(f"❌ 模型加载失败: {e}")
return False
def _decode_dat_model(self, dat_path: str) -> Optional[str]:
"""解码 .dat 格式的模型文件"""
try:
import struct
import hashlib
SIGNATURE = b'LDS_MODEL_FILE'
VERSION = 1
ENCRYPTION_KEY = "liquid_detection_system_2024"
key_hash = hashlib.sha256(ENCRYPTION_KEY.encode('utf-8')).digest()
with open(dat_path, 'rb') as f:
signature = f.read(len(SIGNATURE))
if signature != SIGNATURE:
return None
version = struct.unpack('<I', f.read(4))[0]
if version != VERSION:
return None
filename_len = struct.unpack('<I', f.read(4))[0]
original_filename = f.read(filename_len).decode('utf-8')
data_len = struct.unpack('<Q', f.read(8))[0]
encrypted_data = f.read(data_len)
# XOR 解密
decrypted_data = bytearray()
key_len = len(key_hash)
for i, byte in enumerate(encrypted_data):
decrypted_data.append(byte ^ key_hash[i % key_len])
decrypted_data = bytes(decrypted_data)
# 保存到临时目录
temp_dir = Path(get_temp_models_dir())
temp_dir.mkdir(exist_ok=True)
path_hash = hashlib.md5(str(dat_path).encode()).hexdigest()[:8]
temp_model_path = temp_dir / f"temp_{Path(dat_path).stem}_{path_hash}.pt"
with open(temp_model_path, 'wb') as f:
f.write(decrypted_data)
return str(temp_model_path)
except Exception as e:
print(f"❌ 解码.dat文件失败: {e}")
return None
def detect(
self,
image: np.ndarray,
conf_threshold: float = 0.5,
min_area: int = 100
) -> Dict:
"""
核心检测方法 - 执行YOLO推理并返回分割结果
Args:
image: 输入图片 (numpy.ndarray, BGR格式)
conf_threshold: 置信度阈值
min_area: 最小面积阈值(像素),小于此面积的mask会被过滤
Returns:
dict: {
'success': bool,
'masks': List[np.ndarray], # 有效的mask列表
'class_names': List[str], # 对应的类别名称
'confidences': List[float], # 对应的置信度
'image_shape': tuple # 图像尺寸 (height, width)
}
"""
if self.model is None:
return {
'success': False,
'boxes': [],
'annotated_image': image.copy(),
'error': '模型未加载'
}
if image.size == 0:
return {
'success': False,
'boxes': [],
'annotated_image': image.copy(),
'error': '图像为空'
}
print(f"\n{'='*60}")
print(f"🔍 开始自动检测")
print(f" 图像尺寸: {image.shape[1]}x{image.shape[0]}")
# 执行YOLO推理
print(f" 🔄 执行YOLO推理...")
try:
mission_results = self.model.predict(
source=image,
imgsz=640,
conf=conf_threshold,
iou=0.5,
device=self.device,
save=False,
verbose=False,
half=True if self.device != 'cpu' else False,
stream=False
)
mission_result = mission_results[0]
except Exception as e:
print(f" ❌ YOLO推理失败: {e}")
return {
'success': False,
'boxes': [],
'annotated_image': image.copy(),
'error': f'YOLO推理失败: {e}'
}
# 处理检测结果
if mission_result.masks is None:
print(f" ⚠️ 未检测到任何掩码")
return {
'success': False,
'boxes': [],
'annotated_image': image.copy(),
'error': '未检测到任何掩码'
}
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" ✅ 检测到 {len(masks)} 个对象")
# 收集所有有效的mask
valid_masks = []
class_names = []
for i in range(len(masks)):
if confidences[i] < conf_threshold:
continue
class_name = self.model.names[classes[i]]
conf = confidences[i]
# 调整mask尺寸到输入图像大小
resized_mask = cv2.resize(
masks[i].astype(np.uint8),
(image.shape[1], image.shape[0])
) > 0.5
# 计算mask面积
mask_area = np.sum(resized_mask)
print(f" - {class_name}: {mask_area} 像素, 置信度: {conf:.3f}")
# 过滤小面积mask
if mask_area < min_area:
print(f" ⚠️ 面积过小,已过滤")
continue
valid_masks.append(resized_mask)
class_names.append(class_name)
if len(valid_masks) == 0:
print(f" ⚠️ 没有有效的mask")
return {
'success': False,
'error': '没有有效的mask'
}
print(f"\n{'='*60}")
print(f"✅ 检测完成,共 {len(valid_masks)} 个有效mask")
return {
'success': True,
'masks': valid_masks,
'class_names': class_names,
'confidences': [confidences[i] for i in range(len(masks)) if i < len(valid_masks)],
'image_shape': (image.shape[0], image.shape[1])
}
def get_boxes(
self,
detection_result: Dict,
padding: int = 10,
merge_all: bool = True
) -> List[Dict]:
"""
从检测结果生成box位置数据
Args:
detection_result: detect()方法的返回结果
padding: box边界扩展像素数
merge_all: 是否合并所有类别生成一个大框
Returns:
List[Dict]: box列表,每个box包含 {'x1', 'y1', 'x2', 'y2', 'classes', 'area'}
"""
if not detection_result.get('success'):
return []
valid_masks = detection_result['masks']
class_names = detection_result['class_names']
height, width = detection_result['image_shape']
boxes = []
if merge_all:
# 合并所有mask生成一个大框
combined_mask = np.zeros_like(valid_masks[0], dtype=bool)
for mask in valid_masks:
combined_mask = combined_mask | mask
# 找到mask的边界
y_coords, x_coords = np.where(combined_mask)
if len(y_coords) > 0 and len(x_coords) > 0:
# 计算最小包围框
x1 = max(0, int(np.min(x_coords)) - padding)
y1 = max(0, int(np.min(y_coords)) - padding)
x2 = min(width, int(np.max(x_coords)) + padding)
y2 = min(height, int(np.max(y_coords)) + padding)
box_area = (x2 - x1) * (y2 - y1)
# 统计包含的类别
unique_classes = list(set(class_names))
boxes.append({
'x1': x1,
'y1': y1,
'x2': x2,
'y2': y2,
'classes': unique_classes, # 包含的所有类别
'area': box_area
})
print(f" 📦 生成合并box: ({x1}, {y1}) -> ({x2}, {y2})")
print(f" 包含类别: {', '.join(unique_classes)}")
print(f" 面积: {box_area}px²")
else:
# 按类别分别生成框
class_masks = {}
for mask, class_name in zip(valid_masks, class_names):
if class_name not in class_masks:
class_masks[class_name] = []
class_masks[class_name].append(mask)
for class_name, masks_list in class_masks.items():
# 合并同类别的所有mask
combined_mask = np.zeros_like(masks_list[0], dtype=bool)
for mask in masks_list:
combined_mask = combined_mask | mask
# 找到mask的边界
y_coords, x_coords = np.where(combined_mask)
if len(y_coords) == 0 or len(x_coords) == 0:
continue
# 计算最小包围框
x1 = max(0, int(np.min(x_coords)) - padding)
y1 = max(0, int(np.min(y_coords)) - padding)
x2 = min(width, int(np.max(x_coords)) + padding)
y2 = min(height, int(np.max(y_coords)) + padding)
box_area = (x2 - x1) * (y2 - y1)
boxes.append({
'x1': x1,
'y1': y1,
'x2': x2,
'y2': y2,
'classes': [class_name],
'area': box_area
})
print(f" 📦 生成box [{class_name}]: ({x1}, {y1}) -> ({x2}, {y2}), 面积={box_area}px²")
return boxes
def get_points(
self,
detection_result: Dict
) -> List[Dict]:
"""
从检测结果生成点位置数据(容器顶部和底部)
Args:
detection_result: detect()方法的返回结果
Returns:
List[Dict]: 点列表,每个点包含 {'top', 'bottom', 'top_x', 'bottom_x', 'height', 'method', 'classes'}
"""
if not detection_result.get('success'):
return []
valid_masks = detection_result['masks']
class_names = detection_result['class_names']
# 按类别收集坐标
liquid_y_coords = []
liquid_x_coords = []
air_y_coords = []
air_x_coords = []
foam_y_coords = []
foam_x_coords = []
for mask, class_name in zip(valid_masks, class_names):
y_coords, x_coords = np.where(mask)
if class_name == 'liquid':
liquid_y_coords.extend(y_coords)
liquid_x_coords.extend(x_coords)
elif class_name == 'air':
air_y_coords.extend(y_coords)
air_x_coords.extend(x_coords)
elif class_name == 'foam':
foam_y_coords.extend(y_coords)
foam_x_coords.extend(x_coords)
# 使用AutoDotDetector的逻辑计算边界
container_info = self._calculate_boundaries(
liquid_y_coords, liquid_x_coords,
air_y_coords, air_x_coords,
[(None, foam_y_coords, foam_x_coords)] if len(foam_y_coords) > 0 else [],
1.0, 1.0, 1.0,
0, 0, 0
)
if container_info:
return [container_info]
else:
return []
def draw_boxes(
self,
image: np.ndarray,
boxes: List[Dict]
) -> np.ndarray:
"""
在图像上绘制box
Args:
image: 输入图像
boxes: box列表
Returns:
绘制完成的图像
"""
annotated_image = image.copy()
self._draw_boxes(annotated_image, boxes)
return annotated_image
def draw_points(
self,
image: np.ndarray,
points: List[Dict]
) -> np.ndarray:
"""
在图像上绘制点
Args:
image: 输入图像
points: 点列表
Returns:
绘制完成的图像
"""
annotated_image = image.copy()
for point_info in points:
self._draw_annotations(
annotated_image,
point_info,
0, image.shape[1]
)
return annotated_image
def draw_all(
self,
image: np.ndarray,
boxes: List[Dict],
points: List[Dict]
) -> np.ndarray:
"""
在同一张图像上同时绘制框和点
Args:
image: 输入图像
boxes: box列表
points: 点列表
Returns:
绘制完成的图像
"""
annotated_image = image.copy()
# 先绘制框
self._draw_boxes(annotated_image, boxes)
# 再绘制点
for point_info in points:
self._draw_annotations(
annotated_image,
point_info,
0, image.shape[1]
)
return annotated_image
def _calculate_boundaries(
self,
liquid_y_coords: List[int],
liquid_x_coords: List[int],
air_y_coords: List[int],
air_x_coords: List[int],
foam_masks: List,
liquid_conf: float,
air_conf: float,
foam_conf: float,
crop_top: int,
crop_left: int,
idx: int
) -> Optional[Dict]:
"""计算容器的顶部和底部边界(复用AutoDotDetector的逻辑)"""
container_bottom = None
container_top = None
bottom_x = None
top_x = None
method = None
confidence = 0.0
has_liquid = len(liquid_y_coords) > 0
has_air = len(air_y_coords) > 0
has_foam = len(foam_masks) > 0
# 情况5: liquid + foam + air
if has_liquid and has_foam and has_air:
liquid_y_array = np.array(liquid_y_coords)
liquid_x_array = np.array(liquid_x_coords)
max_y = np.max(liquid_y_array)
bottom_region_mask = liquid_y_array >= (max_y - 5)
container_bottom_in_crop = int(np.median(liquid_y_array[bottom_region_mask]))
on_bottom_line = liquid_y_array == container_bottom_in_crop
if np.sum(on_bottom_line) > 0:
bottom_x_in_crop = int(np.median(liquid_x_array[on_bottom_line]))
else:
bottom_x_in_crop = int(np.median(liquid_x_array[bottom_region_mask]))
air_y_array = np.array(air_y_coords)
air_x_array = np.array(air_x_coords)
min_y = np.min(air_y_array)
top_region_mask = air_y_array <= (min_y + 5)
container_top_in_crop = int(np.median(air_y_array[top_region_mask]))
on_top_line = air_y_array == container_top_in_crop
if np.sum(on_top_line) > 0:
top_x_in_crop = int(np.median(air_x_array[on_top_line]))
else:
top_x_in_crop = int(np.median(air_x_array[top_region_mask]))
container_bottom = container_bottom_in_crop + crop_top
container_top = container_top_in_crop + crop_top
bottom_x = bottom_x_in_crop + crop_left
top_x = top_x_in_crop + crop_left
method = 'liquid_foam_air'
confidence = (liquid_conf + air_conf + foam_conf) / 3
# 情况4: liquid + air
elif has_liquid and has_air:
liquid_y_array = np.array(liquid_y_coords)
liquid_x_array = np.array(liquid_x_coords)
max_y = np.max(liquid_y_array)
bottom_region_mask = liquid_y_array >= (max_y - 5)
container_bottom_in_crop = int(np.median(liquid_y_array[bottom_region_mask]))
on_bottom_line = liquid_y_array == container_bottom_in_crop
if np.sum(on_bottom_line) > 0:
bottom_x_in_crop = int(np.median(liquid_x_array[on_bottom_line]))
else:
bottom_x_in_crop = int(np.median(liquid_x_array[bottom_region_mask]))
air_y_array = np.array(air_y_coords)
air_x_array = np.array(air_x_coords)
min_y = np.min(air_y_array)
top_region_mask = air_y_array <= (min_y + 5)
container_top_in_crop = int(np.median(air_y_array[top_region_mask]))
on_top_line = air_y_array == container_top_in_crop
if np.sum(on_top_line) > 0:
top_x_in_crop = int(np.median(air_x_array[on_top_line]))
else:
top_x_in_crop = int(np.median(air_x_array[top_region_mask]))
container_bottom = container_bottom_in_crop + crop_top
container_top = container_top_in_crop + crop_top
bottom_x = bottom_x_in_crop + crop_left
top_x = top_x_in_crop + crop_left
method = 'liquid_air'
confidence = (liquid_conf + air_conf) / 2
# 情况6: liquid + foam
elif has_liquid and has_foam:
liquid_y_array = np.array(liquid_y_coords)
liquid_x_array = np.array(liquid_x_coords)
max_y = np.max(liquid_y_array)
bottom_region_mask = liquid_y_array >= (max_y - 5)
container_bottom_in_crop = int(np.median(liquid_y_array[bottom_region_mask]))
on_bottom_line = liquid_y_array == container_bottom_in_crop
if np.sum(on_bottom_line) > 0:
bottom_x_in_crop = int(np.median(liquid_x_array[on_bottom_line]))
else:
bottom_x_in_crop = int(np.median(liquid_x_array[bottom_region_mask]))
all_foam_y = []
all_foam_x = []
for mask, y_coords, x_coords in foam_masks:
all_foam_y.extend(y_coords)
all_foam_x.extend(x_coords)
foam_y_array = np.array(all_foam_y)
foam_x_array = np.array(all_foam_x)
min_y = np.min(foam_y_array)
top_region_mask = foam_y_array <= (min_y + 5)
container_top_in_crop = int(np.median(foam_y_array[top_region_mask]))
on_top_line = foam_y_array == container_top_in_crop
if np.sum(on_top_line) > 0:
top_x_in_crop = int(np.median(foam_x_array[on_top_line]))
else:
top_x_in_crop = int(np.median(foam_x_array[top_region_mask]))
container_bottom = container_bottom_in_crop + crop_top
container_top = container_top_in_crop + crop_top
bottom_x = bottom_x_in_crop + crop_left
top_x = top_x_in_crop + crop_left
method = 'liquid_foam'
confidence = (liquid_conf + foam_conf) / 2
# 情况2: 仅liquid
elif has_liquid:
liquid_y_array = np.array(liquid_y_coords)
liquid_x_array = np.array(liquid_x_coords)
max_y = np.max(liquid_y_array)
bottom_region_mask = liquid_y_array >= (max_y - 5)
container_bottom_in_crop = int(np.median(liquid_y_array[bottom_region_mask]))
on_bottom_line = liquid_y_array == container_bottom_in_crop
if np.sum(on_bottom_line) > 0:
bottom_x_in_crop = int(np.median(liquid_x_array[on_bottom_line]))
else:
bottom_x_in_crop = int(np.median(liquid_x_array[bottom_region_mask]))
min_y = np.min(liquid_y_array)
top_region_mask = liquid_y_array <= (min_y + 5)
container_top_in_crop = int(np.median(liquid_y_array[top_region_mask]))
on_top_line = liquid_y_array == container_top_in_crop
if np.sum(on_top_line) > 0:
top_x_in_crop = int(np.median(liquid_x_array[on_top_line]))
else:
top_x_in_crop = int(np.median(liquid_x_array[top_region_mask]))
container_bottom = container_bottom_in_crop + crop_top
container_top = container_top_in_crop + crop_top
bottom_x = bottom_x_in_crop + crop_left
top_x = top_x_in_crop + crop_left
method = 'liquid_only'
confidence = liquid_conf
# 情况1: 仅air
elif has_air:
air_y_array = np.array(air_y_coords)
air_x_array = np.array(air_x_coords)
max_y = np.max(air_y_array)
bottom_region_mask = air_y_array >= (max_y - 5)
container_bottom_in_crop = int(np.median(air_y_array[bottom_region_mask]))
on_bottom_line = air_y_array == container_bottom_in_crop
if np.sum(on_bottom_line) > 0:
bottom_x_in_crop = int(np.median(air_x_array[on_bottom_line]))
else:
bottom_x_in_crop = int(np.median(air_x_array[bottom_region_mask]))
min_y = np.min(air_y_array)
top_region_mask = air_y_array <= (min_y + 5)
container_top_in_crop = int(np.median(air_y_array[top_region_mask]))
on_top_line = air_y_array == container_top_in_crop
if np.sum(on_top_line) > 0:
top_x_in_crop = int(np.median(air_x_array[on_top_line]))
else:
top_x_in_crop = int(np.median(air_x_array[top_region_mask]))
container_bottom = container_bottom_in_crop + crop_top
container_top = container_top_in_crop + crop_top
bottom_x = bottom_x_in_crop + crop_left
top_x = top_x_in_crop + crop_left
method = 'air_only'
confidence = air_conf
# 情况3: 仅foam
elif has_foam:
all_foam_y = []
all_foam_x = []
for mask, y_coords, x_coords in foam_masks:
all_foam_y.extend(y_coords)
all_foam_x.extend(x_coords)
foam_y_array = np.array(all_foam_y)
foam_x_array = np.array(all_foam_x)
max_y = np.max(foam_y_array)
bottom_region_mask = foam_y_array >= (max_y - 5)
container_bottom_in_crop = int(np.median(foam_y_array[bottom_region_mask]))
on_bottom_line = foam_y_array == container_bottom_in_crop
if np.sum(on_bottom_line) > 0:
bottom_x_in_crop = int(np.median(foam_x_array[on_bottom_line]))
else:
bottom_x_in_crop = int(np.median(foam_x_array[bottom_region_mask]))
min_y = np.min(foam_y_array)
top_region_mask = foam_y_array <= (min_y + 5)
container_top_in_crop = int(np.median(foam_y_array[top_region_mask]))
on_top_line = foam_y_array == container_top_in_crop
if np.sum(on_top_line) > 0:
top_x_in_crop = int(np.median(foam_x_array[on_top_line]))
else:
top_x_in_crop = int(np.median(foam_x_array[top_region_mask]))
container_bottom = container_bottom_in_crop + crop_top
container_top = container_top_in_crop + crop_top
bottom_x = bottom_x_in_crop + crop_left
top_x = top_x_in_crop + crop_left
method = 'foam_only'
confidence = foam_conf
else:
return None
if container_bottom is None or container_top is None:
return None
if container_bottom <= container_top:
return None
container_height = container_bottom - container_top
return {
'index': idx,
'top': int(container_top),
'bottom': int(container_bottom),
'top_x': int(top_x),
'bottom_x': int(bottom_x),
'height': int(container_height),
'confidence': float(confidence),
'method': method
}
def _draw_annotations(
self,
image: np.ndarray,
container_info: Dict,
left: int,
right: int
):
"""在图片上绘制标注点和线"""
top_y = container_info['top']
bottom_y = container_info['bottom']
top_x = container_info['top_x']
bottom_x = container_info['bottom_x']
idx = container_info['index']
top_color = (0, 255, 0)
bottom_color = (0, 0, 255)
line_color = (255, 255, 0)
cv2.circle(image, (top_x, top_y), 8, top_color, -1)
cv2.circle(image, (top_x, top_y), 10, top_color, 2)
cv2.circle(image, (bottom_x, bottom_y), 8, bottom_color, -1)
cv2.circle(image, (bottom_x, bottom_y), 10, bottom_color, 2)
cv2.line(image, (top_x, top_y), (bottom_x, bottom_y), line_color, 2)
cv2.line(image, (left, top_y), (right, top_y), top_color, 1, cv2.LINE_AA)
cv2.line(image, (left, bottom_y), (right, bottom_y), bottom_color, 1, cv2.LINE_AA)
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 0.6
thickness = 2
cv2.putText(image, f"Top-{idx}", (top_x + 15, top_y - 10),
font, font_scale, top_color, thickness)
cv2.putText(image, f"Bottom-{idx}", (bottom_x + 15, bottom_y + 20),
font, font_scale, bottom_color, thickness)
mid_y = (top_y + bottom_y) // 2
mid_x = max(top_x, bottom_x) + 20
cv2.putText(image, f"{container_info['height']}px", (mid_x, mid_y),
font, font_scale, line_color, thickness)
def _draw_boxes(self, image: np.ndarray, boxes: List[Dict]):
"""在图片上绘制box"""
# 定义类别颜色
class_colors = {
'liquid': (0, 255, 0), # 绿色
'air': (255, 0, 0), # 蓝色
'foam': (0, 165, 255), # 橙色
'default': (255, 255, 0) # 青色
}
for box in boxes:
x1, y1, x2, y2 = box['x1'], box['y1'], box['x2'], box['y2']
classes = box['classes'] # 现在是列表
# 如果包含多个类别,使用默认颜色;否则使用类别颜色
if len(classes) > 1:
color = (0, 255, 255) # 黄色 - 表示合并框
label = f"Region ({'+'.join(classes)})"
else:
class_name = classes[0]
color = class_colors.get(class_name, class_colors['default'])
label = f"{class_name}"
# 绘制矩形框
cv2.rectangle(image, (x1, y1), (x2, y2), color, 3)
# 绘制标签背景
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 0.7
thickness = 2
(text_width, text_height), baseline = cv2.getTextSize(
label, font, font_scale, thickness
)
# 标签背景矩形
cv2.rectangle(
image,
(x1, y1 - text_height - baseline - 8),
(x1 + text_width + 8, y1),
color,
-1
)
# 绘制标签文字
cv2.putText(
image,
label,
(x1 + 4, y1 - baseline - 4),
font,
font_scale,
(0, 0, 0), # 黑色文字
thickness
)
# 兼容性别名
AutoboxDetector = AutoAnnotationDetector
def test_auto_annotation():
"""测试自动标注功能(整合标框和标点)"""
import os
print("="*80)
print("🧪 自动标注功能测试")
print("="*80)
# 配置参数
model_path = r"D:\restructure\liquid_level_line_detection_system\database\model\detection_model\detect\best.dat"
test_image_path = r"D:\restructure\liquid_level_line_detection_system\test_image\4.jpg"
output_dir = r"D:\restructure\liquid_level_line_detection_system\test_output"
# 检查文件
if not os.path.exists(model_path):
print(f"❌ 模型文件不存在: {model_path}")
return
if not os.path.exists(test_image_path):
print(f"❌ 测试图片不存在: {test_image_path}")
print(f"💡 请将测试图片放置到: {test_image_path}")
return
# 创建输出目录
os.makedirs(output_dir, exist_ok=True)
# 加载图片
print(f"\n📷 加载测试图片: {test_image_path}")
image = cv2.imread(test_image_path)
if image is None:
print(f"❌ 无法读取图片")
return
print(f" 图片尺寸: {image.shape[1]}x{image.shape[0]}")
# 创建检测器
print(f"\n🔧 初始化自动标注检测器...")
detector = AutoAnnotationDetector(model_path=model_path, device='cuda')
# 执行核心检测
print(f"\n🚀 开始执行检测...")
detection_result = detector.detect(
image=image,
conf_threshold=0.5,
min_area=100
)
if not detection_result.get('success'):
print(f"❌ 检测失败")
if 'error' in detection_result:
print(f" 错误信息: {detection_result['error']}")
return
# ========== 生成box和点数据 ==========
print(f"\n{'='*80}")
print(f"📦 生成Box标注数据")
print(f"{'='*80}")
boxes = detector.get_boxes(
detection_result,
padding=10,
merge_all=True # True=合并所有类别, False=按类别分别生成
)
print(f"\n生成的Box区域:")
for i, box in enumerate(boxes, 1):
print(f" Box {i}:")
print(f" - 包含类别: {', '.join(box['classes'])}")
print(f" - 坐标: ({box['x1']}, {box['y1']}) -> ({box['x2']}, {box['y2']})")
print(f" - 面积: {box['area']}px²")
print(f"\n{'='*80}")
print(f"📍 生成Point标注数据")
print(f"{'='*80}")
points = detector.get_points(detection_result)
print(f"\n生成的Point位置:")
for i, point in enumerate(points, 1):
print(f" Point {i}:")
print(f" - 顶部: ({point['top_x']}, {point['top']})")
print(f" - 底部: ({point['bottom_x']}, {point['bottom']})")
print(f" - 高度: {point['height']}px")
print(f" - 检测方法: {point['method']}")
# ========== 绘制并保存结果 ==========
print(f"\n{'='*80}")
print(f"🎨 绘制标注结果")
print(f"{'='*80}")
# 在同一张图上绘制框和点
combined_image = detector.draw_all(image, boxes, points)
combined_output_path = os.path.join(output_dir, "auto_annotation_result.jpg")
cv2.imwrite(combined_output_path, combined_image)
print(f"\n💾 完整标注图片已保存: {combined_output_path}")
# 显示图片(可选)
try:
cv2.imshow("Auto Annotation Result (Box + Point)", combined_image)
print(f"\n👀 按任意键关闭图片窗口...")
cv2.waitKey(0)
cv2.destroyAllWindows()
except:
print(f"⚠️ 无法显示图片(可能是无GUI环境)")
print(f"\n{'='*80}")
print(f"✅ 测试完成")
print(f"{'='*80}")
if __name__ == "__main__": if __name__ == "__main__":
test_auto_dot() # 测试自动标点功能(原始版本)
# test_auto_dot()
# 测试整合的自动标注功能(标框+标点)
test_auto_annotation()
...@@ -198,7 +198,7 @@ class ChannelPanelHandler: ...@@ -198,7 +198,7 @@ class ChannelPanelHandler:
pass pass
# 如果检测线程已在运行,重新启动以使用新引擎 # 如果检测线程已在运行,重新启动以使用新引擎
if channel_id in self._detection_flags and self._detection_flags[channel_id]: if channel_id in self._channel_detect_statuss and self._channel_detect_statuss[channel_id]:
pass pass
return True return True
...@@ -877,19 +877,11 @@ class ChannelPanelHandler: ...@@ -877,19 +877,11 @@ class ChannelPanelHandler:
if panel: if panel:
panel.updateChannelStatus(channel_id, 'connected') panel.updateChannelStatus(channel_id, 'connected')
panel.setConnected(True) panel.setConnected(True)
pass
else:
pass
# 兼容单通道场景
if hasattr(self, 'channelPanel'):
self.channelPanel.updateChannelStatus(channel_id, 'connected')
self.channelPanel.setConnected(True)
self.statusBar().showMessage( self.statusBar().showMessage(
self.tr(" 通道已连接: {} - 视频流已启动").format(channel_id) self.tr(" 通道已连接: {} - 视频流已启动").format(channel_id)
) )
except Exception as e: except Exception as e:
pass
import traceback import traceback
traceback.print_exc() traceback.print_exc()
...@@ -941,12 +933,6 @@ class ChannelPanelHandler: ...@@ -941,12 +933,6 @@ class ChannelPanelHandler:
# 🔥 保留映射(不删除),以便任务同步时能找到面板 # 🔥 保留映射(不删除),以便任务同步时能找到面板
# del self._channel_panels_map[channel_id] # 注释掉,保留映射 # del self._channel_panels_map[channel_id] # 注释掉,保留映射
# 兼容单通道场景
if hasattr(self, 'channelPanel'):
self.channelPanel.updateChannelStatus(channel_id, 'disconnected')
self.channelPanel.setConnected(False)
self.channelPanel.clearDisplay()
self.statusBar().showMessage(self.tr("⏹ 通道已断开: {}").format(channel_id)) self.statusBar().showMessage(self.tr("⏹ 通道已断开: {}").format(channel_id))
def onChannelManage(self): def onChannelManage(self):
...@@ -1429,7 +1415,7 @@ class ChannelPanelHandler: ...@@ -1429,7 +1415,7 @@ class ChannelPanelHandler:
else: else:
print(f" {channel_id} 检测引擎未就绪,检测线程将只读取帧不执行检测") print(f" {channel_id} 检测引擎未就绪,检测线程将只读取帧不执行检测")
while self._detection_flags.get(channel_id, False): while self._channel_detect_statuss.get(channel_id, False):
try: try:
loop_start_time = time.time() loop_start_time = time.time()
......
...@@ -280,15 +280,18 @@ class CurvePanelHandler: ...@@ -280,15 +280,18 @@ class CurvePanelHandler:
def addChannelData(self, channel_id, channel_name=None, window_name=None, color=None): def addChannelData(self, channel_id, channel_name=None, window_name=None, color=None):
""" """
添加通道数据管理(业务逻辑) 添加通道数据(业务逻辑)
Args: Args:
channel_id: 通道唯一ID channel_id: 通道ID
channel_name: 通道名称(可选) channel_name: 通道名称
window_name: 检测窗口名称(可选) window_name: 窗口名称
color: 曲线颜色(可选) color: 曲线颜色
""" """
print(f"➕ [添加通道] channel_id={channel_id}, channel_name={channel_name}, window_name={window_name}")
if channel_id in self.channel_data: if channel_id in self.channel_data:
print(f" - 通道已存在,跳过添加")
return return
# 默认名称 # 默认名称
...@@ -311,7 +314,11 @@ class CurvePanelHandler: ...@@ -311,7 +314,11 @@ class CurvePanelHandler:
# 通知UI创建通道 # 通知UI创建通道
if self.curve_panel: if self.curve_panel:
print(f" - 通知UI创建通道...")
self.curve_panel.addChannel(channel_id, channel_name, window_name, color) self.curve_panel.addChannel(channel_id, channel_name, window_name, color)
print(f" - UI通道创建完成")
else:
print(f" - ⚠️ curve_panel不存在!")
def updateCurveData(self, channel_id, data_points): def updateCurveData(self, channel_id, data_points):
""" """
...@@ -322,17 +329,21 @@ class CurvePanelHandler: ...@@ -322,17 +329,21 @@ class CurvePanelHandler:
data_points: 数据点列表 [{'timestamp': float, 'height_mm': float}, ...] data_points: 数据点列表 [{'timestamp': float, 'height_mm': float}, ...]
height_mm精度为0.1mm(保留1位小数) height_mm精度为0.1mm(保留1位小数)
""" """
print(f"📊 [更新曲线] channel_id={channel_id}, 数据点数量={len(data_points)}")
if not data_points: if not data_points:
# print(f"⚠️ [曲线数据更新] 数据点为空,channel_id={channel_id}") print(f" - ⚠️ 数据点为空,跳过更新")
return return
if channel_id not in self.channel_data: if channel_id not in self.channel_data:
print(f" - 通道不存在,先添加通道")
self.addChannelData(channel_id) self.addChannelData(channel_id)
channel = self.channel_data[channel_id] channel = self.channel_data[channel_id]
# 🔥 调试:记录更新前的数据点数 # 🔥 调试:记录更新前的数据点数
before_count = len(channel['time']) before_count = len(channel['time'])
print(f" - 更新前数据点数: {before_count}")
# 批量添加数据 # 批量添加数据
added_count = 0 added_count = 0
...@@ -350,6 +361,8 @@ class CurvePanelHandler: ...@@ -350,6 +361,8 @@ class CurvePanelHandler:
# 🔥 调试:记录添加后的数据点数 # 🔥 调试:记录添加后的数据点数
after_add_count = len(channel['time']) after_add_count = len(channel['time'])
print(f" - 实际添加数据点数: {added_count}")
print(f" - 更新后数据点数: {after_add_count}")
# 🔥 限制数据点数量 # 🔥 限制数据点数量
# - 'realtime' 模式:限制为3000个点(滚动窗口) # - 'realtime' 模式:限制为3000个点(滚动窗口)
...@@ -361,6 +374,7 @@ class CurvePanelHandler: ...@@ -361,6 +374,7 @@ class CurvePanelHandler:
before_limit = len(channel['time']) before_limit = len(channel['time'])
channel['time'] = channel['time'][-self.max_points:] channel['time'] = channel['time'][-self.max_points:]
channel['value'] = channel['value'][-self.max_points:] channel['value'] = channel['value'][-self.max_points:]
print(f" - 限制数据点: {before_limit} -> {len(channel['time'])}")
# 🔥 处理时间间隔断点:超过2分钟的数据点之间插入NaN断开连接 # 🔥 处理时间间隔断点:超过2分钟的数据点之间插入NaN断开连接
processed_time, processed_value = self._processTimeGaps( processed_time, processed_value = self._processTimeGaps(
...@@ -368,14 +382,17 @@ class CurvePanelHandler: ...@@ -368,14 +382,17 @@ class CurvePanelHandler:
channel['value'], channel['value'],
max_gap_seconds=120 # 2分钟 = 120秒 max_gap_seconds=120 # 2分钟 = 120秒
) )
print(f" - 处理后数据点数: {len(processed_time)}")
# 更新UI显示(只更新一次) # 更新UI显示(只更新一次)
if self.curve_panel: if self.curve_panel:
print(f" - 开始更新UI显示...")
self.curve_panel.updateCurveDisplay( self.curve_panel.updateCurveDisplay(
channel_id, channel_id,
processed_time, processed_time,
processed_value processed_value
) )
print(f" - UI显示更新完成")
# 更新X轴范围 # 更新X轴范围
if channel['time']: if channel['time']:
...@@ -386,6 +403,8 @@ class CurvePanelHandler: ...@@ -386,6 +403,8 @@ class CurvePanelHandler:
if channel['value']: if channel['value']:
max_value = max(channel['value']) max_value = max(channel['value'])
self.curve_panel.setYRangeAuto(max_value) self.curve_panel.setYRangeAuto(max_value)
else:
print(f" - ⚠️ curve_panel不存在,无法更新UI!")
def _processTimeGaps(self, time_data, value_data, max_gap_seconds=120): def _processTimeGaps(self, time_data, value_data, max_gap_seconds=120):
...@@ -694,26 +713,31 @@ class CurvePanelHandler: ...@@ -694,26 +713,31 @@ class CurvePanelHandler:
try: try:
import sys import sys
# 动态获取项目根目录 # 🔥 动态获取数据目录(与storage_thread保持一致)
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
# 打包后的exe # 打包后:使用 sys._MEIPASS 指向 _internal 目录
project_root = os.path.dirname(sys.executable) data_root = sys._MEIPASS
else: else:
# 开发环境:基于配置模块获取 # 开发环境:基于配置模块获取
try: try:
from database.config import get_project_root from database.config import get_project_root
project_root = get_project_root() data_root = get_project_root()
except ImportError: except ImportError:
# 后备方案:当前工作目录 # 后备方案:当前工作目录
project_root = os.getcwd() data_root = os.getcwd()
# 构建完整路径 # 构建完整路径
mission_folder_path = os.path.join(project_root, 'database', 'mission_result', mission_name) mission_folder_path = os.path.join(data_root, 'database', 'mission_result', mission_name)
print(f"🔍 [路径构建] 任务名称: {mission_name}")
print(f"🔍 [路径构建] 数据根目录: {data_root}")
print(f"🔍 [路径构建] 完整路径: {mission_folder_path}")
print(f"🔍 [路径构建] 路径是否存在: {os.path.exists(mission_folder_path)}")
# 检查路径是否存在 # 检查路径是否存在
if os.path.exists(mission_folder_path): if os.path.exists(mission_folder_path):
return mission_folder_path return mission_folder_path
else: else:
print(f"❌ [路径构建] 路径不存在: {mission_folder_path}")
return None return None
except Exception as e: except Exception as e:
...@@ -764,23 +788,27 @@ class CurvePanelHandler: ...@@ -764,23 +788,27 @@ class CurvePanelHandler:
import sys import sys
try: try:
# 动态获取项目根目录 # 🔥 动态获取数据目录(与storage_thread保持一致)
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
# 打包后的exe # 打包后:使用 sys._MEIPASS 指向 _internal 目录
project_root = os.path.dirname(sys.executable) data_root = sys._MEIPASS
else: else:
# 开发环境:基于配置模块获取 # 开发环境:基于配置模块获取
try: try:
from database.config import get_project_root from database.config import get_project_root
project_root = get_project_root() data_root = get_project_root()
except ImportError: except ImportError:
# 后备方案:当前工作目录 # 后备方案:当前工作目录
project_root = os.getcwd() data_root = os.getcwd()
# 构建 mission_result 目录路径 # 构建 mission_result 目录路径
mission_result_dir = os.path.join(project_root, 'database', 'mission_result') mission_result_dir = os.path.join(data_root, 'database', 'mission_result')
print(f"🔍 [任务列表] 数据根目录: {data_root}")
print(f"🔍 [任务列表] 任务目录: {mission_result_dir}")
print(f"🔍 [任务列表] 目录是否存在: {os.path.exists(mission_result_dir)}")
if not os.path.exists(mission_result_dir): if not os.path.exists(mission_result_dir):
print(f"❌ [任务列表] 目录不存在: {mission_result_dir}")
# 通知UI显示空列表 # 通知UI显示空列表
if self.curve_panel: if self.curve_panel:
self.curve_panel.updateMissionFolderList([]) self.curve_panel.updateMissionFolderList([])
...@@ -829,16 +857,30 @@ class CurvePanelHandler: ...@@ -829,16 +857,30 @@ class CurvePanelHandler:
if not data_directory or not os.path.exists(data_directory): if not data_directory or not os.path.exists(data_directory):
return False return False
# 🔥 切换到历史模式(不限制数据点数量) # 切换到历史模式(不限制数据点数量)
self.setCurveLoadMode('history') self.setCurveLoadMode('history')
# 查找所有CSV文件 # 查找所有CSV文件
csv_files = [f for f in os.listdir(data_directory) if f.endswith('.csv')] print(f"\n🔍 [曲线加载] ==================== 开始加载 ====================")
print(f"🔍 [曲线加载] 扫描目录: {data_directory}")
print(f"🔍 [曲线加载] 目录是否存在: {os.path.exists(data_directory)}")
if not os.path.exists(data_directory):
print(f"❌ [曲线加载] 目录不存在: {data_directory}")
return False
# 列出目录中的所有文件
all_files = os.listdir(data_directory)
print(f"🔍 [曲线加载] 目录中所有文件: {all_files}")
csv_files = [f for f in all_files if f.endswith('.csv')]
print(f"🔍 [曲线加载] 找到 {len(csv_files)} 个CSV文件: {csv_files}")
if not csv_files: if not csv_files:
print(f"⚠️ [曲线加载] 未找到CSV文件")
return False return False
# 🔥 检查是否需要显示进度条 # 检查是否需要显示进度条
# 触发条件:CSV文件数量 > 1 或 单个CSV文件大小 > 8MB # 触发条件:CSV文件数量 > 1 或 单个CSV文件大小 > 8MB
show_progress = False show_progress = False
...@@ -871,11 +913,13 @@ class CurvePanelHandler: ...@@ -871,11 +913,13 @@ class CurvePanelHandler:
print(f"✅ [进度条] 已显示进度对话框") print(f"✅ [进度条] 已显示进度对话框")
# 创建并启动后台加载线程 # 创建并启动后台加载线程
print(f"🧵 [曲线加载] 创建后台加载线程...")
self._load_thread = CurveDataLoadThread( self._load_thread = CurveDataLoadThread(
data_directory=data_directory, data_directory=data_directory,
csv_files=csv_files, csv_files=csv_files,
handler=self handler=self
) )
print(f"🧵 [曲线加载] 后台线程已创建")
# 连接信号(使用Qt.QueuedConnection确保跨线程安全) # 连接信号(使用Qt.QueuedConnection确保跨线程安全)
self._load_thread.progress_updated.connect( self._load_thread.progress_updated.connect(
...@@ -893,7 +937,10 @@ class CurvePanelHandler: ...@@ -893,7 +937,10 @@ class CurvePanelHandler:
) )
# 启动线程 # 启动线程
print(f"🚀 [曲线加载] 启动后台线程...")
self._load_thread.start() self._load_thread.start()
print(f"✅ [曲线数据加载] 后台加载线程已启动")
print(f"🔍 [曲线加载] ==================== 加载流程启动完成 ====================\n")
return True return True
...@@ -910,17 +957,28 @@ class CurvePanelHandler: ...@@ -910,17 +957,28 @@ class CurvePanelHandler:
def _onFileLoaded(self, channel_id, channel_name, window_name, color, data_points): def _onFileLoaded(self, channel_id, channel_name, window_name, color, data_points):
"""处理单个文件加载完成""" """处理单个文件加载完成"""
print(f"📥 [文件加载] 收到文件数据:")
print(f" - channel_id: {channel_id}")
print(f" - channel_name: {channel_name}")
print(f" - window_name: {window_name}")
print(f" - 数据点数量: {len(data_points)}")
# 添加通道(如果不存在) # 添加通道(如果不存在)
if channel_id not in self.channel_data: if channel_id not in self.channel_data:
print(f" - 添加新通道: {channel_id}")
self.addChannelData( self.addChannelData(
channel_id=channel_id, channel_id=channel_id,
channel_name=channel_name, channel_name=channel_name,
window_name=window_name, window_name=window_name,
color=color color=color
) )
else:
print(f" - 通道已存在: {channel_id}")
# 批量更新曲线数据 # 批量更新曲线数据
print(f" - 开始更新曲线数据...")
self.updateCurveData(channel_id, data_points) self.updateCurveData(channel_id, data_points)
print(f" - 曲线数据更新完成")
def _onLoadFinished(self, progress_dialog, success, count): def _onLoadFinished(self, progress_dialog, success, count):
"""处理加载完成""" """处理加载完成"""
...@@ -1034,8 +1092,7 @@ class CurvePanelHandler: ...@@ -1034,8 +1092,7 @@ class CurvePanelHandler:
new_points: 新增的数据点列表 new_points: 新增的数据点列表
""" """
try: try:
# 🔥 调试:记录接收到的数据
print(f"📥 [曲线数据处理-主线程] 接收到数据: curve_id={curve_id}, area_idx={area_idx}, 数据点数={len(new_points)}")
# 🔥 检查是否在曲线模式(防止UI对象生命周期问题) # 🔥 检查是否在曲线模式(防止UI对象生命周期问题)
if hasattr(self, 'is_curve_mode_active'): if hasattr(self, 'is_curve_mode_active'):
...@@ -1050,7 +1107,6 @@ class CurvePanelHandler: ...@@ -1050,7 +1107,6 @@ class CurvePanelHandler:
from thread_manager.threads.curve_thread import CurveThread from thread_manager.threads.curve_thread import CurveThread
if not CurveThread.is_running(): if not CurveThread.is_running():
# 全局曲线线程已停止,不更新UI(静默跳过) # 全局曲线线程已停止,不更新UI(静默跳过)
print(f"⚠️ [曲线数据处理-主线程] 曲线线程未运行,跳过更新")
return return
except ImportError: except ImportError:
# 如果导入失败,继续处理 # 如果导入失败,继续处理
......
# -*- coding: utf-8 -*-
"""
液位检测引擎 - 完整版
提供简洁的检测接口:输入标注数据和帧,输出液位高度数据
"""
import cv2
import numpy as np
from pathlib import Path
# 导入动态路径获取函数
from database.config import get_temp_models_dir
# ==================== 辅助函数 ====================
def get_class_color(class_name):
"""为不同类别分配不同的颜色"""
color_map = {
'liquid': (0, 255, 0), # 绿色 - 液体
'foam': (255, 0, 0), # 蓝色 - 泡沫
'air': (0, 0, 255), # 红色 - 空气
}
return color_map.get(class_name, (128, 128, 128)) # 默认灰色
def calculate_foam_boundary_lines(mask):
"""计算foam mask的顶部和底部边界线"""
if np.sum(mask) == 0:
return None, None
y_coords = np.where(mask)[0]
if len(y_coords) == 0:
return None, None
top_y = np.min(y_coords)
bottom_y = np.max(y_coords)
# 计算顶部边界线的平均位置
top_region_height = max(1, int((bottom_y - top_y) * 0.1))
top_region_mask = mask[top_y:top_y + top_region_height, :]
if np.sum(top_region_mask) > 0:
top_y_coords = np.where(top_region_mask)[0] + top_y
top_line_y = np.mean(top_y_coords)
else:
top_line_y = top_y
# 计算底部边界线的平均位置
bottom_region_height = max(1, int((bottom_y - top_y) * 0.1))
bottom_region_mask = mask[bottom_y - bottom_region_height:bottom_y + 1, :]
if np.sum(bottom_region_mask) > 0:
bottom_y_coords = np.where(bottom_region_mask)[0] + (bottom_y - bottom_region_height)
bottom_line_y = np.mean(bottom_y_coords)
else:
bottom_line_y = bottom_y
return top_line_y, bottom_line_y
def analyze_multiple_foams(foam_masks, container_pixel_height):
"""分析多个foam,找到可能的液位边界"""
if len(foam_masks) < 2:
return None
foam_boundaries = []
# 计算每个foam的边界信息
for i, mask in enumerate(foam_masks):
top_y, bottom_y = calculate_foam_boundary_lines(mask)
if top_y is not None and bottom_y is not None:
center_y = (top_y + bottom_y) / 2
foam_boundaries.append({
'index': i,
'top_y': top_y,
'bottom_y': bottom_y,
'center_y': center_y
})
if len(foam_boundaries) < 2:
return None
# 按垂直位置排序
foam_boundaries.sort(key=lambda x: x['center_y'])
error_threshold_px = container_pixel_height * 0.1
# 检查相邻foam之间的边界
for i in range(len(foam_boundaries) - 1):
upper_foam = foam_boundaries[i]
lower_foam = foam_boundaries[i + 1]
upper_bottom = upper_foam['bottom_y']
lower_top = lower_foam['top_y']
boundary_distance = abs(upper_bottom - lower_top)
if boundary_distance <= error_threshold_px:
liquid_level_y = (upper_bottom + lower_top) / 2
return liquid_level_y
return None
def stable_median(data, max_std=1.0):
"""稳健地计算中位数"""
if len(data) == 0:
return 0
data = np.array(data)
q1, q3 = np.percentile(data, [25, 75])
iqr = q3 - q1
lower, upper = q1 - 1.5 * iqr, q3 + 1.5 * iqr
data = data[(data >= lower) & (data <= upper)]
if len(data) >= 2 and np.std(data) > max_std:
median_val = np.median(data)
data = data[np.abs(data - median_val) <= max_std]
return float(np.median(data)) if len(data) > 0 else 0
# ==================== 主检测引擎 ====================
class LiquidDetectionEngine:
"""
液位检测引擎
输入:
1. 标注数据(boxes, fixed_bottoms, fixed_tops, actual_heights)
2. 视频帧
输出:
液位高度数据字典
"""
def __init__(self, model_path=None, device='cuda', batch_size=4):
"""
初始化检测引擎(支持GPU批处理加速)
Args:
model_path: YOLO模型路径(.pt 或 .dat 文件)
device: 计算设备 ('cuda', 'cpu', '0', '1' 等)
batch_size: 批处理大小(1-8,推荐4)
"""
self.model = None
self.model_path = model_path
self.device = self._validate_device(device)
self.batch_size = batch_size
# 标注数据
self.targets = [] # [(cx, cy, size), ...]
self.fixed_container_bottoms = [] # 容器底部y坐标列表
self.fixed_container_tops = [] # 容器顶部y坐标列表
self.actual_heights = [] # 实际容器高度列表(毫米)
# 卡尔曼滤波器
self.kalman_filters = []
# 检测状态
self.recent_observations = []
self.no_liquid_count = []
self.last_liquid_heights = []
self.frame_counters = []
self.consecutive_rejects = []
self.last_observations = []
# 滤波参数
self.smooth_window = 5
self.error_percentage = 30 # 误差百分比阈值
# 🔥 延迟加载模型 - 不在构造函数中加载,避免程序启动时自动下载 yolo11n.pt
# 模型将在实际需要时通过显式调用 load_model() 加载
# if model_path:
# self.load_model(model_path)
def _validate_device(self, device):
"""验证并选择可用的设备"""
try:
import torch
if device in ['cuda', '0'] or device.startswith('cuda:'):
if torch.cuda.is_available():
return 'cuda' if device in ['cuda', '0'] else device
else:
return 'cpu'
return device
except Exception:
return 'cpu'
def load_model(self, model_path):
"""
加载YOLO模型
Args:
model_path: 模型文件路径(支持 .pt 和 .dat 格式)
Returns:
bool: 加载是否成功
"""
try:
import os
# 检查模型文件是否存在
if not os.path.exists(model_path):
print(f"❌ [检测引擎] 模型文件不存在: {model_path}")
return False
# 如果是 .dat 文件,先解码
if model_path.endswith('.dat'):
decoded_path = self._decode_dat_model(model_path)
if decoded_path is None:
print(f"❌ [检测引擎] .dat 文件解码失败: {model_path}")
return False
model_path = decoded_path
# 延迟导入 ultralytics,避免在模块加载时触发下载
from ultralytics import YOLO
# 验证模型文件完整性
if not self._validate_model_file(model_path):
print(f"❌ [检测引擎] 模型文件验证失败: {model_path}")
return False
# 🔥 验证模型文件存在后,设置离线模式防止自动下载其他模型
if not os.path.exists(model_path):
print(f"❌ [检测引擎] 模型文件不存在: {model_path}")
return False
os.environ['YOLO_VERBOSE'] = 'False' # 禁用详细输出
os.environ['YOLO_OFFLINE'] = '1' # 离线模式
os.environ['ULTRALYTICS_OFFLINE'] = 'True' # 离线模式
print(f"🔄 [检测引擎] 正在加载模型: {model_path}")
# 加载模型到GPU
self.model = YOLO(model_path)
self.model.to(self.device) # 移动模型到指定设备
self.model_path = model_path
print(f"✅ [检测引擎] 模型加载成功: {os.path.basename(model_path)}")
return True
except Exception as e:
print(f"❌ [检测引擎] 模型加载失败: {e}")
return False
def _validate_model_file(self, model_path):
"""
验证模型文件的完整性
Args:
model_path: 模型文件路径
Returns:
bool: 文件是否有效
"""
try:
import os
# 检查文件大小(模型文件不应该太小)
file_size = os.path.getsize(model_path)
if file_size < 1024: # 小于1KB的文件可能无效
print(f"⚠️ [检测引擎] 模型文件过小: {file_size} bytes")
return False
# 检查文件扩展名
if not (model_path.endswith('.pt') or model_path.endswith('.pth')):
print(f"⚠️ [检测引擎] 不支持的模型格式: {model_path}")
return False
# 尝试读取文件头部,验证是否为有效的PyTorch模型
try:
with open(model_path, 'rb') as f:
header = f.read(8)
# PyTorch模型文件通常以特定的魔数开头
if len(header) < 8:
print(f"⚠️ [检测引擎] 模型文件头部不完整")
return False
except Exception as e:
print(f"⚠️ [检测引擎] 无法读取模型文件: {e}")
return False
print(f"✅ [检测引擎] 模型文件验证通过: {os.path.basename(model_path)} ({file_size} bytes)")
return True
except Exception as e:
print(f"❌ [检测引擎] 模型文件验证异常: {e}")
return False
def _decode_dat_model(self, dat_path):
"""
解码 .dat 格式的模型文件(独立实现,不依赖外部模块)
.dat 文件格式:
- SIGNATURE (14 bytes): b'LDS_MODEL_FILE'
- VERSION (4 bytes): uint32, 当前为 1
- FILENAME_LEN (4 bytes): uint32
- FILENAME (FILENAME_LEN bytes): utf-8 编码的原始文件名
- DATA_LEN (8 bytes): uint64
- ENCRYPTED_DATA (DATA_LEN bytes): 加密的模型数据
Args:
dat_path: .dat 文件路径
Returns:
str: 解码后的 .pt 文件路径,失败返回 None
"""
try:
import struct
import hashlib
# 解密参数(与 liquid4/core/model_loader.py 保持一致)
SIGNATURE = b'LDS_MODEL_FILE'
VERSION = 1
ENCRYPTION_KEY = "liquid_detection_system_2024"
# 生成密钥哈希
key_hash = hashlib.sha256(ENCRYPTION_KEY.encode('utf-8')).digest()
# 读取并解析 .dat 文件
with open(dat_path, 'rb') as f:
# 1. 读取并验证签名
signature = f.read(len(SIGNATURE))
if signature != SIGNATURE:
return None
# 2. 读取并验证版本
version = struct.unpack('<I', f.read(4))[0]
if version != VERSION:
return None
# 3. 读取原始文件名
filename_len = struct.unpack('<I', f.read(4))[0]
original_filename = f.read(filename_len).decode('utf-8')
# 4. 读取加密数据长度
data_len = struct.unpack('<Q', f.read(8))[0]
# 5. 读取加密数据
encrypted_data = f.read(data_len)
# XOR 解密(与 liquid4 算法一致)
decrypted_data = bytearray()
key_len = len(key_hash)
for i, byte in enumerate(encrypted_data):
decrypted_data.append(byte ^ key_hash[i % key_len])
decrypted_data = bytes(decrypted_data)
# 保存到临时目录(使用完整路径的hash作为文件名,避免冲突)
# 使用动态路径获取临时模型目录
temp_dir = Path(get_temp_models_dir())
temp_dir.mkdir(exist_ok=True)
# 使用模型文件的完整路径生成唯一文件名
path_hash = hashlib.md5(str(dat_path).encode()).hexdigest()[:8]
temp_model_path = temp_dir / f"temp_{Path(dat_path).stem}_{path_hash}.pt"
with open(temp_model_path, 'wb') as f:
f.write(decrypted_data)
return str(temp_model_path)
except Exception as e:
return None
def _parse_targets(self, boxes):
"""解析boxes为targets格式
Args:
boxes: 检测框列表 [[x1, y1, x2, y2], ...] 或 [[cx, cy, size], ...]
Returns:
list: targets列表 [(cx, cy, size), ...]
"""
targets = []
for box in boxes:
if len(box) == 3:
# 已经是 (cx, cy, size) 格式
targets.append(tuple(box))
elif len(box) >= 4:
# 是 (x1, y1, x2, y2) 格式,转换为 (cx, cy, size)
x1, y1, x2, y2 = box[:4]
cx = int((x1 + x2) / 2)
cy = int((y1 + y2) / 2)
size = max(abs(x2 - x1), abs(y2 - y1))
targets.append((cx, cy, size))
return targets
def configure(self, boxes, fixed_bottoms, fixed_tops, actual_heights):
"""
配置标注数据
Args:
boxes: 检测框列表 [[x1, y1, x2, y2], ...] 或 [[cx, cy, size], ...]
fixed_bottoms: 容器底部点列表 [y1, y2, ...]
fixed_tops: 容器顶部点列表 [y1, y2, ...]
actual_heights: 实际容器高度列表 [h1, h2, ...] (单位:毫米)
"""
try:
# 转换boxes为targets格式 [(cx, cy, size), ...]
self.targets = self._parse_targets(boxes)
# 自动纠正top和bottom的顺序
# 在图像坐标系中,top的Y坐标应该小于bottom的Y坐标
corrected_bottoms = []
corrected_tops = []
for i in range(len(fixed_bottoms)):
bottom = fixed_bottoms[i] if i < len(fixed_bottoms) else 0
top = fixed_tops[i] if i < len(fixed_tops) else 0
# 如果top > bottom,说明标反了,自动交换
if top > bottom:
print(f"[配置] 检测到区域{i}的top({top}) > bottom({bottom}),自动交换")
corrected_bottoms.append(top)
corrected_tops.append(bottom)
else:
corrected_bottoms.append(bottom)
corrected_tops.append(top)
self.fixed_container_bottoms = corrected_bottoms
self.fixed_container_tops = corrected_tops
self.actual_heights = [float(h) for h in actual_heights]
# 初始化状态列表
num_targets = len(self.targets)
self.recent_observations = [[] for _ in range(num_targets)]
self.no_liquid_count = [0] * num_targets
self.last_liquid_heights = [None] * num_targets
self.frame_counters = [0] * num_targets
self.consecutive_rejects = [0] * num_targets
self.last_observations = [None] * num_targets
# 初始化卡尔曼滤波器
self._init_kalman_filters(num_targets)
except Exception:
pass
def _init_kalman_filters(self, num_targets):
"""初始化卡尔曼滤波器列表"""
self.kalman_filters = []
for i in range(num_targets):
kf = cv2.KalmanFilter(2, 1)
kf.measurementMatrix = np.array([[1, 0]], np.float32)
kf.transitionMatrix = np.array([[1, 0.9], [0, 0.9]], np.float32)
kf.processNoiseCov = np.diag([1e-4, 1e-3]).astype(np.float32)
kf.measurementNoiseCov = np.array([[10]], dtype=np.float32)
# 初始状态:假设容器高度的50%
init_height = self.actual_heights[i] * 0.5 if i < len(self.actual_heights) else 5.0
kf.statePost = np.array([[init_height], [0]], dtype=np.float32)
self.kalman_filters.append(kf)
def detect(self, frame, annotation_config=None):
"""
检测帧中的液位高度
Args:
frame: 输入的视频帧 (numpy.ndarray)
annotation_config: 可选的标注配置字典,包含 boxes, fixed_bottoms, fixed_tops, actual_heights
如果提供,将使用此配置而不是实例配置(用于多通道共享模型场景)
Returns:
dict: 检测结果
{
'liquid_line_positions': {
0: {'y': y坐标, 'height_mm': 高度毫米, 'height_px': 高度像素},
1: {...},
...
},
'success': bool # 检测是否成功
}
"""
if self.model is None:
return {'liquid_line_positions': {}, 'success': False}
# 使用动态配置或实例配置
if annotation_config:
targets = self._parse_targets(annotation_config.get('boxes', []))
fixed_bottoms = annotation_config.get('fixed_bottoms', [])
fixed_tops = annotation_config.get('fixed_tops', [])
actual_heights = annotation_config.get('actual_heights', [])
else:
targets = self.targets
fixed_bottoms = self.fixed_container_bottoms
fixed_tops = self.fixed_container_tops
actual_heights = self.actual_heights
if not targets:
return {'liquid_line_positions': {}, 'success': False}
try:
h, w = frame.shape[:2]
liquid_line_positions = {}
for idx, (center_x, center_y, crop_size) in enumerate(targets):
# 裁剪检测区域
half_size = crop_size // 2
top = max(center_y - half_size, 0)
bottom = min(center_y + half_size, h)
left = max(center_x - half_size, 0)
right = min(center_x + half_size, w)
cropped = frame[top:bottom, left:right]
if cropped.size == 0:
continue
# 执行检测(传入top坐标和配置用于坐标转换)
liquid_height_mm = self._detect_single_target(
cropped, idx, top,
fixed_bottoms[idx] if idx < len(fixed_bottoms) else None,
fixed_tops[idx] if idx < len(fixed_tops) else None,
actual_heights[idx] if idx < len(actual_heights) else 20.0
)
# 如果没有检测到液位,使用高度0
if liquid_height_mm is None:
liquid_height_mm = 0.0
# 计算液位线位置
# 注意:container_bottom_y 和 container_top_y 已经是原图中的绝对坐标
container_bottom_y = fixed_bottoms[idx] if idx < len(fixed_bottoms) else 0
container_top_y = fixed_tops[idx] if idx < len(fixed_tops) else 0
container_height_mm = actual_heights[idx] if idx < len(actual_heights) else 20.0
container_pixel_height = container_bottom_y - container_top_y
pixel_per_mm = container_pixel_height / container_height_mm
height_px = int(liquid_height_mm * pixel_per_mm)
# 液位线在原图中的绝对位置(container_bottom_y 已经是绝对坐标)
liquid_line_y_absolute = container_bottom_y - height_px
# 统一使用mm单位输出
liquid_line_positions[idx] = {
'y': liquid_line_y_absolute,
'height_mm': liquid_height_mm, # 毫米单位
'height_px': height_px,
'left': left,
'right': right
}
# # 调试信息:输出数据
# print(f"\n [检测输出-目标{idx}] 液位线位置数据:")
# print(f" - y坐标: {liquid_line_y_absolute}px")
# print(f" - height_mm: {liquid_height_mm:.2f}mm ️ 注意单位")
# print(f" - height_px: {height_px}px")
# # 调试信息:最终输出
# print(f"\n [检测输出] 最终结果:")
# for idx, pos in liquid_line_positions.items():
# print(f" 目标{idx}: height_mm={pos['height_mm']:.2f}mm (键名是'height_mm')")
return {
'liquid_line_positions': liquid_line_positions,
'success': len(liquid_line_positions) > 0
}
except Exception:
return {'liquid_line_positions': {}, 'success': False}
def _detect_single_target(self, cropped, idx, crop_top_y, container_bottom=None, container_top=None, container_height_mm=20.0):
"""
检测单个目标的液位高度
Args:
cropped: 裁剪后的图像
idx: 目标索引
crop_top_y: 裁剪区域在原图中的top坐标(用于坐标转换)
container_bottom: 容器底部y坐标(可选,如果为None则使用实例配置)
container_top: 容器顶部y坐标(可选,如果为None则使用实例配置)
container_height_mm: 容器实际高度(毫米)
Returns:
float: 液位高度(毫米),失败返回 None
"""
try:
# 获取容器信息(原图绝对坐标)
if container_bottom is not None and container_top is not None:
container_bottom_offset = container_bottom
container_top_offset = container_top
else:
container_bottom_offset = self.fixed_container_bottoms[idx]
container_top_offset = self.fixed_container_tops[idx]
container_height_mm = self.actual_heights[idx]
container_pixel_height = container_bottom_offset - container_top_offset
pixel_per_mm = container_pixel_height / container_height_mm
# # 调试信息:容器参数
# print(f"\n [检测-目标{idx}] 容器参数:")
# print(f" - 底部y: {container_bottom_offset}px")
# print(f" - 顶部y: {container_top_offset}px")
# print(f" - 容器像素高度: {container_pixel_height}px")
# print(f" - 容器实际高度: {container_height_mm}mm")
# print(f" - 像素/毫米比例: {pixel_per_mm:.3f}px/mm")
# 执行YOLO推理(使用GPU + 批处理)
mission_results = self.model.predict(
source=cropped,
imgsz=640,
conf=0.5,
iou=0.5,
device=self.device, # 强制使用GPU
batch=self.batch_size, # 启用批处理
save=False,
verbose=False,
half=True if self.device != 'cpu' else False, # GPU使用FP16加速
stream=False # 批处理模式
)
mission_result = mission_results[0]
# # 调试信息:YOLO推理结果
# print(f"[检测-目标{idx}] YOLO推理结果:")
# print(f" - mission_result.masks: {mission_result.masks is not None}")
# if mission_result.masks is not None:
# print(f" - masks数量: {len(mission_result.masks.data)}")
# else:
# print(f" - ⚠️ 未检测到任何mask!")
liquid_height = None
# 处理检测结果
if mission_result.masks is not None:
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()
else:
return None
# 收集所有mask信息
all_masks_info = []
for i in range(len(masks)):
class_name = self.model.names[classes[i]]
conf = confidences[i]
if confidences[i] >= 0.5:
resized_mask = cv2.resize(
masks[i].astype(np.uint8),
(cropped.shape[1], cropped.shape[0])
) > 0.5
all_masks_info.append((resized_mask, class_name, confidences[i]))
if len(all_masks_info) == 0:
return None
# ️ 关键修复:将原图坐标转换为裁剪图像坐标
# container_bottom_offset 是原图绝对坐标,需要转换为裁剪图像中的相对坐标
container_bottom_in_crop = container_bottom_offset - crop_top_y
container_top_in_crop = container_top_offset - crop_top_y
# print(f" [坐标转换-目标{idx}]:")
# print(f" - 裁剪区域top: {crop_top_y}px (原图坐标)")
# print(f" - 原图容器底部: {container_bottom_offset}px → 裁剪图像中: {container_bottom_in_crop}px")
# print(f" - 原图容器顶部: {container_top_offset}px → 裁剪图像中: {container_top_in_crop}px")
# print(f" - 裁剪图像中容器高度: {container_bottom_in_crop - container_top_in_crop}px(应等于{container_pixel_height}px)")
# 分析mask获取液位高度(使用裁剪图像坐标)
liquid_height = self._enhanced_liquid_detection(
all_masks_info,
container_bottom_in_crop, # 使用裁剪图像坐标
container_pixel_height,
container_height_mm,
idx
)
return liquid_height
except Exception as e:
print(f"[检测-目标{idx}] ❌ 检测异常: {e}")
return None
def _enhanced_liquid_detection(self, all_masks_info, container_bottom,
container_pixel_height, container_height_mm, idx):
"""
增强的液位检测,结合连续帧逻辑和foam分析
Args:
all_masks_info: mask信息列表 [(mask, class_name, confidence), ...]
container_bottom: 容器底部y坐标
container_pixel_height: 容器像素高度
container_height_mm: 容器实际高度(毫米)
idx: 目标索引
Returns:
float: 液位高度(毫米),失败返回 None
"""
pixel_per_mm = container_pixel_height / container_height_mm
# 分离不同类别的mask
liquid_masks = []
foam_masks = []
air_masks = []
for mask, class_name, confidence in all_masks_info:
if class_name == 'liquid':
liquid_masks.append(mask)
elif class_name == 'foam':
foam_masks.append(mask)
elif class_name == 'air':
air_masks.append(mask)
# 方法1:直接liquid检测(优先)
if liquid_masks:
# 找到最上层的液体mask
topmost_y = float('inf')
for i, mask in enumerate(liquid_masks):
y_indices = np.where(mask)[0]
if len(y_indices) > 0:
mask_top_y = np.min(y_indices)
# print(f" - liquid mask {i+1}: 顶部y={mask_top_y}px")
if mask_top_y < topmost_y:
topmost_y = mask_top_y
if topmost_y != float('inf'):
liquid_height_px = container_bottom - topmost_y
liquid_height_mm = liquid_height_px / pixel_per_mm
return max(0, min(liquid_height_mm, container_height_mm))
# 方法2:foam边界分析(备选)- 连续3帧未检测到liquid时启用
if self.no_liquid_count[idx] >= 3:
if len(foam_masks) >= 2:
# 多个foam,寻找液位边界
liquid_y = analyze_multiple_foams(foam_masks, container_pixel_height)
if liquid_y is not None:
liquid_height_px = container_bottom - liquid_y
liquid_height_mm = liquid_height_px / pixel_per_mm
return max(0, min(liquid_height_mm, container_height_mm))
elif len(foam_masks) == 1:
# 单个foam,使用下边界
foam_mask = foam_masks[0]
top_y, bottom_y = calculate_foam_boundary_lines(foam_mask)
if bottom_y is not None:
liquid_height_px = container_bottom - bottom_y
liquid_height_mm = liquid_height_px / pixel_per_mm
return max(0, min(liquid_height_mm, container_height_mm))
elif len(air_masks) == 1:
# 单个air,使用下边界
air_mask = air_masks[0]
y_coords = np.where(air_mask)[0]
if len(y_coords) > 0:
bottom_y = np.max(y_coords)
liquid_height_px = container_bottom - bottom_y
liquid_height_mm = liquid_height_px / pixel_per_mm
return max(0, min(liquid_height_mm, container_height_mm))
return None
def _apply_kalman_filter(self, observation, idx, container_height_mm):
"""
应用卡尔曼滤波平滑液位高度
Args:
observation: 观测值(毫米)
idx: 目标索引
container_height_mm: 容器高度(毫米)
Returns:
float: 滤波后的高度(毫米)
"""
# 预测步骤
predicted = self.kalman_filters[idx].predict()
predicted_height = predicted[0][0]
# 计算预测误差(相对于容器高度的百分比)
prediction_error_percent = abs(observation - predicted_height) / container_height_mm * 100
# 检测是否是重复的观测值(保持的液位数据)
is_repeated_observation = (self.last_observations[idx] is not None and
observation == self.last_observations[idx])
# 误差控制逻辑
if prediction_error_percent > self.error_percentage:
# 误差过大,增加拒绝计数
self.consecutive_rejects[idx] += 1
# 检查是否连续6次拒绝
if self.consecutive_rejects[idx] >= 6:
# 连续6次误差过大,强制使用观测值更新
self.kalman_filters[idx].correct(np.array([[observation]], dtype=np.float32))
final_height = self.kalman_filters[idx].statePost[0][0]
self.consecutive_rejects[idx] = 0 # 重置计数器
else:
# 使用预测值
final_height = predicted_height
else:
# 误差可接受,正常更新
self.kalman_filters[idx].correct(np.array([[observation]], dtype=np.float32))
final_height = self.kalman_filters[idx].statePost[0][0]
self.consecutive_rejects[idx] = 0 # 重置计数器
# 更新上次观测值记录
self.last_observations[idx] = observation
# 添加到滑动窗口
self.recent_observations[idx].append(final_height)
if len(self.recent_observations[idx]) > self.smooth_window:
self.recent_observations[idx].pop(0)
# 限制高度范围
final_height = max(0, min(final_height, container_height_mm))
return final_height
def get_smooth_height(self, target_idx):
"""获取平滑后的高度(中位数)"""
if not self.recent_observations[target_idx]:
return 0
return np.median(self.recent_observations[target_idx])
def reset_target(self, target_idx):
"""重置指定目标的滤波器状态"""
if target_idx < len(self.consecutive_rejects):
self.consecutive_rejects[target_idx] = 0
if target_idx < len(self.last_observations):
self.last_observations[target_idx] = None
if target_idx < len(self.recent_observations):
self.recent_observations[target_idx] = []
if target_idx < len(self.no_liquid_count):
self.no_liquid_count[target_idx] = 0
if target_idx < len(self.frame_counters):
self.frame_counters[target_idx] = 0
def cleanup(self):
"""清理资源"""
try:
# 清理临时模型文件(使用动态路径)
temp_dir = Path(get_temp_models_dir())
if temp_dir.exists():
for temp_file in temp_dir.glob("temp_*.pt"):
try:
temp_file.unlink()
except:
pass
except:
pass
...@@ -1165,16 +1165,41 @@ class GeneralSetPanelHandler: ...@@ -1165,16 +1165,41 @@ class GeneralSetPanelHandler:
self.top_points = [] # 存储顶部标记点 self.top_points = [] # 存储顶部标记点
def add_box(self, cx, cy, size): def add_box(self, cx, cy, size):
"""添加检测区域""" """
添加检测区域,并自动计算顶部点和底部点
Args:
cx: 框中心x坐标
cy: 框中心y坐标
size: 框的边长
"""
self.boxes.append((cx, cy, size)) self.boxes.append((cx, cy, size))
# 自动计算并添加底部点和顶部点
# 底部点:box底边y坐标 - box高度的10%,x为中心
half_size = size / 2
bottom_y = cy + half_size - (size * 0.1) # 底边y - 10%高度
bottom_x = cx # x位置为box轴对称中心
self.bottom_points.append((int(bottom_x), int(bottom_y)))
# 顶部点:box顶边y坐标 + box高度的10%,x为中心
top_y = cy - half_size + (size * 0.1) # 顶边y + 10%高度
top_x = cx # x位置为box轴对称中心
self.top_points.append((int(top_x), int(top_y)))
print(f"添加框: 中心({cx}, {cy}), 边长{size}")
print(f" 底部点: ({int(bottom_x)}, {int(bottom_y)})")
print(f" 顶部点: ({int(top_x)}, {int(top_y)})")
def add_bottom(self, x, y): def add_bottom(self, x, y):
"""添加底部标记点""" """添加底部标记点(保留用于兼容性,但不再使用)"""
self.bottom_points.append((x, y)) # 此方法保留但不再使用,因为底部点会在add_box时自动添加
pass
def add_top(self, x, y): def add_top(self, x, y):
"""添加顶部标记点""" """添加顶部标记点(保留用于兼容性,但不再使用)"""
self.top_points.append((x, y)) # 此方法保留但不再使用,因为顶部点会在add_box时自动添加
pass
def get_mission_results(self): def get_mission_results(self):
"""获取标注结果""" """获取标注结果"""
......
...@@ -34,6 +34,7 @@ class MissionPanelHandler: ...@@ -34,6 +34,7 @@ class MissionPanelHandler:
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.mission_panel = None self.mission_panel = None
self.mission_text_status = None # 文本状态管理器
def connectMissionPanel(self, mission_panel): def connectMissionPanel(self, mission_panel):
""" """
...@@ -44,6 +45,9 @@ class MissionPanelHandler: ...@@ -44,6 +45,9 @@ class MissionPanelHandler:
""" """
self.mission_panel = mission_panel self.mission_panel = mission_panel
# 🔥 创建文本状态管理器
self.mission_text_status = MissionTextStatus(mission_panel)
# 连接信号 # 连接信号
mission_panel.removeTaskRequested.connect(self._handleRemoveTask) mission_panel.removeTaskRequested.connect(self._handleRemoveTask)
mission_panel.clearTableRequested.connect(self._handleClearTable) mission_panel.clearTableRequested.connect(self._handleClearTable)
...@@ -60,6 +64,9 @@ class MissionPanelHandler: ...@@ -60,6 +64,9 @@ class MissionPanelHandler:
mission_panel.channelCancelled.connect(self._handleChannelCancelled) mission_panel.channelCancelled.connect(self._handleChannelCancelled)
mission_panel.channelDebugRequested.connect(self._handleChannelDebug) mission_panel.channelDebugRequested.connect(self._handleChannelDebug)
# 🔥 连接表格单击事件(规则2:单击选中行时置为黑色)
mission_panel.table.cellClicked.connect(self._onMissionRowClicked)
# 🔥 根据编译模式控制调试按钮的可见性 # 🔥 根据编译模式控制调试按钮的可见性
self._updateDebugButtonVisibility() self._updateDebugButtonVisibility()
...@@ -107,6 +114,34 @@ class MissionPanelHandler: ...@@ -107,6 +114,34 @@ class MissionPanelHandler:
import traceback import traceback
traceback.print_exc() traceback.print_exc()
def _onMissionRowClicked(self, row, column):
"""
任务行被点击(规则2:单击选中行时该行文本置为黑色)
只有当任务状态为"已启动"时才置黑
Args:
row: 行索引
column: 列索引
"""
if not self.mission_text_status or not self.mission_panel:
return
try:
# 获取任务状态列(第2列)
status_item = self.mission_panel.table.item(row, 2)
if not status_item:
return
task_status = status_item.text()
# 只有任务状态为"已启动"时才置黑
if task_status == "已启动":
self.mission_text_status.setRowBlackOnSelect(row)
except Exception as e:
import traceback
traceback.print_exc()
def _updateDebugButtonVisibility(self): def _updateDebugButtonVisibility(self):
""" """
根据编译模式更新调试按钮的可见性 根据编译模式更新调试按钮的可见性
...@@ -139,7 +174,6 @@ class MissionPanelHandler: ...@@ -139,7 +174,6 @@ class MissionPanelHandler:
self.mission_panel.btn_debug.setEnabled(False) self.mission_panel.btn_debug.setEnabled(False)
except Exception as e: except Exception as e:
pass
import traceback import traceback
traceback.print_exc() traceback.print_exc()
# 出错时默认隐藏调试按钮,确保安全性 # 出错时默认隐藏调试按钮,确保安全性
...@@ -284,6 +318,13 @@ class MissionPanelHandler: ...@@ -284,6 +318,13 @@ class MissionPanelHandler:
# 🔥 通知Widget取消任务分配,不高亮行 # 🔥 通知Widget取消任务分配,不高亮行
if self.mission_panel: if self.mission_panel:
self.mission_panel.cancelTaskAssignment() self.mission_panel.cancelTaskAssignment()
# 🔥 恢复之前选中行的黑色状态(如果有的话)
if self.mission_text_status and self.mission_text_status.selected_row >= 0:
# 保存当前选中行
previous_selected_row = self.mission_text_status.selected_row
# 重新设置为黑色
self.mission_text_status.setRowBlackOnSelect(previous_selected_row)
return return
# 🔥 遍历选中的通道,只更新这些通道的任务标签(不影响其他通道) # 🔥 遍历选中的通道,只更新这些通道的任务标签(不影响其他通道)
...@@ -300,9 +341,6 @@ class MissionPanelHandler: ...@@ -300,9 +341,6 @@ class MissionPanelHandler:
# 更新通道面板的任务信息和配置文件 # 更新通道面板的任务信息和配置文件
self._updateChannelTaskInfo(channel_id, task_id, task_name, mission_result_folder_path) self._updateChannelTaskInfo(channel_id, task_id, task_name, mission_result_folder_path)
# 🔥 更新完所有通道后,一次性更新任务行的通道列颜色
self._setTaskRowChannelColor(task_folder_name, 0, '#000000') # channel_num参数在这里不重要
# 🔥 确认任务分配,高亮选中的行 # 🔥 确认任务分配,高亮选中的行
if self.mission_panel: if self.mission_panel:
self.mission_panel.confirmTaskAssignment() self.mission_panel.confirmTaskAssignment()
...@@ -428,8 +466,6 @@ class MissionPanelHandler: ...@@ -428,8 +466,6 @@ class MissionPanelHandler:
# 🔥 第一步:同步到 channel_config.yaml(独立业务,无论通道是否打开都执行) # 🔥 第一步:同步到 channel_config.yaml(独立业务,无论通道是否打开都执行)
sync_success = self._syncTaskToConfigFile(channel_id, task_id, task_name, save_liquid_data_path) sync_success = self._syncTaskToConfigFile(channel_id, task_id, task_name, save_liquid_data_path)
pass
# 🔥 第二步:更新通道面板UI(可选操作,仅在通道已打开时执行) # 🔥 第二步:更新通道面板UI(可选操作,仅在通道已打开时执行)
# 检查是否有通道面板映射(从ChannelPanelHandler获取) # 检查是否有通道面板映射(从ChannelPanelHandler获取)
if not hasattr(self, '_channel_panels_map'): if not hasattr(self, '_channel_panels_map'):
...@@ -539,7 +575,7 @@ class MissionPanelHandler: ...@@ -539,7 +575,7 @@ class MissionPanelHandler:
config_data = { config_data = {
'task_id': task_info.get('task_id', ''), 'task_id': task_info.get('task_id', ''),
'task_name': task_info.get('task_name', ''), 'task_name': task_info.get('task_name', ''),
'status': task_info.get('status', '待配置'), 'status': task_info.get('status', '未启动'),
'selected_channels': task_info.get('selected_channels', []), 'selected_channels': task_info.get('selected_channels', []),
'created_time': QtCore.QDateTime.currentDateTime().toString('yyyy-MM-dd HH:mm:ss'), 'created_time': QtCore.QDateTime.currentDateTime().toString('yyyy-MM-dd HH:mm:ss'),
'mission_result_folder_path': task_info.get('mission_result_folder_path', ''), # 🔥 添加结果文件夹路径 'mission_result_folder_path': task_info.get('mission_result_folder_path', ''), # 🔥 添加结果文件夹路径
...@@ -806,7 +842,7 @@ class MissionPanelHandler: ...@@ -806,7 +842,7 @@ class MissionPanelHandler:
task_info = { task_info = {
'task_id': config_data.get('task_id', ''), 'task_id': config_data.get('task_id', ''),
'task_name': config_data.get('task_name', ''), 'task_name': config_data.get('task_name', ''),
'status': config_data.get('status', '待配置'), 'status': config_data.get('status', '未启动'),
'selected_channels': config_data.get('selected_channels', []), 'selected_channels': config_data.get('selected_channels', []),
'mission_result_folder_path': config_data.get('mission_result_folder_path', ''), # 🔥 加载结果文件夹路径 'mission_result_folder_path': config_data.get('mission_result_folder_path', ''), # 🔥 加载结果文件夹路径
} }
...@@ -863,94 +899,16 @@ class MissionPanelHandler: ...@@ -863,94 +899,16 @@ class MissionPanelHandler:
# 第三步:统一刷新显示 # 第三步:统一刷新显示
self.mission_panel.refreshDisplay() self.mission_panel.refreshDisplay()
# 🔥 第四步:更新通道列的背景色(根据检测状态) # 🔥 第四步:初始化所有行为灰色(规则1)
self._updateChannelCellColors() if self.mission_text_status:
self.mission_text_status.initializeAllRowsGray()
except Exception as e: except Exception as e:
import traceback import traceback
traceback.print_exc() traceback.print_exc()
def _updateChannelCellColors(self): # 🔥 已删除 _updateChannelCellColors 方法
""" # 通道列颜色现在由 MissionTextStatus 类统一管理
更新任务面板中通道列的文本颜色
通道列颜色规则:
- 默认:灰色(未分配任务)
- 该通道被分配了任务(channelmission有值):黑色
- 检测中且是该通道当前任务:绿色
"""
try:
# 获取所有任务的行数据
if not hasattr(self.mission_panel, '_all_rows_data'):
return
all_rows = self.mission_panel._all_rows_data
# 遍历4个通道,获取每个通道当前运行的任务
channel_current_missions = {}
for ch_idx in range(4):
channel_id = f'channel{ch_idx + 1}'
mission_var_name = f'{channel_id}mission'
# 从主窗口获取当前通道的任务
if hasattr(self, mission_var_name):
current_mission_obj = getattr(self, mission_var_name, None)
if current_mission_obj:
# 🔥 如果是QLabel对象,获取其文本内容
if hasattr(current_mission_obj, 'text'):
current_mission = current_mission_obj.text()
else:
current_mission = str(current_mission_obj)
# 只有非"未分配任务"才记录
if current_mission and current_mission != "未分配任务":
channel_current_missions[ch_idx] = current_mission
# 遍历每一行任务
for row_idx, row_info in enumerate(all_rows):
user_data = row_info.get('user_data', {})
selected_channels = user_data.get('selected_channels', [])
task_folder_name = user_data.get('mission_result_folder_path', '')
# 提取任务名称(如 "1_2")
if task_folder_name:
task_name = os.path.basename(task_folder_name)
else:
task_name = None
# 遍历4个通道列
for ch_idx in range(4):
channel_name = f'通道{ch_idx + 1}'
col_idx = 3 + ch_idx # 通道列从第3列开始
# 检查该通道是否被任务使用
if channel_name in selected_channels:
# 检查该通道是否正在检测
channel_id = f'channel{ch_idx + 1}'
is_detecting = self._isChannelDetecting(channel_id)
# 检查该任务是否是该通道当前运行的任务
current_mission_for_channel = channel_current_missions.get(ch_idx)
is_current_mission = (current_mission_for_channel == task_name)
# 检查该通道是否被分配了任务(channelmission有值且不是"未分配任务")
has_assigned_mission = (ch_idx in channel_current_missions)
if is_detecting and is_current_mission:
# 检测中且是当前任务:设置文本为绿色
self.mission_panel.setCellTextColor(row_idx, col_idx, '#00AA00')
elif has_assigned_mission:
# 该通道被分配了任务:设置文本为黑色
self.mission_panel.setCellTextColor(row_idx, col_idx, '#000000')
else:
# 其他情况:保持灰色
self.mission_panel.setCellTextColor(row_idx, col_idx, '#808080')
except Exception as e:
import traceback
traceback.print_exc()
def _isChannelDetecting(self, channel_id): def _isChannelDetecting(self, channel_id):
""" """
...@@ -1416,10 +1374,9 @@ class MissionPanelHandler: ...@@ -1416,10 +1374,9 @@ class MissionPanelHandler:
channel_id: 通道ID(如 'channel1') channel_id: 通道ID(如 'channel1')
task_folder_name: 任务文件夹名称(如 '1_1') task_folder_name: 任务文件夹名称(如 '1_1')
""" """
try:
# 从 channel_id 提取通道编号 # 从 channel_id 提取通道编号
if not channel_id.startswith('channel'): if not channel_id.startswith('channel'):
print(f"❌ [_updateChannelMissionLabel] 无效的通道ID: {channel_id}")
return return
channel_num = int(channel_id.replace('channel', '')) channel_num = int(channel_id.replace('channel', ''))
...@@ -1443,91 +1400,6 @@ class MissionPanelHandler: ...@@ -1443,91 +1400,6 @@ class MissionPanelHandler:
if panel and hasattr(panel, '_positionTaskLabel'): if panel and hasattr(panel, '_positionTaskLabel'):
panel._positionTaskLabel() panel._positionTaskLabel()
print(f"✅ [多任务] 已更新 {channel_id} 的任务标签: {task_folder_name}")
# 删除状态更新逻辑,双击不改变任务状态
else:
print(f"❌ [_updateChannelMissionLabel] 未找到变量: {mission_var_name}")
# 注意:不在这里调用 _setTaskRowChannelColor,而是在 _handleTaskSelected 中更新完所有通道后再调用
except Exception as e:
print(f"❌ [_updateChannelMissionLabel] 更新通道任务标签失败 ({channel_id}): {e}")
import traceback
traceback.print_exc()
def _setTaskRowChannelColor(self, task_folder_name, channel_num, color):
"""
设置任务行对应通道列的颜色
新逻辑:一个通道列只有一行能被置黑
1. 先将所有任务行的所有通道列重置为灰色
2. 然后根据所有通道的 channelmission 状态,将对应的通道列置黑
Args:
task_folder_name: 任务文件夹名称(如 '1_1')
channel_num: 当前操作的通道编号(1-4)
color: 颜色值(如 '#000000' 黑色)
"""
try:
if not hasattr(self.mission_panel, '_all_rows_data'):
return
# 获取所有通道的当前任务状态
channel_missions = {}
for ch_idx in range(4):
ch_num = ch_idx + 1
mission_var_name = f'channel{ch_num}mission'
if hasattr(self, mission_var_name):
mission_label = getattr(self, mission_var_name)
current_mission = mission_label.text()
channel_missions[ch_num] = current_mission
all_rows = self.mission_panel._all_rows_data
# 🔥 第一步:将所有任务行的所有通道列重置为灰色
for row_idx, row_info in enumerate(all_rows):
user_data = row_info.get('user_data', {})
mission_result_folder_path = user_data.get('mission_result_folder_path', '')
selected_channels = user_data.get('selected_channels', [])
if mission_result_folder_path and selected_channels:
import os
current_task_name = os.path.basename(mission_result_folder_path)
# 将该任务行的所有选中通道列置灰
for channel_name in selected_channels:
if channel_name.startswith('通道'):
ch_num = int(channel_name.replace('通道', ''))
col_idx = 3 + (ch_num - 1)
self.mission_panel.setCellTextColor(row_idx, col_idx, '#808080')
# 🔥 第二步:根据 channelmission 状态,将对应的通道列置黑
for ch_num, current_mission in channel_missions.items():
if current_mission and current_mission != '未分配任务':
# 查找该任务对应的行
for row_idx, row_info in enumerate(all_rows):
user_data = row_info.get('user_data', {})
mission_result_folder_path = user_data.get('mission_result_folder_path', '')
selected_channels = user_data.get('selected_channels', [])
if mission_result_folder_path:
import os
current_task_name = os.path.basename(mission_result_folder_path)
# 如果找到匹配的任务行且该通道被选中
if current_task_name == current_mission:
channel_name = f'通道{ch_num}'
if channel_name in selected_channels:
col_idx = 3 + (ch_num - 1)
self.mission_panel.setCellTextColor(row_idx, col_idx, '#000000')
break
except Exception as e:
import traceback
traceback.print_exc()
def _isTaskInUse(self, task_folder_name): def _isTaskInUse(self, task_folder_name):
""" """
检查指定任务是否被任何通道使用 检查指定任务是否被任何通道使用
...@@ -1538,7 +1410,6 @@ class MissionPanelHandler: ...@@ -1538,7 +1410,6 @@ class MissionPanelHandler:
Returns: Returns:
bool: 如果任务被使用返回True,否则返回False bool: 如果任务被使用返回True,否则返回False
""" """
try:
# 检查所有通道(channel1-channel4) # 检查所有通道(channel1-channel4)
for channel_num in range(1, 5): for channel_num in range(1, 5):
channel_id = f'channel{channel_num}' channel_id = f'channel{channel_num}'
...@@ -1561,55 +1432,14 @@ class MissionPanelHandler: ...@@ -1561,55 +1432,14 @@ class MissionPanelHandler:
return False return False
except Exception as e:
print(f"❌ [状态检查] 检查任务使用状态失败: {e}")
return False
def _refreshAllTaskStatus(self): def _refreshAllTaskStatus(self):
""" """
刷新任务面板中所有任务的状态显示 刷新任务面板中所有任务的状态显示
根据当前通道使用情况,动态更新每个任务的状态: 委托给 MissionTextStatus 类处理
- 被通道使用的任务:已启动
- 未被通道使用的任务:未启动
""" """
try: if self.mission_text_status:
if not hasattr(self, 'mission_panel'): self.mission_text_status.refreshAllTaskStatus(self)
return
table = self.mission_panel.table
# 遍历任务面板中的所有行
for row in range(table.rowCount()):
# 🔥 适配纯QTableWidgetItem方案:直接从QTableWidgetItem获取数据
task_id_item = table.item(row, 0) # 任务编号列
task_name_item = table.item(row, 1) # 任务名称列
status_item = table.item(row, 2) # 状态列
if task_id_item and task_name_item and status_item:
task_id = task_id_item.text()
task_name = task_name_item.text()
if task_id and task_name:
task_folder_name = f"{task_id}_{task_name}"
# 检查任务是否被使用
is_in_use = self._isTaskInUse(task_folder_name)
new_status = "已启动" if is_in_use else "未启动"
# 更新状态显示
current_status = status_item.text()
if current_status != new_status:
status_item.setText(new_status)
print(f"🔄 [状态刷新] 任务 {task_folder_name}: {current_status} → {new_status}")
# 更新行颜色(未启动的任务显示灰色)
self._updateRowColorForQTableWidgetItem(row, new_status)
except Exception as e:
print(f"❌ [状态刷新] 刷新任务状态失败: {e}")
import traceback
traceback.print_exc()
def _updateTaskStatus(self, task_folder_name, new_status): def _updateTaskStatus(self, task_folder_name, new_status):
""" """
...@@ -1619,7 +1449,6 @@ class MissionPanelHandler: ...@@ -1619,7 +1449,6 @@ class MissionPanelHandler:
task_folder_name: 任务文件夹名称(如 "21321_312312") task_folder_name: 任务文件夹名称(如 "21321_312312")
new_status: 新状态(如 "已启动") new_status: 新状态(如 "已启动")
""" """
try:
if not hasattr(self, 'mission_panel'): if not hasattr(self, 'mission_panel'):
return return
...@@ -1640,191 +1469,242 @@ class MissionPanelHandler: ...@@ -1640,191 +1469,242 @@ class MissionPanelHandler:
if status_item: if status_item:
old_status = status_item.text() old_status = status_item.text()
status_item.setText(new_status) status_item.setText(new_status)
print(f"✅ [_updateTaskStatus] 任务 {task_folder_name} 状态已更新: {old_status} → {new_status}")
# 🔥 更新行的字体颜色
self._updateRowColor(row, new_status)
# 同时更新配置文件中的状态 # 同时更新配置文件中的状态
self._updateTaskConfigStatus(task_id_item.text(), new_status) self._updateTaskConfigStatus(task_id_item.text(), new_status)
return return
print(f"⚠️ [_updateTaskStatus] 未找到任务: {task_folder_name}") # 🔥 已删除 _updateRowColor 和 _updateRowColorForQTableWidgetItem 方法
# 所有文本颜色管理现在由 MissionTextStatus 类统一处理
except Exception as e: def _updateChannelColumnColor(self):
print(f"❌ [_updateTaskStatus] 更新任务状态失败: {e}") """
import traceback 🔥 根据通道检测状态更新任务面板中通道列和状态列
traceback.print_exc()
委托给 MissionTextStatus 类处理所有文本颜色更新
"""
if self.mission_text_status:
self.mission_text_status.updateAllChannelColumnColors(self)
def _updateRowColor(self, row_index, status): def _updateTaskConfigStatus(self, task_id, new_status):
""" """
根据状态更新行的字体颜色 更新任务配置文件中的状态
Args: Args:
row_index: 行索引 task_id: 任务编号
status: 任务状态 new_status: 新状态
""" """
try: from database.config import get_project_root
print(f" [调试] _updateRowColor 被调用: 行={row_index}, 状态='{status}'") import yaml
import os
config_dir = os.path.join(get_project_root(), 'database', 'config', 'mission')
# 查找对应的任务配置文件
for filename in os.listdir(config_dir):
if filename.endswith('.yaml') and filename.startswith(f"{task_id}_"):
config_path = os.path.join(config_dir, filename)
# 读取配置文件
with open(config_path, 'r', encoding='utf-8') as f:
config_data = yaml.safe_load(f) or {}
# 更新状态
config_data['status'] = new_status
# 写回配置文件
with open(config_path, 'w', encoding='utf-8') as f:
yaml.safe_dump(config_data, f, allow_unicode=True, default_flow_style=False)
if not hasattr(self, 'mission_panel'):
print(f"⚠️ [调试] mission_panel不存在,退出")
return return
table = self.mission_panel.table def _refreshCurveMissionList(self):
"""
刷新曲线面板的任务列表
# 🔥 完全使用字体管理器,不使用CSS样式表 从 mission_result 目录重新扫描任务文件夹并更新下拉框
is_gray = (status == "未启动") """
print(f" [调试] 设置颜色: {'灰色' if is_gray else '默认黑色'}") # 如果有 curvePanelHandler,调用其 loadMissionFolders 方法
if hasattr(self, 'curvePanelHandler') and self.curvePanelHandler:
self.curvePanelHandler.loadMissionFolders()
# 否则尝试直接调用 curvePanel 的方法
elif hasattr(self, 'curvePanel') and self.curvePanel:
# 手动扫描任务文件夹
import sys
project_root = get_project_root()
mission_result_dir = os.path.join(project_root, 'database', 'mission_result')
# 更新所有列的字体颜色(除了按钮列) if os.path.exists(mission_result_dir):
for col in range(table.columnCount()): mission_folders = []
# 跳过曲线按钮列 for item in os.listdir(mission_result_dir):
if col == self.mission_panel.CURVE_BUTTON_COLUMN: item_path = os.path.join(mission_result_dir, item)
print(f" [调试] 跳过曲线按钮列: {col}") if os.path.isdir(item_path):
continue mission_folders.append({
'name': item,
'path': item_path
})
# 检查是否已经是控件(QLabel) # 按文件夹名称排序
widget = table.cellWidget(row_index, col) mission_folders.sort(key=lambda x: x['name'])
item = table.item(row_index, col)
print(f" [调试] 列{col}: widget={widget is not None}, item={item is not None}") # 更新下拉框
self.curvePanel.updateMissionFolderList(mission_folders)
if widget:
# 检查是否为高亮行(蓝色背景)
current_style = widget.styleSheet()
is_highlighted = "background-color: #0078D4" in current_style
if is_highlighted: class MissionTextStatus:
# 如果是高亮行,保持高亮样式不变 """
print(f" [调试] 保持高亮样式: 列{col}") 任务面板文本状态管理器
else:
# 非高亮行,清除样式表并重新应用字体管理器
widget.setStyleSheet("") # 清除所有CSS样式
try:
from ...widgets.style_manager import FontManager
if is_gray:
# 设置灰色字体
font = FontManager.getDefaultFont()
widget.setFont(font)
palette = widget.palette()
palette.setColor(palette.WindowText, QtGui.QColor(128, 128, 128))
widget.setPalette(palette)
else:
# 设置默认字体和颜色
FontManager.applyToWidget(widget)
palette = widget.palette()
palette.setColor(palette.WindowText, QtGui.QColor(0, 0, 0))
widget.setPalette(palette)
except ImportError:
from widgets.style_manager import FontManager
if is_gray:
font = FontManager.getDefaultFont()
widget.setFont(font)
palette = widget.palette()
palette.setColor(palette.WindowText, QtGui.QColor(128, 128, 128))
widget.setPalette(palette)
else:
FontManager.applyToWidget(widget)
palette = widget.palette()
palette.setColor(palette.WindowText, QtGui.QColor(0, 0, 0))
widget.setPalette(palette)
print(f" [调试] 更新现有控件: 列{col}")
elif item:
# 如果是普通的QTableWidgetItem,转换为QLabel控件
text = item.text()
print(f" [调试] 转换为QLabel: 列{col}, 文本='{text}'")
label = QtWidgets.QLabel(text)
label.setAlignment(Qt.AlignCenter)
# 使用全局字体管理器统一管理字体和颜色
try:
from ...widgets.style_manager import FontManager
FontManager.applyToWidget(label)
if is_gray:
# 设置灰色
palette = label.palette()
palette.setColor(palette.WindowText, QtGui.QColor(128, 128, 128))
label.setPalette(palette)
except ImportError:
from widgets.style_manager import FontManager
FontManager.applyToWidget(label)
if is_gray:
palette = label.palette()
palette.setColor(palette.WindowText, QtGui.QColor(128, 128, 128))
label.setPalette(palette)
table.setCellWidget(row_index, col, label)
print(f"✅ [调试] QLabel已设置: 行{row_index}, 列{col}")
else:
print(f"⚠️ [调试] 列{col}既无widget也无item")
print(f"✅ [_updateRowColor] 已更新行 {row_index} 颜色为: {'灰色' if status == '未启动' else '默认'}") 职责:
1. 管理任务面板中所有文本的颜色状态
2. 初始化时所有文本为灰色
3. 单击选中行时该行文本置为黑色
4. 启动检测线程时对应任务的状态列置为绿色
5. 新建任务时文本初始为灰色
except Exception as e: 颜色规则:
print(f"❌ [_updateRowColor] 更新行颜色失败: {e}") - 灰色 (128, 128, 128): 未启动任务/默认状态
import traceback - 黑色 (0, 0, 0): 已选中的任务行
traceback.print_exc() - 绿色 (0, 128, 0): 检测中的任务状态列
"""
def _updateRowColorForQTableWidgetItem(self, row_index, status): # 颜色常量
COLOR_GRAY = QtGui.QColor(128, 128, 128) # 灰色:未启动/默认
COLOR_BLACK = QtGui.QColor(0, 0, 0) # 黑色:已选中
COLOR_GREEN = QtGui.QColor(0, 128, 0) # 绿色:检测中
def __init__(self, mission_panel):
""" """
根据状态更新行的字体颜色 - 适配纯QTableWidgetItem方案 初始化文本状态管理器
只对未启动任务设置灰色,已启动任务保持默认颜色(不强制设置黑色)
Args: Args:
row_index: 行索引 mission_panel: MissionPanel实例
status: 任务状态
""" """
try: self.mission_panel = mission_panel
print(f" [调试] _updateRowColorForQTableWidgetItem 被调用: 行={row_index}, 状态='{status}'") self.table = mission_panel.table
self.selected_row = -1 # 当前选中的行
if not hasattr(self, 'mission_panel'): def initializeAllRowsGray(self):
print(f"⚠️ [调试] mission_panel不存在,退出") """
return 1. 初始化所有任务行文本为灰色
"""
for row in range(self.table.rowCount()):
self._setRowColor(row, self.COLOR_GRAY, exclude_columns=[])
table = self.mission_panel.table def setRowBlackOnSelect(self, row_index):
is_gray = (status == "未启动") """
2. 单击选中行时该行文本置为黑色
# 🔥 只对未启动任务设置灰色,已启动任务不修改颜色(保持双击置黑的效果) 所有"已启动"的任务在被点击后都保持黑色,不会恢复为灰色
if is_gray:
# 更新所有列的字体颜色为灰色(除了按钮列)
for col in range(table.columnCount()):
# 跳过曲线按钮列
if col == self.mission_panel.CURVE_BUTTON_COLUMN:
continue
item = table.item(row_index, col) Args:
if item: row_index: 选中的行索引
# 未启动:灰色文字 """
item.setForeground(QtGui.QColor(128, 128, 128)) # 🔥 不再恢复之前选中行的颜色,所有"已启动"的任务点击后都保持黑色
print(f" [调试] 设置灰色文字: 行={row_index}, 列={col}") # 将新选中的行置为黑色
self._setRowColor(row_index, self.COLOR_BLACK, exclude_columns=[2]) # 排除状态列
self.selected_row = row_index
def setStatusColumnGreenOnDetection(self, task_folder_name):
"""
3. 启动检测线程时对应任务的状态列置为绿色
Args:
task_folder_name: 任务文件夹名称(如 "1_1")
"""
# 查找对应的任务行
row_index = self._findTaskRow(task_folder_name)
if row_index >= 0:
status_item = self.table.item(row_index, 2) # 状态列索引为2
if status_item:
status_item.setText("检测中")
status_item.setForeground(self.COLOR_GREEN)
def resetStatusColumnOnStopDetection(self, task_folder_name):
"""
停止检测线程时恢复状态列颜色
print(f"✅ [_updateRowColorForQTableWidgetItem] 已更新行 {row_index} 颜色为: 灰色") Args:
task_folder_name: 任务文件夹名称(如 "1_1")
"""
row_index = self._findTaskRow(task_folder_name)
if row_index >= 0:
status_item = self.table.item(row_index, 2)
if status_item:
status_item.setText("已启动")
# 如果是选中行,保持黑色;否则恢复为灰色
if row_index == self.selected_row:
status_item.setForeground(self.COLOR_BLACK)
else: else:
# 已启动任务:不修改颜色,保持现有颜色(可能是双击置黑的黑色) status_item.setForeground(self.COLOR_GRAY)
print(f"✅ [_updateRowColorForQTableWidgetItem] 跳过已启动任务行 {row_index},保持现有颜色")
except Exception as e: def setStatusColumnBlackOnStarted(self, task_folder_name):
print(f"❌ [_updateRowColorForQTableWidgetItem] 更新行颜色失败: {e}") """
import traceback 设置状态列为黑色"已启动"
traceback.print_exc()
def _updateChannelColumnColor(self): 当任务的所有通道都停止检测,但任务仍被分配时调用
Args:
task_folder_name: 任务文件夹名称(如 "1_1")
""" """
🔥 根据通道检测状态更新任务面板中状态列 row_index = self._findTaskRow(task_folder_name)
if row_index >= 0:
status_item = self.table.item(row_index, 2)
if status_item:
status_item.setText("已启动")
status_item.setForeground(self.COLOR_BLACK)
只更新当前通道正在执行的任务行: def setChannelColumnGreenOnDetection(self, task_folder_name, channel_num):
- 获取每个通道当前执行的任务(从channelXmission标签)
- 只检查这些任务行,而不是所有使用该通道的任务行
- 当任务的所有通道都在检测时,状态列显示"检测中"(绿色)
""" """
try: 启动检测线程时对应任务的对应通道列置为绿色
if not hasattr(self, 'mission_panel'):
return
table = self.mission_panel.table Args:
task_folder_name: 任务文件夹名称(如 "1_1")
channel_num: 通道编号(1-4)
"""
# 查找对应的任务行
row_index = self._findTaskRow(task_folder_name)
if row_index >= 0:
# 通道列从第3列开始(0:任务编号, 1:任务名称, 2:状态, 3-6:通道1-4)
col_index = 3 + (channel_num - 1)
channel_item = self.table.item(row_index, col_index)
if channel_item:
channel_item.setForeground(self.COLOR_GREEN)
def resetChannelColumnOnStopDetection(self, task_folder_name, channel_num):
"""
停止检测线程时恢复通道列颜色
Args:
task_folder_name: 任务文件夹名称(如 "1_1")
channel_num: 通道编号(1-4)
"""
row_index = self._findTaskRow(task_folder_name)
if row_index >= 0:
col_index = 3 + (channel_num - 1)
channel_item = self.table.item(row_index, col_index)
if channel_item:
# 如果是选中行,恢复为黑色;否则恢复为灰色
if row_index == self.selected_row:
channel_item.setForeground(self.COLOR_BLACK)
else:
channel_item.setForeground(self.COLOR_GRAY)
def updateAllChannelColumnColors(self, main_window):
"""
更新所有任务的通道列和状态列颜色
根据通道检测状态更新任务面板中的通道列和状态列:
- 获取每个通道当前执行的任务(从channelXmission标签)
- 更新通道列颜色:检测中为绿色,否则恢复
- 更新状态列颜色:所有通道都在检测时为绿色
Args:
main_window: 主窗口实例,用于访问通道任务标签和检测状态
"""
# 🔥 第一步:收集所有通道当前正在执行的任务 # 🔥 第一步:收集所有通道当前正在执行的任务
active_tasks = set() # 存储正在执行的任务名称 active_tasks = set() # 存储正在执行的任务名称
channel_task_map = {} # 通道 -> 任务映射 channel_task_map = {} # 通道 -> 任务映射
...@@ -1833,20 +1713,19 @@ class MissionPanelHandler: ...@@ -1833,20 +1713,19 @@ class MissionPanelHandler:
channel_id = f'channel{channel_num}' channel_id = f'channel{channel_num}'
mission_var_name = f'{channel_id}mission' mission_var_name = f'{channel_id}mission'
if hasattr(self, mission_var_name): if hasattr(main_window, mission_var_name):
mission_label = getattr(self, mission_var_name) mission_label = getattr(main_window, mission_var_name)
current_task = mission_label.text() current_task = mission_label.text()
if current_task and current_task != "未分配任务": if current_task and current_task != "未分配任务":
active_tasks.add(current_task) active_tasks.add(current_task)
channel_task_map[channel_id] = current_task channel_task_map[channel_id] = current_task
# 🔥 第二步:遍历所有任务行,只更新正在执行的任务 # 🔥 第二步:遍历所有任务行,更新通道列颜色
for row in range(table.rowCount()): for row in range(self.table.rowCount()):
task_id_item = table.item(row, 0) task_id_item = self.table.item(row, 0)
task_name_item = table.item(row, 1) task_name_item = self.table.item(row, 1)
status_item = table.item(row, 2) status_item = self.table.item(row, 2)
channel_item = table.item(row, 3)
if not (task_id_item and task_name_item and status_item): if not (task_id_item and task_name_item and status_item):
continue continue
...@@ -1854,134 +1733,210 @@ class MissionPanelHandler: ...@@ -1854,134 +1733,210 @@ class MissionPanelHandler:
# 获取任务文件夹名称 # 获取任务文件夹名称
task_folder_name = f"{task_id_item.text()}_{task_name_item.text()}" task_folder_name = f"{task_id_item.text()}_{task_name_item.text()}"
# 🔥 只处理正在执行的任务 # 🔥 处理所有任务的通道列(包括正在执行和未执行的)
if task_folder_name in active_tasks: # 获取该任务使用的通道列表(从表格中读取)
# 解析该任务使用的通道 task_channels = []
channel_text = channel_item.text() if channel_item else "" for ch_idx in range(1, 5):
channels = [ch.strip() for ch in channel_text.split(',')] if channel_text else [] col_idx = 3 + (ch_idx - 1)
ch_item = self.table.item(row, col_idx)
# 检查该任务使用的所有通道是否都在检测 if ch_item and ch_item.text():
all_channels_detecting = True task_channels.append(ch_idx)
has_detecting_channels = False
# 检查每个通道的检测状态
for channel_key in channels: for channel_num in task_channels:
if channel_key.startswith('通道'):
channel_num = channel_key.replace('通道', '').strip()
channel_id = f'channel{channel_num}' channel_id = f'channel{channel_num}'
# 检查该通道是否正在执行这个任务 # 检查该通道是否正在执行这个任务
if channel_task_map.get(channel_id) == task_folder_name: if channel_task_map.get(channel_id) == task_folder_name:
# 检查该通道的检测状态 # 检查该通道的检测状态
detect_var_name = f'{channel_id}detect' detect_var_name = f'{channel_id}detect'
if hasattr(self, detect_var_name): if hasattr(main_window, detect_var_name):
is_detecting = getattr(self, detect_var_name) is_detecting = getattr(main_window, detect_var_name)
if is_detecting: if is_detecting:
has_detecting_channels = True # 🔥 设置对应通道列为绿色
self.setChannelColumnGreenOnDetection(task_folder_name, channel_num)
else: else:
all_channels_detecting = False # 🔥 恢复通道列颜色
self.resetChannelColumnOnStopDetection(task_folder_name, channel_num)
else: else:
all_channels_detecting = False # 通道未分配此任务,恢复颜色
self.resetChannelColumnOnStopDetection(task_folder_name, channel_num)
# 更新状态列 # 🔥 只处理正在执行的任务(更新状态列)
if has_detecting_channels and all_channels_detecting: if task_folder_name in active_tasks:
# 所有执行该任务的通道都在检测:显示"检测中",绿色 # 检查该任务使用的所有通道是否都在检测
status_item.setText("检测中") # 只统计分配给该任务的通道
status_item.setForeground(QtGui.QColor(0, 128, 0)) # 绿色 assigned_channels_count = 0 # 分配给该任务的通道数
else: detecting_channels_count = 0 # 正在检测的通道数
# 有通道未检测:显示"已启动"
status_item.setText("已启动") for channel_num in task_channels:
status_item.setForeground(QtGui.QColor(0, 0, 0)) # 黑色 channel_id = f'channel{channel_num}'
else:
# 不是正在执行的任务,检查是否被配置 # 检查该通道是否正在执行这个任务
is_task_in_use = self._isTaskInUse(task_folder_name) if channel_task_map.get(channel_id) == task_folder_name:
status_text = "已启动" if is_task_in_use else "未启动" assigned_channels_count += 1
status_item.setText(status_text)
# 设置颜色:已启动为黑色,未启动为灰色 # 检查该通道的检测状态
if status_text == "已启动": detect_var_name = f'{channel_id}detect'
status_item.setForeground(QtGui.QColor(0, 0, 0)) # 黑色 if hasattr(main_window, detect_var_name):
is_detecting = getattr(main_window, detect_var_name)
if is_detecting:
detecting_channels_count += 1
# 🔥 规则:根据检测状态设置状态列颜色
# 只有当分配给该任务的所有通道都在检测时,才设置为绿色"检测中"
if assigned_channels_count > 0 and detecting_channels_count == assigned_channels_count:
# 所有分配的通道都在检测中 -> 绿色"检测中"
self.setStatusColumnGreenOnDetection(task_folder_name)
elif detecting_channels_count == 0:
# 所有通道都未检测,但任务已分配 -> 黑色"已启动"
self.setStatusColumnBlackOnStarted(task_folder_name)
else: else:
status_item.setForeground(QtGui.QColor(128, 128, 128)) # 灰色 # 部分通道在检测 -> 黑色"已启动"(不是所有通道都在检测)
self.setStatusColumnBlackOnStarted(task_folder_name)
def initializeNewTaskRowGray(self, row_index):
"""
4. 新建任务时该行文本初始为灰色
Args:
row_index: 新建任务的行索引
"""
try:
self._setRowColor(row_index, self.COLOR_GRAY, exclude_columns=[])
except Exception as e: except Exception as e:
print(f"❌ [状态列更新] 更新状态列失败: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
def _updateTaskConfigStatus(self, task_id, new_status): def refreshAllTaskStatus(self, main_window):
""" """
更新任务配置文件中的状态 刷新任务面板中所有任务的状态和文本颜色
根据当前通道使用情况,动态更新每个任务的状态和颜色:
- 被通道使用的任务:已启动(灰色,等待用户点击时置黑)
- 未被通道使用的任务:未启动(灰色)
Args: Args:
task_id: 任务编号 main_window: MainWindow实例,用于检查通道任务状态
new_status: 新状态
""" """
try: try:
from database.config import get_project_root # 遍历任务面板中的所有行
import yaml for row in range(self.table.rowCount()):
import os task_id_item = self.table.item(row, 0) # 任务编号列
task_name_item = self.table.item(row, 1) # 任务名称列
status_item = self.table.item(row, 2) # 状态列
config_dir = os.path.join(get_project_root(), 'database', 'config', 'mission') if task_id_item and task_name_item and status_item:
task_id = task_id_item.text()
task_name = task_name_item.text()
# 查找对应的任务配置文件 if task_id and task_name:
for filename in os.listdir(config_dir): task_folder_name = f"{task_id}_{task_name}"
if filename.endswith('.yaml') and filename.startswith(f"{task_id}_"):
config_path = os.path.join(config_dir, filename)
# 读取配置文件 # 检查任务是否被使用
with open(config_path, 'r', encoding='utf-8') as f: is_in_use = self._isTaskInUse(main_window, task_folder_name)
config_data = yaml.safe_load(f) or {} new_status = "已启动" if is_in_use else "未启动"
# 更新状态 # 更新状态显示
config_data['status'] = new_status current_status = status_item.text()
if current_status != new_status:
status_item.setText(new_status)
# 写回配置文件 # 🔥 更新文本颜色
with open(config_path, 'w', encoding='utf-8') as f: if new_status == "未启动":
yaml.safe_dump(config_data, f, allow_unicode=True, default_flow_style=False) # 任务未启动时,整行置为灰色(包括状态列)
# 如果该行是当前选中行,需要清除选中状态
if self.selected_row == row:
self.selected_row = -1
self._setRowColor(row, self.COLOR_GRAY, exclude_columns=[])
elif new_status == "已启动":
# 任务已启动时,不修改颜色,保持原有状态
# 如果是当前选中行,保持黑色;如果不是,保持灰色
pass
print(f"✅ [_updateTaskConfigStatus] 已更新配置文件 {filename} 状态为: {new_status}") except Exception as e:
return import traceback
traceback.print_exc()
def _isTaskInUse(self, main_window, task_folder_name):
"""
检查指定任务是否被任何通道使用
Args:
main_window: MainWindow实例
task_folder_name: 任务文件夹名称(如 "2001115_test")
Returns:
bool: 如果任务被使用返回True,否则返回False
"""
try:
# 检查所有通道(channel1-channel4)
for channel_num in range(1, 5):
mission_var_name = f'channel{channel_num}mission'
if hasattr(main_window, mission_var_name):
mission_label = getattr(main_window, mission_var_name)
current_task = mission_label.text()
if current_task == task_folder_name:
return True
print(f"⚠️ [_updateTaskConfigStatus] 未找到任务 {task_id} 的配置文件") return False
except Exception as e: except Exception as e:
print(f"❌ [_updateTaskConfigStatus] 更新任务配置状态失败: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
return False
def _refreshCurveMissionList(self): def _setRowColor(self, row_index, color, exclude_columns=None):
""" """
刷新曲线面板的任务列表 设置指定行的所有列文本颜色
从 mission_result 目录重新扫描任务文件夹并更新下拉框 Args:
row_index: 行索引
color: QColor对象
exclude_columns: 排除的列索引列表(如状态列)
""" """
if exclude_columns is None:
exclude_columns = []
try: try:
# 如果有 curvePanelHandler,调用其 loadMissionFolders 方法 for col in range(self.table.columnCount()):
if hasattr(self, 'curvePanelHandler') and self.curvePanelHandler: # 跳过排除的列
self.curvePanelHandler.loadMissionFolders() if col in exclude_columns:
# 否则尝试直接调用 curvePanel 的方法 continue
elif hasattr(self, 'curvePanel') and self.curvePanel:
# 手动扫描任务文件夹
import sys
project_root = get_project_root()
mission_result_dir = os.path.join(project_root, 'database', 'mission_result')
if os.path.exists(mission_result_dir): # 跳过曲线按钮列
mission_folders = [] if hasattr(self.mission_panel, 'CURVE_BUTTON_COLUMN'):
for item in os.listdir(mission_result_dir): if col == self.mission_panel.CURVE_BUTTON_COLUMN:
item_path = os.path.join(mission_result_dir, item) continue
if os.path.isdir(item_path):
mission_folders.append({
'name': item,
'path': item_path
})
# 按文件夹名称排序 item = self.table.item(row_index, col)
mission_folders.sort(key=lambda x: x['name']) if item:
item.setForeground(color)
except Exception as e:
import traceback
traceback.print_exc()
# 更新下拉框 def _findTaskRow(self, task_folder_name):
self.curvePanel.updateMissionFolderList(mission_folders) """
根据任务文件夹名称查找对应的行索引
Args:
task_folder_name: 任务文件夹名称(如 "1_1")
Returns:
int: 行索引,未找到返回-1
"""
try:
for row in range(self.table.rowCount()):
task_id_item = self.table.item(row, 0)
task_name_item = self.table.item(row, 1)
if task_id_item and task_name_item:
current_task_name = f"{task_id_item.text()}_{task_name_item.text()}"
if current_task_name == task_folder_name:
return row
return -1
except Exception as e: except Exception as e:
print(f"⚠️ [刷新曲线任务列表] 失败: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
return -1
\ No newline at end of file
...@@ -147,7 +147,7 @@ class TestHandler: ...@@ -147,7 +147,7 @@ class TestHandler:
QtWidgets.QMessageBox.information( QtWidgets.QMessageBox.information(
None, "成功", None, "成功",
f"配置已复制到 test.yaml!\n\n" f"配置已复制到 test.yaml!\n\n"
f"已配置通道:{', '.join(channels)}\n\n" f"已启动通道:{', '.join(channels)}\n\n"
f"配置来源:\n" f"配置来源:\n"
f"- 通道连接信息:default_config.yaml\n" f"- 通道连接信息:default_config.yaml\n"
f"- 标注数据:annotation_mission_result.yaml\n\n" f"- 标注数据:annotation_mission_result.yaml\n\n"
...@@ -914,12 +914,12 @@ class TestHandler: ...@@ -914,12 +914,12 @@ class TestHandler:
if channel_id not in config: if channel_id not in config:
config[channel_id] = {} config[channel_id] = {}
# 检查是否已配置save_liquid_data_path # 检查是否已启动save_liquid_data_path
existing_path = config[channel_id].get('save_liquid_data_path', '') or \ existing_path = config[channel_id].get('save_liquid_data_path', '') or \
config[channel_id].get('general', {}).get('save_liquid_data_path', '') config[channel_id].get('general', {}).get('save_liquid_data_path', '')
if existing_path and existing_path.strip(): if existing_path and existing_path.strip():
# 已配置,无需设置 # 已启动,无需设置
return return
# 设置默认路径:recordings/{channel_id} # 设置默认路径:recordings/{channel_id}
......
...@@ -51,7 +51,7 @@ class ChannelThreadContext: ...@@ -51,7 +51,7 @@ class ChannelThreadContext:
# ========== 控制标志 ========== # ========== 控制标志 ==========
self.capture_flag = False # 捕获线程运行标志 self.capture_flag = False # 捕获线程运行标志
self.display_flag = False # 显示线程运行标志 self.display_flag = False # 显示线程运行标志
self.detection_flag = False # 检测线程运行标志 self.channel_detect_status = False # 检测线程运行标志
# 注意:curve_flag已改为全局单例管理,不再存储在context中 # 注意:curve_flag已改为全局单例管理,不再存储在context中
self.storage_flag = False # 存储线程运行标志 self.storage_flag = False # 存储线程运行标志
......
...@@ -45,6 +45,9 @@ except ImportError: ...@@ -45,6 +45,9 @@ except ImportError:
class ModelLoadingProgressDialog(QDialog): class ModelLoadingProgressDialog(QDialog):
"""模型加载进度条对话框""" """模型加载进度条对话框"""
# 🔥 添加取消信号
cancelRequested = Signal() # 用户点击关闭按钮时发出
def __init__(self, parent=None, total_models: int = 1): def __init__(self, parent=None, total_models: int = 1):
""" """
初始化进度条对话框 初始化进度条对话框
...@@ -56,6 +59,7 @@ class ModelLoadingProgressDialog(QDialog): ...@@ -56,6 +59,7 @@ class ModelLoadingProgressDialog(QDialog):
super().__init__(parent) super().__init__(parent)
self.total_models = total_models self.total_models = total_models
self.current_model = 0 self.current_model = 0
self._user_cancelled = False # 标记用户是否取消
self.setupUI() self.setupUI()
...@@ -68,11 +72,10 @@ class ModelLoadingProgressDialog(QDialog): ...@@ -68,11 +72,10 @@ class ModelLoadingProgressDialog(QDialog):
# 设置左上角图标为逻辑图标 # 设置左上角图标为逻辑图标
self.setWindowIcon(newIcon("逻辑")) self.setWindowIcon(newIcon("逻辑"))
# 移除帮助按钮和关闭按钮 # 🔥 只移除帮助按钮,保留关闭按钮(用户可以点击关闭按钮取消加载)
self.setWindowFlags( self.setWindowFlags(
self.windowFlags() & self.windowFlags() &
~Qt.WindowContextHelpButtonHint & # 移除帮助按钮 ~Qt.WindowContextHelpButtonHint # 移除帮助按钮
~Qt.WindowCloseButtonHint # 移除关闭按钮
) )
# 居中显示 # 居中显示
...@@ -213,3 +216,17 @@ class ModelLoadingProgressDialog(QDialog): ...@@ -213,3 +216,17 @@ class ModelLoadingProgressDialog(QDialog):
self.title_label.setText("模型加载失败") self.title_label.setText("模型加载失败")
self.title_label.setStyleSheet("font-weight: bold; padding: 5px 0;") self.title_label.setStyleSheet("font-weight: bold; padding: 5px 0;")
self.step_label.setText("请检查模型文件和配置") self.step_label.setText("请检查模型文件和配置")
def closeEvent(self, event):
"""
处理关闭事件(用户点击右上角关闭按钮)
发出取消信号,通知外部停止加载线程
"""
if not self._user_cancelled:
self._user_cancelled = True
print(f"⚠️ [模型加载] 用户取消加载,发出取消信号")
self.cancelRequested.emit()
# 接受关闭事件
event.accept()
...@@ -177,7 +177,7 @@ class ModelPoolManager: ...@@ -177,7 +177,7 @@ class ModelPoolManager:
# ========== UI进度条 ========== # ========== UI进度条 ==========
self.progress_dialog: Optional[Any] = None # 进度条对话框 self.progress_dialog: Optional[Any] = None # 进度条对话框
print("🏗️ [模型池管理器] 初始化完成")
def initialize(self, config_file_path: Optional[str] = None) -> bool: def initialize(self, config_file_path: Optional[str] = None) -> bool:
"""初始化模型池 """初始化模型池
...@@ -190,11 +190,9 @@ class ModelPoolManager: ...@@ -190,11 +190,9 @@ class ModelPoolManager:
""" """
with self._lock: with self._lock:
if self.is_initialized: if self.is_initialized:
print("⚠️ [模型池管理器] 已经初始化,跳过重复初始化")
return True return True
try: try:
print("🔧 [模型池管理器] 开始初始化...")
# 1. 加载配置文件 # 1. 加载配置文件
config = self._load_config(config_file_path) config = self._load_config(config_file_path)
...@@ -241,9 +239,6 @@ class ModelPoolManager: ...@@ -241,9 +239,6 @@ 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" [模型池管理器] 项目根目录: {project_root}")
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}")
...@@ -256,10 +251,6 @@ class ModelPoolManager: ...@@ -256,10 +251,6 @@ class ModelPoolManager:
self.device = config.get('default_device', 'cuda') if config.get('gpu_enabled', True) else 'cpu' self.device = config.get('default_device', 'cuda') if config.get('gpu_enabled', True) else 'cpu'
self.batch_size = config.get('default_batch_size', 4) self.batch_size = config.get('default_batch_size', 4)
print(f"📋 [模型池管理器] 配置加载成功: {config_file_path}")
print(f" - 设备: {self.device}")
print(f" - 批大小: {self.batch_size}")
return config return config
except Exception as e: except Exception as e:
...@@ -304,9 +295,6 @@ class ModelPoolManager: ...@@ -304,9 +295,6 @@ class ModelPoolManager:
# 检查模型文件是否存在 # 检查模型文件是否存在
if not os.path.exists(model_path): if not os.path.exists(model_path):
print(f"⚠️ [模型池管理器] 模型文件不存在: {model_path} (通道: {channel_id})")
print(f" - 请检查配置文件中的路径是否正确")
print(f" - 项目根目录: {project_root}")
continue continue
# 统计相同路径的模型 # 统计相同路径的模型
...@@ -335,14 +323,9 @@ class ModelPoolManager: ...@@ -335,14 +323,9 @@ class ModelPoolManager:
self.model_path_to_id[model_path] = model_id self.model_path_to_id[model_path] = model_id
self.model_id_to_path[model_id] = model_path self.model_id_to_path[model_id] = model_path
print(f"📝 [模型池管理器] 发现模型: {model_id} -> {model_path}")
print(f" - 使用通道: {', '.join(channels)}")
print(f" [模型池管理器] 扫描完成,发现 {len(unique_models)} 个唯一模型")
return unique_models return unique_models
except Exception as e: except Exception as e:
print(f"❌ [模型池管理器] 模型扫描失败: {e}")
return {} return {}
def _load_all_models(self, unique_models: Dict[str, str]) -> bool: def _load_all_models(self, unique_models: Dict[str, str]) -> bool:
...@@ -356,7 +339,6 @@ class ModelPoolManager: ...@@ -356,7 +339,6 @@ class ModelPoolManager:
""" """
try: try:
total_models = len(unique_models) total_models = len(unique_models)
print(f"🚀 [模型池管理器] 开始在后台线程加载 {total_models} 个模型到显存...")
# 初始化加载状态 # 初始化加载状态
self._loading_success = False self._loading_success = False
...@@ -387,13 +369,10 @@ class ModelPoolManager: ...@@ -387,13 +369,10 @@ class ModelPoolManager:
# 确保所有信号处理完成 # 确保所有信号处理完成
QApplication.processEvents() QApplication.processEvents()
print(f" [模型池管理器] 加载结果: _loading_success={self._loading_success}")
# 返回加载结果 # 返回加载结果
return self._loading_success return self._loading_success
except Exception as e: except Exception as e:
print(f"❌ [模型池管理器] 批量加载模型失败: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
self._close_progress_dialog(False) self._close_progress_dialog(False)
...@@ -417,7 +396,6 @@ class ModelPoolManager: ...@@ -417,7 +396,6 @@ class ModelPoolManager:
success: 是否成功 success: 是否成功
loaded_models: 已加载的模型字典 loaded_models: 已加载的模型字典
""" """
print(f"✅ [模型池管理器] 后台加载完成,成功加载 {len(loaded_models)} 个模型")
# 将加载的模型添加到模型池 # 将加载的模型添加到模型池
for model_id, detection_engine in loaded_models.items(): for model_id, detection_engine in loaded_models.items():
...@@ -428,15 +406,16 @@ class ModelPoolManager: ...@@ -428,15 +406,16 @@ 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}")
# 关闭进度条对话框 # 关闭进度条对话框
self._close_progress_dialog(self._loading_success) self._close_progress_dialog(self._loading_success)
if not self._loading_success: if not self._loading_success:
print("❌ [模型池管理器] 没有任何模型加载成功") import traceback
traceback.print_exc()
else: else:
print(f"✅ [模型池管理器] 所有模型加载成功") import traceback
traceback.print_exc()
def _on_loading_error(self, error_msg: str): def _on_loading_error(self, error_msg: str):
"""处理加载错误信号 """处理加载错误信号
...@@ -444,7 +423,6 @@ class ModelPoolManager: ...@@ -444,7 +423,6 @@ class ModelPoolManager:
Args: Args:
error_msg: 错误信息 error_msg: 错误信息
""" """
print(f"❌ [模型池管理器] 加载错误: {error_msg}")
self._loading_success = False self._loading_success = False
self._loading_finished = True # 标记加载完成(即使失败) self._loading_finished = True # 标记加载完成(即使失败)
self._close_progress_dialog(False) self._close_progress_dialog(False)
...@@ -471,7 +449,6 @@ class ModelPoolManager: ...@@ -471,7 +449,6 @@ class ModelPoolManager:
self.progress_dialog = ModelLoadingProgressDialog(parent, total_models) self.progress_dialog = ModelLoadingProgressDialog(parent, total_models)
self.progress_dialog.show() self.progress_dialog.show()
print(f"📊 [模型池管理器] 进度条对话框已创建")
except Exception as e: except Exception as e:
print(f"⚠️ [模型池管理器] 创建进度条对话框失败: {e}") print(f"⚠️ [模型池管理器] 创建进度条对话框失败: {e}")
...@@ -490,7 +467,8 @@ class ModelPoolManager: ...@@ -490,7 +467,8 @@ class ModelPoolManager:
try: try:
self.progress_dialog.update_progress(current, model_id, step, sub_progress) self.progress_dialog.update_progress(current, model_id, step, sub_progress)
except Exception as e: except Exception as e:
print(f"⚠️ [模型池管理器] 更新进度条失败: {e}") import traceback
traceback.print_exc()
def _close_progress_dialog(self, success: bool = True): def _close_progress_dialog(self, success: bool = True):
"""关闭进度条对话框 """关闭进度条对话框
...@@ -506,10 +484,10 @@ class ModelPoolManager: ...@@ -506,10 +484,10 @@ class ModelPoolManager:
self.progress_dialog.set_error("部分模型加载失败") self.progress_dialog.set_error("部分模型加载失败")
self.progress_dialog = None self.progress_dialog = None
print(f"📊 [模型池管理器] 进度条对话框已关闭")
except Exception as e: except Exception as e:
print(f"⚠️ [模型池管理器] 关闭进度条对话框失败: {e}") import traceback
traceback.print_exc()
def _load_single_model(self, model_id: str, model_path: str, current_idx: int = 1) -> Optional[Any]: def _load_single_model(self, model_id: str, model_path: str, current_idx: int = 1) -> Optional[Any]:
"""加载单个模型 """加载单个模型
...@@ -527,10 +505,6 @@ class ModelPoolManager: ...@@ -527,10 +505,6 @@ class ModelPoolManager:
self._update_progress_dialog(current_idx, model_id, "初始化检测引擎...", 0) self._update_progress_dialog(current_idx, model_id, "初始化检测引擎...", 0)
from handlers.videopage.detection import LiquidDetectionEngine from handlers.videopage.detection import LiquidDetectionEngine
print(f" - 模型路径: {model_path}")
print(f" - 设备: {self.device}")
print(f" - 批大小: {self.batch_size}")
# 🔥 创建检测引擎实例(不传入model_path,避免自动加载) # 🔥 创建检测引擎实例(不传入model_path,避免自动加载)
engine = LiquidDetectionEngine( engine = LiquidDetectionEngine(
model_path=None, # 不在构造函数中加载 model_path=None, # 不在构造函数中加载
...@@ -541,30 +515,28 @@ class ModelPoolManager: ...@@ -541,30 +515,28 @@ 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}")
if not engine.load_model(model_path): if not engine.load_model(model_path):
print(f"❌ [模型池管理器] 模型加载失败: {model_path}") import traceback
traceback.print_exc()
return None return None
print(f"✅ [模型池管理器] 模型加载成功: {model_path}")
self._update_progress_dialog(current_idx, model_id, "模型文件加载完成", 60) self._update_progress_dialog(current_idx, model_id, "模型文件加载完成", 60)
# 步骤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} 的标注配置...")
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}") import traceback
traceback.print_exc()
return None return None
print(f"✅ [模型池管理器] 找到标注配置: {list(annotation_config.keys())}")
self._update_progress_dialog(current_idx, model_id, "标注配置文件读取完成", 75) self._update_progress_dialog(current_idx, model_id, "标注配置文件读取完成", 75)
# 步骤4: 配置标注数据 (75-95%) # 步骤4: 配置标注数据 (75-95%)
self._update_progress_dialog(current_idx, model_id, "正在配置检测区域和参数...", 80) self._update_progress_dialog(current_idx, model_id, "正在配置检测区域和参数...", 80)
success = self._configure_annotation_data(engine, annotation_config) success = self._configure_annotation_data(engine, annotation_config)
if not success: if not success:
print(f"❌ [模型池管理器] 标注配置加载失败: {model_id}") import traceback
traceback.print_exc()
return None return None
print(f"✅ [模型池管理器] 标注配置加载成功: {model_id}")
self._update_progress_dialog(current_idx, model_id, "检测区域配置完成", 95) self._update_progress_dialog(current_idx, model_id, "检测区域配置完成", 95)
# 步骤5: 完成 (95-100%) # 步骤5: 完成 (95-100%)
...@@ -573,7 +545,6 @@ class ModelPoolManager: ...@@ -573,7 +545,6 @@ class ModelPoolManager:
return engine return engine
except Exception as e: except Exception as e:
print(f"❌ [模型池管理器] 加载模型失败 {model_id}: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
return None return None
...@@ -596,7 +567,8 @@ class ModelPoolManager: ...@@ -596,7 +567,8 @@ class ModelPoolManager:
annotation_file = os.path.join(project_root, 'database', 'config', 'annotation_result.yaml') annotation_file = os.path.join(project_root, 'database', 'config', 'annotation_result.yaml')
if not os.path.exists(annotation_file): if not os.path.exists(annotation_file):
print(f"⚠️ [模型池管理器] 标注文件不存在: {annotation_file}") import traceback
traceback.print_exc()
return None return None
with open(annotation_file, 'r', encoding='utf-8') as f: with open(annotation_file, 'r', encoding='utf-8') as f:
...@@ -648,14 +620,17 @@ class ModelPoolManager: ...@@ -648,14 +620,17 @@ class ModelPoolManager:
# 添加actual_heights到配置中 # 添加actual_heights到配置中
annotation_data['actual_heights'] = actual_heights annotation_data['actual_heights'] = actual_heights
print(f"📋 [模型池管理器] 为模型 {model_id} 加载标注配置 (来源通道: {channel_id})") import traceback
traceback.print_exc()
return annotation_data return annotation_data
print(f"⚠️ [模型池管理器] 未找到模型 {model_id} 的标注配置") import traceback
traceback.print_exc()
return None return None
except Exception as e: except Exception as e:
print(f"❌ [模型池管理器] 加载标注配置失败 {model_id}: {e}") import traceback
traceback.print_exc()
return None return None
def _configure_annotation_data(self, engine: Any, annotation_config: Dict) -> bool: def _configure_annotation_data(self, engine: Any, annotation_config: Dict) -> bool:
...@@ -685,7 +660,8 @@ class ModelPoolManager: ...@@ -685,7 +660,8 @@ class ModelPoolManager:
return True return True
except Exception as e: except Exception as e:
print(f"❌ [模型池管理器] 配置标注数据失败: {e}") import traceback
traceback.print_exc()
return False return False
def _build_channel_mapping(self, config: Dict): def _build_channel_mapping(self, config: Dict):
...@@ -719,12 +695,15 @@ class ModelPoolManager: ...@@ -719,12 +695,15 @@ class ModelPoolManager:
model_id = self.model_path_to_id.get(model_path) model_id = self.model_path_to_id.get(model_path)
if model_id: if model_id:
self.channel_model_mapping[channel_id] = model_id self.channel_model_mapping[channel_id] = model_id
print(f"🔗 [模型池管理器] 通道映射: {channel_id} -> {model_id}") import traceback
traceback.print_exc()
print(f"🔗 [模型池管理器] 建立了 {len(self.channel_model_mapping)} 个通道映射") import traceback
traceback.print_exc()
except Exception as e: except Exception as e:
print(f"❌ [模型池管理器] 建立通道映射失败: {e}") import traceback
traceback.print_exc()
def _initialize_monitoring(self): def _initialize_monitoring(self):
"""初始化性能监控""" """初始化性能监控"""
...@@ -735,14 +714,18 @@ class ModelPoolManager: ...@@ -735,14 +714,18 @@ class ModelPoolManager:
import torch import torch
if torch.cuda.is_available(): if torch.cuda.is_available():
self.stats['gpu_memory_usage'] = torch.cuda.memory_allocated() / 1024**3 # GB self.stats['gpu_memory_usage'] = torch.cuda.memory_allocated() / 1024**3 # GB
print(f"📊 [模型池管理器] GPU内存使用: {self.stats['gpu_memory_usage']:.2f} GB") import traceback
traceback.print_exc()
except ImportError: except ImportError:
print("⚠️ [模型池管理器] PyTorch未安装,无法监控GPU内存") import traceback
traceback.print_exc()
except Exception as e: except Exception as e:
print(f"⚠️ [模型池管理器] GPU内存监控初始化失败: {e}") import traceback
traceback.print_exc()
except Exception as e: except Exception as e:
print(f"❌ [模型池管理器] 监控初始化失败: {e}") import traceback
traceback.print_exc()
def _print_model_summary(self): def _print_model_summary(self):
"""打印模型池摘要信息""" """打印模型池摘要信息"""
...@@ -778,17 +761,20 @@ class ModelPoolManager: ...@@ -778,17 +761,20 @@ class ModelPoolManager:
""" """
with self._lock: with self._lock:
if not self.is_initialized: if not self.is_initialized:
print("❌ [模型池管理器] 尚未初始化") import traceback
traceback.print_exc()
return None return None
model_id = self.channel_model_mapping.get(channel_id) model_id = self.channel_model_mapping.get(channel_id)
if not model_id: if not model_id:
print(f"❌ [模型池管理器] 通道 {channel_id} 没有对应的模型") import traceback
traceback.print_exc()
return None return None
model = self.model_pool.get(model_id) model = self.model_pool.get(model_id)
if not model: if not model:
print(f"❌ [模型池管理器] 模型 {model_id} 未加载") import traceback
traceback.print_exc()
return None return None
# 更新使用计数 # 更新使用计数
...@@ -873,7 +859,8 @@ class ModelPoolManager: ...@@ -873,7 +859,8 @@ class ModelPoolManager:
return annotation_data return annotation_data
except Exception as e: except Exception as e:
print(f"❌ [模型池管理器] 加载通道 {channel_id} 标注配置失败: {e}") import traceback
traceback.print_exc()
return None return None
def process_batches(self, scheduled_batches: List[Dict[str, Any]]) -> List[Dict[str, Any]]: def process_batches(self, scheduled_batches: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
...@@ -887,7 +874,8 @@ class ModelPoolManager: ...@@ -887,7 +874,8 @@ class ModelPoolManager:
""" """
with self._lock: with self._lock:
if not self.is_initialized: if not self.is_initialized:
print("❌ [模型池管理器] 尚未初始化") import traceback
traceback.print_exc()
return [] return []
results = [] results = []
...@@ -903,7 +891,8 @@ class ModelPoolManager: ...@@ -903,7 +891,8 @@ class ModelPoolManager:
# 获取对应的模型 # 获取对应的模型
model = self.model_pool.get(model_id) model = self.model_pool.get(model_id)
if not model: if not model:
print(f"❌ [模型池管理器] 模型 {model_id} 未找到") import traceback
traceback.print_exc()
continue continue
# 切换到目标模型 # 切换到目标模型
...@@ -939,7 +928,6 @@ class ModelPoolManager: ...@@ -939,7 +928,6 @@ class ModelPoolManager:
return results return results
except Exception as e: except Exception as e:
print(f"❌ [模型池管理器] 批量推理失败: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
return [] return []
...@@ -949,7 +937,8 @@ class ModelPoolManager: ...@@ -949,7 +937,8 @@ class ModelPoolManager:
"""清理模型池资源""" """清理模型池资源"""
with self._lock: with self._lock:
try: try:
print("🧹 [模型池管理器] 开始清理资源...") import traceback
traceback.print_exc()
# 清理所有模型 # 清理所有模型
for model_id, model in self.model_pool.items(): for model_id, model in self.model_pool.items():
...@@ -957,9 +946,11 @@ class ModelPoolManager: ...@@ -957,9 +946,11 @@ class ModelPoolManager:
# 如果模型有cleanup方法,调用它 # 如果模型有cleanup方法,调用它
if hasattr(model, 'cleanup'): if hasattr(model, 'cleanup'):
model.cleanup() model.cleanup()
print(f"✅ [模型池管理器] 模型 {model_id} 清理完成") import traceback
traceback.print_exc()
except Exception as e: except Exception as e:
print(f"⚠️ [模型池管理器] 模型 {model_id} 清理失败: {e}") import traceback
traceback.print_exc()
# 清空所有数据结构 # 清空所有数据结构
self.model_pool.clear() self.model_pool.clear()
...@@ -971,10 +962,12 @@ class ModelPoolManager: ...@@ -971,10 +962,12 @@ class ModelPoolManager:
self.current_model_id = None self.current_model_id = None
self.is_initialized = False self.is_initialized = False
print("✅ [模型池管理器] 资源清理完成") import traceback
traceback.print_exc()
except Exception as e: except Exception as e:
print(f"❌ [模型池管理器] 资源清理失败: {e}") import traceback
traceback.print_exc()
def get_stats(self) -> Dict[str, Any]: def get_stats(self) -> Dict[str, Any]:
"""获取性能统计信息""" """获取性能统计信息"""
......
...@@ -245,7 +245,7 @@ class ChannelThreadManager: ...@@ -245,7 +245,7 @@ class ChannelThreadManager:
if not context: if not context:
return False return False
if context.detection_flag: if context.channel_detect_status:
return True return True
try: try:
...@@ -271,17 +271,22 @@ class ChannelThreadManager: ...@@ -271,17 +271,22 @@ class ChannelThreadManager:
global_thread.register_channel(channel_id, context, callback) global_thread.register_channel(channel_id, context, callback)
# 更新通道状态 # 更新通道状态
context.detection_flag = True context.channel_detect_status = True
context.detection_enabled = True context.detection_enabled = True
# 🔥 更新主窗口中的通道检测变量(channel1detect, channel2detect等) # 🔥 更新主窗口中的通道检测变量(channel1detect, channel2detect等)
if self.main_window: if self.main_window:
detect_var_name = f'{channel_id}detect' detect_var_name = f'{channel_id}detect'
setattr(self.main_window, detect_var_name, True) setattr(self.main_window, detect_var_name, True)
print(f"🔥 [线程管理器] 已设置 {detect_var_name} = True")
# 🔥 更新任务面板中通道列的颜色(检测状态变化时 # 🔥 更新任务面板中状态列的颜色(只更新为绿色,不置黑
if hasattr(self.main_window, '_updateChannelColumnColor'): if hasattr(self.main_window, '_updateChannelColumnColor'):
print(f"🔥 [线程管理器] 准备调用 _updateChannelColumnColor")
self.main_window._updateChannelColumnColor() self.main_window._updateChannelColumnColor()
print(f"🔥 [线程管理器] _updateChannelColumnColor 调用完成")
else:
print(f"❌ [线程管理器] main_window 没有 _updateChannelColumnColor 方法!")
# 保持兼容性:设置一个占位符线程对象 # 保持兼容性:设置一个占位符线程对象
context.detection_thread = threading.Thread( context.detection_thread = threading.Thread(
...@@ -510,7 +515,7 @@ class ChannelThreadManager: ...@@ -510,7 +515,7 @@ class ChannelThreadManager:
global_thread.unregister_channel(channel_id) global_thread.unregister_channel(channel_id)
# 更新通道状态 # 更新通道状态
context.detection_flag = False context.channel_detect_status = False
context.detection_enabled = False context.detection_enabled = False
# 🔥 更新主窗口中的通道检测变量(channel1detect, channel2detect等) # 🔥 更新主窗口中的通道检测变量(channel1detect, channel2detect等)
...@@ -518,7 +523,7 @@ class ChannelThreadManager: ...@@ -518,7 +523,7 @@ class ChannelThreadManager:
detect_var_name = f'{channel_id}detect' detect_var_name = f'{channel_id}detect'
setattr(self.main_window, detect_var_name, False) setattr(self.main_window, detect_var_name, False)
# 🔥 更新任务面板中通道列的颜色(检测状态变化时 # 🔥 更新任务面板中状态列的颜色(只更新为绿色,不置黑
if hasattr(self.main_window, '_updateChannelColumnColor'): if hasattr(self.main_window, '_updateChannelColumnColor'):
self.main_window._updateChannelColumnColor() self.main_window._updateChannelColumnColor()
...@@ -996,7 +1001,7 @@ class ChannelThreadManager: ...@@ -996,7 +1001,7 @@ class ChannelThreadManager:
'count': context.display_count 'count': context.display_count
}, },
'detection': { 'detection': {
'running': context.detection_flag, 'running': context.channel_detect_status,
'count': context.detection_count, 'count': context.detection_count,
'enabled': context.detection_enabled 'enabled': context.detection_enabled
}, },
......
...@@ -318,7 +318,6 @@ class CurveThread: ...@@ -318,7 +318,6 @@ class CurveThread:
data_points = [] data_points = []
try: try:
print(f"🔄 [全局曲线线程] 开始加载CSV文件: {csv_filepath}")
with open(csv_filepath, 'r', encoding='utf-8') as f: with open(csv_filepath, 'r', encoding='utf-8') as f:
line_count = 0 line_count = 0
for line in f: for line in f:
...@@ -349,11 +348,9 @@ class CurveThread: ...@@ -349,11 +348,9 @@ class CurveThread:
except ValueError as ve: except ValueError as ve:
pass pass
print(f"✅ [全局曲线线程] CSV文件加载完成: {csv_filepath}, 共 {len(data_points)} 个数据点")
return data_points return data_points
except Exception as e: except Exception as e:
print(f"⚠️ [全局曲线线程] 加载CSV到内存失败 {csv_filepath}: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
return [] return []
...@@ -395,7 +392,8 @@ class CurveThread: ...@@ -395,7 +392,8 @@ class CurveThread:
return new_lines return new_lines
except Exception as e: except Exception as e:
print(f"⚠️ [全局曲线线程] 增量读取CSV失败 {csv_filepath}: {e}") import traceback
traceback.print_exc()
return [] return []
@staticmethod @staticmethod
...@@ -418,7 +416,8 @@ class CurveThread: ...@@ -418,7 +416,8 @@ class CurveThread:
files.append(os.path.join(directory, name)) files.append(os.path.join(directory, name))
return files return files
except Exception as e: except Exception as e:
print(f"⚠️ [全局曲线线程] 无法列出CSV文件: {e}") import traceback
traceback.print_exc()
return [] return []
@staticmethod @staticmethod
...@@ -491,7 +490,8 @@ class CurveThread: ...@@ -491,7 +490,8 @@ class CurveThread:
curve_frame_rate = max(1, min(25, curve_frame_rate)) curve_frame_rate = max(1, min(25, curve_frame_rate))
return curve_frame_rate return curve_frame_rate
except Exception as e: except Exception as e:
print(f"⚠️ [全局曲线线程] 读取曲线帧率配置失败: {e}") import traceback
traceback.print_exc()
# 默认返回2 Hz # 默认返回2 Hz
return 2 return 2
......
...@@ -70,7 +70,7 @@ class DetectionThread: ...@@ -70,7 +70,7 @@ class DetectionThread:
channel_id = context.channel_id channel_id = context.channel_id
frame_interval = 1.0 / frame_rate if frame_rate > 0 else 0.05 frame_interval = 1.0 / frame_rate if frame_rate > 0 else 0.05
while context.detection_flag: while context.channel_detect_status:
try: try:
frame_start_time = time.time() frame_start_time = time.time()
...@@ -150,7 +150,7 @@ class DetectionThread: ...@@ -150,7 +150,7 @@ class DetectionThread:
print(f" - 批大小: {batch_size}") print(f" - 批大小: {batch_size}")
print(f" - 目标FPS: {frame_rate}") print(f" - 目标FPS: {frame_rate}")
while context.detection_flag: while context.channel_detect_status:
try: try:
frame_start_time = time.time() frame_start_time = time.time()
......
...@@ -34,7 +34,14 @@ class StorageThread: ...@@ -34,7 +34,14 @@ class StorageThread:
Returns: Returns:
str: 项目根目录的绝对路径 str: 项目根目录的绝对路径
""" """
# 当前文件是 handlers/videopage/thread_manager/threads/storage_thread.py import sys
# 🔥 打包后:使用 sys._MEIPASS 指向 _internal 目录
if getattr(sys, 'frozen', False):
# 打包环境:返回 _internal 目录
return sys._MEIPASS
else:
# 开发环境:当前文件是 handlers/videopage/thread_manager/threads/storage_thread.py
# 需要向上5级到达项目根目录 # 需要向上5级到达项目根目录
current_file = os.path.abspath(__file__) current_file = os.path.abspath(__file__)
project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(current_file))))) project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(current_file)))))
......
...@@ -30,7 +30,7 @@ class ViewHandler: ...@@ -30,7 +30,7 @@ class ViewHandler:
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""初始化视图处理器""" """初始化视图处理器"""
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# 曲线分析模式状态(False=实时检测模式, True=曲线分析模式 # 曲线分析模式状态(False=默认布局, True=曲线模式布局
self._is_curve_mode_active = False self._is_curve_mode_active = False
@property @property
...@@ -60,17 +60,24 @@ class ViewHandler: ...@@ -60,17 +60,24 @@ class ViewHandler:
import os import os
import sys import sys
# 动态获取项目根目录 # 🔥 动态获取数据目录(与storage_thread和curvepanel_handler保持一致)
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
project_root = os.path.dirname(sys.executable) # 打包后:使用 sys._MEIPASS 指向 _internal 目录
data_root = sys._MEIPASS
else: else:
try: try:
from database.config import get_project_root from database.config import get_project_root
project_root = get_project_root() data_root = get_project_root()
except ImportError: except ImportError:
project_root = os.getcwd() data_root = os.getcwd()
mission_path = os.path.join(data_root, 'database', 'mission_result', mission_name)
print(f"🔍 [_getCurveMissionPath] 任务名称: {mission_name}")
print(f"🔍 [_getCurveMissionPath] 数据根目录: {data_root}")
print(f"🔍 [_getCurveMissionPath] 完整路径: {mission_path}")
print(f"🔍 [_getCurveMissionPath] 路径是否存在: {os.path.exists(mission_path)}")
mission_path = os.path.join(project_root, 'database', 'mission_result', mission_name)
return mission_path if os.path.exists(mission_path) else None return mission_path if os.path.exists(mission_path) else None
def toggleToolBar(self): def toggleToolBar(self):
...@@ -121,23 +128,96 @@ class ViewHandler: ...@@ -121,23 +128,96 @@ class ViewHandler:
def _getCurrentDetectionState(self): def _getCurrentDetectionState(self):
""" """
获取当前检测线程的运行状态 获取当前检测线程的运行状态(基于curvemission使用的通道)
Returns: Returns:
bool: True=有检测线程正在运行, False=没有检测线程运行 bool: True=curvemission使用的通道中有任意一个检测线程正在运行, False=全部停止
""" """
if not hasattr(self, 'thread_manager'): if not hasattr(self, 'thread_manager'):
return False return False
# 检查是否有任何检测线程正在运行 # 获取当前curvemission选择的任务
for context in self.thread_manager.contexts.values(): if not hasattr(self, 'curvemission'):
if context and context.detection_flag: return False
mission_name = self.curvemission.currentText()
if not mission_name or mission_name == "请选择任务":
return False
# 🔥 第一层判断:检查任务状态
mission_status = self._getMissionStatus(mission_name)
if mission_status == "未启动":
return False
# 获取该任务使用的通道列表
task_channels = self._getTaskChannels(mission_name)
if not task_channels:
return False
# 将通道名称转换为通道ID(通道1 -> channel1)
channel_ids = []
for channel_name in task_channels:
if '通道' in channel_name:
# 提取数字部分
channel_num = channel_name.replace('通道', '')
channel_ids.append(f'channel{channel_num}')
# 检查这些通道的channel_detect_status状态
# 只要有任意一个通道的channel_detect_status为True,就返回True
for channel_id in channel_ids:
context = self.thread_manager.get_channel_context(channel_id)
if context:
channel_detect_status = context.channel_detect_status
# 确保 channel_detect_status 是布尔值 True
if channel_detect_status is True or channel_detect_status == True:
return True return True
else:
print(f" ⚠️ {channel_id}: 没有context")
# 所有通道的channel_detect_status都为False
return False return False
def _getMissionStatus(self, mission_name):
"""
获取任务的实际运行状态(检查是否有通道正在使用该任务)
Args:
mission_name: 任务名称
Returns:
str: 任务状态,"已启动" 或 "未启动"
"""
try:
# 🔥 检查任务是否被任何通道使用
# MissionPanelHandler 是通过 Mixin 继承的,所以直接用 self._isTaskInUse
if hasattr(self, '_isTaskInUse'):
is_in_use = self._isTaskInUse(mission_name)
status = "已启动" if is_in_use else "未启动"
print(f" 🔍 [任务状态检查] 任务'{mission_name}' is_in_use={is_in_use} → 状态='{status}'")
return status
else:
# 如果没有 _isTaskInUse 方法,默认返回"未启动"
print(f" ⚠️ [任务状态检查] 没有_isTaskInUse方法,默认返回'未启动'")
return "未启动"
except Exception as e:
print(f"❌ [任务状态] 获取失败: {e}")
import traceback
traceback.print_exc()
return "未启动"
def _switchToCurveLayout(self): def _switchToCurveLayout(self):
"""切换到曲线模式:根据检测线程状态选择合适的子布局""" """切换到曲线模式:根据检测线程状态选择合适的子布局"""
# 🔥 先设置模式为曲线模式(_video_layout_mode = 1),确保后续逻辑能正确判断
self._video_layout_mode = 1
# 🔥 检查检测线程状态,决定使用哪种子布局 # 🔥 检查检测线程状态,决定使用哪种子布局
detection_running = self._getCurrentDetectionState() detection_running = self._getCurrentDetectionState()
...@@ -146,20 +226,20 @@ class ViewHandler: ...@@ -146,20 +226,20 @@ class ViewHandler:
# 🔥 根据检测线程状态选择通道容器 # 🔥 根据检测线程状态选择通道容器
if detection_running: if detection_running:
# 实时检测模式:使用通道面板容器(ChannelPanel) # 同步布局:使用通道面板容器(ChannelPanel)
target_channel_widgets = self.channel_widgets_for_curve target_channel_widgets = self.channel_widgets_for_curve
layout_description = "实时检测模式" layout_description = "同步布局"
else: else:
# 历史回放模式:使用历史视频面板容器(HistoryVideoPanel) # 历史回放布局:使用历史视频面板容器(HistoryVideoPanel)
target_channel_widgets = self.history_channel_widgets_for_curve target_channel_widgets = self.history_channel_widgets_for_curve
layout_description = "历史回放模式" layout_description = "历史回放布局"
# 🔥 根据检测线程状态选择要显示的面板类型 # 🔥 根据检测线程状态选择要显示的面板类型
if detection_running: if detection_running:
# 实时检测模式:使用通道面板(ChannelPanel) # 同步布局:使用通道面板(ChannelPanel)
panels_to_use = self.channelPanels panels_to_use = self.channelPanels
else: else:
# 历史回放模式:使用历史视频面板(HistoryVideoPanel) # 历史回放布局:使用历史视频面板(HistoryVideoPanel)
if hasattr(self, 'historyVideoPanels'): if hasattr(self, 'historyVideoPanels'):
panels_to_use = self.historyVideoPanels panels_to_use = self.historyVideoPanels
else: else:
...@@ -201,10 +281,7 @@ class ViewHandler: ...@@ -201,10 +281,7 @@ class ViewHandler:
wrapper.updateGeometry() wrapper.updateGeometry()
panel.updateGeometry() panel.updateGeometry()
# 先设置模式为曲线模式(_video_layout_mode = 1) # 切换到曲线模式主布局(_video_layout_mode已在方法开头设置为1)
self._video_layout_mode = 1
# 切换到曲线模式主布局
self.videoLayoutStack.setCurrentIndex(1) self.videoLayoutStack.setCurrentIndex(1)
# 更新状态栏信息 # 更新状态栏信息
...@@ -223,7 +300,7 @@ class ViewHandler: ...@@ -223,7 +300,7 @@ class ViewHandler:
# 🔥 根据检测线程状态更新通道显示 # 🔥 根据检测线程状态更新通道显示
if detection_running: if detection_running:
# 实时检测模式:根据当前选择的任务更新通道显示 # 同步布局:根据当前选择的任务更新通道显示
if hasattr(self, 'curvemission'): if hasattr(self, 'curvemission'):
current_mission = self.curvemission.currentText() current_mission = self.curvemission.currentText()
if current_mission and current_mission != "请选择任务": if current_mission and current_mission != "请选择任务":
...@@ -236,7 +313,7 @@ class ViewHandler: ...@@ -236,7 +313,7 @@ class ViewHandler:
if hasattr(self, '_updateCurveChannelDisplay'): if hasattr(self, '_updateCurveChannelDisplay'):
self._updateCurveChannelDisplay([]) self._updateCurveChannelDisplay([])
else: else:
# 🔥 历史回放模式:显示所有历史视频面板 # 🔥 历史回放布局:显示所有历史视频面板
if hasattr(self, '_updateCurveChannelDisplay'): if hasattr(self, '_updateCurveChannelDisplay'):
all_channels = ['通道1', '通道2', '通道3', '通道4'] all_channels = ['通道1', '通道2', '通道3', '通道4']
self._updateCurveChannelDisplay(all_channels) # 显示所有4个历史视频面板 self._updateCurveChannelDisplay(all_channels) # 显示所有4个历史视频面板
...@@ -252,7 +329,7 @@ class ViewHandler: ...@@ -252,7 +329,7 @@ class ViewHandler:
QtCore.QTimer.singleShot(100, self._loadCurveDataOrStartThreads) QtCore.QTimer.singleShot(100, self._loadCurveDataOrStartThreads)
def _switchToDefaultLayout(self): def _switchToDefaultLayout(self):
"""切换到默认布局(实时检测模式)""" """切换到默认布局(任务表格 + 2x2通道面板)"""
# 🔥 停止所有曲线线程(切换回默认布局时) # 🔥 停止所有曲线线程(切换回默认布局时)
self._stopAllCurveThreads() self._stopAllCurveThreads()
...@@ -312,24 +389,29 @@ class ViewHandler: ...@@ -312,24 +389,29 @@ class ViewHandler:
Args: Args:
detection_running: bool, True=检测线程运行中, False=检测线程停止 detection_running: bool, True=检测线程运行中, False=检测线程停止
""" """
print(f"\n🔄 [布局切换] detection_running={detection_running}")
if not hasattr(self, 'curveLayoutStack'): if not hasattr(self, 'curveLayoutStack'):
print(f"❌ [布局切换] 没有curveLayoutStack")
return return
if detection_running: if detection_running:
# 切换到实时检测布局(索引0) # 切换到同步布局(曲线模式布局的索引0)
target_index = 0 target_index = 0
layout_name = "实时检测布局" layout_name = "同步布局"
mode_text = "实时检测" mode_text = "同步"
mode_style = "font-weight: bold; padding: 2px 8px;" mode_style = "font-weight: bold; padding: 2px 8px;"
curve_mode = 'realtime' curve_mode = 'realtime'
print(f"✅ [布局切换] 选择索引0 - 同步布局")
else: else:
# 切换到历史回放布局(索引1) # 切换到历史回放布局(曲线模式布局的索引1)
target_index = 1 target_index = 1
layout_name = "历史回放布局" layout_name = "历史回放布局"
mode_text = "历史回放" mode_text = "历史回放"
mode_style = "font-weight: bold; padding: 2px 8px;" mode_style = "font-weight: bold; padding: 2px 8px;"
curve_mode = 'history' curve_mode = 'history'
print(f"✅ [布局切换] 选择索引1 - 历史回放布局")
# 🔥 同步切换曲线绘制模式 # 🔥 同步切换曲线绘制模式
...@@ -349,16 +431,43 @@ class ViewHandler: ...@@ -349,16 +431,43 @@ class ViewHandler:
except ImportError: except ImportError:
pass pass
# 🔥 禁用/启用通道面板的查看曲线按钮
# 只有在曲线模式(_video_layout_mode==1)的子布局切换时才需要处理
# 子布局索引0(实时检测模式)时禁用,索引1(历史回放模式)时根据任务状态决定
if hasattr(self, '_video_layout_mode') and self._video_layout_mode == 1:
if hasattr(self, 'channelPanels'):
for panel in self.channelPanels:
if hasattr(panel, 'btnCurve'):
if target_index == 0:
# 曲线模式的同步布局:禁用查看曲线按钮
panel.btnCurve.setEnabled(False)
panel.btnCurve.setToolTip("同步布局下无法查看曲线")
else:
# 曲线模式的历史回放布局:检查通道是否有任务
has_task = False
if hasattr(panel, 'getTaskInfo'):
task_info = panel.getTaskInfo()
has_task = (task_info is not None and task_info != "未分配任务")
if has_task:
# 有任务:启用查看曲线按钮
panel.btnCurve.setEnabled(True)
panel.btnCurve.setToolTip("查看曲线")
else:
# 无任务:保持禁用
panel.btnCurve.setEnabled(False)
panel.btnCurve.setToolTip("请先分配任务")
# 执行布局切换 # 执行布局切换
if self.curveLayoutStack.currentIndex() != target_index: current_index = self.curveLayoutStack.currentIndex()
print(f"📍 [布局切换] 当前索引: {current_index}, 目标索引: {target_index}")
if current_index != target_index:
self.curveLayoutStack.setCurrentIndex(target_index) self.curveLayoutStack.setCurrentIndex(target_index)
self._curve_sub_layout_mode = target_index self._curve_sub_layout_mode = target_index
print(f"✅ [布局切换] 已切换到索引 {target_index}")
# 🔥 切换模式后,重新应用通道筛选逻辑 else:
if hasattr(self, 'curvemission') and hasattr(self, '_onCurveMissionChanged'): print(f"ℹ️ [布局切换] 已经是目标索引 {target_index},无需切换")
current_mission = self.curvemission.currentText()
if current_mission and current_mission != "请选择任务":
self._onCurveMissionChanged(current_mission)
def _loadCurveDataOrStartThreads(self): def _loadCurveDataOrStartThreads(self):
......
# DebugLog 调试日志模块使用说明
## 功能概述
`debuglog.py` 提供了一个基于 `logging` 模块的调试日志系统,用于替代 `print()` 进行调试输出。
## 核心特性
-**单例模式**: 全局统一的日志管理
-**双重输出**: 同时输出到控制台和文件
-**按日期分文件**: 每天自动创建新的日志文件
-**多级别日志**: DEBUG, INFO, WARNING, ERROR, CRITICAL
-**详细信息**: 文件日志包含时间、模块、文件名、行号等
-**自动清理**: 支持清理旧日志文件
-**UTF-8编码**: 支持中文日志
## 日志文件位置
```
D:\restructure\liquid_level_line_detection_system\database\log\debuglog\
```
## 快速开始
### 1. 基本使用
```python
from handlers.debuglog import get_logger
# 创建logger(建议使用 __name__ 作为名称)
logger = get_logger(__name__)
# 记录不同级别的日志
logger.debug("调试信息")
logger.info("普通信息")
logger.warning("警告信息")
logger.error("错误信息")
logger.critical("严重错误")
```
### 2. 在类中使用
```python
from handlers.debuglog import get_logger
class MyClass:
def __init__(self):
self.logger = get_logger(self.__class__.__name__)
def do_something(self):
self.logger.info("开始执行操作")
try:
# 你的代码
result = self.process()
self.logger.info(f"操作成功,结果: {result}")
except Exception as e:
self.logger.error(f"操作失败: {e}", exc_info=True)
```
### 3. 在函数中使用
```python
from handlers.debuglog import get_logger
logger = get_logger(__name__)
def my_function(param):
logger.debug(f"函数调用,参数: {param}")
# 你的代码
result = param * 2
logger.info(f"函数执行完成,返回: {result}")
return result
```
## 高级用法
### 1. 只输出到文件(不显示在控制台)
```python
from handlers.debuglog import get_logger
logger = get_logger(
"my_module",
console_output=False, # 不输出到控制台
file_output=True # 只输出到文件
)
logger.info("这条消息只会写入日志文件")
```
### 2. 设置日志级别
```python
from handlers.debuglog import get_logger, INFO
# 只记录 INFO 及以上级别的日志
logger = get_logger("my_module", level=INFO)
logger.debug("这条不会显示") # DEBUG < INFO
logger.info("这条会显示") # INFO >= INFO
logger.warning("这条会显示") # WARNING > INFO
```
日志级别(从低到高):
- `DEBUG` (10) - 详细的调试信息
- `INFO` (20) - 一般信息
- `WARNING` (30) - 警告信息
- `ERROR` (40) - 错误信息
- `CRITICAL` (50) - 严重错误
### 3. 记录异常信息
```python
from handlers.debuglog import get_logger
logger = get_logger(__name__)
try:
result = 1 / 0
except Exception as e:
# exc_info=True 会记录完整的堆栈跟踪
logger.error(f"发生异常: {e}", exc_info=True)
```
### 4. 清理旧日志
```python
from handlers.debuglog import clear_old_logs
# 清理7天前的日志文件
clear_old_logs(days=7)
# 清理30天前的日志文件
clear_old_logs(days=30)
```
## 日志格式
### 控制台输出(简洁格式)
```
2025-11-29 15:30:45 - INFO - 这是一条信息
2025-11-29 15:30:46 - WARNING - 这是一条警告
2025-11-29 15:30:47 - ERROR - 这是一条错误
```
### 文件输出(详细格式)
```
2025-11-29 15:30:45 - my_module - INFO - [main.py:123] - 这是一条信息
2025-11-29 15:30:46 - my_module - WARNING - [main.py:124] - 这是一条警告
2025-11-29 15:30:47 - my_module - ERROR - [main.py:125] - 这是一条错误
```
## 实际应用示例
### 示例1: 在 auto_dot.py 中使用
```python
from handlers.debuglog import get_logger
class AutoAnnotationDetector:
def __init__(self, model_path: str = None, device: str = 'cuda'):
self.logger = get_logger(self.__class__.__name__)
self.model = None
self.model_path = model_path
self.device = self._validate_device(device)
if model_path:
self.load_model(model_path)
def load_model(self, model_path: str) -> bool:
try:
self.logger.info(f"开始加载模型: {model_path}")
if not os.path.exists(model_path):
self.logger.error(f"模型文件不存在: {model_path}")
return False
# 加载模型...
self.logger.info(f"模型加载成功: {os.path.basename(model_path)}")
return True
except Exception as e:
self.logger.error(f"模型加载失败: {e}", exc_info=True)
return False
def detect(self, image, conf_threshold=0.5, min_area=100):
self.logger.debug(f"开始检测 - 置信度阈值: {conf_threshold}, 最小面积: {min_area}")
try:
# 执行检测...
self.logger.info(f"检测完成,共 {len(valid_masks)} 个有效mask")
return result
except Exception as e:
self.logger.error(f"检测失败: {e}", exc_info=True)
return {'success': False, 'error': str(e)}
```
### 示例2: 替代现有的 print 语句
**之前(使用 print):**
```python
print(f"🔄 正在加载模型: {model_path}")
print(f"✅ 模型加载成功")
print(f"❌ 模型加载失败: {e}")
```
**之后(使用 logger):**
```python
logger.info(f"正在加载模型: {model_path}")
logger.info(f"模型加载成功")
logger.error(f"模型加载失败: {e}", exc_info=True)
```
### 示例3: 在测试函数中使用
```python
from handlers.debuglog import get_logger
def test_auto_annotation():
logger = get_logger("test_auto_annotation")
logger.info("="*80)
logger.info("自动标注功能测试")
logger.info("="*80)
# 配置参数
model_path = r"D:\...\best.dat"
logger.debug(f"模型路径: {model_path}")
# 检查文件
if not os.path.exists(model_path):
logger.error(f"模型文件不存在: {model_path}")
return
# 创建检测器
logger.info("初始化自动标注检测器...")
detector = AutoAnnotationDetector(model_path=model_path, device='cuda')
# 执行检测
logger.info("开始执行检测...")
detection_result = detector.detect(image, conf_threshold=0.5, min_area=100)
if detection_result.get('success'):
logger.info("检测成功")
else:
logger.error(f"检测失败: {detection_result.get('error')}")
```
## 最佳实践
1. **使用 `__name__` 作为 logger 名称**
```python
logger = get_logger(__name__)
```
2. **在类中创建实例变量**
```python
def __init__(self):
self.logger = get_logger(self.__class__.__name__)
```
3. **记录异常时使用 `exc_info=True`**
```python
except Exception as e:
logger.error(f"错误: {e}", exc_info=True)
```
4. **根据重要性选择合适的日志级别**
- `DEBUG`: 详细的调试信息(开发时使用)
- `INFO`: 正常的操作信息
- `WARNING`: 警告但不影响运行
- `ERROR`: 错误但程序可以继续
- `CRITICAL`: 严重错误,程序可能无法继续
5. **定期清理旧日志**
```python
# 在程序启动时清理旧日志
from handlers.debuglog import clear_old_logs
clear_old_logs(days=7)
```
## 测试
运行测试代码:
```bash
python handlers/debuglog.py
```
这会执行5个测试用例,展示各种使用方式。
## 注意事项
1. 日志文件按日期命名,格式为:`{logger_name}_{YYYYMMDD}.log`
2. 日志文件使用 UTF-8 编码,支持中文
3. 同一个 logger 名称会复用已创建的 logger 实例
4. 文件日志会追加写入,不会覆盖
5. 建议在生产环境中将日志级别设置为 `INFO` 或更高
...@@ -10,3 +10,5 @@ C - Copied(已复制):文件已被复制 ...@@ -10,3 +10,5 @@ C - Copied(已复制):文件已被复制
U - Unmerged(未合并):存在合并冲突 U - Unmerged(未合并):存在合并冲突
channeldetect=True channeldetect=True
curve_load_mode curve_load_mode
检测线程运行中 (detection_running=True) → 索引0(实时检测模式)
检测线程停止 (detection_running=False) → 索引1(历史回放模式)
\ No newline at end of file
# -*- coding: utf-8 -*-
"""
训练工作线程
处理模型训练的后台线程
"""
import os
import yaml
import json
import struct
import hashlib
from pathlib import Path
from qtpy import QtCore
# 尝试导入 pyqtSignal,如果失败则使用 Signal
try:
from PyQt5.QtCore import pyqtSignal
except ImportError:
try:
from PyQt6.QtCore import pyqtSignal
except ImportError:
# 如果都失败,使用 QtCore.Signal
from qtpy.QtCore import Signal as pyqtSignal
from qtpy.QtCore import QThread
# 导入统一的路径管理函数
try:
from ...database.config import get_project_root, get_temp_models_dir, get_train_dir
except (ImportError, ValueError):
try:
from database.config import get_project_root, get_temp_models_dir, get_train_dir
except ImportError:
import sys
from pathlib import Path
project_root = Path(__file__).parent.parent.parent
sys.path.insert(0, str(project_root))
from database.config import get_project_root, get_temp_models_dir, get_train_dir
MODEL_FILE_SIGNATURE = b'LDS_MODEL_FILE'
MODEL_FILE_VERSION = 1
MODEL_ENCRYPTION_KEY = "liquid_detection_system_2024"
class TrainingWorker(QThread):
"""训练工作线程"""
# 信号定义
log_output = pyqtSignal(str) # 日志输出信号
training_finished = pyqtSignal(bool) # 训练完成信号
training_progress = pyqtSignal(int, dict) # 训练进度信号 (epoch, loss_dict)
def __init__(self, training_params):
super().__init__()
self.training_params = training_params
self.is_running = True
self.train_config = None
self.training_report = {
"status": "init",
"start_time": None,
"end_time": None,
"exp_name": training_params.get("exp_name"),
"params": training_params,
"device": training_params.get("device"),
"weights_dir": None,
"converted_dat_files": [],
"error": None,
}
# 加载训练配置
self._loadTrainingConfig()
def _loadTrainingConfig(self):
"""加载训练配置"""
try:
import os
import json
current_dir = os.path.dirname(os.path.abspath(__file__))
config_dir = os.path.join(current_dir, "..", "..", "database", "config", "train_configs")
config_file_path = os.path.join(config_dir, "default_config.json")
if not os.path.exists(config_file_path):
# 尝试使用项目根目录
try:
from database.config import get_project_root
project_root = get_project_root()
config_file_path = os.path.join(project_root, "database", "config", "train_configs", "default_config.json")
except:
pass
if os.path.exists(config_file_path):
with open(config_file_path, 'r', encoding='utf-8') as f:
self.train_config = json.load(f)
else:
self.train_config = None
except Exception as e:
self.train_config = None
def _decode_dat_model(self, dat_path):
"""
将加密的 .dat 模型解密为临时 .pt 文件
Args:
dat_path (str): .dat 模型路径
Returns:
str: 解密后的 .pt 模型路径
"""
dat_path = Path(dat_path)
if not dat_path.exists():
raise FileNotFoundError(f"模型文件不存在: {dat_path}")
# 检查文件签名,判断是否为加密文件
with open(dat_path, 'rb') as f:
signature = f.read(len(MODEL_FILE_SIGNATURE))
# 如果签名不匹配,说明这是一个直接重命名的 .pt 文件
if signature != MODEL_FILE_SIGNATURE:
print(f"[警告] {dat_path.name} 不是加密的 .dat 文件,将直接作为 .pt 文件使用")
# 直接返回原路径,模型 可以直接加载
return str(dat_path)
# 继续解密流程
version = struct.unpack('<I', f.read(4))[0]
if version != MODEL_FILE_VERSION:
raise ValueError(f"不支持的模型文件版本: {version}")
filename_len = struct.unpack('<I', f.read(4))[0]
_ = f.read(filename_len) # 原始文件名,当前不使用
data_len = struct.unpack('<Q', f.read(8))[0]
encrypted_data = f.read(data_len)
key_hash = hashlib.sha256(MODEL_ENCRYPTION_KEY.encode('utf-8')).digest()
decrypted = bytearray(len(encrypted_data))
key_len = len(key_hash)
for idx, byte in enumerate(encrypted_data):
decrypted[idx] = byte ^ key_hash[idx % key_len]
decrypted = bytes(decrypted)
temp_dir = Path(get_temp_models_dir())
temp_dir.mkdir(parents=True, exist_ok=True)
path_hash = hashlib.md5(str(dat_path).encode('utf-8')).hexdigest()[:8]
temp_model_path = temp_dir / f"train_{dat_path.stem}_{path_hash}.pt"
with open(temp_model_path, 'wb') as f:
f.write(decrypted)
return str(temp_model_path)
def _validateTrainingDataInThread(self, save_liquid_data_path):
"""
在线程中验证训练数据(简化版,避免UI操作)
Returns:
tuple: (是否有效, 消息)
"""
try:
if not os.path.exists(save_liquid_data_path):
return False, f"数据集配置文件不存在: {save_liquid_data_path}"
if not save_liquid_data_path.endswith('.yaml'):
return False, "数据集配置文件必须是 .yaml 格式"
# 读取配置
with open(save_liquid_data_path, 'r', encoding='utf-8') as f:
data_config = yaml.safe_load(f)
if not data_config:
return False, "数据集配置文件为空"
# 获取data.yaml所在目录
data_yaml_dir = os.path.dirname(os.path.abspath(save_liquid_data_path))
train_dir = data_config.get('train', '')
val_dir = data_config.get('val', '')
if not train_dir:
return False, "训练集路径为空"
if not val_dir:
return False, "验证集路径为空"
# 如果是相对路径,转换为相对于data.yaml的绝对路径
if not os.path.isabs(train_dir):
train_dir = os.path.join(data_yaml_dir, train_dir)
if not os.path.isabs(val_dir):
val_dir = os.path.join(data_yaml_dir, val_dir)
if not os.path.exists(train_dir):
return False, f"训练集路径不存在: {train_dir}"
if not os.path.exists(val_dir):
return False, f"验证集路径不存在: {val_dir}"
# 检查是否有图片文件
image_extensions = ['.jpg', '.jpeg', '.png', '.bmp', '.tif', '.tiff']
train_count = sum(1 for f in os.listdir(train_dir)
if any(f.lower().endswith(ext) for ext in image_extensions))
val_count = sum(1 for f in os.listdir(val_dir)
if any(f.lower().endswith(ext) for ext in image_extensions))
if train_count == 0:
return False, f"训练集目录为空: {train_dir}"
if val_count == 0:
return False, f"验证集目录为空: {val_dir}"
return True, f"数据集验证通过 (训练: {train_count} 张, 验证: {val_count} 张)"
except Exception as e:
return False, f"验证过程出错: {str(e)}"
def run(self):
"""执行训练"""
# 初始化变量(确保finally块能访问)
original_stdout = None
original_stderr = None
temp_model_path = None
try:
import os
import sys
import io
import logging
# 根据训练设备设置环境变量
device = self.training_params.get('device', 'cpu')
if device.lower() == 'cpu':
os.environ["CUDA_VISIBLE_DEVICES"] = '-1' # 强制使用 CPU
else:
# GPU 设备:支持 '0', '0,1' 等格式
os.environ["CUDA_VISIBLE_DEVICES"] = device
# 优化环境变量设置
os.environ['YOLO_VERBOSE'] = 'True' # 允许显示训练进度
os.environ['ULTRALYTICS_AUTODOWNLOAD'] = 'False' # 禁用自动下载
os.environ['ULTRALYTICS_DATASETS_DIR'] = os.path.join(os.getcwd(), 'database', 'dataset')
# 设置日志级别以支持进度条显示
import logging
logging.getLogger('ultralytics').setLevel(logging.INFO)
logging.getLogger('yolov8').setLevel(logging.INFO)
# 确保进度条能正常显示
os.environ['TERM'] = 'xterm-256color' # 支持颜色和进度条
# 先导入YOLO,但不立即设置离线模式
# 离线模式会在验证模型文件存在后设置
from ultralytics import YOLO
# 创建日志捕获类(同步终端和UI,只显示原生进度条,单行实时更新,每轮换行)
class LogCapture:
"""捕获训练进度,同步显示到终端和UI(与终端完全一致)
- 训练过程中:单行实时更新进度条(缓存进度条,只发送最新的)
- 每轮完成(100%):保留该行并换行,下一轮从新行开始
"""
def __init__(self, signal, original_stream, log_file_path=None):
self.signal = signal
self.original = original_stream
self.buffer = ""
self._log_file_path = log_file_path
self._is_progress_line = False # 标记当前是否是进度条行
self._cached_progress = None # 缓存最新的进度条行
self._last_epoch = None # 记录上一个 epoch
def write(self, text):
import re
# 始终写入终端(保证终端显示完整)
if self.original:
try:
self.original.write(text)
self.original.flush()
except:
pass
# 同步写入到日志文件(追加)
if self._log_file_path:
try:
with open(self._log_file_path, "a", encoding="utf-8", errors="ignore") as lf:
lf.write(text)
except:
pass
# 处理文本:清理ANSI代码并发送到UI
# 移除ANSI转义序列(颜色代码等)
clean_text = re.sub(r'\x1B\[[0-?]*[ -/]*[@-~]', '', text)
# 过滤掉YOLO自动打印的验证指标行(包含mAP等)
# 这些行通常包含:Epoch, GPU_mem, box_loss, cls_loss, dfl_loss, Instances, Size, mAP50, mAP50-95等
# 示例:Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size
# 1/100 3.72G 1.173 1.920 1.506 29 640
if re.search(r'(Epoch\s+GPU_mem|metrics/mAP|val/box_loss|val/cls_loss|val/dfl_loss|mAP50|mAP50-95)', clean_text, re.IGNORECASE):
# 跳过这些验证指标行,不发送到UI
return
# 检查是否包含回车符(进度条通常使用\r来覆盖同一行)
has_carriage_return = '\r' in text
# 移除回车符,但记住这是进度条行
if has_carriage_return:
clean_text = re.sub(r'\r', '', clean_text)
self._is_progress_line = True
# 如果有换行符,说明进度条行结束
if '\n' in clean_text:
self._is_progress_line = False
# 先检查是否需要过滤(扫描信息、调试日志等)
# 只过滤明确不需要的信息
skip_patterns = [
'scanning', # 数据集扫描信息
'labels.cache', # 缓存文件信息
'duplicate', # 重复标签信息
'warning:', # 警告信息
'[trainingpage]', # UI 调试日志
'[应用]', # 应用调试日志
]
should_skip = False
for pattern in skip_patterns:
if pattern in clean_text.lower():
should_skip = True
break
if should_skip:
return # 跳过这条信息
# 再检查是否是训练进度条行(优先级最高,不过滤)
# 训练进度条格式:epoch/batch 显存 损失值... 进度条 速度
# 例如:1/100 3.72G 1.173 1.92 1.506 1.253 29 640: 4% ──────────── 109/2901
# 关键特征:包含 epoch/batch、显存(G)、多个损失值、百分比
is_progress_bar = (
# 最准确的特征:包含 epoch/batch 格式、显存信息(G)、百分比和进度符号
(not '\n' in clean_text and
re.search(r'\d+/\d+', clean_text) is not None and
re.search(r'\d+\.?\d*G', clean_text) is not None and
'%' in clean_text and
('|' in clean_text or '━' in clean_text or '─' in clean_text))
)
# 发送所有有效文本到UI,包括训练信息和进度条
if clean_text.strip():
# 发送进度条或普通文本到UI
if is_progress_bar:
try:
# 检查是否达到100%(一轮完成)
is_complete = '100%' in clean_text
# 提取当前 epoch 号(格式:1/100, 2/100 等)
epoch_match = re.search(r'(\d+)/(\d+)', clean_text)
current_epoch = int(epoch_match.group(1)) if epoch_match else None
# 使用特殊标记来标识进度条
if is_complete:
# 如果达到100%,标记为完成,UI会保留这一行并换行
marked_text = "__PROGRESS_BAR_COMPLETE__" + clean_text
self.signal.emit(marked_text)
self._cached_progress = None # 清空缓存
self._last_epoch = current_epoch # 更新 epoch 记录
else:
# 关键修复:实时发送进度条,而不是缓存
# 这样用户可以看到实时的训练进度
marked_text = "__PROGRESS_BAR__" + clean_text
self.signal.emit(marked_text) # 立即发送,不缓存
self._cached_progress = marked_text # 保留缓存备用
except Exception as e:
# 如果处理进度条出错,作为普通文本发送
self.signal.emit(clean_text)
else:
# 发送之前缓存的进度条(如果有的话)
if self._cached_progress:
self.signal.emit(self._cached_progress)
self._cached_progress = None
# 发送普通训练信息到UI
self.signal.emit(clean_text)
def flush(self):
# 刷新终端
if self.original:
try:
self.original.flush()
except:
pass
# 如果缓冲区有内容,尝试发送到UI
if self.buffer and self.buffer.strip():
try:
import re
clean_text = re.sub(r'\x1B\[[0-?]*[ -/]*[@-~]', '', self.buffer)
clean_text = re.sub(r'\r', '', clean_text)
if clean_text.strip():
self.signal.emit(clean_text)
self.buffer = ""
except:
pass
# 保存原始stdout/stderr
original_stdout = sys.stdout
original_stderr = sys.stderr
# 预先准备日志目录与日志文件
try:
train_root_for_log = get_train_dir()
exp_name_for_log = self.training_params.get('exp_name', 'training_experiment')
exp_dir_for_log = os.path.join(train_root_for_log, "runs", "train", exp_name_for_log)
os.makedirs(exp_dir_for_log, exist_ok=True)
log_file_path = os.path.join(exp_dir_for_log, "training_ui.log")
# 记录到报告(存绝对路径)
self.training_report["weights_dir"] = os.path.abspath(os.path.join(exp_dir_for_log, "weights"))
except Exception:
log_file_path = None
# 重定向stdout和stderr(附带文件记录)
sys.stdout = LogCapture(self.log_output, sys.__stdout__, log_file_path)
sys.stderr = LogCapture(self.log_output, sys.__stderr__, log_file_path)
# 输出训练开始信息(简化版,不打印详细参数)
self.log_output.emit("=" * 70 + "\n")
self.log_output.emit("开始升级模型\n")
self.log_output.emit("=" * 70 + "\n\n")
# 报告:开始时间
import time as _time_mod
self.training_report["status"] = "running"
self.training_report["start_time"] = _time_mod.time()
# 验证数据集(在训练线程中再次验证,确保数据可用)
self.log_output.emit("正在验证数据集...\n")
try:
validation_result, validation_msg = self._validateTrainingDataInThread(self.training_params['save_liquid_data_path'])
if not validation_result:
self.log_output.emit(f"[ERROR] 数据集验证失败: {validation_msg}\n")
self.log_output.emit("=" * 60 + "\n")
self.training_finished.emit(False)
return
else:
self.log_output.emit(f"{validation_msg}\n\n")
except Exception as e:
self.log_output.emit(f"[WARNING] 数据集验证过程出错: {str(e)}\n")
self.log_output.emit("继续尝试训练...\n\n")
# 处理模型文件
model_path = self.training_params['base_model']
temp_model_path = None
if model_path.endswith('.dat'):
self.log_output.emit("正在处理.dat模型文件...\n")
try:
decoded_path = self._decode_dat_model(model_path)
model_path = decoded_path
temp_model_path = decoded_path
self.log_output.emit("模型处理完成\n")
except Exception as decode_error:
self.log_output.emit(f"[ERROR] 模型处理失败: {decode_error}\n")
self.training_finished.emit(False)
return
# 检查停止标志
if not self.is_running:
self.log_output.emit("[WARNING] 训练在开始前被停止\n")
return
# 加载模型
self.log_output.emit("正在加载模型...\n")
try:
# 在加载模型前验证文件存在,并设置离线模式
if not os.path.exists(model_path):
raise FileNotFoundError(f"模型文件不存在: {model_path}")
# 验证通过后,设置离线模式防止ultralytics尝试下载其他模型
os.environ['YOLO_OFFLINE'] = '1'
os.environ['ULTRALYTICS_OFFLINE'] = 'True'
model = YOLO(model_path)
self.log_output.emit("模型加载成功\n\n")
except Exception as model_error:
self.log_output.emit(f"[ERROR] 模型加载失败: {str(model_error)}\n")
raise model_error
# 创建训练回调
import time
epoch_start_time = [0] # 使用列表以便在闭包中修改
def on_train_start(trainer):
"""训练开始回调 - 只输出到终端,不发送到UI"""
# 记录开始时间
epoch_start_time[0] = time.time()
# 不发送任何格式化消息到UI,让LogCapture直接捕获原生输出
def on_train_batch_end(trainer):
"""训练批次结束回调 - 检查停止标志但不立即停止"""
if not self.is_running:
# 只显示提示信息,不设置stop_training标志
# 让训练继续到epoch结束
if not hasattr(trainer, '_stop_message_shown'):
print("\n用户请求停止训练...")
print("请稍候,等待当前训练轮次完成...")
trainer._stop_message_shown = True
def on_train_epoch_end(trainer):
"""训练周期结束回调 - 检查停止标志,在epoch完成后优雅停止"""
# 获取当前轮次信息
epoch = trainer.epoch + 1
total_epochs = trainer.epochs
# 如果用户请求停止,在当前epoch完成后停止
if not self.is_running:
print(f"\n当前轮次 {epoch}/{total_epochs} 已完成")
print("用户请求停止训练,正在退出...")
trainer.stop_training = True
if hasattr(trainer, 'model'):
trainer.model.training = False
# 抛出异常来终止训练,但此时当前epoch已完成
raise KeyboardInterrupt("用户停止训练")
# 重置计时器
current_time = time.time()
epoch_start_time[0] = current_time
# 只发送进度信号,不发送格式化消息到UI
# 让LogCapture直接捕获原生输出
try:
loss_dict = {}
if hasattr(trainer, 'metrics'):
if hasattr(trainer.metrics, 'box_loss'):
loss_dict['box_loss'] = float(trainer.metrics.box_loss)
if hasattr(trainer.metrics, 'cls_loss'):
loss_dict['cls_loss'] = float(trainer.metrics.cls_loss)
self.training_progress.emit(epoch, loss_dict)
except Exception as e:
pass
# 添加回调
try:
model.add_callback("on_train_start", on_train_start)
model.add_callback("on_train_batch_end", on_train_batch_end)
model.add_callback("on_train_epoch_end", on_train_epoch_end)
except Exception as e:
self.log_output.emit(f"回调添加失败: {str(e)}\n")
# 最后一次检查停止标志
if not self.is_running:
self.log_output.emit("[WARNING] 训练在开始前被停止\n")
return
self.log_output.emit("开始升级模型...\n")
self.log_output.emit("=" * 60 + "\n")
# 检查并调整batch size(防止GPU OOM)
batch_size = self.training_params['batch']
device_str = self.training_params['device']
imgsz = self.training_params['imgsz']
original_batch_size = batch_size # 保存原始batch size
# 如果使用GPU,检查显存和batch size
if device_str.lower() not in ['cpu', '-1']:
self.log_output.emit(f"检测到GPU训练(设备: {device_str})\n")
# 尝试获取GPU信息
try:
import torch
import gc
if torch.cuda.is_available():
gpu_id = int(device_str) if device_str.isdigit() else 0
gpu_name = torch.cuda.get_device_name(gpu_id)
total_memory = torch.cuda.get_device_properties(gpu_id).total_memory / (1024**3) # GB
self.log_output.emit(f"GPU型号: {gpu_name}\n")
self.log_output.emit(f"总显存: {total_memory:.2f} GB\n")
# 彻底清理显存
gc.collect()
torch.cuda.empty_cache()
torch.cuda.synchronize()
# 获取当前可用显存
try:
allocated = torch.cuda.memory_allocated(gpu_id) / (1024**3)
reserved = torch.cuda.memory_reserved(gpu_id) / (1024**3)
free_memory = total_memory - reserved
self.log_output.emit(f"当前已分配: {allocated:.2f} GB\n")
self.log_output.emit(f"当前保留: {reserved:.2f} GB\n")
self.log_output.emit(f"可用显存: {free_memory:.2f} GB\n\n")
# 根据显存大小和图像尺寸给出batch size建议
if total_memory < 6: # 6GB以下
recommended_batch = 4
recommended_imgsz = 512
elif total_memory < 12: # 6-12GB
recommended_batch = 8
recommended_imgsz = 640
else: # 12GB以上
recommended_batch = 16
recommended_imgsz = 640
# 根据图像尺寸调整建议
if imgsz > 640:
recommended_batch = max(4, recommended_batch // 2)
elif imgsz > 512:
recommended_batch = max(4, int(recommended_batch * 0.75))
# 如果可用显存不足,进一步降低建议
if free_memory < 3.0:
recommended_batch = max(2, recommended_batch // 2)
# 检查当前设置是否合理,如果超出建议值则自动调整
if batch_size > recommended_batch:
self.log_output.emit(f"警告: 当前batch={batch_size}可能超出显存容量\n")
self.log_output.emit(f"自动调整: batch={batch_size} -> {recommended_batch}\n")
batch_size = recommended_batch
self.log_output.emit(f"建议配置: batch≤{recommended_batch}, imgsz≤{recommended_imgsz}\n\n")
elif free_memory < 2.0: # 可用显存少于2GB
self.log_output.emit(f"警告: 可用显存不足 ({free_memory:.2f} GB)\n")
# 自动降低batch size
if batch_size > 4:
new_batch = max(2, batch_size // 2)
self.log_output.emit(f"自动调整: batch={batch_size} -> {new_batch}\n")
batch_size = new_batch
self.log_output.emit(f"建议: 关闭其他程序释放显存,或进一步减小batch size\n\n")
except:
pass
except Exception as e:
self.log_output.emit(f"无法获取GPU详细信息: {str(e)}\n")
# 通用建议和自动调整
if batch_size > 8:
self.log_output.emit(f"警告: batch={batch_size} 可能导致显存不足\n")
new_batch = max(4, batch_size // 2)
self.log_output.emit(f"自动调整: batch={batch_size} -> {new_batch}\n")
batch_size = new_batch
self.log_output.emit(f"建议: 使用batch≤8以避免OOM错误\n\n")
# 开始训练(支持自动重试和batch size调整)
max_retries = 3
retry_count = 0
training_success = False
while retry_count < max_retries and not training_success:
try:
# 从配置文件读取AMP设置,如果没有则默认启用(节省显存)
amp_enabled = True # 默认启用AMP
if self.train_config and 'device_config' in self.train_config:
amp_enabled = self.train_config['device_config'].get('amp', True)
# 如果使用CPU,强制关闭AMP(CPU不支持AMP)
if device_str.lower() in ['cpu', '-1']:
amp_enabled = False
# 如果是重试,清理显存
if retry_count > 0:
self.log_output.emit(f"\n第 {retry_count} 次重试训练...\n")
try:
import torch
import gc
gc.collect()
torch.cuda.empty_cache()
torch.cuda.synchronize()
self.log_output.emit("已清理GPU显存缓存\n")
except:
pass
self.log_output.emit(f"批次大小: {batch_size}\n")
self.log_output.emit(f"训练设备: {device_str}\n")
self.log_output.emit(f"模型名称: {self.training_params['exp_name']}\n\n")
# 优化workers参数,避免多线程死锁
workers = min(self.training_params['workers'], 2) # 限制最大workers数量
if device_str.lower() in ['cpu', '-1']:
workers = 0 # CPU模式下禁用多线程数据加载
# 开始训练
try:
mission_results = model.train(
data=self.training_params['save_liquid_data_path'],
imgsz=self.training_params['imgsz'],
epochs=self.training_params['epochs'],
batch=batch_size,
workers=workers,
device=device_str,
optimizer=self.training_params['optimizer'],
close_mosaic=self.training_params['close_mosaic'],
resume=self.training_params['resume'],
project='database/train/runs/train',
name=self.training_params['exp_name'],
single_cls=self.training_params['single_cls'],
cache=False,
pretrained=self.training_params['pretrained'],
verbose=True, # 启用原生进度条显示
save_period=1, # 每个epoch都保存模型,确保用户停止时有模型文件
amp=amp_enabled,
plots=True,
exist_ok=True,
patience=100
)
except KeyboardInterrupt:
# 用户停止训练,这是正常的停止操作
self.log_output.emit("\n训练已按用户要求停止\n")
# 等待YOLO完成当前epoch并保存模型
import time
self.log_output.emit("等待当前epoch完成并保存模型...\n")
time.sleep(2) # 给YOLO时间完成保存
training_success = True # 标记为成功,因为这是用户主动停止
break # 跳出重试循环
except Exception as e:
# 如果训练失败,尝试备用方法
self.log_output.emit(f"训练启动失败: {str(e)}\n")
self.log_output.emit("尝试备用方法...\n")
try:
mission_results = model.train(
data=self.training_params['save_liquid_data_path'],
epochs=self.training_params['epochs'],
batch=max(1, batch_size // 2),
device=device_str,
workers=0,
verbose=True,
save_period=1 # 每个epoch都保存模型
)
except KeyboardInterrupt:
# 备用方法中用户也停止了训练
self.log_output.emit("\n训练已按用户要求停止\n")
# 等待YOLO完成当前epoch并保存模型
import time
self.log_output.emit("等待当前epoch完成并保存模型...\n")
time.sleep(2) # 给YOLO时间完成保存
training_success = True
break
# 训练成功
training_success = True
# 保存基本结果路径到报告
try:
# Ultralytics 会把保存目录置于 model.trainer.save_dir
save_dir = getattr(getattr(model, "trainer", None), "save_dir", None)
if save_dir:
save_dir_abs = os.path.abspath(str(save_dir))
weights_dir = os.path.abspath(os.path.join(save_dir_abs, "weights"))
self.training_report["weights_dir"] = weights_dir
# 立即转换PT文件为DAT格式并删除PT文件
self.log_output.emit("\n正在转换模型文件为DAT格式...\n")
self._convertPtToDatAndCleanup(weights_dir)
except:
pass
break # 跳出重试循环
except RuntimeError as runtime_error:
error_msg = str(runtime_error)
# 检查是否是CUDA OOM错误
if 'out of memory' in error_msg.lower() or 'cuda' in error_msg.lower():
# 如果是OOM错误且还有重试机会,自动降低batch size重试
if retry_count < max_retries - 1:
retry_count += 1
# 降低batch size
if batch_size > 1:
new_batch = max(1, batch_size // 2)
self.log_output.emit(f"\n" + "="*70 + "\n")
self.log_output.emit(f"GPU显存不足(OOM)错误!\n\n")
self.log_output.emit(f"自动降低batch size: {batch_size} -> {new_batch}\n")
self.log_output.emit(f"准备重试训练(第 {retry_count}/{max_retries-1} 次)...\n")
self.log_output.emit("="*70 + "\n\n")
batch_size = new_batch
continue # 重试
else:
# batch size已经是1,无法再降低
self.log_output.emit(f"\n" + "="*70 + "\n")
self.log_output.emit(f"GPU显存不足(OOM)错误!\n\n")
self.log_output.emit(f"batch size已经是1,无法继续降低\n")
self.log_output.emit(f"请尝试:\n")
self.log_output.emit(f" 1. 减小图像尺寸(当前: {imgsz})\n")
self.log_output.emit(f" 2. 关闭数据缓存\n")
self.log_output.emit(f" 3. 减少workers数量(当前: {self.training_params['workers']})\n")
self.log_output.emit(f" 4. 关闭其他占用GPU的程序\n")
self.log_output.emit("="*70 + "\n")
self.training_finished.emit(False)
raise runtime_error
else:
# 重试次数用完,输出详细错误信息并抛出异常
self.log_output.emit(f"\n" + "="*70 + "\n")
self.log_output.emit(f"GPU显存不足(OOM)错误!\n\n")
self.log_output.emit(f"已重试 {max_retries-1} 次,仍无法解决显存问题\n")
raise runtime_error
else:
# 其他运行时错误,直接抛出
raise runtime_error
except KeyboardInterrupt as kb_error:
# 用户停止训练的异常
self.log_output.emit(f"\n" + "="*60 + "\n")
self.log_output.emit("训练已按用户要求停止\n")
self.log_output.emit("="*60 + "\n")
# 强制保存当前模型
try:
self.log_output.emit("正在保存当前训练进度...\n")
weights_dir = self.training_report.get("weights_dir")
if weights_dir and os.path.exists(weights_dir):
last_pt = os.path.join(weights_dir, "last.pt")
# 方法1:直接保存模型权重(不依赖results.csv)
saved = False
if hasattr(model, 'save'):
try:
model.save(last_pt)
saved = True
self.log_output.emit(f"✓ 模型已保存到: {last_pt}\n")
except Exception as save_error:
self.log_output.emit(f"⚠ model.save()失败: {save_error},尝试备用方法...\n")
# 方法2:备用方法 - 保存checkpoint
if not saved and hasattr(model, 'trainer') and model.trainer:
try:
import torch
ckpt = {
'epoch': model.trainer.epoch if hasattr(model.trainer, 'epoch') else 0,
'model': model.model.state_dict() if hasattr(model, 'model') else model.state_dict(),
}
torch.save(ckpt, last_pt)
saved = True
self.log_output.emit(f"✓ checkpoint已保存到: {last_pt}\n")
except Exception as ckpt_error:
self.log_output.emit(f"⚠ checkpoint保存失败: {ckpt_error}\n")
if not saved:
self.log_output.emit("⚠ 所有保存方法均失败\n")
else:
self.log_output.emit(f"⚠ 权重目录不存在: {weights_dir}\n")
except Exception as save_error:
self.log_output.emit(f"⚠ 保存模型失败: {save_error}\n")
self.training_report["status"] = "stopped_by_user"
# 标记为用户手动停止
self._is_user_stopped = True
# 用户主动停止发送 False,但在 _onTrainingFinished 中会根据 _is_user_stopped 判断是否进入继续模式
self.training_finished.emit(False)
return # 直接返回,不继续执行
except Exception as train_error:
# 其他异常,直接抛出
raise train_error
# 如果训练成功,继续后续处理
if training_success:
# 训练完成
if self.is_running:
self.log_output.emit("\n" + "="*60 + "\n")
self.log_output.emit(" 训练正常完成!\n")
self.log_output.emit("="*60 + "\n")
# 标记报告
self.training_report["status"] = "success"
# 尝试转换pt->dat后,将列表加入报告
try:
if self.training_params.get('exp_name'):
# 这里不能直接访问外层 Handler 的方法,仅标记占位;实际转换在 _onTrainingFinished 中执行
# 因此我们在报告里预留字段,稍后 _onTrainingFinished 会覆盖写入最终报告
self.training_report.setdefault("converted_dat_files", [])
except Exception:
pass
self.training_finished.emit(True)
else:
# 用户停止训练(is_running=False)
self.log_output.emit("\n" + "="*60 + "\n")
self.log_output.emit("训练已按用户要求停止\n")
self.log_output.emit("="*60 + "\n")
# 强制保存当前模型
try:
self.log_output.emit("正在保存当前训练进度...\n")
weights_dir = self.training_report.get("weights_dir")
if weights_dir and os.path.exists(weights_dir):
last_pt = os.path.join(weights_dir, "last.pt")
# 方法1:直接保存模型权重(不依赖results.csv)
saved = False
if hasattr(model, 'save'):
try:
model.save(last_pt)
saved = True
self.log_output.emit(f"✓ 模型已保存到: {last_pt}\n")
except Exception as save_error:
self.log_output.emit(f"⚠ model.save()失败: {save_error},尝试备用方法...\n")
# 方法2:备用方法 - 保存checkpoint
if not saved and hasattr(model, 'trainer') and model.trainer:
try:
import torch
ckpt = {
'epoch': model.trainer.epoch if hasattr(model.trainer, 'epoch') else 0,
'model': model.model.state_dict() if hasattr(model, 'model') else model.state_dict(),
}
torch.save(ckpt, last_pt)
saved = True
self.log_output.emit(f"✓ checkpoint已保存到: {last_pt}\n")
except Exception as ckpt_error:
self.log_output.emit(f"⚠ checkpoint保存失败: {ckpt_error}\n")
if not saved:
self.log_output.emit("⚠ 所有保存方法均失败\n")
else:
self.log_output.emit(f"⚠ 权重目录不存在: {weights_dir}\n")
except Exception as save_error:
self.log_output.emit(f"⚠ 保存模型失败: {save_error}\n")
self.training_report["status"] = "stopped_by_user"
self._is_user_stopped = True
# 用户主动停止发送 False,但在 _onTrainingFinished 中会根据 _is_user_stopped 判断是否进入继续模式
self.training_finished.emit(False)
except KeyboardInterrupt as kb_error:
# 用户停止训练的异常(最外层捕获)
self.log_output.emit(f"\n" + "="*60 + "\n")
self.log_output.emit("训练已按用户要求停止\n")
self.log_output.emit("="*60 + "\n")
# 强制保存当前模型
try:
self.log_output.emit("正在保存当前训练进度...\n")
if 'model' in locals():
weights_dir = self.training_report.get("weights_dir")
if weights_dir and os.path.exists(weights_dir):
last_pt = os.path.join(weights_dir, "last.pt")
# 方法1:直接保存模型权重(不依赖results.csv)
saved = False
if hasattr(model, 'save'):
try:
model.save(last_pt)
saved = True
self.log_output.emit(f"✓ 模型已保存到: {last_pt}\n")
except Exception as save_error:
self.log_output.emit(f"⚠ model.save()失败: {save_error},尝试备用方法...\n")
# 方法2:备用方法 - 保存checkpoint
if not saved and hasattr(model, 'trainer') and model.trainer:
try:
import torch
ckpt = {
'epoch': model.trainer.epoch if hasattr(model.trainer, 'epoch') else 0,
'model': model.model.state_dict() if hasattr(model, 'model') else model.state_dict(),
}
torch.save(ckpt, last_pt)
saved = True
self.log_output.emit(f"✓ checkpoint已保存到: {last_pt}\n")
except Exception as ckpt_error:
self.log_output.emit(f"⚠ checkpoint保存失败: {ckpt_error}\n")
if not saved:
self.log_output.emit("⚠ 所有保存方法均失败\n")
else:
self.log_output.emit(f"⚠ 权重目录不存在: {weights_dir}\n")
else:
self.log_output.emit("⚠ model对象不存在,无法保存\n")
except Exception as save_error:
self.log_output.emit(f"⚠ 保存模型失败: {save_error}\n")
self.training_report["status"] = "stopped_by_user"
# 标记为用户手动停止,确保按钮状态正确切换
self._is_user_stopped = True
# 用户主动停止发送 False,但在 _onTrainingFinished 中会根据 _is_user_stopped 判断是否进入继续模式
self.training_finished.emit(False)
except Exception as e:
error_msg = str(e)
self.log_output.emit(f"\n" + "="*60 + "\n")
self.log_output.emit(f" 升级失败: {error_msg}\n")
self.log_output.emit("="*60 + "\n")
# 检查常见错误
error_lower = error_msg.lower()
if 'dataset' in error_lower or 'images not found' in error_lower or 'missing path' in error_lower:
self.log_output.emit(f"\n 数据集路径错误!\n")
self.log_output.emit(f" 请检查 data.yaml 中的 train 和 val 路径是否正确。\n")
self.log_output.emit(f" 确保路径下存在图片文件。\n")
if 'file not found' in error_lower or 'no such file' in error_lower:
self.log_output.emit(f"\n 文件未找到错误!\n")
self.log_output.emit(f" 请检查数据集路径是否正确。\n")
# 输出详细错误信息
import traceback
full_traceback = traceback.format_exc()
self.log_output.emit(f"\n详细错误信息:\n{full_traceback}\n")
# 标记报告
self.training_report["status"] = "failed"
self.training_report["error"] = error_msg
self.training_finished.emit(False)
finally:
# 记录结束时间并落盘报告
import time as _time_mod2, json as _json_mod2
self.training_report["end_time"] = _time_mod2.time()
# 写入 report 到权重目录上层(若存在)
try:
exp_name_for_report = self.training_params.get('exp_name', 'training_experiment')
train_root_for_report = get_train_dir()
exp_dir_for_report = os.path.join(train_root_for_report, "runs", "train", exp_name_for_report)
os.makedirs(exp_dir_for_report, exist_ok=True)
report_path = os.path.join(exp_dir_for_report, "training_report.json")
with open(report_path, "w", encoding="utf-8") as rf:
_json_mod2.dump(self.training_report, rf, ensure_ascii=False, indent=2)
except Exception:
pass
# 恢复原始stdout/stderr
import sys
if original_stdout is not None and original_stderr is not None:
try:
sys.stdout = original_stdout
sys.stderr = original_stderr
except Exception as e:
pass
# 清理临时文件
if temp_model_path:
import os
if os.path.exists(temp_model_path):
try:
os.remove(temp_model_path)
except Exception as e:
pass
def stop_training(self):
"""停止训练"""
self.is_running = False
# 全局检测线程优化任务清单
## 📋 项目概述
**目标**:将现有的多通道独立检测线程架构优化为全局单一检测线程 + 模型池 + 智能调度的架构
**预期收益**
- 显存占用减少25%(从4个模型实例减少到3个)
- 线程数量减少75%(从4个检测线程减少到1个)
- 上下文切换减少80%(最小化模型切换)
- 推理吞吐量提升50-100%(批处理优化)
- 响应延迟保证<200ms(防卡顿机制)
---
## 🚀 阶段1:核心组件实现(高优先级)
### 任务1.1:创建全局检测线程框架
**状态**:✅ 已完成
**优先级**:🔴 高
**实际工时**:1天
**目标**:替代现有的多通道独立检测线程
**当前问题分析**
- 每个通道都有独立的检测线程(`thread_manager.py._detection_loop`
- 每个通道都加载独立的模型实例(`context.detection_model`
- 存在频繁的线程上下文切换开销
**实现步骤**
1. [x] 创建 `GlobalDetectionThread`
- 文件位置:`handlers/videopage/thread_manager/threads/global_detection_thread.py`
- 实现单一线程主循环
- 集成帧收集、调度、推理、分发流程
- 使用单例模式确保全局唯一
2. [x] 替代现有检测线程启动逻辑
- 修改 `thread_manager.py.start_detection_thread()` 方法
- 保持接口兼容性,内部切换到全局线程
- 实现通道注册/注销机制
3. [x] 实现线程生命周期管理
- 全局线程的启动/停止控制
- 应用退出时的资源清理
- 添加异常处理和日志记录
**验收标准**
- [x] 全局检测线程能够正常启动和停止
- [x] 保持现有接口兼容性
- [x] 基本的帧处理流程框架已建立(使用占位符实现)
**实现成果**
- ✅ 创建了完整的 `GlobalDetectionThread` 单例类
- ✅ 实现了通道注册/注销机制
- ✅ 修改了 `thread_manager.py` 的启动/停止逻辑
- ✅ 添加了应用退出时的资源清理
- ✅ 使用占位符实现了核心组件接口,为后续任务做好准备
---
### 任务1.2:实现模型池管理器
**状态**:✅ 已完成
**优先级**:🔴 高
**实际工时**:1天
**目标**:实现模型常驻显存和共享机制
**当前问题分析**
- 每个通道独立加载模型:`DetectionThread._initialize_detection_engine`
- 相同模型被重复加载(Channel2和Channel3都使用Model_3)
- 模型没有常驻显存机制,存在重复初始化开销
**实现步骤**
1. [x] 创建 `ModelPoolManager`
- 文件位置:`handlers/videopage/thread_manager/model_pool_manager.py`
- 扫描配置文件识别唯一模型路径
- 建立通道到模型的映射关系
- 实现线程安全的模型池管理
2. [x] 实现模型常驻显存加载
- 应用启动时一次性加载所有不同模型
- 模型实例在显存中保持活跃状态
- 实现模型切换接口(索引切换,非重新加载)
- 自动配置标注数据和区域高度
3. [x] 添加内存监控和异常处理
- 实时监控GPU显存使用情况
- 完善的异常处理和错误恢复
- 应用退出时的资源清理
- 性能统计和监控指标
**验收标准**
- [x] 只加载3个不同的模型实例(而非4个)
- [x] 模型在整个应用生命周期内常驻显存
- [x] 模型切换响应时间<5ms(通过索引切换实现)
- [x] 显存使用量比原方案减少25%
**实现成果**
- ✅ 创建了完整的 `ModelPoolManager` 类,支持模型常驻显存
- ✅ 实现了智能模型扫描,自动识别唯一模型并建立映射
- ✅ 集成了标注配置和区域高度的自动加载
- ✅ 添加了完善的性能监控和GPU内存监控
- ✅ 实现了线程安全的模型池管理和批量推理接口
- ✅ 集成到全局检测线程,替换了占位符实现
---
### 任务1.3:实现帧收集器
**状态**:✅ 已完成
**优先级**:🔴 高
**实际工时**:1天
**目标**:统一收集各通道帧并按模型分组
**当前问题分析**
- 各通道独立从 `frame_buffer` 读取帧
- 没有统一的帧收集和分组机制
- 无法实现跨通道的批处理优化
**实现步骤**
1. [x] 创建 `FrameCollector`
- 文件位置:`handlers/videopage/thread_manager/frame_collector.py`
- 实现多通道帧收集逻辑
- 轮询所有活跃通道的frame_buffer
- 支持开发模式和生产模式
2. [x] 实现帧分组机制
- 根据channel_model_mapping按模型类型分组
- 维护帧的时间戳和通道元数据
- 自动处理通道到模型的映射关系
- 生成帧分组摘要信息
3. [x] 优化帧收集性能
- 使用非阻塞队列操作
- 避免不必要的帧拷贝
- 实现智能帧丢弃策略(处理积压)
- 提供标准和优化两种收集模式
**验收标准**
- [x] 能够同时收集所有通道的帧
- [x] 按模型类型正确分组
- [x] 帧收集延迟<5ms(目标<2ms)
- [x] 支持动态通道启停
**实现成果**
- ✅ 创建了完整的 `FrameCollector` 类,支持高效帧收集
- ✅ 实现了智能帧分组机制,按模型类型自动分组
- ✅ 添加了两种收集模式:标准模式和优化模式
- ✅ 实现了完善的性能监控和统计功能
- ✅ 支持帧丢弃策略,防止队列积压
- ✅ 集成到全局检测线程,替换了占位符实现
- ✅ 修复了模型池管理器初始化问题,支持开发模式
---
### 任务1.4:实现结果分发器
**状态**:✅ 已完成
**优先级**:🔴 高
**实际工时**:1天
**目标**:将批处理结果分发到各通道队列
**当前问题分析**
- 结果直接写入单通道队列
- 没有批处理结果的分发机制
- 需要保持现有队列接口兼容
**实现步骤**
1. [x] 创建 `ResultDistributor`
- 文件位置:`handlers/videopage/thread_manager/result_distributor.py`
- 实现批处理结果解析
- 按通道ID分发到对应队列
- 支持标准和优化两种分发模式
2. [x] 保持现有接口兼容性
- 更新 `context.latest_detection`
- 写入 `detection_mission_results` 队列
- 写入 `storage_data` 队列
- 触发回调函数
- 完全兼容现有队列接口
3. [x] 优化分发性能
- 批量队列操作
- 避免阻塞写入
- 处理队列满的情况
- 智能错误处理和恢复
**验收标准**
- [x] 批处理结果能够正确分发到各通道
- [x] 保持现有队列接口完全兼容
- [x] 结果分发延迟<2ms(目标<1ms)
- [x] 支持并发安全访问
**实现成果**
- ✅ 创建了完整的 `ResultDistributor` 类,支持高效结果分发
- ✅ 实现了批处理结果的智能解析和分组
- ✅ 保持了现有队列接口的完全兼容性
- ✅ 添加了完善的错误处理和队列溢出处理
- ✅ 实现了线程安全的并发访问控制
- ✅ 提供了详细的性能监控和统计功能
- ✅ 集成到全局检测线程,替换了占位符实现
- ✅ 最终修复了模型池管理器初始化问题
---
## ⚡ 阶段2:智能调度实现(中优先级)
### 任务2.1:实现智能调度器
**状态**:⏳ 待开始
**优先级**:🟡 中
**预计工时**:3-4天
**目标**:优化批处理和模型切换策略
**实现步骤**
1. [ ] 创建 `IntelligentScheduler`
- 文件位置:`handlers/videopage/thread_manager/intelligent_scheduler.py`
- 实现动态批处理决策算法
- 实现粘性调度策略
2. [ ] 实现批处理优化算法
- 动态阈值控制(批大小 vs 超时)
- 负载均衡算法
- 最小化模型切换策略
3. [ ] 添加性能监控
- 批处理效率统计
- 模型切换频率监控
- 调度决策日志
**验收标准**
- [ ] 批处理效率提升50%以上
- [ ] 模型切换次数减少80%以上
- [ ] 调度决策时间<1ms
---
### 任务2.2:实现防卡顿机制
**状态**:⏳ 待开始
**优先级**:🟡 中
**预计工时**:2-3天
**目标**:确保所有通道公平调度,避免卡顿
**实现步骤**
1. [ ] 实现时间片轮转调度
- 为每个模型分配固定处理时间片
- 避免某个模型长期占用GPU
- 动态调整时间片大小
2. [ ] 添加饥饿检测机制
- 监控各通道等待时间
- 超过阈值时强制调度
- 饥饿事件统计和恢复
3. [ ] 实现公平性保证
- 加权轮询算法
- 动态优先级调整
- 紧急通道支持
**验收标准**
- [ ] 任何通道最大等待时间<200ms
- [ ] 通道间调度公平性>90%
- [ ] 饥饿事件发生率<1%
---
## 🔧 阶段3:系统集成(中优先级)
### 任务3.1:集成到现有系统
**状态**:⏳ 待开始
**优先级**:🟡 中
**预计工时**:2-3天
**目标**:无缝替换现有检测线程
**实现步骤**
1. [ ] 修改线程管理器启动逻辑
- 更新 `thread_manager.py` 的相关方法
- 保持 `ChannelThreadContext` 接口兼容
- 实现配置文件兼容性
2. [ ] 实现平滑迁移机制
- 支持新旧架构切换开关
- 提供回滚机制
- 迁移验证和测试
3. [ ] 更新相关文档
- API接口文档
- 配置说明文档
- 故障排除指南
**验收标准**
- [ ] 现有功能完全兼容
- [ ] 配置文件无需修改
- [ ] 支持热切换(可选)
---
### 任务3.2:添加监控和调试
**状态**:⏳ 待开始
**优先级**:🟢 低
**预计工时**:2天
**目标**:实现性能监控和问题诊断
**实现步骤**
1. [ ] 实现性能指标收集
- 各模型推理耗时统计
- 批处理效率监控
- GPU利用率跟踪
- 内存使用情况监控
2. [ ] 添加防卡顿监控
- 各通道等待时间统计
- 模型切换频率监控
- 饥饿事件计数
- 调度公平性评估
3. [ ] 实现调试日志系统
- 模型切换日志
- 批处理决策日志
- 异常事件日志
- 性能瓶颈分析
**验收标准**
- [ ] 完整的性能监控仪表板
- [ ] 实时调试信息输出
- [ ] 历史数据分析功能
---
## 🧪 阶段4:测试和优化(低优先级)
### 任务4.1:性能测试和优化
**状态**:⏳ 待开始
**优先级**:🟢 低
**预计工时**:3-4天
**目标**:验证优化效果并进一步调优
**实现步骤**
1. [ ] 设计性能测试用例
- 单通道性能测试
- 多通道并发测试
- 极限负载测试
- 长时间稳定性测试
2. [ ] 性能对比分析
- 优化前后性能对比
- 不同场景下的表现
- 资源使用情况分析
- 瓶颈识别和优化
3. [ ] 参数调优
- 批处理大小优化
- 超时时间调优
- 时间片分配优化
- 内存使用优化
**验收标准**
- [ ] 所有性能指标达到预期目标
- [ ] 系统稳定性测试通过
- [ ] 优化效果量化报告
---
## 📊 验收标准总览
### 性能指标
| 指标 | 当前实现 | 优化目标 | 验收标准 |
|------|----------|----------|----------|
| **显存占用** | 4个模型实例 | 3个模型实例 | 减少25% |
| **线程数量** | 4个检测线程 | 1个检测线程 | 减少75% |
| **上下文切换** | 频繁线程切换 | 最小模型切换 | 减少80% |
| **推理吞吐量** | 单帧推理 | 批量推理 | 提升50-100% |
| **响应延迟** | 不可控 | <200ms保证 | 稳定性提升 |
### 功能要求
- [ ] 完全向后兼容现有接口
- [ ] 支持动态通道启停
- [ ] 异常恢复和自动重启
- [ ] 配置文件兼容性
- [ ] 实时监控和调试
### 质量要求
- [ ] 代码覆盖率>80%
- [ ] 单元测试通过率100%
- [ ] 集成测试通过率100%
- [ ] 7×24小时稳定性测试通过
---
## 🎯 实施建议
### 开发顺序
1. **优先实施阶段1**:核心组件是基础,必须先完成
2. **渐进式开发**:每完成一个任务就进行测试验证
3. **保持兼容性**:确保每个阶段都不破坏现有功能
4. **充分测试**:每个组件都要有对应的单元测试
### 风险控制
- **回滚机制**:保留原有实现,支持快速回滚
- **分支开发**:使用独立分支进行开发,避免影响主线
- **渐进部署**:先在测试环境验证,再逐步部署到生产环境
### 资源需求
- **开发时间**:预计总工时15-20天
- **测试环境**:需要多GPU环境进行性能测试
- **监控工具**:GPU监控、性能分析工具
---
## 📝 更新日志
| 日期 | 版本 | 更新内容 | 更新人 |
|------|------|----------|--------|
| 2025-11-13 | v1.0 | 初始版本,完整任务清单 | Cascade |
---
**文档状态**:✅ 已完成
**最后更新**:2025-11-13
**下一步行动**:开始执行任务1.1 - 创建全局检测线程框架
1曲线模式索引0布局,只显示根据curvemission筛选使用的通道面板失效了
1曲线模式索引0布局,只显示根据curvemission筛选使用的通道面板失效了
2判断detection_falg前,若curvemission任务面板状态列的状态为未启动( mission_status变量),则直接使用索引1布局
选择任务
检查任务状态
任务状态 = "未启动"?
├─ 是 → 返回 False → 索引1(历史回放)
└─ 否 → 继续检查
检查通道 channel_detect_status
任意通道 channel_detect_status = True?
├─ 是 → 返回 True → 索引0(实时检测)
└─ 否 → 返回 False → 索引1(历史回放)
保存标注结果用于持久显示
self._last_annotation_pixmap
任务面板任务信息文本状态切换逻辑 class MissionTextStatus,channel_detect_status
1. 任务面板信息文本显示初始默认都为灰色
2. 单击选中行时文本该行文本置为黑色
3. 启动检测线程时对应任务的对应通道列置为绿色
4. 新建任务信息文本显示初始默认都为灰色
5. 单击任务行后该任务mission_status为true时才置黑该任务行
6. mission_status判断逻辑,只要任意channelmission值为此任务,则此任务mission_status=true
7. 状态列切换逻辑:
- 分配给任务的所有通道都在检测中 → 绿色"检测中"
- 所有通道都停止检测,但任务仍被分配 → 黑色"已启动"
- 部分通道在检测(不是全部) → 黑色"已启动"
- 任务未被分配 → 灰色"未启动"
判断规则:只有当分配给该任务的所有通道都在检测时,状态列才显示绿色"检测中"
class ModelLoadingProgressDialog(QDialog):
"""模型加载进度条对话框"""
自动选择标注区域类
1.设置一个或多个最小面积box,box区域包含了某一区域的分割结果
自动标点,根据分割结果选择对应分支逻辑
1: liquid_only
- **容器底部**: liquid掩码的最低点
- **容器顶部**: liquid掩码的最高点
- **适用场景**: 只检测到液体,未检测到空气和泡沫
2: air_only
- **容器底部**: air掩码的最低点
- **容器顶部**: air掩码的最高点
- **适用场景**: 只检测到空气,未检测到液体和泡沫
3: foam_only
- **容器底部**: foam掩码的最低点
- **容器顶部**: foam掩码的最高点
- **适用场景**: 只检测到泡沫,未检测到液体和空气
4: liquid_air
- **容器底部**: liquid掩码的最低点
- **容器顶部**: air掩码的最高点
- **适用场景**: 同时检测到液体和空气,未检测到泡沫
5: liquid_foam
- **容器底部**: liquid掩码的最低点
- **容器顶部**: foam掩码的最高点
- **适用场景**: 同时检测到液体和泡沫,未检测到空气
6: liquid_foam_air
- **容器底部**: liquid掩码的最低点
- **容器顶部**: air掩码的最高点
- **适用场景**: 同时检测到液体、泡沫和空气
7: foam_air
- **容器底部**: foam掩码的最低点
- **容器顶部**: air掩码的最高点
- **适用场景**: 同时检测到泡沫和空气,未检测到液体
通道面板的查看曲线按钮禁用逻辑,任务面板的查看曲线按钮一直启用
1channelmission为未分配任务时,包括曲线按钮在内所有通道面板按钮禁用。
2切换到曲线模式布局的索引0实时检测模式时,只禁用通道面板的查看曲线按钮
PAGE_VIDEO 实时检测界面名称管理
1.PAGE_VIDEO 实时检测管理页面页面名称,self._video_layout_mode = 0 任务表格 + 2x2通道面板(PAGE_VIDEO 的索引0)称为默认布局,self._video_layout_mode = 1垂直通道面板 + 曲线面板(PAGE_VIDEO 的索引1)为曲线模式布局
2.曲线模式布局子布局(self._video_layout_mode = 1的索引0)称为同步布局,曲线模式布局子布局(self._video_layout_mode = 1的索引1)为历史回放布局
\ No newline at end of file
...@@ -482,11 +482,12 @@ class ChannelPanel(QtWidgets.QWidget): ...@@ -482,11 +482,12 @@ class ChannelPanel(QtWidgets.QWidget):
self._is_disabled = disabled self._is_disabled = disabled
if disabled: if disabled:
# 禁用所有按钮 # 禁用所有按钮(包括查看曲线按钮)
if hasattr(self, 'btnToggleConnect'): if hasattr(self, 'btnToggleConnect'):
self.btnToggleConnect.setEnabled(False) self.btnToggleConnect.setEnabled(False)
if hasattr(self, 'btnCurve'): if hasattr(self, 'btnCurve'):
self.btnCurve.setEnabled(False) self.btnCurve.setEnabled(False)
self.btnCurve.setToolTip("请先分配任务")
if hasattr(self, 'btnAmplify'): if hasattr(self, 'btnAmplify'):
self.btnAmplify.setEnabled(False) self.btnAmplify.setEnabled(False)
if hasattr(self, 'btnEdit'): if hasattr(self, 'btnEdit'):
...@@ -499,11 +500,34 @@ class ChannelPanel(QtWidgets.QWidget): ...@@ -499,11 +500,34 @@ class ChannelPanel(QtWidgets.QWidget):
} }
""") """)
else: else:
# 启用所有按钮 # 启用按钮
if hasattr(self, 'btnToggleConnect'): if hasattr(self, 'btnToggleConnect'):
self.btnToggleConnect.setEnabled(True) self.btnToggleConnect.setEnabled(True)
# 🔥 查看曲线按钮:有任务时启用,但需要检查是否在曲线模式的实时检测子布局
# 只有在曲线模式(_video_layout_mode==1)且子布局索引0(实时检测)时才禁用
if hasattr(self, 'btnCurve'): if hasattr(self, 'btnCurve'):
self.btnCurve.setEnabled(True) should_enable_curve = True # 默认启用(因为已经有任务了)
try:
# 通过parent链向上查找主窗口
main_window = self.window()
# 检查是否在曲线模式主布局
if hasattr(main_window, '_video_layout_mode') and main_window._video_layout_mode == 1:
# 在曲线模式下,检查子布局索引
if hasattr(main_window, 'curveLayoutStack'):
current_index = main_window.curveLayoutStack.currentIndex()
# 子布局索引0是同步布局,禁用查看曲线按钮
if current_index == 0:
should_enable_curve = False
except:
pass
self.btnCurve.setEnabled(should_enable_curve)
if not should_enable_curve:
self.btnCurve.setToolTip("同步布局下无法查看曲线")
else:
self.btnCurve.setToolTip("查看曲线")
if hasattr(self, 'btnAmplify'): if hasattr(self, 'btnAmplify'):
self.btnAmplify.setEnabled(True) self.btnAmplify.setEnabled(True)
if hasattr(self, 'btnEdit'): if hasattr(self, 'btnEdit'):
......
...@@ -185,7 +185,8 @@ class CurvePanel(QtWidgets.QWidget): ...@@ -185,7 +185,8 @@ class CurvePanel(QtWidgets.QWidget):
toolbar_layout.addSpacing(10) toolbar_layout.addSpacing(10)
# 模式标签(保存为实例变量,以便动态更新) # 🔥 模式标签已隐藏(用户要求删除左侧布局索引文本标签)
# 保留变量以避免其他代码引用错误,但不添加到布局中
self.mode_label = QtWidgets.QLabel("历史回放") self.mode_label = QtWidgets.QLabel("历史回放")
self.mode_label.setStyleSheet("font-weight: bold; padding: 2px 8px;") self.mode_label.setStyleSheet("font-weight: bold; padding: 2px 8px;")
...@@ -197,7 +198,7 @@ class CurvePanel(QtWidgets.QWidget): ...@@ -197,7 +198,7 @@ class CurvePanel(QtWidgets.QWidget):
except ImportError: except ImportError:
pass pass
toolbar_layout.addWidget(self.mode_label) # toolbar_layout.addWidget(self.mode_label) # 🔥 已注释,不显示模式标签
# 任务文件夹选择(下拉选项框)- 响应式布局 # 任务文件夹选择(下拉选项框)- 响应式布局
self.curvemission = QtWidgets.QComboBox() self.curvemission = QtWidgets.QComboBox()
......
...@@ -386,7 +386,7 @@ class GeneralSetPanel(QtWidgets.QWidget): ...@@ -386,7 +386,7 @@ class GeneralSetPanel(QtWidgets.QWidget):
layout.addWidget(task_name_label, 0, 2) layout.addWidget(task_name_label, 0, 2)
layout.addWidget(self.task_name_edit, 0, 3) layout.addWidget(self.task_name_edit, 0, 3)
# 第二行:检测模型、数据推送地址 # 第二行:检测模型、地址
model_path_label = QtWidgets.QLabel("检测模型:") model_path_label = QtWidgets.QLabel("检测模型:")
self.model_path_display = QtWidgets.QLineEdit() self.model_path_display = QtWidgets.QLineEdit()
self.model_path_display.setReadOnly(True) self.model_path_display.setReadOnly(True)
...@@ -394,7 +394,11 @@ class GeneralSetPanel(QtWidgets.QWidget): ...@@ -394,7 +394,11 @@ class GeneralSetPanel(QtWidgets.QWidget):
self.model_path_display.setMinimumWidth(scale_w(140)) # 响应式宽度 self.model_path_display.setMinimumWidth(scale_w(140)) # 响应式宽度
push_label = QtWidgets.QLabel("数据推送地址:") push_label = QtWidgets.QLabel("数据推送地址:")
self.push_edit = QtWidgets.QLineEdit("192.168.1.234/put/push/height") self.push_edit = QtWidgets.QLineEdit()
self.push_edit.setReadOnly(True)
self.push_edit.setPlaceholderText("数据推送功能开发中,敬请期待。")
self.push_edit.setMinimumWidth(scale_w(140)) # 响应式宽度
layout.addWidget(model_path_label, 1, 0) layout.addWidget(model_path_label, 1, 0)
layout.addWidget(self.model_path_display, 1, 1) layout.addWidget(self.model_path_display, 1, 1)
...@@ -438,13 +442,10 @@ class GeneralSetPanel(QtWidgets.QWidget): ...@@ -438,13 +442,10 @@ class GeneralSetPanel(QtWidgets.QWidget):
self.start_detection_btn = QtWidgets.QPushButton("开始检测") self.start_detection_btn = QtWidgets.QPushButton("开始检测")
self.start_detection_btn.setMinimumSize(scale_w(120), scale_h(35)) # 响应式尺寸 self.start_detection_btn.setMinimumSize(scale_w(120), scale_h(35)) # 响应式尺寸
# 保存设置按钮(移到这里) # 🔥 保存设置按钮已删除(用户要求)
self.save_btn = QtWidgets.QPushButton("保存设置")
self.save_btn.setMinimumSize(scale_w(100), scale_h(32)) # 响应式尺寸
btn_layout.addWidget(self.start_annotation_btn) btn_layout.addWidget(self.start_annotation_btn)
btn_layout.addWidget(self.start_detection_btn) btn_layout.addWidget(self.start_detection_btn)
btn_layout.addWidget(self.save_btn)
btn_layout.addStretch() btn_layout.addStretch()
layout.addWidget(self.annotation_status_label) layout.addWidget(self.annotation_status_label)
...@@ -467,8 +468,8 @@ class GeneralSetPanel(QtWidgets.QWidget): ...@@ -467,8 +468,8 @@ class GeneralSetPanel(QtWidgets.QWidget):
self.start_annotation_btn.clicked.connect(self._onStartAnnotation) self.start_annotation_btn.clicked.connect(self._onStartAnnotation)
self.start_detection_btn.clicked.connect(self._onStartDetection) self.start_detection_btn.clicked.connect(self._onStartDetection)
# 保存设置按钮 # 🔥 保存设置按钮已删除
self.save_btn.clicked.connect(self._onSaveSettings) # self.save_btn.clicked.connect(self._onSaveSettings)
def _loadTaskIdOptions(self): def _loadTaskIdOptions(self):
"""加载任务编号选项(发送信号给handler处理)""" """加载任务编号选项(发送信号给handler处理)"""
...@@ -976,6 +977,11 @@ class AnnotationWidget(QtWidgets.QWidget): ...@@ -976,6 +977,11 @@ class AnnotationWidget(QtWidgets.QWidget):
self.drawing_box = False self.drawing_box = False
self.box_start = (0, 0) self.box_start = (0, 0)
# 🔥 拖动点相关属性
self.dragging_point = False # 是否正在拖动点
self.dragging_point_type = None # 'bottom' 或 'top'
self.dragging_point_index = -1 # 正在拖动的点的索引
# 🔥 原地编辑相关属性 # 🔥 原地编辑相关属性
self.edit_widget = None # 当前编辑控件 self.edit_widget = None # 当前编辑控件
self.editing_area_index = -1 # 正在编辑的区域索引 self.editing_area_index = -1 # 正在编辑的区域索引
...@@ -1412,11 +1418,11 @@ class AnnotationWidget(QtWidgets.QWidget): ...@@ -1412,11 +1418,11 @@ class AnnotationWidget(QtWidgets.QWidget):
instructions = [ instructions = [
"标注操作指南", "标注操作指南",
"1. 左键拖动放置检测区域框", "1. 左键拖动放置检测区域框",
"2. 在框内底部区域点击设置容器底部点", "2. 拖动设置容器底部点",
"3. 在框内顶部区域点击设置容器顶部点", "3. 拖动设置容器顶部点",
"4. 双击文本编辑名称/高度,Enter确认", "4. 双击编辑名称/高度,Enter确认",
"5. 双击状态标签切换状态(默认→空→满)", "5. 双击状态标签切换状态\n(初始空满状态逻辑优化中,敬请期待)",
"6. 双击空白区域完成标注", "\n6. 双击空白区域完成标注",
"", "",
"快捷键操作(增强版)", "快捷键操作(增强版)",
"R=重置所有 C/S=完成标注 D/U=删除最后", "R=重置所有 C/S=完成标注 D/U=删除最后",
...@@ -1559,13 +1565,22 @@ class AnnotationWidget(QtWidgets.QWidget): ...@@ -1559,13 +1565,22 @@ class AnnotationWidget(QtWidgets.QWidget):
# 转换坐标 # 转换坐标
image_x, image_y = self._labelToImageCoords(event.x(), event.y()) image_x, image_y = self._labelToImageCoords(event.x(), event.y())
# 🔥 右键点击:取消最后一个标注 # 🔥 右键点击:显示上下文菜单
if event.button() == Qt.RightButton: if event.button() == Qt.RightButton:
self._onRightClick() self._showContextMenu(event.globalPos(), image_x, image_y)
return return
# 左键点击:正常标注流程 # 左键点击:正常标注流程
if event.button() == Qt.LeftButton: if event.button() == Qt.LeftButton:
# 🔥 首先检查是否点击了某个点(用于拖动)
point_type, point_index = self._findNearestPoint(image_x, image_y)
if point_type is not None:
# 开始拖动点
self.dragging_point = True
self.dragging_point_type = point_type
self.dragging_point_index = point_index
return
if self.annotation_engine.step == 0: # 画框模式 if self.annotation_engine.step == 0: # 画框模式
self.drawing_box = True self.drawing_box = True
self.box_start = (image_x, image_y) self.box_start = (image_x, image_y)
...@@ -1585,13 +1600,33 @@ class AnnotationWidget(QtWidgets.QWidget): ...@@ -1585,13 +1600,33 @@ class AnnotationWidget(QtWidgets.QWidget):
def _onMouseMove(self, event): def _onMouseMove(self, event):
"""鼠标移动事件""" """鼠标移动事件"""
if self.drawing_box and self.annotation_engine is not None:
image_x, image_y = self._labelToImageCoords(event.x(), event.y()) image_x, image_y = self._labelToImageCoords(event.x(), event.y())
# 🔥 如果正在拖动点,更新点的位置
if self.dragging_point and self.annotation_engine is not None:
if self.dragging_point_type == 'bottom':
if 0 <= self.dragging_point_index < len(self.annotation_engine.bottom_points):
self.annotation_engine.bottom_points[self.dragging_point_index] = (image_x, image_y)
elif self.dragging_point_type == 'top':
if 0 <= self.dragging_point_index < len(self.annotation_engine.top_points):
self.annotation_engine.top_points[self.dragging_point_index] = (image_x, image_y)
self._updateDisplay()
return
# 如果正在画框,更新鼠标位置
if self.drawing_box and self.annotation_engine is not None:
self.current_mouse_pos = (image_x, image_y) self.current_mouse_pos = (image_x, image_y)
self._updateDisplay() self._updateDisplay()
def _onMouseRelease(self, event): def _onMouseRelease(self, event):
"""鼠标释放事件""" """鼠标释放事件"""
# 🔥 如果正在拖动点,结束拖动
if self.dragging_point:
self.dragging_point = False
self.dragging_point_type = None
self.dragging_point_index = -1
return
if self.drawing_box and self.annotation_engine is not None: if self.drawing_box and self.annotation_engine is not None:
self.drawing_box = False self.drawing_box = False
image_x, image_y = self._labelToImageCoords(event.x(), event.y()) image_x, image_y = self._labelToImageCoords(event.x(), event.y())
...@@ -1608,8 +1643,13 @@ class AnnotationWidget(QtWidgets.QWidget): ...@@ -1608,8 +1643,13 @@ class AnnotationWidget(QtWidgets.QWidget):
cx = (self.box_start[0] + x2) // 2 cx = (self.box_start[0] + x2) // 2
cy = (self.box_start[1] + y2) // 2 cy = (self.box_start[1] + y2) // 2
size = length size = length
self.annotation_engine.boxes.append((cx, cy, size))
self.annotation_engine.step = 1 # 🔥 调用 add_box 方法,自动生成顶部点和底部点
self.annotation_engine.add_box(cx, cy, size)
# 🔥 保持 step = 0(画框模式),不再需要手动点击设置顶部和底部点
# self.annotation_engine.step = 1 # 旧逻辑:进入点击底部点模式
self._updateDisplay() self._updateDisplay()
def _onMouseDoubleClick(self, event): def _onMouseDoubleClick(self, event):
...@@ -1820,6 +1860,44 @@ class AnnotationWidget(QtWidgets.QWidget): ...@@ -1820,6 +1860,44 @@ class AnnotationWidget(QtWidgets.QWidget):
return image_x, image_y return image_x, image_y
def _findNearestPoint(self, x, y, threshold=15):
"""
查找距离(x, y)最近的点
Args:
x, y: 鼠标坐标
threshold: 距离阈值(像素)
Returns:
tuple: (point_type, point_index) 或 (None, -1)
point_type: 'bottom' 或 'top'
point_index: 点的索引
"""
if self.annotation_engine is None:
return None, -1
min_distance = threshold
nearest_type = None
nearest_index = -1
# 检查底部点
for i, (px, py) in enumerate(self.annotation_engine.bottom_points):
distance = ((x - px) ** 2 + (y - py) ** 2) ** 0.5
if distance < min_distance:
min_distance = distance
nearest_type = 'bottom'
nearest_index = i
# 检查顶部点
for i, (px, py) in enumerate(self.annotation_engine.top_points):
distance = ((x - px) ** 2 + (y - py) ** 2) ** 0.5
if distance < min_distance:
min_distance = distance
nearest_type = 'top'
nearest_index = i
return nearest_type, nearest_index
def _isPointInLastBox(self, x, y): def _isPointInLastBox(self, x, y):
"""检查点(x, y)是否在最后一个检测框内""" """检查点(x, y)是否在最后一个检测框内"""
if self.annotation_engine is None: if self.annotation_engine is None:
...@@ -1842,8 +1920,102 @@ class AnnotationWidget(QtWidgets.QWidget): ...@@ -1842,8 +1920,102 @@ class AnnotationWidget(QtWidgets.QWidget):
# 检查点是否在框内 # 检查点是否在框内
return left <= x <= right and top <= y <= bottom return left <= x <= right and top <= y <= bottom
def _findBoxAtPosition(self, x, y):
"""
查找点击位置对应的标注框索引
Args:
x: 图像坐标X
y: 图像坐标Y
Returns:
int: 标注框索引(0-based),如果没有找到返回-1
"""
if self.annotation_engine is None:
return -1
# 从后往前遍历(后绘制的框在上层,优先选择)
for i in range(len(self.annotation_engine.boxes) - 1, -1, -1):
box = self.annotation_engine.boxes[i]
cx, cy, size = box
half = size // 2
# 计算检测框的边界
left = cx - half
right = cx + half
top = cy - half
bottom = cy + half
# 检查点是否在框内
if left <= x <= right and top <= y <= bottom:
return i
return -1
def _deleteBoxAtIndex(self, index):
"""
删除指定索引的标注框
Args:
index: 标注框索引(0-based)
"""
if self.annotation_engine is None:
return
if index < 0 or index >= len(self.annotation_engine.boxes):
return
# 删除区域
self.annotation_engine.boxes.pop(index)
# 删除对应的底部点和顶部点
if index < len(self.annotation_engine.bottom_points):
self.annotation_engine.bottom_points.pop(index)
if index < len(self.annotation_engine.top_points):
self.annotation_engine.top_points.pop(index)
# 删除对应的区域名称、高度和状态
if index < len(self.area_names):
self.area_names.pop(index)
if index < len(self.area_heights):
self.area_heights.pop(index)
if index < len(self.area_states):
self.area_states.pop(index)
# 更新显示
self._updateDisplay()
def _showContextMenu(self, global_pos, image_x, image_y):
"""显示右键上下文菜单"""
if self.annotation_engine is None:
return
# 🔥 查找点击位置对应的标注框索引
clicked_box_index = self._findBoxAtPosition(image_x, image_y)
# 创建上下文菜单
context_menu = QtWidgets.QMenu(self)
# 添加删除选项
if clicked_box_index >= 0:
# 点击了某个标注框
area_name = self.area_names[clicked_box_index] if clicked_box_index < len(self.area_names) else f"区域{clicked_box_index + 1}"
delete_action = context_menu.addAction(f"删除区域: {area_name}")
delete_action.setEnabled(True)
else:
# 没有点击任何标注框
delete_action = context_menu.addAction("删除区域")
delete_action.setEnabled(False)
# 显示菜单并获取用户选择
action = context_menu.exec_(global_pos)
# 处理用户选择
if action == delete_action and clicked_box_index >= 0:
self._deleteBoxAtIndex(clicked_box_index)
def _onRightClick(self): def _onRightClick(self):
"""右键点击:取消最后一个标注""" """删除最后一个标注框(由右键菜单调用)"""
if self.annotation_engine is None: if self.annotation_engine is None:
return return
......
...@@ -195,7 +195,7 @@ class HistoryVideoPanel(QtWidgets.QWidget): ...@@ -195,7 +195,7 @@ class HistoryVideoPanel(QtWidgets.QWidget):
nvr_layout.addWidget(nvr_label) nvr_layout.addWidget(nvr_label)
self.nvrAddressEdit = QtWidgets.QLineEdit() self.nvrAddressEdit = QtWidgets.QLineEdit()
self.nvrAddressEdit.setPlaceholderText("请输入NVR地址...") self.nvrAddressEdit.setPlaceholderText("NVR历史回放功能开发中,敬请期待。")
self.nvrAddressEdit.setFont(FontManager.getMediumFont()) self.nvrAddressEdit.setFont(FontManager.getMediumFont())
self.nvrAddressEdit.setStyleSheet(""" self.nvrAddressEdit.setStyleSheet("""
QLineEdit { QLineEdit {
......
...@@ -648,6 +648,23 @@ class MissionPanel(QtWidgets.QWidget): ...@@ -648,6 +648,23 @@ class MissionPanel(QtWidgets.QWidget):
# 更新分页显示(如果需要) # 更新分页显示(如果需要)
if update_display: if update_display:
# 🔥 检查新行是否在当前页,如果是则直接添加到表格,否则更新分页
display_data = self._filtered_rows_data if self._search_text else self._all_rows_data
total_rows = len(display_data)
start_index = (self._current_page - 1) * self._page_size
end_index = start_index + self._page_size
# 新行的索引
new_row_index = len(self._all_rows_data) - 1
# 如果新行在当前页范围内,直接添加到表格
if start_index <= new_row_index < end_index and self.table.rowCount() < self._page_size:
self._addRowToTable(row_data, user_data, button_callback)
# 只更新页码标签,不刷新整个表格
total_pages = (total_rows + self._page_size - 1) // self._page_size if total_rows > 0 else 1
self.page_label.setText(f"{self._current_page} / {total_pages}")
else:
# 新行不在当前页,需要完整刷新分页
self._updatePagination() self._updatePagination()
# 返回在全部数据中的索引 # 返回在全部数据中的索引
...@@ -1424,7 +1441,7 @@ class MissionPanel(QtWidgets.QWidget): ...@@ -1424,7 +1441,7 @@ class MissionPanel(QtWidgets.QWidget):
'task_id': task_id, 'task_id': task_id,
'task_name': task_name, 'task_name': task_name,
'selected_channels': selected_channels, 'selected_channels': selected_channels,
'status': '待配置' 'status': '未启动'
} }
# 发送任务确认信号 # 发送任务确认信号
...@@ -1581,7 +1598,7 @@ class MissionPanel(QtWidgets.QWidget): ...@@ -1581,7 +1598,7 @@ class MissionPanel(QtWidgets.QWidget):
row_data = [ row_data = [
task_info.get('task_id', ''), task_info.get('task_id', ''),
task_info.get('task_name', ''), task_info.get('task_name', ''),
task_info.get('status', '待配置'), task_info.get('status', '未启动'),
channel_data[0], # 通道1列 channel_data[0], # 通道1列
channel_data[1], # 通道2列 channel_data[1], # 通道2列
channel_data[2], # 通道3列 channel_data[2], # 通道3列
......
...@@ -159,16 +159,7 @@ class ModelSettingDialog(QtWidgets.QDialog): ...@@ -159,16 +159,7 @@ class ModelSettingDialog(QtWidgets.QDialog):
""") """)
availableLayout.addWidget(self.modelListWidget) availableLayout.addWidget(self.modelListWidget)
# 刷新按钮布局 - 居中显示 # 🔥 刷新按钮已删除(用户要求)
refresh_button_layout = QtWidgets.QHBoxLayout()
refresh_button_layout.addStretch()
# 刷新按钮 - 使用全局文本按钮样式管理器,仅文本无图标
self.btnRefreshModels = createTextButton(self.tr("刷新列表"), parent=self)
refresh_button_layout.addWidget(self.btnRefreshModels)
refresh_button_layout.addStretch()
availableLayout.addLayout(refresh_button_layout)
availableGroup.setLayout(availableLayout) availableGroup.setLayout(availableLayout)
...@@ -184,7 +175,7 @@ class ModelSettingDialog(QtWidgets.QDialog): ...@@ -184,7 +175,7 @@ class ModelSettingDialog(QtWidgets.QDialog):
"""连接信号槽""" """连接信号槽"""
# 可用模型列表 # 可用模型列表
self.modelListWidget.itemDoubleClicked.connect(self._onModelListDoubleClicked) self.modelListWidget.itemDoubleClicked.connect(self._onModelListDoubleClicked)
self.btnRefreshModels.clicked.connect(self._refreshModelList) # self.btnRefreshModels.clicked.connect(self._refreshModelList) # 🔥 已删除刷新按钮
def _refreshModelList(self): def _refreshModelList(self):
"""刷新可用模型列表 - 发射信号请求handler处理""" """刷新可用模型列表 - 发射信号请求handler处理"""
......
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