体验升级:路径记忆、可视化深度扫描、文件名汉化
This commit is contained in:
@ -2105,7 +2105,7 @@ class WaterQualityInversionPipeline:
|
|||||||
training_spectra_path: Optional[str] = None,
|
training_spectra_path: Optional[str] = None,
|
||||||
formula_csv_file: Optional[str] = None,
|
formula_csv_file: Optional[str] = None,
|
||||||
formula_names: Optional[List[str]] = None,
|
formula_names: Optional[List[str]] = None,
|
||||||
output_filename: str = "water_quality_indices.csv",
|
output_file: Optional[str] = None,
|
||||||
enabled: bool = True,
|
enabled: bool = True,
|
||||||
skip_dependency_check: bool = False) -> str:
|
skip_dependency_check: bool = False) -> str:
|
||||||
"""
|
"""
|
||||||
@ -2117,7 +2117,7 @@ class WaterQualityInversionPipeline:
|
|||||||
training_spectra_path: 训练光谱数据CSV路径(如果为None,使用步骤5的结果)
|
training_spectra_path: 训练光谱数据CSV路径(如果为None,使用步骤5的结果)
|
||||||
formula_csv_file: 公式CSV文件路径,包含公式名称和具体公式
|
formula_csv_file: 公式CSV文件路径,包含公式名称和具体公式
|
||||||
formula_names: 要计算的公式名称列表,如果为None则计算所有公式
|
formula_names: 要计算的公式名称列表,如果为None则计算所有公式
|
||||||
output_filename: 输出文件名
|
output_file: 输出文件完整路径(支持绝对路径),如果为None则使用默认路径
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
包含计算结果的新CSV文件路径
|
包含计算结果的新CSV文件路径
|
||||||
@ -2148,8 +2148,12 @@ class WaterQualityInversionPipeline:
|
|||||||
|
|
||||||
if formula_csv_file is None:
|
if formula_csv_file is None:
|
||||||
raise ValueError("必须提供formula_csv_file参数,包含水质指数公式")
|
raise ValueError("必须提供formula_csv_file参数,包含水质指数公式")
|
||||||
|
|
||||||
output_path = str(self.indices_dir / output_filename)
|
# 支持绝对路径:output_file 完整路径;否则 fallback 到 indices_dir + 默认文件名
|
||||||
|
if output_file:
|
||||||
|
output_path = str(Path(output_file))
|
||||||
|
else:
|
||||||
|
output_path = str(self.indices_dir / "water_quality_indices.csv")
|
||||||
|
|
||||||
# 如果文件已存在且配置了跳过机制,则直接复用
|
# 如果文件已存在且配置了跳过机制,则直接复用
|
||||||
if Path(output_path).exists():
|
if Path(output_path).exists():
|
||||||
|
|||||||
@ -12,6 +12,67 @@ from PyQt5.QtWidgets import (
|
|||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
|
|
||||||
|
|
||||||
|
class DirSelectWidget(QWidget):
|
||||||
|
"""目录选择组件"""
|
||||||
|
def __init__(self, label_text, parent=None):
|
||||||
|
"""
|
||||||
|
初始化目录选择组件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
label_text: 标签文本
|
||||||
|
parent: 父控件
|
||||||
|
"""
|
||||||
|
super().__init__(parent)
|
||||||
|
self.init_ui(label_text)
|
||||||
|
|
||||||
|
def init_ui(self, label_text):
|
||||||
|
layout = QHBoxLayout()
|
||||||
|
layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
|
self.label = QLabel(label_text)
|
||||||
|
self.label.setMinimumWidth(120)
|
||||||
|
self.line_edit = QLineEdit()
|
||||||
|
self.line_edit.setPlaceholderText("请选择目录...")
|
||||||
|
self.browse_btn = QPushButton("浏览...")
|
||||||
|
self.browse_btn.setMaximumWidth(80)
|
||||||
|
self.browse_btn.clicked.connect(self.browse_dir)
|
||||||
|
|
||||||
|
layout.addWidget(self.label)
|
||||||
|
layout.addWidget(self.line_edit, 1)
|
||||||
|
layout.addWidget(self.browse_btn)
|
||||||
|
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
def browse_dir(self):
|
||||||
|
"""浏览目录 - 智能记忆上次选择位置"""
|
||||||
|
current_text = self.line_edit.text().strip()
|
||||||
|
initial_dir = ""
|
||||||
|
|
||||||
|
# 最高优先级:输入框已有路径存在
|
||||||
|
if current_text:
|
||||||
|
if os.path.isdir(current_text):
|
||||||
|
initial_dir = current_text
|
||||||
|
else:
|
||||||
|
dir_path = os.path.dirname(current_text)
|
||||||
|
if dir_path and os.path.exists(dir_path):
|
||||||
|
initial_dir = dir_path
|
||||||
|
|
||||||
|
# 调用目录选择对话框
|
||||||
|
dir_path = QFileDialog.getExistingDirectory(
|
||||||
|
self, "选择目录", initial_dir
|
||||||
|
)
|
||||||
|
if dir_path:
|
||||||
|
self.line_edit.setText(dir_path)
|
||||||
|
|
||||||
|
def get_path(self):
|
||||||
|
"""获取路径"""
|
||||||
|
return self.line_edit.text()
|
||||||
|
|
||||||
|
def set_path(self, path):
|
||||||
|
"""设置路径"""
|
||||||
|
self.line_edit.setText(str(path))
|
||||||
|
|
||||||
|
|
||||||
class FileSelectWidget(QWidget):
|
class FileSelectWidget(QWidget):
|
||||||
"""文件选择组件"""
|
"""文件选择组件"""
|
||||||
def __init__(self, label_text, file_filter="All Files (*.*)", mode="open", parent=None):
|
def __init__(self, label_text, file_filter="All Files (*.*)", mode="open", parent=None):
|
||||||
@ -49,14 +110,18 @@ class FileSelectWidget(QWidget):
|
|||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
|
|
||||||
def browse_file(self):
|
def browse_file(self):
|
||||||
"""浏览文件"""
|
"""浏览文件 - 智能记忆上次选择位置"""
|
||||||
current_text = self.line_edit.text().strip()
|
current_text = self.line_edit.text().strip()
|
||||||
initial_dir = ""
|
initial_dir = ""
|
||||||
|
|
||||||
|
# 最高优先级:输入框已有路径存在
|
||||||
if current_text:
|
if current_text:
|
||||||
dir_path = os.path.dirname(current_text)
|
if os.path.isdir(current_text):
|
||||||
if dir_path and os.path.exists(dir_path):
|
initial_dir = current_text
|
||||||
initial_dir = dir_path
|
else:
|
||||||
|
dir_path = os.path.dirname(current_text)
|
||||||
|
if dir_path and os.path.exists(dir_path):
|
||||||
|
initial_dir = dir_path
|
||||||
|
|
||||||
if self.mode == "save":
|
if self.mode == "save":
|
||||||
file_path, _ = QFileDialog.getSaveFileName(
|
file_path, _ = QFileDialog.getSaveFileName(
|
||||||
|
|||||||
@ -85,11 +85,10 @@ class Step5_5Panel(QWidget):
|
|||||||
output_group = QGroupBox("输出设置")
|
output_group = QGroupBox("输出设置")
|
||||||
output_layout = QVBoxLayout()
|
output_layout = QVBoxLayout()
|
||||||
|
|
||||||
output_hbox = QHBoxLayout()
|
self.output_file_widget = FileSelectWidget(
|
||||||
output_hbox.addWidget(QLabel("输出文件名:"))
|
"输出文件:", "CSV Files (*.csv)", mode="save"
|
||||||
self.output_filename = QLineEdit("water_quality_indices.csv")
|
)
|
||||||
output_hbox.addWidget(self.output_filename)
|
output_layout.addWidget(self.output_file_widget)
|
||||||
output_layout.addLayout(output_hbox)
|
|
||||||
|
|
||||||
output_group.setLayout(output_layout)
|
output_group.setLayout(output_layout)
|
||||||
main_layout.addWidget(output_group)
|
main_layout.addWidget(output_group)
|
||||||
@ -258,11 +257,12 @@ class Step5_5Panel(QWidget):
|
|||||||
name for name, checkbox in self.index_checkboxes.items()
|
name for name, checkbox in self.index_checkboxes.items()
|
||||||
if checkbox.isChecked()
|
if checkbox.isChecked()
|
||||||
]
|
]
|
||||||
|
output_path = self.output_file_widget.get_path()
|
||||||
return {
|
return {
|
||||||
'training_spectra_path': self.training_data_widget.get_path() or None,
|
'training_spectra_path': self.training_data_widget.get_path() or None,
|
||||||
'formula_csv_file': self.formula_csv_widget.get_path() or None,
|
'formula_csv_file': self.formula_csv_widget.get_path() or None,
|
||||||
'formula_names': selected,
|
'formula_names': selected,
|
||||||
'output_filename': self.output_filename.text().strip() or "water_quality_indices.csv",
|
'output_file': output_path or None,
|
||||||
'enabled': self.enable_checkbox.isChecked()
|
'enabled': self.enable_checkbox.isChecked()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,7 +273,6 @@ class Step5_5Panel(QWidget):
|
|||||||
|
|
||||||
if 'formula_csv_file' in config:
|
if 'formula_csv_file' in config:
|
||||||
self.formula_csv_widget.set_path(config['formula_csv_file'])
|
self.formula_csv_widget.set_path(config['formula_csv_file'])
|
||||||
# 设置CSV路径后自动刷新公式信息
|
|
||||||
self.refresh_formulas()
|
self.refresh_formulas()
|
||||||
|
|
||||||
if 'formula_names' in config:
|
if 'formula_names' in config:
|
||||||
@ -281,8 +280,10 @@ class Step5_5Panel(QWidget):
|
|||||||
for name, checkbox in self.index_checkboxes.items():
|
for name, checkbox in self.index_checkboxes.items():
|
||||||
checkbox.setChecked(name in selected_formulas)
|
checkbox.setChecked(name in selected_formulas)
|
||||||
|
|
||||||
if 'output_filename' in config:
|
if 'output_file' in config and config['output_file']:
|
||||||
self.output_filename.setText(config['output_filename'])
|
self.output_file_widget.set_path(config['output_file'])
|
||||||
|
elif 'output_filename' in config and config['output_filename']:
|
||||||
|
self.output_file_widget.set_path(config['output_filename'])
|
||||||
|
|
||||||
if 'enabled' in config:
|
if 'enabled' in config:
|
||||||
self.enable_checkbox.setChecked(config['enabled'])
|
self.enable_checkbox.setChecked(config['enabled'])
|
||||||
@ -328,9 +329,11 @@ class Step5_5Panel(QWidget):
|
|||||||
if training_path:
|
if training_path:
|
||||||
self.training_data_widget.set_path(training_path)
|
self.training_data_widget.set_path(training_path)
|
||||||
|
|
||||||
# 2. 自动填充输出文件名(通用逻辑,由 run_step 根据输入文件名动态覆盖)
|
# 2. 自动填入输出文件的绝对路径
|
||||||
# 核心方法只接受文件名,不接受完整路径。
|
if self.work_dir:
|
||||||
# 保持默认值,run_step 会根据输入自动填入 _indices.csv 后缀
|
output_abs = os.path.join(self.work_dir, "6_water_quality_indices",
|
||||||
|
"training_spectra_indices.csv").replace('\\', '/')
|
||||||
|
self.output_file_widget.set_path(output_abs)
|
||||||
|
|
||||||
def is_enabled(self) -> bool:
|
def is_enabled(self) -> bool:
|
||||||
return self.enable_checkbox.isChecked()
|
return self.enable_checkbox.isChecked()
|
||||||
@ -348,7 +351,7 @@ class Step5_5Panel(QWidget):
|
|||||||
def run_step(self):
|
def run_step(self):
|
||||||
"""独立运行步骤5.5:计算水质指数。
|
"""独立运行步骤5.5:计算水质指数。
|
||||||
|
|
||||||
动态根据输入 CSV 文件名生成输出文件名,自动填入 output_filename 文本框。
|
动态根据输入 CSV 文件名生成输出文件名,自动填入 output_file_widget。
|
||||||
例如:training_spectra.csv → training_spectra_indices.csv
|
例如:training_spectra.csv → training_spectra_indices.csv
|
||||||
sampling_spectra.csv → sampling_spectra_indices.csv
|
sampling_spectra.csv → sampling_spectra_indices.csv
|
||||||
"""
|
"""
|
||||||
@ -369,10 +372,18 @@ class Step5_5Panel(QWidget):
|
|||||||
QMessageBox.warning(self, "输入验证失败", "公式CSV文件不存在")
|
QMessageBox.warning(self, "输入验证失败", "公式CSV文件不存在")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 动态生成输出文件名:自动拼接 _indices 后缀
|
# 动态生成输出文件:自动拼接 _indices 后缀
|
||||||
input_name = Path(training_csv_path).stem
|
input_name = Path(training_csv_path).stem
|
||||||
dynamic_output = f"{input_name}_indices.csv"
|
dynamic_output = f"{input_name}_indices.csv"
|
||||||
self.output_filename.setText(dynamic_output)
|
|
||||||
|
# 合成完整绝对路径(优先使用 work_dir,其次从 training_csv_path 推导)
|
||||||
|
work_dir = getattr(self, 'work_dir', None)
|
||||||
|
if work_dir:
|
||||||
|
dynamic_output = os.path.join(
|
||||||
|
work_dir, "6_water_quality_indices", dynamic_output
|
||||||
|
).replace('\\', '/')
|
||||||
|
|
||||||
|
self.output_file_widget.set_path(dynamic_output)
|
||||||
|
|
||||||
# 获取配置
|
# 获取配置
|
||||||
config = self.get_config()
|
config = self.get_config()
|
||||||
|
|||||||
@ -503,6 +503,42 @@ class ImageCategoryTree(QTreeWidget):
|
|||||||
("含量分布图", [], "📁"),
|
("含量分布图", [], "📁"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# 文件名中文翻译映射
|
||||||
|
NAME_MAPPING = {
|
||||||
|
"hsi_preview": "高光谱影像预览",
|
||||||
|
"hsi_original": "原始高光谱影像",
|
||||||
|
"hsi_deglint": "去耀斑高光谱影像",
|
||||||
|
"water_mask_overlay": "水域掩膜叠加图",
|
||||||
|
"water_mask": "水域掩膜图",
|
||||||
|
"glint_mask": "耀斑掩膜预览",
|
||||||
|
"glint_overlay": "耀斑叠加对比图",
|
||||||
|
"deglint_comparison": "去耀斑前后对比",
|
||||||
|
"training_spectra": "训练光谱特征",
|
||||||
|
"spectrum_by_param": "参数光谱图",
|
||||||
|
"model_evaluation": "模型评估散点图",
|
||||||
|
"model_scatter": "模型散点图",
|
||||||
|
"regression": "回归分析图",
|
||||||
|
"validation": "验证结果图",
|
||||||
|
"spatial_distribution": "参数空间分布图",
|
||||||
|
"distribution_map": "分布图",
|
||||||
|
"thematic_map": "水质专题图",
|
||||||
|
"water_quality_map": "水质分布图",
|
||||||
|
"prediction_map": "预测结果图",
|
||||||
|
"inversion_map": "反演结果图",
|
||||||
|
"correlation_matrix": "特征相关性矩阵",
|
||||||
|
"feature_correlation": "特征相关性",
|
||||||
|
"sampling_point_map": "采样点分布图",
|
||||||
|
"sampling_points": "采样点图",
|
||||||
|
"point_locations": "采样位置图",
|
||||||
|
"boxplot": "箱线图",
|
||||||
|
"histogram": "直方图",
|
||||||
|
"statistics": "统计图表",
|
||||||
|
"statistical_chart": "统计图",
|
||||||
|
"error_analysis": "误差分析图",
|
||||||
|
"rmse": "RMSE评估图",
|
||||||
|
"r2_score": "R²得分图",
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.setHeaderLabel("图像目录")
|
self.setHeaderLabel("图像目录")
|
||||||
@ -545,16 +581,18 @@ class ImageCategoryTree(QTreeWidget):
|
|||||||
category_item.removeChild(category_item.child(0))
|
category_item.removeChild(category_item.child(0))
|
||||||
|
|
||||||
def add_image(self, file_path: Path, display_name: str = None):
|
def add_image(self, file_path: Path, display_name: str = None):
|
||||||
"""添加图像到对应的类别"""
|
"""添加图像到对应的类别(带中文名称翻译)"""
|
||||||
if display_name is None:
|
if display_name is None:
|
||||||
display_name = file_path.stem
|
# 使用翻译映射,查询不到则用原文件名
|
||||||
|
file_base = file_path.stem
|
||||||
|
display_name = self.NAME_MAPPING.get(file_base, file_base)
|
||||||
|
|
||||||
category = self._determine_category(file_path.name)
|
category = self._determine_category(file_path.name)
|
||||||
category_item = self.category_items.get(category, self.category_items["含量分布图"])
|
category_item = self.category_items.get(category, self.category_items["含量分布图"])
|
||||||
|
|
||||||
image_item = QTreeWidgetItem(category_item)
|
image_item = QTreeWidgetItem(category_item)
|
||||||
image_item.setText(0, f" └─ {display_name}")
|
image_item.setText(0, f" └─ {display_name}")
|
||||||
image_item.setData(0, Qt.UserRole, {"type": "image", "path": str(file_path)})
|
image_item.setData(0, Qt.UserRole, {"type": "image", "path": str(file_path), "display_name": display_name})
|
||||||
image_item.setToolTip(0, str(file_path))
|
image_item.setToolTip(0, str(file_path))
|
||||||
|
|
||||||
return image_item
|
return image_item
|
||||||
@ -570,7 +608,7 @@ class ImageCategoryTree(QTreeWidget):
|
|||||||
return "含量分布图"
|
return "含量分布图"
|
||||||
|
|
||||||
def scan_directory(self, work_dir: str):
|
def scan_directory(self, work_dir: str):
|
||||||
"""扫描目录中的所有图像文件"""
|
"""扫描目录中的所有图像文件(深度递归扫描)"""
|
||||||
self.clear_all_images()
|
self.clear_all_images()
|
||||||
|
|
||||||
work_path = Path(work_dir)
|
work_path = Path(work_dir)
|
||||||
@ -578,13 +616,17 @@ class ImageCategoryTree(QTreeWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
image_extensions = ['*.png', '*.jpg', '*.jpeg', '*.tif', '*.tiff', '*.bmp']
|
image_extensions = ['*.png', '*.jpg', '*.jpeg', '*.tif', '*.tiff', '*.bmp']
|
||||||
scan_roots: List[Path] = []
|
|
||||||
_viz = work_path / "14_visualization"
|
# 扫描根目录列表(按优先级)
|
||||||
if _viz.is_dir():
|
scan_roots: List[Path] = [
|
||||||
scan_roots.append(_viz)
|
work_path / "14_visualization",
|
||||||
_wm = work_path / "1_water_mask"
|
work_path / "1_water_mask",
|
||||||
if _wm.is_dir():
|
work_path / "9_water_quality_prediction",
|
||||||
scan_roots.append(_wm)
|
work_path / "10_feature_construction",
|
||||||
|
]
|
||||||
|
|
||||||
|
# 只保留存在的目录,并补充工作根目录作为兜底
|
||||||
|
scan_roots = [p for p in scan_roots if p.is_dir()]
|
||||||
if not scan_roots:
|
if not scan_roots:
|
||||||
scan_roots.append(work_path)
|
scan_roots.append(work_path)
|
||||||
|
|
||||||
@ -592,7 +634,8 @@ class ImageCategoryTree(QTreeWidget):
|
|||||||
image_files: List[Path] = []
|
image_files: List[Path] = []
|
||||||
for root in scan_roots:
|
for root in scan_roots:
|
||||||
for ext in image_extensions:
|
for ext in image_extensions:
|
||||||
for p in root.glob(f"**/{ext}"):
|
# 使用 rglob 进行深度递归扫描
|
||||||
|
for p in root.rglob(ext):
|
||||||
key = os.path.normcase(os.path.normpath(str(p.resolve())))
|
key = os.path.normcase(os.path.normpath(str(p.resolve())))
|
||||||
if key in seen_norm:
|
if key in seen_norm:
|
||||||
continue
|
continue
|
||||||
|
|||||||
Reference in New Issue
Block a user