Commit c94177be by Yuhaibo

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

parents c3228c2c 25bb16b1
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
!widgets/** !widgets/**
!handlers/ !handlers/
!handlers/** !handlers/**
!labelme/
!labelme/**
!app.py !app.py
!__main__.py !__main__.py
!__init__.py !__init__.py
......
...@@ -47,7 +47,7 @@ class CropPreviewHandler(QtCore.QObject): ...@@ -47,7 +47,7 @@ class CropPreviewHandler(QtCore.QObject):
# 定时器(用于轮询检测,作为文件监控的补充) # 定时器(用于轮询检测,作为文件监控的补充)
self._poll_timer = None self._poll_timer = None
self._poll_interval = 2000 # 2秒轮询一次 self._poll_interval = 500 # 500毫秒轮询一次(提高实时性)
# 是否启用自动监控 # 是否启用自动监控
self._auto_monitor_enabled = False self._auto_monitor_enabled = False
...@@ -98,7 +98,13 @@ class CropPreviewHandler(QtCore.QObject): ...@@ -98,7 +98,13 @@ class CropPreviewHandler(QtCore.QObject):
print(f"[CropPreviewHandler] 设置保存路径(不自动刷新)") print(f"[CropPreviewHandler] 设置保存路径(不自动刷新)")
self._panel.setSavePath(save_liquid_data_path, auto_refresh=False, video_name=video_name) self._panel.setSavePath(save_liquid_data_path, auto_refresh=False, video_name=video_name)
# 初始化已知图片列表(扫描现有图片,避免重复显示) # 初始化已知图片列表
if clear_first:
# 如果清空显示,则不扫描现有图片,所有图片都视为新增
print(f"[CropPreviewHandler] 清空模式:不扫描现有图片,所有图片将被视为新增")
self._known_images.clear()
else:
# 否则扫描现有图片,避免重复显示
print(f"[CropPreviewHandler] 扫描现有图片...") print(f"[CropPreviewHandler] 扫描现有图片...")
self._initKnownImages(save_liquid_data_path, video_name) self._initKnownImages(save_liquid_data_path, video_name)
...@@ -113,6 +119,13 @@ class CropPreviewHandler(QtCore.QObject): ...@@ -113,6 +119,13 @@ class CropPreviewHandler(QtCore.QObject):
# 清除重置标志 # 清除重置标志
self._is_resetting = False self._is_resetting = False
# 如果是清空模式,立即执行一次检查,显示所有现有图片
if clear_first:
print(f"[CropPreviewHandler] 清空模式:立即检查并显示所有现有图片")
for i, region_path in enumerate(self._monitored_paths):
if region_path and osp.exists(region_path):
self._checkRegionForNewImages(i, region_path)
print(f"[CropPreviewHandler] === 监控已启动 ===") print(f"[CropPreviewHandler] === 监控已启动 ===")
def stopMonitoring(self): def stopMonitoring(self):
...@@ -409,6 +422,12 @@ class CropPreviewHandler(QtCore.QObject): ...@@ -409,6 +422,12 @@ class CropPreviewHandler(QtCore.QObject):
# 更新已知图片列表 # 更新已知图片列表
self._known_images[region_index] = current_images self._known_images[region_index] = current_images
# 检测到新图片后,立即触发一次额外检查(提高响应速度)
if self._poll_timer and self._poll_timer.isActive():
# 重启定时器,立即进行下一次检查
self._poll_timer.stop()
self._poll_timer.start(self._poll_interval)
except Exception as e: except Exception as e:
print(f"[CropPreviewHandler] 检查区域{region_index+1}新图片失败: {e}") print(f"[CropPreviewHandler] 检查区域{region_index+1}新图片失败: {e}")
...@@ -417,6 +436,20 @@ class CropPreviewHandler(QtCore.QObject): ...@@ -417,6 +436,20 @@ class CropPreviewHandler(QtCore.QObject):
if self._panel: if self._panel:
self._panel.refreshImages() self._panel.refreshImages()
def forceRefresh(self):
"""
强制立即检查所有区域的新图片
用于在裁剪过程中手动触发检查,确保实时更新
"""
if not self._auto_monitor_enabled or self._is_resetting:
return
print(f"[CropPreviewHandler] 强制刷新检查")
for i, region_path in enumerate(self._monitored_paths):
if region_path and osp.exists(region_path):
self._checkRegionForNewImages(i, region_path)
def clearPanel(self): def clearPanel(self):
"""清空面板显示""" """清空面板显示"""
if self._panel: if self._panel:
......
...@@ -184,17 +184,17 @@ class DataCollectionChannelHandler: ...@@ -184,17 +184,17 @@ class DataCollectionChannelHandler:
if channel_config: if channel_config:
success = self._connectRealChannel(channel_config) success = self._connectRealChannel(channel_config)
if not success: if not success:
error_detail = f"无法连接到配置的通道 {channel_source}\n请检查:\n 相机是否已开机并连接到网络\n2. IP地址和端口是否正确" error_detail = f"无法连接到配置的通道 {channel_source}\n请检查:\n相机是否已开机并连接到网络\nIP地址和端口是否正确"
else: else:
# 如果没有配置,尝试直接连接USB通道 # 如果没有配置,尝试直接连接USB通道
success = self._connectUSBChannel(channel_source) success = self._connectUSBChannel(channel_source)
if not success: if not success:
error_detail = f"无法连接到USB通道 {channel_source}\n请检查:\nUSB相机是否已连接\n2. 相机驱动是否已安装\n3. 相机是否被其他程序占用" error_detail = f"无法连接到USB通道 {channel_source}\n请检查:\nUSB相机是否已连接\n相机驱动是否已安装\n相机是否被其他程序占用"
else: else:
# 如果是RTSP地址,直接连接 # 如果是RTSP地址,直接连接
success = self._connectRTSPChannel(channel_source) success = self._connectRTSPChannel(channel_source)
if not success: if not success:
error_detail = f"无法连接到RTSP地址\n请检查:\n1. 网络连接是否正常\n2. RTSP地址格式是否正确\n3. 相机是否支持RTSP协议\n\n地址:{channel_source}" error_detail = f"无法连接到RTSP地址\n请检查:\n网络连接是否正常\nRTSP地址格式是否正确\n相机是否支持RTSP协议\n\n地址:{channel_source}"
if not success: if not success:
self._showDataCollectionChannelError( self._showDataCollectionChannelError(
......
...@@ -32,6 +32,10 @@ except Exception: ...@@ -32,6 +32,10 @@ except Exception:
DEFAULT_CROP_SAVE_DIR = osp.join(get_project_root(), 'database', 'Corp_picture') DEFAULT_CROP_SAVE_DIR = osp.join(get_project_root(), 'database', 'Corp_picture')
os.makedirs(DEFAULT_CROP_SAVE_DIR, exist_ok=True) os.makedirs(DEFAULT_CROP_SAVE_DIR, exist_ok=True)
# 调试输出
print(f"[DataPreprocessHandler] 项目根目录: {get_project_root()}")
print(f"[DataPreprocessHandler] 默认裁剪保存目录: {DEFAULT_CROP_SAVE_DIR}")
class DrawableLabel(QtWidgets.QLabel): class DrawableLabel(QtWidgets.QLabel):
""" """
...@@ -2243,12 +2247,30 @@ class DataPreprocessHandler(QtCore.QObject): ...@@ -2243,12 +2247,30 @@ class DataPreprocessHandler(QtCore.QObject):
# 转换为绝对路径 # 转换为绝对路径
save_liquid_data_path = osp.abspath(save_liquid_data_path) save_liquid_data_path = osp.abspath(save_liquid_data_path)
# 打开视频 # 输出保存路径信息(帮助用户定位图片位置)
print(f"[DataPreprocessHandler] ========== 裁剪配置 ==========")
print(f"[DataPreprocessHandler] 视频文件: {osp.basename(video_path)}")
print(f"[DataPreprocessHandler] 保存根目录: {save_liquid_data_path}")
print(f"[DataPreprocessHandler] 裁剪区域数: {len(crop_rects)}")
print(f"[DataPreprocessHandler] 裁剪频率: 每{crop_frequency}帧")
print(f"[DataPreprocessHandler] 图片格式: {image_format}")
print(f"[DataPreprocessHandler] =====================================")
# 打开视频(设置参数以提高容错性)
cap = cv2.VideoCapture(video_path) cap = cv2.VideoCapture(video_path)
# 设置视频读取参数,提高对损坏帧的容错性
# CAP_PROP_BUFFERSIZE: 减小缓冲区大小,加快错误恢复
try:
cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
except:
pass
if not cap.isOpened(): if not cap.isOpened():
raise Exception("无法打开视频文件") raise Exception("无法打开视频文件")
print(f"[DataPreprocessHandler] 视频已打开: {osp.basename(video_path)}")
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
# 检查是否启用了时间段选择 # 检查是否启用了时间段选择
...@@ -2269,12 +2291,14 @@ class DataPreprocessHandler(QtCore.QObject): ...@@ -2269,12 +2291,14 @@ class DataPreprocessHandler(QtCore.QObject):
# 为每个裁剪区域创建子目录(使用"视频名_区域X"的中文形式) # 为每个裁剪区域创建子目录(使用"视频名_区域X"的中文形式)
region_paths = [] region_paths = []
print(f"[DataPreprocessHandler] 创建区域文件夹:")
for i in range(len(crop_rects)): for i in range(len(crop_rects)):
region_folder_name = f"{video_name}_区域{i+1}" region_folder_name = f"{video_name}_区域{i+1}"
region_path = osp.join(save_liquid_data_path, region_folder_name) region_path = osp.join(save_liquid_data_path, region_folder_name)
region_paths.append(region_path) region_paths.append(region_path)
try: try:
os.makedirs(region_path, exist_ok=True) os.makedirs(region_path, exist_ok=True)
print(f"[DataPreprocessHandler] 区域{i+1}: {region_path}")
except Exception as mkdir_err: except Exception as mkdir_err:
raise Exception(f"无法创建区域{i+1}的保存目录: {mkdir_err}") raise Exception(f"无法创建区域{i+1}的保存目录: {mkdir_err}")
...@@ -2289,16 +2313,43 @@ class DataPreprocessHandler(QtCore.QObject): ...@@ -2289,16 +2313,43 @@ class DataPreprocessHandler(QtCore.QObject):
# 计算实际需要处理的帧数(用于进度显示) # 计算实际需要处理的帧数(用于进度显示)
frames_to_process = end_frame_limit - start_frame_limit + 1 frames_to_process = end_frame_limit - start_frame_limit + 1
# 连续读取失败计数器
consecutive_read_failures = 0
max_consecutive_failures = 10 # 允许最多连续失败10次
# 进度更新计数器(减少processEvents调用频率)
progress_update_counter = 0
progress_update_interval = 5 # 每处理5帧更新一次进度(平衡性能和响应性)
while frame_count <= end_frame_limit: while frame_count <= end_frame_limit:
# 检查是否取消 # 检查是否取消
if progress_dialog.wasCanceled(): if progress_dialog.wasCanceled():
print("[DataPreprocessHandler] 用户取消裁剪")
break
# 检查连续失败次数
if consecutive_read_failures >= max_consecutive_failures:
print(f"[DataPreprocessHandler] 连续读取失败{consecutive_read_failures}次,跳过剩余帧")
break break
# 读取帧 # 读取帧
try:
ret, frame = cap.read() ret, frame = cap.read()
if not ret: if not ret:
break consecutive_read_failures += 1
print(f"[DataPreprocessHandler] 读取帧{frame_count}失败,跳过 (连续失败:{consecutive_read_failures})")
frame_count += 1
continue
# 读取成功,重置失败计数器
consecutive_read_failures = 0
except Exception as read_err:
consecutive_read_failures += 1
print(f"[DataPreprocessHandler] 读取帧{frame_count}异常: {read_err} (连续失败:{consecutive_read_failures})")
frame_count += 1
continue
# 根据频率决定是否裁剪 # 根据频率决定是否裁剪
if (frame_count - start_frame_limit) % crop_frequency == 0: if (frame_count - start_frame_limit) % crop_frequency == 0:
...@@ -2324,11 +2375,19 @@ class DataPreprocessHandler(QtCore.QObject): ...@@ -2324,11 +2375,19 @@ class DataPreprocessHandler(QtCore.QObject):
f.write(encoded_img.tobytes()) f.write(encoded_img.tobytes())
saved_counts[i] += 1 saved_counts[i] += 1
else:
print(f"[DataPreprocessHandler] 编码失败: 区域{i+1}, 帧{frame_count}")
except Exception as save_err: except Exception as save_err:
pass print(f"[DataPreprocessHandler] 保存失败: 区域{i+1}, 帧{frame_count}, 错误: {save_err}")
# 继续处理下一个区域,不中断整个流程
frame_count += 1 frame_count += 1
progress_update_counter += 1
# 优化进度更新频率(减少processEvents调用)
if progress_update_counter >= progress_update_interval:
progress_update_counter = 0
# 更新进度(基于实际处理的帧数) # 更新进度(基于实际处理的帧数)
processed_frames = frame_count - start_frame_limit processed_frames = frame_count - start_frame_limit
...@@ -2336,6 +2395,10 @@ class DataPreprocessHandler(QtCore.QObject): ...@@ -2336,6 +2395,10 @@ class DataPreprocessHandler(QtCore.QObject):
progress_dialog.setValue(progress) progress_dialog.setValue(progress)
self.cropProgress.emit(progress) self.cropProgress.emit(progress)
# 强制刷新预览面板(确保实时更新)
if self._crop_preview_handler is not None:
self._crop_preview_handler.forceRefresh()
# 处理事件,保持界面响应 # 处理事件,保持界面响应
QtWidgets.QApplication.processEvents() QtWidgets.QApplication.processEvents()
...@@ -2345,6 +2408,15 @@ class DataPreprocessHandler(QtCore.QObject): ...@@ -2345,6 +2408,15 @@ class DataPreprocessHandler(QtCore.QObject):
# 完成 # 完成
progress_dialog.setValue(100) progress_dialog.setValue(100)
# 输出统计信息
print(f"[DataPreprocessHandler] ===== 裁剪完成 =====")
print(f"[DataPreprocessHandler] 处理帧数: {frame_count - start_frame_limit}/{frames_to_process}")
for i in range(len(crop_rects)):
print(f"[DataPreprocessHandler] 区域{i+1}: 保存 {saved_counts[i]} 张图片")
if consecutive_read_failures > 0:
print(f"[DataPreprocessHandler] 警告: 跳过了 {consecutive_read_failures} 个损坏/无法读取的帧")
print(f"[DataPreprocessHandler] =========================")
# 保存视频与裁剪图片的映射关系 # 保存视频与裁剪图片的映射关系
import time import time
self._video_crop_mapping[video_path] = { self._video_crop_mapping[video_path] = {
......
...@@ -34,6 +34,14 @@ from .widgets import ToolBar ...@@ -34,6 +34,14 @@ from .widgets import ToolBar
from .widgets import UniqueLabelQListWidget from .widgets import UniqueLabelQListWidget
from .widgets import ZoomWidget from .widgets import ZoomWidget
# Import DialogManager for styled dialogs
try:
import sys
sys.path.insert(0, osp.dirname(osp.dirname(osp.abspath(__file__))))
from widgets.style_manager import DialogManager
except ImportError:
DialogManager = None
# FIXME # FIXME
# - [medium] Set max zoom value to something big enough for FitWidth/Window # - [medium] Set max zoom value to something big enough for FitWidth/Window
...@@ -131,11 +139,12 @@ class MainWindow(QtWidgets.QMainWindow): ...@@ -131,11 +139,12 @@ class MainWindow(QtWidgets.QMainWindow):
self.shape_dock.setWidget(self.labelList) self.shape_dock.setWidget(self.labelList)
self.uniqLabelList = UniqueLabelQListWidget() self.uniqLabelList = UniqueLabelQListWidget()
self.uniqLabelList.setToolTip( # 隐藏提示框
self.tr( # self.uniqLabelList.setToolTip(
"点击选择标签\n或按 'Esc' 键取消" # self.tr(
) # "点击选择标签\n或按 'Esc' 键取消"
) # )
# )
if self._config["labels"]: if self._config["labels"]:
for label in self._config["labels"]: for label in self._config["labels"]:
item = self.uniqLabelList.createItemFromLabel(label) item = self.uniqLabelList.createItemFromLabel(label)
...@@ -725,8 +734,8 @@ class MainWindow(QtWidgets.QMainWindow): ...@@ -725,8 +734,8 @@ class MainWindow(QtWidgets.QMainWindow):
utils.addActions( utils.addActions(
self.canvas.menus[1], self.canvas.menus[1],
( (
action("&Copy here", self.copyShape), action("复制到此处(&C)", self.copyShape),
action("&Move here", self.moveShape), action("移动到此处(&M)", self.moveShape),
), ),
) )
...@@ -1884,12 +1893,25 @@ class MainWindow(QtWidgets.QMainWindow): ...@@ -1884,12 +1893,25 @@ class MainWindow(QtWidgets.QMainWindow):
return label_file return label_file
def deleteFile(self): def deleteFile(self):
mb = QtWidgets.QMessageBox
msg = self.tr( msg = self.tr(
"" ""
"" ""
) )
answer = mb.warning(self, self.tr(""), msg, mb.Yes | mb.No)
# Use DialogManager if available
if DialogManager:
result = DialogManager.show_question_warning(
self,
"警告",
msg,
yes_text="是",
no_text="否"
)
if not result:
return
else:
mb = QtWidgets.QMessageBox
answer = mb.warning(self, "警告", msg, mb.Yes | mb.No)
if answer != mb.Yes: if answer != mb.Yes:
return return
...@@ -1923,13 +1945,45 @@ class MainWindow(QtWidgets.QMainWindow): ...@@ -1923,13 +1945,45 @@ class MainWindow(QtWidgets.QMainWindow):
def mayContinue(self): def mayContinue(self):
if not self.dirty: if not self.dirty:
return True return True
mb = QtWidgets.QMessageBox
msg = self.tr('"{}"').format( msg = self.tr('"{}"').format(
self.filename self.filename
) )
# Use DialogManager if available
if DialogManager:
# Create custom dialog with Save/Discard/Cancel buttons
msg_box = QtWidgets.QMessageBox(self)
msg_box.setWindowTitle("提示")
msg_box.setText(msg)
msg_box.setIcon(QtWidgets.QMessageBox.Question)
# Add custom buttons with Chinese text
save_btn = msg_box.addButton("保存", QtWidgets.QMessageBox.AcceptRole)
discard_btn = msg_box.addButton("不保存", QtWidgets.QMessageBox.DestructiveRole)
cancel_btn = msg_box.addButton("取消", QtWidgets.QMessageBox.RejectRole)
msg_box.setDefaultButton(save_btn)
# Apply DialogManager styling
msg_box.setStyleSheet(DialogManager.DEFAULT_STYLE)
from widgets.style_manager import FontManager
FontManager.applyToWidgetRecursive(msg_box)
msg_box.exec_()
clicked = msg_box.clickedButton()
if clicked == discard_btn:
return True
elif clicked == save_btn:
self.saveFile()
return True
else: # cancel_btn
return False
else:
mb = QtWidgets.QMessageBox
answer = mb.question( answer = mb.question(
self, self,
self.tr(""), "提示",
msg, msg,
mb.Save | mb.Discard | mb.Cancel, mb.Save | mb.Discard | mb.Cancel,
mb.Save, mb.Save,
...@@ -1943,9 +1997,13 @@ class MainWindow(QtWidgets.QMainWindow): ...@@ -1943,9 +1997,13 @@ class MainWindow(QtWidgets.QMainWindow):
return False return False
def errorMessage(self, title, message): def errorMessage(self, title, message):
return QtWidgets.QMessageBox.critical( formatted_message = "<p><b>%s</b></p>%s" % (title, message)
self, title, "<p><b>%s</b></p>%s" % (title, message)
) # Use DialogManager if available
if DialogManager:
DialogManager.show_critical(self, title, formatted_message)
else:
return QtWidgets.QMessageBox.critical(self, title, formatted_message)
def currentPath(self): def currentPath(self):
return osp.dirname(str(self.filename)) if self.filename else "." return osp.dirname(str(self.filename)) if self.filename else "."
...@@ -1965,13 +2023,28 @@ class MainWindow(QtWidgets.QMainWindow): ...@@ -1965,13 +2023,28 @@ class MainWindow(QtWidgets.QMainWindow):
self.setDirty() self.setDirty()
def deleteSelectedShape(self): def deleteSelectedShape(self):
msg = "确定删除该标签?"
# Use DialogManager if available, otherwise fallback to QMessageBox
if DialogManager:
result = DialogManager.show_question_warning(
self,
"警告",
msg,
yes_text="是",
no_text="否",
text_alignment=DialogManager.ALIGN_CENTER
)
if result:
self.remLabels(self.canvas.deleteSelected())
self.setDirty()
if self.noShapes():
for action in self.actions.onShapesPresent:
action.setEnabled(False)
else:
yes, no = QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No yes, no = QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No
msg = self.tr(
" {} "
""
).format(len(self.canvas.selectedShapes))
if yes == QtWidgets.QMessageBox.warning( if yes == QtWidgets.QMessageBox.warning(
self, self.tr(""), msg, yes | no, yes self, "警告", msg, yes | no, yes
): ):
self.remLabels(self.canvas.deleteSelected()) self.remLabels(self.canvas.deleteSelected())
self.setDirty() self.setDirty()
...@@ -1994,10 +2067,16 @@ class MainWindow(QtWidgets.QMainWindow): ...@@ -1994,10 +2067,16 @@ class MainWindow(QtWidgets.QMainWindow):
if not self.mayContinue(): if not self.mayContinue():
return return
defaultOpenDirPath = dirpath if dirpath else "." # 默认打开 database\Corp_picture 目录
default_dir = osp.join(osp.dirname(osp.dirname(osp.abspath(__file__))), "database", "Corp_picture")
defaultOpenDirPath = dirpath if dirpath else default_dir
if self.lastOpenDir and osp.exists(self.lastOpenDir): if self.lastOpenDir and osp.exists(self.lastOpenDir):
defaultOpenDirPath = self.lastOpenDir defaultOpenDirPath = self.lastOpenDir
else: else:
# 优先使用 database\Corp_picture,如果不存在则使用当前文件目录或当前工作目录
if osp.exists(default_dir):
defaultOpenDirPath = default_dir
else:
defaultOpenDirPath = ( defaultOpenDirPath = (
osp.dirname(self.filename) if self.filename else "." osp.dirname(self.filename) if self.filename else "."
) )
......
import PIL.Image import PIL.Image
import PIL.ImageEnhance import PIL.ImageEnhance
import sys
import os
from qtpy.QtCore import Qt from qtpy.QtCore import Qt
from qtpy import QtGui from qtpy import QtGui
from qtpy import QtWidgets from qtpy import QtWidgets
from .. import utils from .. import utils
# 导入全局样式管理器
try:
current_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))
if project_root not in sys.path:
sys.path.insert(0, project_root)
from widgets.style_manager import FontManager
STYLE_MANAGER_AVAILABLE = True
except ImportError:
STYLE_MANAGER_AVAILABLE = False
class BrightnessContrastDialog(QtWidgets.QDialog): class BrightnessContrastDialog(QtWidgets.QDialog):
def __init__(self, img, callback, parent=None): def __init__(self, img, callback, parent=None):
...@@ -21,6 +34,10 @@ class BrightnessContrastDialog(QtWidgets.QDialog): ...@@ -21,6 +34,10 @@ class BrightnessContrastDialog(QtWidgets.QDialog):
formLayout.addRow(self.tr(""), self.slider_contrast) formLayout.addRow(self.tr(""), self.slider_contrast)
self.setLayout(formLayout) self.setLayout(formLayout)
# 应用全局字体样式
if STYLE_MANAGER_AVAILABLE:
FontManager.applyToWidgetRecursive(self)
assert isinstance(img, PIL.Image.Image) assert isinstance(img, PIL.Image.Image)
self.img = img self.img = img
self.callback = callback self.callback = callback
......
...@@ -6,6 +6,12 @@ from labelme import QT5 ...@@ -6,6 +6,12 @@ from labelme import QT5
from labelme.shape import Shape from labelme.shape import Shape
import labelme.utils import labelme.utils
# 导入标签显示名称映射
try:
from labelme.widgets.label_dialog import LABEL_DISPLAY_NAMES
except ImportError:
LABEL_DISPLAY_NAMES = {}
# TODO(unknown): # TODO(unknown):
# - [maybe] Find optimal epsilon value. # - [maybe] Find optimal epsilon value.
...@@ -286,7 +292,7 @@ class Canvas(QtWidgets.QWidget): ...@@ -286,7 +292,7 @@ class Canvas(QtWidgets.QWidget):
# - Highlight shapes # - Highlight shapes
# - Highlight vertex # - Highlight vertex
# Update shape/vertex fill and tooltip value accordingly. # Update shape/vertex fill and tooltip value accordingly.
self.setToolTip(self.tr("Image")) self.setToolTip(self.tr("图像"))
for shape in reversed([s for s in self.shapes if self.isVisible(s)]): for shape in reversed([s for s in self.shapes if self.isVisible(s)]):
# Look for a nearby vertex to highlight. If that fails, # Look for a nearby vertex to highlight. If that fails,
# check if we happen to be inside a shape. # check if we happen to be inside a shape.
...@@ -301,7 +307,7 @@ class Canvas(QtWidgets.QWidget): ...@@ -301,7 +307,7 @@ class Canvas(QtWidgets.QWidget):
self.hEdge = None self.hEdge = None
shape.highlightVertex(index, shape.MOVE_VERTEX) shape.highlightVertex(index, shape.MOVE_VERTEX)
self.overrideCursor(CURSOR_POINT) self.overrideCursor(CURSOR_POINT)
self.setToolTip(self.tr("Click & drag to move point")) self.setToolTip(self.tr("点击并拖动以移动点"))
self.setStatusTip(self.toolTip()) self.setStatusTip(self.toolTip())
self.update() self.update()
break break
...@@ -313,7 +319,7 @@ class Canvas(QtWidgets.QWidget): ...@@ -313,7 +319,7 @@ class Canvas(QtWidgets.QWidget):
self.prevhShape = self.hShape = shape self.prevhShape = self.hShape = shape
self.prevhEdge = self.hEdge = index_edge self.prevhEdge = self.hEdge = index_edge
self.overrideCursor(CURSOR_POINT) self.overrideCursor(CURSOR_POINT)
self.setToolTip(self.tr("Click to create point")) self.setToolTip(self.tr("点击以创建点"))
self.setStatusTip(self.toolTip()) self.setStatusTip(self.toolTip())
self.update() self.update()
break break
...@@ -325,8 +331,10 @@ class Canvas(QtWidgets.QWidget): ...@@ -325,8 +331,10 @@ class Canvas(QtWidgets.QWidget):
self.prevhShape = self.hShape = shape self.prevhShape = self.hShape = shape
self.prevhEdge = self.hEdge self.prevhEdge = self.hEdge
self.hEdge = None self.hEdge = None
# 将英文标签转换为中文显示名称
display_label = LABEL_DISPLAY_NAMES.get(shape.label, shape.label)
self.setToolTip( self.setToolTip(
self.tr("Click & drag to move shape '%s'") % shape.label self.tr("点击并拖动以移动形状 '%s'") % display_label
) )
self.setStatusTip(self.toolTip()) self.setStatusTip(self.toolTip())
self.overrideCursor(CURSOR_GRAB) self.overrideCursor(CURSOR_GRAB)
......
import sys
import os
from qtpy import QtWidgets from qtpy import QtWidgets
# 导入全局样式管理器
try:
current_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))
if project_root not in sys.path:
sys.path.insert(0, project_root)
from widgets.style_manager import FontManager
STYLE_MANAGER_AVAILABLE = True
except ImportError:
STYLE_MANAGER_AVAILABLE = False
class ColorDialog(QtWidgets.QColorDialog): class ColorDialog(QtWidgets.QColorDialog):
def __init__(self, parent=None): def __init__(self, parent=None):
...@@ -15,6 +28,10 @@ class ColorDialog(QtWidgets.QColorDialog): ...@@ -15,6 +28,10 @@ class ColorDialog(QtWidgets.QColorDialog):
self.bb.addButton(QtWidgets.QDialogButtonBox.RestoreDefaults) self.bb.addButton(QtWidgets.QDialogButtonBox.RestoreDefaults)
self.bb.clicked.connect(self.checkRestore) self.bb.clicked.connect(self.checkRestore)
# 应用全局字体样式
if STYLE_MANAGER_AVAILABLE:
FontManager.applyToWidgetRecursive(self)
def getColor(self, value=None, title=None, default=None): def getColor(self, value=None, title=None, default=None):
self.default = default self.default = default
if title: if title:
......
...@@ -3,6 +3,19 @@ from qtpy import QtGui ...@@ -3,6 +3,19 @@ from qtpy import QtGui
from qtpy import QtWidgets from qtpy import QtWidgets
import json import json
import sys
import os
# 导入全局样式管理器
try:
current_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))
if project_root not in sys.path:
sys.path.insert(0, project_root)
from widgets.style_manager import FontManager
STYLE_MANAGER_AVAILABLE = True
except ImportError:
STYLE_MANAGER_AVAILABLE = False
class ScrollAreaPreview(QtWidgets.QScrollArea): class ScrollAreaPreview(QtWidgets.QScrollArea):
...@@ -55,6 +68,10 @@ class FileDialogPreview(QtWidgets.QFileDialog): ...@@ -55,6 +68,10 @@ class FileDialogPreview(QtWidgets.QFileDialog):
self.layout().addLayout(box, 1, 3, 1, 1) self.layout().addLayout(box, 1, 3, 1, 1)
self.currentChanged.connect(self.onChange) self.currentChanged.connect(self.onChange)
# 应用全局字体样式
if STYLE_MANAGER_AVAILABLE:
FontManager.applyToWidgetRecursive(self)
def onChange(self, path): def onChange(self, path):
if path.lower().endswith(".json"): if path.lower().endswith(".json"):
with open(path, "r") as f: with open(path, "r") as f:
......
import re import re
import sys
import os
from qtpy import QT_VERSION from qtpy import QT_VERSION
from qtpy import QtCore from qtpy import QtCore
...@@ -8,6 +10,20 @@ from qtpy import QtWidgets ...@@ -8,6 +10,20 @@ from qtpy import QtWidgets
from labelme.logger import logger from labelme.logger import logger
import labelme.utils import labelme.utils
# 导入全局样式管理器
try:
# 获取项目根目录(labelme的上级目录)
current_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))
if project_root not in sys.path:
sys.path.insert(0, project_root)
from widgets.style_manager import FontManager, DialogManager
STYLE_MANAGER_AVAILABLE = True
except ImportError as e:
print(f"警告: 无法导入样式管理器: {e}")
STYLE_MANAGER_AVAILABLE = False
DialogManager = None
QT5 = QT_VERSION[0] == "5" QT5 = QT_VERSION[0] == "5"
...@@ -16,9 +32,9 @@ FIXED_LABELS = ["liquid", "air", "foam"] ...@@ -16,9 +32,9 @@ FIXED_LABELS = ["liquid", "air", "foam"]
# - UI # - UI
LABEL_DISPLAY_NAMES = { LABEL_DISPLAY_NAMES = {
"liquid": "", "liquid": "液体",
"air": "", "air": "空气",
"foam": "" "foam": "泡沫"
} }
# - # -
...@@ -66,7 +82,9 @@ class LabelDialog(QtWidgets.QDialog): ...@@ -66,7 +82,9 @@ class LabelDialog(QtWidgets.QDialog):
self._fit_to_content = fit_to_content self._fit_to_content = fit_to_content
super(LabelDialog, self).__init__(parent) super(LabelDialog, self).__init__(parent)
self.setWindowTitle("") self.setWindowTitle("请选择标签")
# 隐藏右上角的帮助按钮(?)
self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowContextHelpButtonHint)
# #
# self.edit self.edit_group_id # self.edit self.edit_group_id
...@@ -100,6 +118,10 @@ class LabelDialog(QtWidgets.QDialog): ...@@ -100,6 +118,10 @@ class LabelDialog(QtWidgets.QDialog):
self.labelList.setFixedHeight(150) self.labelList.setFixedHeight(150)
# self.edit.setListWidgetself.edit # self.edit.setListWidgetself.edit
layout.addWidget(self.labelList) layout.addWidget(self.labelList)
# 应用全局字体样式到列表控件
if STYLE_MANAGER_AVAILABLE:
FontManager.applyToWidget(self.labelList)
# label_flags # label_flags
if flags is None: if flags is None:
flags = {} flags = {}
...@@ -111,6 +133,10 @@ class LabelDialog(QtWidgets.QDialog): ...@@ -111,6 +133,10 @@ class LabelDialog(QtWidgets.QDialog):
# #
# self.editDescription # self.editDescription
self.setLayout(layout) self.setLayout(layout)
# 应用全局字体样式到整个对话框
if STYLE_MANAGER_AVAILABLE:
FontManager.applyToWidgetRecursive(self)
# completion # completion
completer = QtWidgets.QCompleter() completer = QtWidgets.QCompleter()
if not QT5 and completion != "startswith": if not QT5 and completion != "startswith":
...@@ -154,11 +180,10 @@ class LabelDialog(QtWidgets.QDialog): ...@@ -154,11 +180,10 @@ class LabelDialog(QtWidgets.QDialog):
current_item = self.labelList.currentItem() current_item = self.labelList.currentItem()
if not current_item: if not current_item:
logger.warning("") logger.warning("")
QtWidgets.QMessageBox.warning( if DialogManager:
self, DialogManager.show_warning(self, "", "")
"", else:
"" QtWidgets.QMessageBox.warning(self, "", "")
)
return return
# #
...@@ -168,11 +193,11 @@ class LabelDialog(QtWidgets.QDialog): ...@@ -168,11 +193,11 @@ class LabelDialog(QtWidgets.QDialog):
# #
if text not in FIXED_LABELS: if text not in FIXED_LABELS:
logger.warning(f" '{text}' : {', '.join(FIXED_LABELS)}") logger.warning(f" '{text}' : {', '.join(FIXED_LABELS)}")
QtWidgets.QMessageBox.warning( msg = f" '{text}' \n\n:\n" + "\n".join(FIXED_LABELS)
self, if DialogManager:
"", DialogManager.show_warning(self, "", msg)
f" '{text}' \n\n:\n" + "\n".join(FIXED_LABELS) else:
) QtWidgets.QMessageBox.warning(self, "", msg)
return return
if text: if text:
......
...@@ -61,6 +61,25 @@ except ImportError as e: ...@@ -61,6 +61,25 @@ except ImportError as e:
labelme_get_config = None labelme_get_config = None
class ColorPreservingDelegate(QtWidgets.QStyledItemDelegate):
"""自定义委托,保持选中状态下的文字颜色"""
def paint(self, painter, option, index):
# 获取item的前景色(文字颜色)
foreground = index.data(Qt.ForegroundRole)
# 如果item被选中,修改选项的调色板以保持文字颜色
if option.state & QtWidgets.QStyle.State_Selected:
# 设置选中背景色为灰色
option.palette.setBrush(QtGui.QPalette.Highlight, QtGui.QBrush(QtGui.QColor(208, 208, 208)))
# 如果有自定义前景色,保持它
if foreground:
option.palette.setBrush(QtGui.QPalette.HighlightedText, foreground)
# 调用父类的绘制方法
super().paint(painter, option, index)
class AnnotationTool(QtWidgets.QWidget): class AnnotationTool(QtWidgets.QWidget):
""" """
数据标注工具组件 数据标注工具组件
...@@ -181,6 +200,10 @@ class AnnotationTool(QtWidgets.QWidget): ...@@ -181,6 +200,10 @@ class AnnotationTool(QtWidgets.QWidget):
self.annotation_list.setAlternatingRowColors(True) self.annotation_list.setAlternatingRowColors(True)
self.annotation_list.setIconSize(QtCore.QSize(80, 80)) self.annotation_list.setIconSize(QtCore.QSize(80, 80))
self.annotation_list.setSpacing(5) self.annotation_list.setSpacing(5)
# 设置自定义委托以保持选中状态下的文字颜色
self.annotation_list.setItemDelegate(ColorPreservingDelegate(self.annotation_list))
self.annotation_list.setStyleSheet(""" self.annotation_list.setStyleSheet("""
QListWidget { QListWidget {
border: 1px solid #c0c0c0; border: 1px solid #c0c0c0;
...@@ -192,8 +215,7 @@ class AnnotationTool(QtWidgets.QWidget): ...@@ -192,8 +215,7 @@ class AnnotationTool(QtWidgets.QWidget):
border-bottom: 1px solid #e0e0e0; border-bottom: 1px solid #e0e0e0;
} }
QListWidget::item:selected { QListWidget::item:selected {
background-color: #0078d7; background-color: #d0d0d0;
color: white;
} }
QListWidget::item:hover { QListWidget::item:hover {
background-color: #e0e0e0; background-color: #e0e0e0;
...@@ -552,8 +574,11 @@ class AnnotationTool(QtWidgets.QWidget): ...@@ -552,8 +574,11 @@ class AnnotationTool(QtWidgets.QWidget):
def onDirectoryChanged(self, dir_path): def onDirectoryChanged(self, dir_path):
"""目录变化时的UI更新(响应handler信号)""" """目录变化时的UI更新(响应handler信号)"""
self.lbl_current_folder.setText(dir_path) # 只显示文件夹名称,不显示完整路径
self.lbl_current_folder.setStyleSheet("color: #2ca02c; font-style: normal; font-weight: bold;") import os.path as osp
folder_name = osp.basename(dir_path) if dir_path else ""
self.lbl_current_folder.setText(folder_name)
self.lbl_current_folder.setStyleSheet("color: #2ca02c; font-style: normal; font-weight: bold; font-size: 9pt;")
def onFileListUpdated(self, file_info_list): def onFileListUpdated(self, file_info_list):
"""文件列表更新时的UI更新(响应handler信号)""" """文件列表更新时的UI更新(响应handler信号)"""
...@@ -634,11 +659,28 @@ class AnnotationTool(QtWidgets.QWidget): ...@@ -634,11 +659,28 @@ class AnnotationTool(QtWidgets.QWidget):
"""列表项点击事件""" """列表项点击事件"""
data = item.data(Qt.UserRole) data = item.data(Qt.UserRole)
if data: if data:
# 强制设置选中项的颜色
if data.get('has_json'):
item.setForeground(QtGui.QBrush(QtGui.QColor(44, 160, 44))) # 绿色
else:
item.setForeground(QtGui.QBrush(QtGui.QColor(128, 128, 128))) # 灰色
image_path = data['image_path'] image_path = data['image_path']
self.loadImageForAnnotation(image_path) self.loadImageForAnnotation(image_path)
def onItemSelectionChanged(self): def onItemSelectionChanged(self):
"""列表项选择变化事件""" """列表项选择变化事件"""
# 重新设置所有项目的颜色(因为选中状态会改变颜色)
for i in range(self.annotation_list.count()):
item = self.annotation_list.item(i)
data = item.data(Qt.UserRole)
if data and data.get('has_json'):
# 已标注 - 保持深绿色
item.setForeground(QtGui.QBrush(QtGui.QColor(44, 160, 44))) # #2ca02c
else:
# 未标注 - 灰色
item.setForeground(QtGui.QBrush(QtGui.QColor(128, 128, 128))) # #808080
items = self.annotation_list.selectedItems() items = self.annotation_list.selectedItems()
if not items: if not items:
return return
......
...@@ -41,6 +41,10 @@ except Exception: ...@@ -41,6 +41,10 @@ except Exception:
DEFAULT_CROP_SAVE_DIR = osp.join(get_project_root(), 'database', 'Corp_picture') DEFAULT_CROP_SAVE_DIR = osp.join(get_project_root(), 'database', 'Corp_picture')
os.makedirs(DEFAULT_CROP_SAVE_DIR, exist_ok=True) os.makedirs(DEFAULT_CROP_SAVE_DIR, exist_ok=True)
# 调试输出
print(f"[CropConfigDialog模块] 项目根目录: {get_project_root()}")
print(f"[CropConfigDialog模块] 默认裁剪保存目录: {DEFAULT_CROP_SAVE_DIR}")
class CropConfigDialog(QtWidgets.QDialog): class CropConfigDialog(QtWidgets.QDialog):
""" """
...@@ -62,8 +66,11 @@ class CropConfigDialog(QtWidgets.QDialog): ...@@ -62,8 +66,11 @@ class CropConfigDialog(QtWidgets.QDialog):
""" """
super(CropConfigDialog, self).__init__(parent) super(CropConfigDialog, self).__init__(parent)
# 保存路径和频率 # 【强制修改】始终使用项目默认路径,忽略传入的参数
self._save_liquid_data_path = default_save_liquid_data_path or DEFAULT_CROP_SAVE_DIR # 这样可以确保图片始终保存在项目目录下
self._save_liquid_data_path = DEFAULT_CROP_SAVE_DIR
print(f"[CropConfigDialog] 强制使用默认路径: {DEFAULT_CROP_SAVE_DIR}")
self._crop_frequency = default_frequency self._crop_frequency = default_frequency
self._file_prefix = "frame" self._file_prefix = "frame"
self._image_format = "jpg" self._image_format = "jpg"
...@@ -321,10 +328,19 @@ class CropConfigDialog(QtWidgets.QDialog): ...@@ -321,10 +328,19 @@ class CropConfigDialog(QtWidgets.QDialog):
try: try:
settings = QtCore.QSettings("Detection", "CropConfigDialog") settings = QtCore.QSettings("Detection", "CropConfigDialog")
saved_path = settings.value("save_liquid_data_path", "") # 【强制修改】清除旧的保存路径设置,不再记住保存路径
if saved_path and osp.exists(saved_path): # 检查是否有旧设置
self._save_liquid_data_path = saved_path old_path = settings.value("save_liquid_data_path", "")
self.path_edit.setText(saved_path) if old_path:
print(f"[CropConfigDialog] 检测到旧的保存路径: {old_path}")
settings.remove("save_liquid_data_path")
print(f"[CropConfigDialog] 已清除旧的保存路径设置")
# 强制使用项目默认路径
# 这样可以确保图片始终保存在项目目录下,避免用户找不到图片
self.path_edit.setText(self._save_liquid_data_path)
print(f"[CropConfigDialog] 对话框路径已设置为: {self._save_liquid_data_path}")
print(f"[CropConfigDialog] 文本框内容: {self.path_edit.text()}")
saved_freq = settings.value("crop_frequency", 1) saved_freq = settings.value("crop_frequency", 1)
try: try:
...@@ -347,7 +363,8 @@ class CropConfigDialog(QtWidgets.QDialog): ...@@ -347,7 +363,8 @@ class CropConfigDialog(QtWidgets.QDialog):
"""保存当前设置""" """保存当前设置"""
try: try:
settings = QtCore.QSettings("Detection", "CropConfigDialog") settings = QtCore.QSettings("Detection", "CropConfigDialog")
settings.setValue("save_liquid_data_path", self.path_edit.text()) # 【修改】不再保存路径,每次都使用默认路径
# settings.setValue("save_liquid_data_path", self.path_edit.text())
settings.setValue("crop_frequency", self.frequency_spinbox.value()) settings.setValue("crop_frequency", self.frequency_spinbox.value())
settings.setValue("file_prefix", self.prefix_edit.text()) settings.setValue("file_prefix", self.prefix_edit.text())
settings.setValue("image_format", self.format_combo.currentText()) settings.setValue("image_format", self.format_combo.currentText())
...@@ -414,12 +431,16 @@ class CropConfigDialog(QtWidgets.QDialog): ...@@ -414,12 +431,16 @@ class CropConfigDialog(QtWidgets.QDialog):
- file_prefix: 文件名前缀 - file_prefix: 文件名前缀
- image_format: 图片格式 - image_format: 图片格式
""" """
return { # 【强制修改】始终返回默认路径,忽略文本框内容
'save_liquid_data_path': self.path_edit.text().strip(), # 确保图片保存在项目目录下
config = {
'save_liquid_data_path': DEFAULT_CROP_SAVE_DIR, # 强制使用默认路径
'crop_frequency': self.frequency_spinbox.value(), 'crop_frequency': self.frequency_spinbox.value(),
'file_prefix': self.prefix_edit.text().strip(), 'file_prefix': self.prefix_edit.text().strip(),
'image_format': self.format_combo.currentText() 'image_format': self.format_combo.currentText()
} }
print(f"[CropConfigDialog] getConfig返回的保存路径: {config['save_liquid_data_path']}")
return config
def setConfig(self, config): def setConfig(self, config):
""" """
......
...@@ -281,7 +281,7 @@ class CropPreviewPanel(QtWidgets.QWidget): ...@@ -281,7 +281,7 @@ class CropPreviewPanel(QtWidgets.QWidget):
self.refreshImages() self.refreshImages()
def _findRegionPaths(self): def _findRegionPaths(self):
"""查找所有区域文件夹(支持新旧命名格式,可根据视频名称过滤)""" """查找所有区域文件夹(支持新旧命名格式,可根据视频名 称过滤)"""
self._region_paths = [] self._region_paths = []
if not self._save_liquid_data_path or not osp.exists(self._save_liquid_data_path): if not self._save_liquid_data_path or not osp.exists(self._save_liquid_data_path):
...@@ -342,9 +342,19 @@ class CropPreviewPanel(QtWidgets.QWidget): ...@@ -342,9 +342,19 @@ class CropPreviewPanel(QtWidgets.QWidget):
if not self._save_liquid_data_path: if not self._save_liquid_data_path:
return return
# 保存当前的视频名称(clearImages会清空它)
current_video_name = self._video_name
# 如果没有视频名称上下文,说明当前没有选中有效的裁剪视频,不应该刷新
if not current_video_name:
return
# 先清空所有图片 # 先清空所有图片
self.clearImages() self.clearImages()
# 恢复视频名称
self._video_name = current_video_name
# 重新查找区域文件夹 # 重新查找区域文件夹
self._findRegionPaths() self._findRegionPaths()
...@@ -485,6 +495,7 @@ class CropPreviewPanel(QtWidgets.QWidget): ...@@ -485,6 +495,7 @@ class CropPreviewPanel(QtWidgets.QWidget):
# 清空缓存数据 # 清空缓存数据
self._region_images.clear() self._region_images.clear()
self._region_paths = [] self._region_paths = []
self._video_name = None # 清空视频名称,防止刷新时显示其他视频的图片
# 更新统计 # 更新统计
self._updateStats() self._updateStats()
...@@ -600,16 +611,16 @@ class CropPreviewPanel(QtWidgets.QWidget): ...@@ -600,16 +611,16 @@ class CropPreviewPanel(QtWidgets.QWidget):
reply = DialogManager.show_question_warning( reply = DialogManager.show_question_warning(
self, self,
"确认删除", "确认删除",
f"确定要删除区域 {current_region + 1} 的所有文件吗?\n\n" f"确定要删除区域 {current_region + 1} 的所有文件吗?\n"
f"当前区域共有 {image_count} 张图片\n\n" f"当前区域共有 {image_count} 张图片"
f"文件夹将被移动到回收站" f"文件夹将被移动到回收站"
) )
else: else:
reply = QtWidgets.QMessageBox.question( reply = QtWidgets.QMessageBox.question(
self, self,
"确认删除", "确认删除",
f"确定要删除区域 {current_region + 1} 的所有文件吗?\n\n" f"确定要删除区域 {current_region + 1} 的所有文件吗?\n"
f"当前区域共有 {image_count} 张图片\n\n" f"当前区域共有 {image_count} 张图片"
f"文件夹将被移动到回收站", f"文件夹将被移动到回收站",
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
QtWidgets.QMessageBox.No QtWidgets.QMessageBox.No
......
...@@ -167,7 +167,7 @@ class DataCollectionPanel(QtWidgets.QWidget): ...@@ -167,7 +167,7 @@ class DataCollectionPanel(QtWidgets.QWidget):
# 标题栏 # 标题栏
title_layout = QtWidgets.QHBoxLayout() title_layout = QtWidgets.QHBoxLayout()
title_label = QtWidgets.QLabel("目录") title_label = QtWidgets.QLabel("数据采集")
title_label.setStyleSheet("font-size: 12pt; font-weight: bold;") title_label.setStyleSheet("font-size: 12pt; font-weight: bold;")
title_layout.addWidget(title_label) title_layout.addWidget(title_label)
...@@ -833,12 +833,25 @@ class DataCollectionPanel(QtWidgets.QWidget): ...@@ -833,12 +833,25 @@ class DataCollectionPanel(QtWidgets.QWidget):
file_name_no_ext, file_ext = osp.splitext(file_name) file_name_no_ext, file_ext = osp.splitext(file_name)
# 弹出输入对话框 # 弹出输入对话框
new_name, ok = QtWidgets.QInputDialog.getText( dialog = QtWidgets.QInputDialog(self)
self, "重命名文件", dialog.setWindowTitle("重命名文件")
"请输入新的文件名(不含扩展名):", dialog.setLabelText("请输入新的文件名(不含扩展名):")
QtWidgets.QLineEdit.Normal, dialog.setTextValue(file_name_no_ext)
file_name_no_ext dialog.setInputMode(QtWidgets.QInputDialog.TextInput)
) # 隐藏问号按钮
dialog.setWindowFlags(dialog.windowFlags() & ~Qt.WindowContextHelpButtonHint)
# 设置中文按钮文本
dialog.setOkButtonText("确定")
dialog.setCancelButtonText("取消")
# 应用全局字体管理
if DialogManager:
from ..style_manager import FontManager
FontManager.applyToWidgetRecursive(dialog)
# 应用统一按钮样式
DialogManager.applyButtonStylesToDialog(dialog)
ok = dialog.exec_()
new_name = dialog.textValue()
if not ok or not new_name.strip(): if not ok or not new_name.strip():
return return
...@@ -990,6 +1003,14 @@ class DataCollectionPanel(QtWidgets.QWidget): ...@@ -990,6 +1003,14 @@ class DataCollectionPanel(QtWidgets.QWidget):
dialog.setLayout(layout) dialog.setLayout(layout)
# 应用全局字体管理和按钮样式
if DialogManager:
from ..style_manager import FontManager, TextButtonStyleManager
FontManager.applyToWidgetRecursive(dialog)
# 应用统一按钮样式
TextButtonStyleManager.applyToButton(ok_btn)
TextButtonStyleManager.applyToButton(cancel_btn)
# 连接按钮信号 # 连接按钮信号
ok_btn.clicked.connect(dialog.accept) ok_btn.clicked.connect(dialog.accept)
cancel_btn.clicked.connect(dialog.reject) cancel_btn.clicked.connect(dialog.reject)
...@@ -1058,9 +1079,9 @@ class DataCollectionPanel(QtWidgets.QWidget): ...@@ -1058,9 +1079,9 @@ class DataCollectionPanel(QtWidgets.QWidget):
if file_info: if file_info:
file_info_text = "、".join(file_info) file_info_text = "、".join(file_info)
message = f"确定要删除文件夹 '{folder_name}' 吗?\n\n文件夹内含有{file_info_text}\n\n所有内容将被移动到回收站" message = f"确定要删除文件夹“{folder_name}”吗?文件夹内含有{file_info_text},所有内容将被移动到回收站。"
else: else:
message = f"确定要删除文件夹 '{folder_name}' 吗?\n\n文件夹为空\n\n将被移动到回收站" message = f"确定要删除文件夹“{folder_name}”吗?文件夹为空,将被移动到回收站。"
# 确认删除 # 确认删除
if self._showQuestionWarning("确认删除", message): if self._showQuestionWarning("确认删除", message):
...@@ -1383,10 +1404,23 @@ def _getSelectedChannel(self): ...@@ -1383,10 +1404,23 @@ def _getSelectedChannel(self):
if current_data == "custom": if current_data == "custom":
# 自定义RTSP地址 # 自定义RTSP地址
rtsp_url, ok = QtWidgets.QInputDialog.getText( dialog = QtWidgets.QInputDialog(self)
self, "自定义RTSP地址", dialog.setWindowTitle("自定义RTSP地址")
"请输入RTSP地址:\n(格式: rtsp://username:password@ip:port/path)" dialog.setLabelText("请输入RTSP地址:\n(格式: rtsp://username:password@ip:port/path)")
) dialog.setInputMode(QtWidgets.QInputDialog.TextInput)
dialog.setWindowFlags(dialog.windowFlags() & ~Qt.WindowContextHelpButtonHint)
dialog.setOkButtonText("确定")
dialog.setCancelButtonText("取消")
# 应用全局字体管理
if DialogManager:
from style_manager import FontManager
FontManager.applyToWidgetRecursive(dialog)
# 应用统一按钮样式
DialogManager.applyButtonStylesToDialog(dialog)
ok = dialog.exec_()
rtsp_url = dialog.textValue()
if ok and rtsp_url.strip(): if ok and rtsp_url.strip():
return rtsp_url.strip() return rtsp_url.strip()
else: else:
......
...@@ -172,7 +172,7 @@ class DataPreprocessPanel(QtWidgets.QWidget): ...@@ -172,7 +172,7 @@ class DataPreprocessPanel(QtWidgets.QWidget):
# 标题栏 # 标题栏
title_layout = QtWidgets.QHBoxLayout() title_layout = QtWidgets.QHBoxLayout()
title_label = QtWidgets.QLabel("目录") title_label = QtWidgets.QLabel("数据预处理")
title_label.setStyleSheet("font-size: 12pt; font-weight: bold;") title_label.setStyleSheet("font-size: 12pt; font-weight: bold;")
title_layout.addWidget(title_label) title_layout.addWidget(title_label)
...@@ -615,12 +615,17 @@ class DataPreprocessPanel(QtWidgets.QWidget): ...@@ -615,12 +615,17 @@ class DataPreprocessPanel(QtWidgets.QWidget):
def getCropConfig(self): def getCropConfig(self):
"""获取裁剪配置""" """获取裁剪配置"""
return { # 【强制修改】始终使用项目默认路径,忽略文本框内容
'save_liquid_data_path': self.crop_path_edit.text().strip(), # 确保图片保存在项目目录下
default_path = self._getDefaultCropFolder()
config = {
'save_liquid_data_path': default_path, # 强制使用默认路径
'crop_frequency': self.crop_frequency_spinbox.value(), 'crop_frequency': self.crop_frequency_spinbox.value(),
'file_prefix': self.crop_prefix_edit.text().strip(), 'file_prefix': self.crop_prefix_edit.text().strip(),
'image_format': self.crop_format_combo.currentText() 'image_format': self.crop_format_combo.currentText()
} }
print(f"[DataPreprocessPanel] getCropConfig返回的保存路径: {config['save_liquid_data_path']}")
return config
def _createCropPreviewPanel(self): def _createCropPreviewPanel(self):
"""创建右侧裁剪图片预览面板""" """创建右侧裁剪图片预览面板"""
...@@ -1124,6 +1129,7 @@ class DataPreprocessPanel(QtWidgets.QWidget): ...@@ -1124,6 +1129,7 @@ class DataPreprocessPanel(QtWidgets.QWidget):
# 创建右键菜单 # 创建右键菜单
menu = QtWidgets.QMenu(self) menu = QtWidgets.QMenu(self)
# 只在空白处点击时显示刷新菜单
if not item: if not item:
# 在空白处点击,显示刷新菜单 # 在空白处点击,显示刷新菜单
action_refresh = menu.addAction(newIcon("刷新"), "刷新") action_refresh = menu.addAction(newIcon("刷新"), "刷新")
...@@ -1134,26 +1140,8 @@ class DataPreprocessPanel(QtWidgets.QWidget): ...@@ -1134,26 +1140,8 @@ class DataPreprocessPanel(QtWidgets.QWidget):
# 处理刷新动作 # 处理刷新动作
if action == action_refresh: if action == action_refresh:
self._onRefreshVideos() self._onRefreshVideos()
return
# 获取视频路径
video_path = item.data(Qt.UserRole)
if not video_path:
return
# 添加菜单项 # 在视频项上右键时不显示任何菜单(已删除重命名和删除功能)
action_rename = menu.addAction(newIcon("设置"), "重命名")
action_delete = menu.addAction(newIcon("关闭"), "删除")
# 显示菜单并获取选择的动作
action = menu.exec_(self.video_grid.mapToGlobal(position))
# 处理选择的动作
if action == action_rename:
self._onRenameVideo(item)
elif action == action_delete:
self._onDeleteVideo(item)
def _onRenameVideo(self, item): def _onRenameVideo(self, item):
"""重命名视频文件""" """重命名视频文件"""
......
...@@ -275,6 +275,10 @@ class DialogManager: ...@@ -275,6 +275,10 @@ class DialogManager:
QMessageBox { QMessageBox {
min-width: 400px; min-width: 400px;
} }
QMessageBox QLabel {
border: none;
background: transparent;
}
""" """
# 文本对齐方式常量 # 文本对齐方式常量
...@@ -323,6 +327,9 @@ class DialogManager: ...@@ -323,6 +327,9 @@ class DialogManager:
# 🔥 根据文本内容自动调整对话框大小 # 🔥 根据文本内容自动调整对话框大小
DialogManager._adjust_dialog_size(msg_box, message) DialogManager._adjust_dialog_size(msg_box, message)
# 🔥 应用统一按钮样式到对话框的所有按钮
DialogManager._apply_button_styles(msg_box)
return msg_box return msg_box
@staticmethod @staticmethod
...@@ -339,6 +346,9 @@ class DialogManager: ...@@ -339,6 +346,9 @@ class DialogManager:
# 只设置消息文本标签的对齐方式,不影响其他标签 # 只设置消息文本标签的对齐方式,不影响其他标签
if label.text() and not label.pixmap(): if label.text() and not label.pixmap():
label.setAlignment(alignment) label.setAlignment(alignment)
# 移除任何边框样式
label.setFrameStyle(QtWidgets.QFrame.NoFrame)
label.setStyleSheet("border: none; background: transparent;")
except Exception as e: except Exception as e:
pass pass
...@@ -382,6 +392,24 @@ class DialogManager: ...@@ -382,6 +392,24 @@ class DialogManager:
pass pass
@staticmethod @staticmethod
def _apply_button_styles(msg_box):
"""应用统一按钮样式到对话框的所有按钮
Args:
msg_box: QMessageBox对象
"""
try:
# 查找对话框中的所有QPushButton
buttons = msg_box.findChildren(QtWidgets.QPushButton)
for button in buttons:
# 应用TextButtonStyleManager样式
TextButtonStyleManager.applyToButton(button)
except Exception as e:
pass
@staticmethod
def _set_chinese_button_texts(msg_box, button_texts=None): def _set_chinese_button_texts(msg_box, button_texts=None):
"""设置按钮为中文文本""" """设置按钮为中文文本"""
# 默认中文按钮文本映射 # 默认中文按钮文本映射
...@@ -703,6 +731,25 @@ class DialogManager: ...@@ -703,6 +731,25 @@ class DialogManager:
"""设置默认样式""" """设置默认样式"""
DialogManager.DEFAULT_STYLE = style_sheet DialogManager.DEFAULT_STYLE = style_sheet
@staticmethod
def applyButtonStylesToDialog(dialog):
"""应用统一按钮样式到对话框的所有按钮
Args:
dialog: QDialog或QInputDialog对象
"""
try:
# 查找对话框中的所有QPushButton
buttons = dialog.findChildren(QtWidgets.QPushButton)
for button in buttons:
# 应用TextButtonStyleManager样式
from widgets.style_manager import TextButtonStyleManager
TextButtonStyleManager.applyToButton(button)
except Exception as e:
pass
# 对话框管理器便捷函数 # 对话框管理器便捷函数
def show_warning(parent, title, message): def show_warning(parent, title, message):
......
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