1303 lines
49 KiB
Python
1303 lines
49 KiB
Python
# -*- coding: utf-8 -*-
|
|
import sys
|
|
import os
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from PyQt5.QtWidgets import (
|
|
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QGroupBox,
|
|
QFormLayout, QLabel, QLineEdit, QPushButton, QFileDialog, QComboBox,
|
|
QCheckBox, QSpinBox, QDoubleSpinBox, QTabWidget, QMessageBox, QScrollArea,
|
|
QStackedWidget, QGridLayout, QDialog, QDialogButtonBox,
|
|
QListWidget, QListWidgetItem # 添加这两个
|
|
)
|
|
from PyQt5.QtCore import Qt
|
|
|
|
|
|
# ==================== 掩膜条件编辑器 ====================
|
|
class MaskConditionEditor(QWidget):
|
|
"""单个掩膜条件的编辑组件,支持类型选择和参数配置"""
|
|
|
|
# 掩膜类型及对应的参数定义
|
|
MASK_TYPES = {
|
|
"ndi": {
|
|
"params": [
|
|
("band_1", int, 550),
|
|
("band_2", int, 2150),
|
|
("min", float, -1.0),
|
|
("max", float, 0.0)
|
|
]
|
|
},
|
|
"ancillary": {
|
|
"params": [
|
|
("name", str, "slope"),
|
|
("min", float, 0.0),
|
|
("max", float, 1.0)
|
|
],
|
|
"name_choices": ["slope", "aspect", "cosine_i", "sensor_zn", "sensor_az",
|
|
"solar_zn", "solar_az", "phase", "path_length", "utc_time"]
|
|
},
|
|
"neon_edge": {
|
|
"params": [
|
|
("radius", int, 1)
|
|
]
|
|
},
|
|
"kernel_finite": {
|
|
"params": []
|
|
},
|
|
"water": {
|
|
"params": [
|
|
("band_1", int, 550),
|
|
("band_2", int, 850),
|
|
("threshold", float, 0.0)
|
|
]
|
|
},
|
|
"external": {
|
|
"params": [
|
|
("file_path", str, ""),
|
|
("class", int, 1)
|
|
]
|
|
},
|
|
"band": {
|
|
"params": [
|
|
("band", int, 650),
|
|
("min", float, 0.0),
|
|
("max", float, 1.0)
|
|
]
|
|
}
|
|
}
|
|
|
|
def __init__(self, condition=None, parent=None):
|
|
super().__init__(parent)
|
|
self.param_widgets = {} # 存储参数名对应的控件
|
|
self.init_ui()
|
|
if condition:
|
|
self.set_condition(condition)
|
|
|
|
def init_ui(self):
|
|
layout = QVBoxLayout(self)
|
|
layout.setContentsMargins(0, 0, 0, 5)
|
|
|
|
# 类型选择行
|
|
type_layout = QHBoxLayout()
|
|
self.type_combo = QComboBox()
|
|
self.type_combo.addItems(list(self.MASK_TYPES.keys()))
|
|
self.type_combo.currentTextChanged.connect(self.on_type_changed)
|
|
type_layout.addWidget(QLabel("类型:"))
|
|
type_layout.addWidget(self.type_combo)
|
|
|
|
self.delete_btn = QPushButton("删除")
|
|
self.delete_btn.setFixedWidth(60)
|
|
type_layout.addStretch()
|
|
type_layout.addWidget(self.delete_btn)
|
|
layout.addLayout(type_layout)
|
|
|
|
# 参数区域(动态切换)
|
|
self.param_stack = QStackedWidget()
|
|
layout.addWidget(self.param_stack)
|
|
|
|
# 为每种类型创建一个参数面板
|
|
self.param_panels = {}
|
|
for mask_type, info in self.MASK_TYPES.items():
|
|
panel = self.create_param_panel(mask_type, info["params"])
|
|
self.param_panels[mask_type] = panel
|
|
self.param_stack.addWidget(panel)
|
|
|
|
self.on_type_changed(self.type_combo.currentText())
|
|
|
|
def create_param_panel(self, mask_type, params):
|
|
"""创建参数输入面板,返回 QWidget"""
|
|
widget = QWidget()
|
|
layout = QFormLayout(widget)
|
|
layout.setContentsMargins(10, 5, 10, 5)
|
|
|
|
for param_name, param_type, default_value in params:
|
|
if param_type == int:
|
|
if "band" in param_name or "radius" in param_name:
|
|
spin = QSpinBox()
|
|
spin.setRange(0, 3000)
|
|
spin.setValue(default_value)
|
|
param_widget = spin
|
|
else:
|
|
spin = QSpinBox()
|
|
spin.setRange(-10000, 10000)
|
|
spin.setValue(default_value)
|
|
param_widget = spin
|
|
elif param_type == float:
|
|
dspin = QDoubleSpinBox()
|
|
dspin.setRange(-1e6, 1e6)
|
|
dspin.setDecimals(6)
|
|
dspin.setValue(default_value)
|
|
param_widget = dspin
|
|
else: # str
|
|
if param_name == "name" and "name_choices" in self.MASK_TYPES[mask_type]:
|
|
combo = QComboBox()
|
|
combo.addItems(self.MASK_TYPES[mask_type]["name_choices"])
|
|
combo.setCurrentText(default_value)
|
|
param_widget = combo
|
|
elif param_name == "method" and "method_choices" in self.MASK_TYPES[mask_type]:
|
|
combo = QComboBox()
|
|
combo.addItems(self.MASK_TYPES[mask_type]["method_choices"])
|
|
combo.setCurrentText(default_value)
|
|
param_widget = combo
|
|
elif param_name == "file_path":
|
|
container = QWidget()
|
|
hbox = QHBoxLayout(container)
|
|
hbox.setContentsMargins(0, 0, 0, 0)
|
|
line_edit = QLineEdit(default_value)
|
|
btn = QPushButton("浏览")
|
|
btn.clicked.connect(lambda: self.browse_file(line_edit))
|
|
hbox.addWidget(line_edit)
|
|
hbox.addWidget(btn)
|
|
param_widget = container
|
|
# 存储 line_edit 以便获取值
|
|
setattr(param_widget, "line_edit", line_edit)
|
|
else:
|
|
line = QLineEdit(str(default_value))
|
|
param_widget = line
|
|
|
|
self.param_widgets[(mask_type, param_name)] = param_widget
|
|
label = param_name.replace("_", " ").title()
|
|
layout.addRow(f"{label}:", param_widget)
|
|
|
|
return widget
|
|
|
|
def browse_file(self, line_edit):
|
|
file_path, _ = QFileDialog.getOpenFileName(self, "选择外部掩膜文件")
|
|
if file_path:
|
|
line_edit.setText(file_path)
|
|
|
|
def on_type_changed(self, mask_type):
|
|
"""切换掩膜类型时显示对应的参数面板"""
|
|
idx = self.param_stack.indexOf(self.param_panels[mask_type])
|
|
if idx >= 0:
|
|
self.param_stack.setCurrentIndex(idx)
|
|
|
|
def get_condition(self):
|
|
"""返回当前条件的列表格式,如 ['ndi', {'band_1':550, ...}]"""
|
|
mask_type = self.type_combo.currentText()
|
|
params = {}
|
|
for (mt, param_name), widget in self.param_widgets.items():
|
|
if mt != mask_type:
|
|
continue
|
|
# 获取值
|
|
if isinstance(widget, QSpinBox):
|
|
value = widget.value()
|
|
elif isinstance(widget, QDoubleSpinBox):
|
|
value = widget.value()
|
|
elif isinstance(widget, QComboBox):
|
|
value = widget.currentText()
|
|
elif hasattr(widget, "line_edit"): # 文件路径容器
|
|
value = widget.line_edit.text()
|
|
elif isinstance(widget, QLineEdit):
|
|
value = widget.text()
|
|
else:
|
|
continue
|
|
# 特殊处理 external 的 class 参数(关键字 class 与 Python 冲突)
|
|
if param_name == "class":
|
|
param_name = "class"
|
|
params[param_name] = value
|
|
|
|
# 对于 kernel_finite 没有参数
|
|
if mask_type == "kernel_finite":
|
|
return [mask_type, {}]
|
|
return [mask_type, params]
|
|
|
|
def set_condition(self, condition):
|
|
"""从已有的条件(列表格式)填充界面"""
|
|
if not isinstance(condition, list) or len(condition) < 2:
|
|
return
|
|
mask_type = condition[0]
|
|
params = condition[1]
|
|
if mask_type not in self.MASK_TYPES:
|
|
return
|
|
self.type_combo.setCurrentText(mask_type)
|
|
# 设置参数值
|
|
for param_name, value in params.items():
|
|
# 处理关键字 class
|
|
if param_name == "class":
|
|
param_name = "class"
|
|
widget = self.param_widgets.get((mask_type, param_name))
|
|
if widget is None:
|
|
continue
|
|
if isinstance(widget, QSpinBox):
|
|
widget.setValue(int(value))
|
|
elif isinstance(widget, QDoubleSpinBox):
|
|
widget.setValue(float(value))
|
|
elif isinstance(widget, QComboBox):
|
|
idx = widget.findText(str(value))
|
|
if idx >= 0:
|
|
widget.setCurrentIndex(idx)
|
|
elif hasattr(widget, "line_edit"):
|
|
widget.line_edit.setText(str(value))
|
|
elif isinstance(widget, QLineEdit):
|
|
widget.setText(str(value))
|
|
|
|
|
|
class MaskListWidget(QWidget):
|
|
"""管理一组掩膜条件的列表,支持添加、删除、清空"""
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.layout = QVBoxLayout(self)
|
|
self.layout.setContentsMargins(0, 0, 0, 0)
|
|
self.layout.setSpacing(5)
|
|
self.conditions = [] # 存储 MaskConditionEditor 控件
|
|
|
|
# 添加按钮
|
|
add_btn = QPushButton("+ 添加掩膜条件")
|
|
add_btn.clicked.connect(self.add_condition)
|
|
self.layout.addWidget(add_btn)
|
|
|
|
# 容器用于存放条件编辑器
|
|
self.conditions_container = QWidget()
|
|
self.conditions_layout = QVBoxLayout(self.conditions_container)
|
|
self.conditions_layout.setContentsMargins(0, 0, 0, 0)
|
|
self.layout.addWidget(self.conditions_container)
|
|
|
|
def add_condition(self, condition=None):
|
|
editor = MaskConditionEditor(condition)
|
|
editor.delete_btn.clicked.connect(lambda: self.remove_condition(editor))
|
|
self.conditions.append(editor)
|
|
self.conditions_layout.addWidget(editor)
|
|
return editor
|
|
|
|
def remove_condition(self, editor):
|
|
if editor in self.conditions:
|
|
self.conditions.remove(editor)
|
|
editor.deleteLater()
|
|
self.conditions_layout.removeWidget(editor)
|
|
|
|
def clear(self):
|
|
for editor in self.conditions[:]:
|
|
self.remove_condition(editor)
|
|
|
|
def get_conditions(self):
|
|
return [editor.get_condition() for editor in self.conditions]
|
|
|
|
def set_conditions(self, conditions_list):
|
|
self.clear()
|
|
for cond in conditions_list:
|
|
self.add_condition(cond)
|
|
|
|
|
|
# ==================== 主窗口 ====================
|
|
class MainWindow(QMainWindow):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.setWindowTitle("BRDF / Topo / Glint 校正配置工具")
|
|
self.setMinimumSize(1100, 800)
|
|
|
|
# 存储匹配后的文件对列表
|
|
self.file_pairs = [] # [(rfl_path, obs_path), ...]
|
|
# 存储每个输入文件对应的辅助文件波段映射
|
|
self.band_mappings = {} # {rfl_path: {param_name: band_index}}
|
|
|
|
# 设置全局 NVIDIA 风格样式表
|
|
self.setStyleSheet("""
|
|
QMainWindow {
|
|
background-color: #000000;
|
|
}
|
|
QWidget {
|
|
background-color: #000000;
|
|
color: #ffffff;
|
|
font-family: Arial, Helvetica, sans-serif;
|
|
font-size: 14px;
|
|
}
|
|
QTabWidget::pane {
|
|
border: 2px solid #76b900;
|
|
border-radius: 2px;
|
|
background-color: #000000;
|
|
padding: 10px;
|
|
}
|
|
QTabBar::tab {
|
|
background-color: #1a1a1a;
|
|
color: #ffffff;
|
|
border: 2px solid #5e5e5e;
|
|
border-radius: 2px;
|
|
padding: 10px 20px;
|
|
margin-right: 5px;
|
|
font-weight: 700;
|
|
font-size: 14px;
|
|
}
|
|
QTabBar::tab:selected {
|
|
background-color: #76b900;
|
|
color: #000000;
|
|
border: 2px solid #76b900;
|
|
}
|
|
QTabBar::tab:hover:!selected {
|
|
background-color: #3860be;
|
|
border: 2px solid #3860be;
|
|
}
|
|
QPushButton {
|
|
background-color: transparent;
|
|
color: #ffffff;
|
|
border: 2px solid #76b900;
|
|
border-radius: 2px;
|
|
padding: 8px 16px;
|
|
font-weight: 700;
|
|
font-size: 14px;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #1eaedb;
|
|
color: #ffffff;
|
|
}
|
|
QPushButton:pressed {
|
|
background-color: #007fff;
|
|
color: #ffffff;
|
|
border: 1px solid #003eff;
|
|
}
|
|
QLineEdit {
|
|
background-color: #1a1a1a;
|
|
color: #ffffff;
|
|
border: 1px solid #5e5e5e;
|
|
border-radius: 2px;
|
|
padding: 5px;
|
|
}
|
|
QLineEdit:focus {
|
|
border: 1px solid #76b900;
|
|
}
|
|
QListWidget {
|
|
background-color: #1a1a1a;
|
|
color: #ffffff;
|
|
border: 1px solid #5e5e5e;
|
|
border-radius: 2px;
|
|
padding: 5px;
|
|
}
|
|
QListWidget::item {
|
|
padding: 5px;
|
|
}
|
|
QListWidget::item:selected {
|
|
background-color: #76b900;
|
|
color: #000000;
|
|
}
|
|
QListWidget::item:hover {
|
|
background-color: #3860be;
|
|
}
|
|
QGroupBox {
|
|
background-color: #1a1a1a;
|
|
border: 1px solid #5e5e5e;
|
|
border-radius: 2px;
|
|
margin-top: 10px;
|
|
padding-top: 10px;
|
|
font-weight: 700;
|
|
}
|
|
QGroupBox::title {
|
|
subcontrol-origin: margin;
|
|
left: 10px;
|
|
padding: 0 5px;
|
|
color: #ffffff;
|
|
}
|
|
QLabel {
|
|
color: #ffffff;
|
|
font-size: 14px;
|
|
}
|
|
QScrollArea {
|
|
border: none;
|
|
background-color: #000000;
|
|
}
|
|
""")
|
|
|
|
central_widget = QWidget()
|
|
self.setCentralWidget(central_widget)
|
|
main_layout = QVBoxLayout(central_widget)
|
|
main_layout.setSpacing(15)
|
|
main_layout.setContentsMargins(20, 20, 20, 20)
|
|
|
|
tabs = QTabWidget()
|
|
tabs.setDocumentMode(True)
|
|
main_layout.addWidget(tabs)
|
|
|
|
# 1. 文件与波段映射页
|
|
self.file_tab = QWidget()
|
|
tabs.addTab(self.file_tab, "文件与波段映射")
|
|
self.setup_file_tab()
|
|
|
|
# 2. 校正参数页
|
|
self.corr_tab = QWidget()
|
|
tabs.addTab(self.corr_tab, "校正参数")
|
|
self.setup_corr_tab()
|
|
|
|
# 3. 输出设置页
|
|
self.output_tab = QWidget()
|
|
tabs.addTab(self.output_tab, "输出设置")
|
|
self.setup_output_tab()
|
|
|
|
# 底部按钮
|
|
btn_layout = QHBoxLayout()
|
|
load_btn = QPushButton("加载 JSON 配置")
|
|
load_btn.clicked.connect(self.load_config)
|
|
save_btn = QPushButton("保存 JSON 配置")
|
|
save_btn.clicked.connect(self.save_config)
|
|
btn_layout.addWidget(load_btn)
|
|
btn_layout.addWidget(save_btn)
|
|
main_layout.addLayout(btn_layout)
|
|
|
|
# ==================== 文件与波段映射页 ====================
|
|
def setup_file_tab(self):
|
|
layout = QVBoxLayout(self.file_tab)
|
|
layout.setSpacing(15)
|
|
layout.setContentsMargins(15, 15, 15, 15)
|
|
|
|
# 文件夹设置组 - 使用 NVIDIA 绿色主题
|
|
folder_group = QGroupBox("文件夹设置 (Folder Settings)")
|
|
folder_group.setStyleSheet("""
|
|
QGroupBox {
|
|
background-color: #1a1a1a;
|
|
border: 2px solid #76b900;
|
|
border-radius: 2px;
|
|
color: #ffffff;
|
|
font-weight: 700;
|
|
font-size: 14px;
|
|
padding: 10px;
|
|
margin-top: 10px;
|
|
}
|
|
QGroupBox::title {
|
|
subcontrol-origin: margin;
|
|
left: 10px;
|
|
padding: 5px 10px;
|
|
color: #000000;
|
|
background-color: #76b900;
|
|
border-radius: 2px;
|
|
font-weight: 700;
|
|
}
|
|
QLabel {
|
|
color: #ffffff;
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
}
|
|
""")
|
|
folder_layout = QFormLayout()
|
|
folder_layout.setVerticalSpacing(10)
|
|
folder_layout.setHorizontalSpacing(10)
|
|
self.input_folder_edit = QLineEdit()
|
|
self.input_folder_edit.setPlaceholderText("选择反射率数据文件夹...")
|
|
self.anc_folder_edit = QLineEdit()
|
|
self.anc_folder_edit.setPlaceholderText("选择观测几何数据文件夹...")
|
|
btn_input = QPushButton("浏览...")
|
|
btn_anc = QPushButton("浏览...")
|
|
btn_input.clicked.connect(lambda: self.select_folder(self.input_folder_edit))
|
|
btn_anc.clicked.connect(lambda: self.select_folder(self.anc_folder_edit))
|
|
folder_layout.addRow("输入文件夹 (反射率):", self._horizontal_widget(self.input_folder_edit, btn_input))
|
|
folder_layout.addRow("辅助文件夹 (观测几何):", self._horizontal_widget(self.anc_folder_edit, btn_anc))
|
|
folder_group.setLayout(folder_layout)
|
|
layout.addWidget(folder_group)
|
|
|
|
# 匹配按钮
|
|
match_btn = QPushButton("自动匹配文件对 (Auto Match File Pairs)")
|
|
match_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: transparent;
|
|
color: #ffffff;
|
|
border: 2px solid #76b900;
|
|
border-radius: 2px;
|
|
padding: 10px 20px;
|
|
font-weight: 700;
|
|
font-size: 14px;
|
|
margin-top: 10px;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #1eaedb;
|
|
}
|
|
""")
|
|
match_btn.clicked.connect(self.match_files)
|
|
layout.addWidget(match_btn)
|
|
|
|
# 文件列表
|
|
list_label = QLabel("匹配的文件对 (点击编辑波段映射):")
|
|
list_label.setStyleSheet("color: #a7a7a7; font-size: 13px; margin-top: 10px;")
|
|
layout.addWidget(list_label)
|
|
|
|
self.file_list_widget = QListWidget()
|
|
self.file_list_widget.setSelectionMode(QListWidget.SingleSelection)
|
|
self.file_list_widget.itemClicked.connect(self.on_file_selected)
|
|
self.file_list_widget.setStyleSheet("""
|
|
QListWidget {
|
|
background-color: #1a1a1a;
|
|
color: #ffffff;
|
|
border: 1px solid #5e5e5e;
|
|
border-radius: 2px;
|
|
padding: 5px;
|
|
}
|
|
QListWidget::item {
|
|
padding: 8px;
|
|
border-bottom: 1px solid #5e5e5e;
|
|
}
|
|
QListWidget::item:selected {
|
|
background-color: #76b900;
|
|
color: #000000;
|
|
}
|
|
QListWidget::item:hover {
|
|
background-color: #3860be;
|
|
}
|
|
""")
|
|
layout.addWidget(self.file_list_widget)
|
|
|
|
# 波段映射组 - 使用蓝色主题
|
|
mapping_group = QGroupBox("当前选中文件的辅助文件波段索引 (0-based)")
|
|
mapping_group.setStyleSheet("""
|
|
QGroupBox {
|
|
background-color: #1a1a1a;
|
|
border: 2px solid #0046a4;
|
|
border-radius: 2px;
|
|
color: #ffffff;
|
|
font-weight: 700;
|
|
font-size: 14px;
|
|
padding: 10px;
|
|
margin-top: 10px;
|
|
}
|
|
QGroupBox::title {
|
|
subcontrol-origin: margin;
|
|
left: 10px;
|
|
padding: 5px 10px;
|
|
color: #ffffff;
|
|
background-color: #0046a4;
|
|
border-radius: 2px;
|
|
font-weight: 700;
|
|
}
|
|
QLabel {
|
|
color: #ffffff;
|
|
font-size: 13px;
|
|
font-weight: 700;
|
|
}
|
|
QSpinBox {
|
|
background-color: #000000;
|
|
color: #ffffff;
|
|
border: 1px solid #0046a4;
|
|
border-radius: 2px;
|
|
padding: 3px;
|
|
min-width: 80px;
|
|
}
|
|
""")
|
|
mapping_layout = QFormLayout()
|
|
mapping_layout.setVerticalSpacing(8)
|
|
self.param_spinboxes = {}
|
|
param_names = [
|
|
"path_length", "sensor_az", "sensor_zn", "solar_az", "solar_zn",
|
|
"phase", "slope", "aspect", "cosine_i", "utc_time"
|
|
]
|
|
for name in param_names:
|
|
spin = QSpinBox()
|
|
spin.setRange(0, 1000)
|
|
spin.setValue(0)
|
|
self.param_spinboxes[name] = spin
|
|
mapping_layout.addRow(f"{name}:", spin)
|
|
mapping_group.setLayout(mapping_layout)
|
|
layout.addWidget(mapping_group)
|
|
|
|
self.current_rfl_path = None
|
|
|
|
def _horizontal_widget(self, line_edit, button):
|
|
w = QWidget()
|
|
hbox = QHBoxLayout(w)
|
|
hbox.setContentsMargins(0, 0, 0, 0)
|
|
hbox.addWidget(line_edit)
|
|
hbox.addWidget(button)
|
|
return w
|
|
|
|
def select_folder(self, line_edit):
|
|
folder = QFileDialog.getExistingDirectory(self, "选择文件夹")
|
|
if folder:
|
|
line_edit.setText(folder)
|
|
|
|
def match_files(self):
|
|
input_dir = self.input_folder_edit.text()
|
|
anc_dir = self.anc_folder_edit.text()
|
|
if not input_dir or not anc_dir:
|
|
QMessageBox.warning(self, "警告", "请先选择输入文件夹和辅助文件夹")
|
|
return
|
|
|
|
input_files = []
|
|
for f in os.listdir(input_dir):
|
|
if f.endswith("_rfl") or (os.path.isdir(os.path.join(input_dir, f)) and f.endswith("_rfl")):
|
|
input_files.append(os.path.join(input_dir, f))
|
|
|
|
anc_files = {}
|
|
for f in os.listdir(anc_dir):
|
|
if f.endswith("_obs"):
|
|
anc_files[f[:-4]] = os.path.join(anc_dir, f)
|
|
|
|
self.file_pairs = []
|
|
self.file_list_widget.clear()
|
|
for rfl_path in input_files:
|
|
basename = os.path.basename(rfl_path)
|
|
key = basename[:-4] if basename.endswith("_rfl") else basename
|
|
if key in anc_files:
|
|
self.file_pairs.append((rfl_path, anc_files[key]))
|
|
self.file_list_widget.addItem(f"{basename} -> {os.path.basename(anc_files[key])}")
|
|
else:
|
|
self.file_list_widget.addItem(f"{basename} -> (未找到匹配的 obs 文件)")
|
|
|
|
if self.file_pairs:
|
|
self.file_list_widget.setCurrentRow(0)
|
|
self.on_file_selected(self.file_list_widget.item(0))
|
|
|
|
def on_file_selected(self, item):
|
|
idx = self.file_list_widget.row(item)
|
|
if idx >= len(self.file_pairs):
|
|
return
|
|
rfl_path, obs_path = self.file_pairs[idx]
|
|
self.current_rfl_path = rfl_path
|
|
if rfl_path in self.band_mappings:
|
|
mapping = self.band_mappings[rfl_path]
|
|
for param, spin in self.param_spinboxes.items():
|
|
spin.setValue(mapping.get(param, 0))
|
|
else:
|
|
for spin in self.param_spinboxes.values():
|
|
spin.setValue(0)
|
|
|
|
for param, spin in self.param_spinboxes.items():
|
|
try:
|
|
spin.valueChanged.disconnect()
|
|
except:
|
|
pass
|
|
spin.valueChanged.connect(lambda val, p=param: self.update_band_mapping(p, val))
|
|
|
|
def update_band_mapping(self, param, value):
|
|
if self.current_rfl_path is None:
|
|
return
|
|
if self.current_rfl_path not in self.band_mappings:
|
|
self.band_mappings[self.current_rfl_path] = {}
|
|
self.band_mappings[self.current_rfl_path][param] = value
|
|
|
|
# ==================== 校正参数页 ====================
|
|
def setup_corr_tab(self):
|
|
scroll = QScrollArea()
|
|
scroll.setWidgetResizable(True)
|
|
container = QWidget()
|
|
scroll.setWidget(container)
|
|
layout = QVBoxLayout(container)
|
|
layout.setSpacing(20)
|
|
|
|
# NVIDIA 设计风格样式表
|
|
# Topo 组 - 蓝色系 (#0046a4)
|
|
# BRDF 组 - NVIDIA 绿色 (#76b900)
|
|
# Glint 组 - 橙色系 (#df6500)
|
|
|
|
# 1. Corrections 选择
|
|
corr_group = QGroupBox("需要执行的校正")
|
|
corr_group.setStyleSheet("""
|
|
QGroupBox {
|
|
background-color: #1a1a1a;
|
|
border: 2px solid #76b900;
|
|
border-radius: 2px;
|
|
color: #ffffff;
|
|
font-weight: 700;
|
|
font-size: 14px;
|
|
padding: 10px;
|
|
margin-top: 10px;
|
|
}
|
|
QGroupBox::title {
|
|
subcontrol-origin: margin;
|
|
left: 10px;
|
|
padding: 0 5px;
|
|
color: #76b900;
|
|
}
|
|
QCheckBox {
|
|
color: #ffffff;
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
spacing: 8px;
|
|
}
|
|
QCheckBox::indicator {
|
|
width: 18px;
|
|
height: 18px;
|
|
border: 2px solid #76b900;
|
|
border-radius: 2px;
|
|
background-color: transparent;
|
|
}
|
|
QCheckBox::indicator:checked {
|
|
background-color: #76b900;
|
|
}
|
|
""")
|
|
corr_layout = QHBoxLayout()
|
|
self.corr_topo_cb = QCheckBox("Topo (地形校正)")
|
|
self.corr_brdf_cb = QCheckBox("BRDF (双向反射分布函数)")
|
|
self.corr_glint_cb = QCheckBox("Glint (耀斑校正)")
|
|
corr_layout.addWidget(self.corr_topo_cb)
|
|
corr_layout.addWidget(self.corr_brdf_cb)
|
|
corr_layout.addWidget(self.corr_glint_cb)
|
|
corr_group.setLayout(corr_layout)
|
|
layout.addWidget(corr_group)
|
|
|
|
# 2. Topo 参数 - 蓝色主题 (#0046a4)
|
|
topo_group = QGroupBox("地形校正 (Topo Correction)")
|
|
topo_group.setStyleSheet("""
|
|
QGroupBox {
|
|
background-color: #1a1a1a;
|
|
border: 2px solid #0046a4;
|
|
border-radius: 2px;
|
|
color: #ffffff;
|
|
font-weight: 700;
|
|
font-size: 14px;
|
|
padding: 10px;
|
|
margin-top: 15px;
|
|
}
|
|
QGroupBox::title {
|
|
subcontrol-origin: margin;
|
|
left: 10px;
|
|
padding: 5px 10px;
|
|
color: #ffffff;
|
|
background-color: #0046a4;
|
|
border-radius: 2px;
|
|
font-weight: 700;
|
|
font-size: 14px;
|
|
}
|
|
QLabel {
|
|
color: #ffffff;
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
}
|
|
QComboBox {
|
|
background-color: #000000;
|
|
color: #ffffff;
|
|
border: 1px solid #0046a4;
|
|
border-radius: 2px;
|
|
padding: 5px;
|
|
min-width: 150px;
|
|
}
|
|
QComboBox::drop-down {
|
|
border: none;
|
|
width: 25px;
|
|
}
|
|
QComboBox::down-arrow {
|
|
image: none;
|
|
border-left: 5px solid transparent;
|
|
border-right: 5px solid transparent;
|
|
border-top: 6px solid #0046a4;
|
|
margin-right: 8px;
|
|
}
|
|
QComboBox QAbstractItemView {
|
|
background-color: #1a1a1a;
|
|
color: #ffffff;
|
|
border: 1px solid #0046a4;
|
|
selection-background-color: #0046a4;
|
|
}
|
|
""")
|
|
topo_layout = QVBoxLayout()
|
|
topo_layout.setSpacing(10)
|
|
# type
|
|
type_layout = QHBoxLayout()
|
|
type_label = QLabel("类型 (type):")
|
|
type_layout.addWidget(type_label)
|
|
self.topo_type = QComboBox()
|
|
self.topo_type.addItems(["mod_minneart", "scs+c", "cosine", "c", "scs"])
|
|
type_layout.addWidget(self.topo_type)
|
|
type_layout.addStretch()
|
|
topo_layout.addLayout(type_layout)
|
|
# c_fit_type
|
|
fit_layout = QHBoxLayout()
|
|
fit_label = QLabel("拟合类型 (c_fit_type):")
|
|
fit_layout.addWidget(fit_label)
|
|
self.c_fit_type = QComboBox()
|
|
self.c_fit_type.addItems(["nnls", "ols", "wls"])
|
|
fit_layout.addWidget(self.c_fit_type)
|
|
fit_layout.addStretch()
|
|
topo_layout.addLayout(fit_layout)
|
|
# 掩膜列表
|
|
calc_mask_label = QLabel("计算掩膜条件 (calc_mask - 取交集):")
|
|
calc_mask_label.setStyleSheet("color: #a7a7a7; font-size: 13px;")
|
|
topo_layout.addWidget(calc_mask_label)
|
|
self.topo_calc_mask_list = MaskListWidget()
|
|
topo_layout.addWidget(self.topo_calc_mask_list)
|
|
apply_mask_label = QLabel("应用掩膜条件 (apply_mask - 取交集):")
|
|
apply_mask_label.setStyleSheet("color: #a7a7a7; font-size: 13px;")
|
|
topo_layout.addWidget(apply_mask_label)
|
|
self.topo_apply_mask_list = MaskListWidget()
|
|
topo_layout.addWidget(self.topo_apply_mask_list)
|
|
topo_group.setLayout(topo_layout)
|
|
layout.addWidget(topo_group)
|
|
|
|
# 3. BRDF 参数 - NVIDIA 绿色主题 (#76b900)
|
|
brdf_group = QGroupBox("BRDF 校正 (BRDF Correction)")
|
|
brdf_group.setStyleSheet("""
|
|
QGroupBox {
|
|
background-color: #1a1a1a;
|
|
border: 2px solid #76b900;
|
|
border-radius: 2px;
|
|
color: #ffffff;
|
|
font-weight: 700;
|
|
font-size: 14px;
|
|
padding: 10px;
|
|
margin-top: 15px;
|
|
}
|
|
QGroupBox::title {
|
|
subcontrol-origin: margin;
|
|
left: 10px;
|
|
padding: 5px 10px;
|
|
color: #000000;
|
|
background-color: #76b900;
|
|
border-radius: 2px;
|
|
font-weight: 700;
|
|
font-size: 14px;
|
|
}
|
|
QLabel {
|
|
color: #ffffff;
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
}
|
|
QComboBox {
|
|
background-color: #000000;
|
|
color: #ffffff;
|
|
border: 1px solid #76b900;
|
|
border-radius: 2px;
|
|
padding: 5px;
|
|
min-width: 150px;
|
|
}
|
|
QComboBox::drop-down {
|
|
border: none;
|
|
width: 25px;
|
|
}
|
|
QComboBox::down-arrow {
|
|
image: none;
|
|
border-left: 5px solid transparent;
|
|
border-right: 5px solid transparent;
|
|
border-top: 6px solid #76b900;
|
|
margin-right: 8px;
|
|
}
|
|
QComboBox QAbstractItemView {
|
|
background-color: #1a1a1a;
|
|
color: #ffffff;
|
|
border: 1px solid #76b900;
|
|
selection-background-color: #76b900;
|
|
selection-color: #000000;
|
|
}
|
|
QCheckBox {
|
|
color: #ffffff;
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
spacing: 8px;
|
|
}
|
|
QCheckBox::indicator {
|
|
width: 18px;
|
|
height: 18px;
|
|
border: 2px solid #76b900;
|
|
border-radius: 2px;
|
|
background-color: transparent;
|
|
}
|
|
QCheckBox::indicator:checked {
|
|
background-color: #76b900;
|
|
}
|
|
QDoubleSpinBox, QSpinBox {
|
|
background-color: #000000;
|
|
color: #ffffff;
|
|
border: 1px solid #76b900;
|
|
border-radius: 2px;
|
|
padding: 5px;
|
|
}
|
|
""")
|
|
brdf_layout = QFormLayout()
|
|
brdf_layout.setVerticalSpacing(10)
|
|
self.brdf_type = QComboBox()
|
|
self.brdf_type.addItems(["flex", "universal"])
|
|
brdf_layout.addRow("算法类型 (type):", self.brdf_type)
|
|
self.brdf_grouped = QCheckBox("启用分组 (grouped)")
|
|
self.brdf_grouped.setChecked(True)
|
|
brdf_layout.addRow("", self.brdf_grouped)
|
|
self.geometric_kernel = QComboBox()
|
|
self.geometric_kernel.addItems(["li_sparse", "li_dense", "li_sparse_r", "li_dense_r", "roujean"])
|
|
brdf_layout.addRow("几何核 (geometric):", self.geometric_kernel)
|
|
self.volume_kernel = QComboBox()
|
|
self.volume_kernel.addItems(["ross_thin", "ross_thick", "hotspot", "roujean"])
|
|
brdf_layout.addRow("体积核 (volume):", self.volume_kernel)
|
|
self.br_b_ratio = QDoubleSpinBox()
|
|
self.br_b_ratio.setRange(0.1, 10.0)
|
|
self.br_b_ratio.setValue(2.5)
|
|
self.br_b_ratio.setDecimals(2)
|
|
brdf_layout.addRow("b/r 比率:", self.br_b_ratio)
|
|
self.hb_ratio = QDoubleSpinBox()
|
|
self.hb_ratio.setRange(0.1, 5.0)
|
|
self.hb_ratio.setValue(2.0)
|
|
self.hb_ratio.setDecimals(2)
|
|
brdf_layout.addRow("h/b 比率:", self.hb_ratio)
|
|
self.sample_perc = QDoubleSpinBox()
|
|
self.sample_perc.setRange(0.01, 1.0)
|
|
self.sample_perc.setValue(0.1)
|
|
self.sample_perc.setDecimals(2)
|
|
brdf_layout.addRow("采样比例 (sample_perc):", self.sample_perc)
|
|
self.interp_kind = QComboBox()
|
|
self.interp_kind.addItems(["linear", "nearest", "cubic"])
|
|
brdf_layout.addRow("插值方法 (interp_kind):", self.interp_kind)
|
|
# BRDF 掩膜
|
|
calc_mask_label_brdf = QLabel("计算掩膜条件 (calc_mask):")
|
|
calc_mask_label_brdf.setStyleSheet("color: #a7a7a7; font-size: 13px;")
|
|
brdf_layout.addRow(calc_mask_label_brdf)
|
|
self.brdf_calc_mask_list = MaskListWidget()
|
|
brdf_layout.addRow(self.brdf_calc_mask_list)
|
|
apply_mask_label_brdf = QLabel("应用掩膜条件 (apply_mask):")
|
|
apply_mask_label_brdf.setStyleSheet("color: #a7a7a7; font-size: 13px;")
|
|
brdf_layout.addRow(apply_mask_label_brdf)
|
|
self.brdf_apply_mask_list = MaskListWidget()
|
|
brdf_layout.addRow(self.brdf_apply_mask_list)
|
|
# bin_type 等
|
|
self.bin_type = QComboBox()
|
|
self.bin_type.addItems(["dynamic", "fixed"])
|
|
brdf_layout.addRow("分箱类型 (bin_type):", self.bin_type)
|
|
self.num_bins = QSpinBox()
|
|
self.num_bins.setRange(1, 100)
|
|
self.num_bins.setValue(18)
|
|
brdf_layout.addRow("分箱数量 (num_bins):", self.num_bins)
|
|
self.ndvi_bin_min = QDoubleSpinBox()
|
|
self.ndvi_bin_min.setRange(0.0, 1.0)
|
|
self.ndvi_bin_min.setValue(0.05)
|
|
self.ndvi_bin_min.setDecimals(2)
|
|
brdf_layout.addRow("NDVI 最小值 (ndvi_bin_min):", self.ndvi_bin_min)
|
|
self.ndvi_bin_max = QDoubleSpinBox()
|
|
self.ndvi_bin_max.setRange(0.0, 1.0)
|
|
self.ndvi_bin_max.setValue(1.0)
|
|
self.ndvi_bin_max.setDecimals(2)
|
|
brdf_layout.addRow("NDVI 最大值 (ndvi_bin_max):", self.ndvi_bin_max)
|
|
self.ndvi_perc_min = QSpinBox()
|
|
self.ndvi_perc_min.setRange(0, 100)
|
|
self.ndvi_perc_min.setValue(10)
|
|
brdf_layout.addRow("NDVI 百分比最小值 (ndvi_perc_min):", self.ndvi_perc_min)
|
|
self.ndvi_perc_max = QSpinBox()
|
|
self.ndvi_perc_max.setRange(0, 100)
|
|
self.ndvi_perc_max.setValue(95)
|
|
brdf_layout.addRow("NDVI 百分比最大值 (ndvi_perc_max):", self.ndvi_perc_max)
|
|
self.solar_zn_type = QComboBox()
|
|
self.solar_zn_type.addItems(["scene", "pixel"])
|
|
brdf_layout.addRow("太阳天顶角类型 (solar_zn_type):", self.solar_zn_type)
|
|
brdf_group.setLayout(brdf_layout)
|
|
layout.addWidget(brdf_group)
|
|
|
|
# 4. Glint 参数 - 橙色主题 (#df6500)
|
|
glint_group = QGroupBox("耀斑校正 (Glint Correction)")
|
|
glint_group.setStyleSheet("""
|
|
QGroupBox {
|
|
background-color: #1a1a1a;
|
|
border: 2px solid #df6500;
|
|
border-radius: 2px;
|
|
color: #ffffff;
|
|
font-weight: 700;
|
|
font-size: 14px;
|
|
padding: 10px;
|
|
margin-top: 15px;
|
|
}
|
|
QGroupBox::title {
|
|
subcontrol-origin: margin;
|
|
left: 10px;
|
|
padding: 5px 10px;
|
|
color: #ffffff;
|
|
background-color: #df6500;
|
|
border-radius: 2px;
|
|
font-weight: 700;
|
|
font-size: 14px;
|
|
}
|
|
QLabel {
|
|
color: #ffffff;
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
}
|
|
QComboBox {
|
|
background-color: #000000;
|
|
color: #ffffff;
|
|
border: 1px solid #df6500;
|
|
border-radius: 2px;
|
|
padding: 5px;
|
|
min-width: 150px;
|
|
}
|
|
QComboBox::drop-down {
|
|
border: none;
|
|
width: 25px;
|
|
}
|
|
QComboBox::down-arrow {
|
|
image: none;
|
|
border-left: 5px solid transparent;
|
|
border-right: 5px solid transparent;
|
|
border-top: 6px solid #df6500;
|
|
margin-right: 8px;
|
|
}
|
|
QComboBox QAbstractItemView {
|
|
background-color: #1a1a1a;
|
|
color: #ffffff;
|
|
border: 1px solid #df6500;
|
|
selection-background-color: #df6500;
|
|
}
|
|
QSpinBox {
|
|
background-color: #000000;
|
|
color: #ffffff;
|
|
border: 1px solid #df6500;
|
|
border-radius: 2px;
|
|
padding: 5px;
|
|
}
|
|
""")
|
|
glint_layout = QFormLayout()
|
|
glint_layout.setVerticalSpacing(10)
|
|
self.glint_type = QComboBox()
|
|
self.glint_type.addItems(["hochberg", "gao", "hedley"])
|
|
glint_layout.addRow("校正算法 (type):", self.glint_type)
|
|
self.correction_wave = QSpinBox()
|
|
self.correction_wave.setRange(1000, 2500)
|
|
self.correction_wave.setValue(2150)
|
|
glint_layout.addRow("校正波长 (nm):", self.correction_wave)
|
|
apply_mask_label_glint = QLabel("应用掩膜条件 (apply_mask):")
|
|
apply_mask_label_glint.setStyleSheet("color: #a7a7a7; font-size: 13px;")
|
|
glint_layout.addRow(apply_mask_label_glint)
|
|
self.glint_apply_mask_list = MaskListWidget()
|
|
glint_layout.addRow(self.glint_apply_mask_list)
|
|
glint_group.setLayout(glint_layout)
|
|
layout.addWidget(glint_group)
|
|
|
|
layout.addStretch()
|
|
self.corr_tab.setLayout(QVBoxLayout())
|
|
self.corr_tab.layout().addWidget(scroll)
|
|
|
|
# 设置容器背景色
|
|
container.setStyleSheet("background-color: #000000;")
|
|
|
|
# ==================== 输出设置页 ====================
|
|
def setup_output_tab(self):
|
|
layout = QFormLayout(self.output_tab)
|
|
layout.setVerticalSpacing(12)
|
|
layout.setHorizontalSpacing(10)
|
|
layout.setContentsMargins(15, 15, 15, 15)
|
|
|
|
# 设置输出设置页的样式
|
|
self.output_tab.setStyleSheet("""
|
|
QLabel {
|
|
color: #ffffff;
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
}
|
|
QLineEdit {
|
|
background-color: #1a1a1a;
|
|
color: #ffffff;
|
|
border: 1px solid #76b900;
|
|
border-radius: 2px;
|
|
padding: 5px;
|
|
min-width: 300px;
|
|
}
|
|
QLineEdit:focus {
|
|
border: 2px solid #76b900;
|
|
}
|
|
QCheckBox {
|
|
color: #ffffff;
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
spacing: 8px;
|
|
}
|
|
QCheckBox::indicator {
|
|
width: 18px;
|
|
height: 18px;
|
|
border: 2px solid #76b900;
|
|
border-radius: 2px;
|
|
background-color: transparent;
|
|
}
|
|
QCheckBox::indicator:checked {
|
|
background-color: #76b900;
|
|
}
|
|
""")
|
|
|
|
# 输出目录
|
|
self.output_dir_edit = QLineEdit()
|
|
self.output_dir_edit.setPlaceholderText("选择输出目录...")
|
|
btn_out = QPushButton("浏览...")
|
|
btn_out.clicked.connect(lambda: self.select_folder(self.output_dir_edit))
|
|
layout.addRow("输出目录 (Output Directory):", self._horizontal_widget(self.output_dir_edit, btn_out))
|
|
|
|
# 后缀
|
|
self.suffix_edit = QLineEdit("topo_brdf_glint")
|
|
self.suffix_edit.setPlaceholderText("输出文件名后缀")
|
|
layout.addRow("后缀 (Suffix):", self.suffix_edit)
|
|
|
|
# 导出选项
|
|
export_label = QLabel("导出选项 (Export Options):")
|
|
export_label.setStyleSheet("color: #76b900; font-size: 14px; font-weight: 700;")
|
|
layout.addRow(export_label)
|
|
|
|
self.coeffs_cb = QCheckBox("输出系数 (coeffs)")
|
|
self.image_cb = QCheckBox("输出图像 (image)")
|
|
self.masks_cb = QCheckBox("输出掩膜 (masks)")
|
|
self.coeffs_cb.setChecked(False)
|
|
self.image_cb.setChecked(True)
|
|
self.masks_cb.setChecked(True)
|
|
layout.addRow("", self.coeffs_cb)
|
|
layout.addRow("", self.image_cb)
|
|
layout.addRow("", self.masks_cb)
|
|
|
|
# 子集波段
|
|
self.subset_waves_edit = QLineEdit("")
|
|
self.subset_waves_edit.setPlaceholderText("例如: 450, 550, 650 (逗号分隔,留空导出全部)")
|
|
layout.addRow("子集波段 (Subset Waves):", self.subset_waves_edit)
|
|
|
|
# 重采样
|
|
self.resample_cb = QCheckBox("启用重采样 (Resample)")
|
|
layout.addRow("", self.resample_cb)
|
|
|
|
# ==================== 配置加载与保存 ====================
|
|
def load_config(self):
|
|
file_path, _ = QFileDialog.getOpenFileName(self, "加载 JSON 配置", "", "JSON Files (*.json)")
|
|
if not file_path:
|
|
return
|
|
try:
|
|
with open(file_path, 'r') as f:
|
|
config = json.load(f)
|
|
except Exception as e:
|
|
QMessageBox.critical(self, "错误", f"无法加载 JSON: {e}")
|
|
return
|
|
|
|
# 文件与波段映射(只恢复映射关系,不自动恢复文件对)
|
|
if "anc_files" in config:
|
|
self.band_mappings = {}
|
|
for rfl_path, anc_info in config["anc_files"].items():
|
|
mapping = {}
|
|
for param, val in anc_info.items():
|
|
if isinstance(val, list) and len(val) >= 2:
|
|
mapping[param] = val[1]
|
|
self.band_mappings[rfl_path] = mapping
|
|
# 刷新当前选中的文件对映射显示
|
|
if self.file_pairs and self.file_list_widget.currentItem():
|
|
self.on_file_selected(self.file_list_widget.currentItem())
|
|
|
|
# corrections
|
|
corrections = config.get("corrections", [])
|
|
self.corr_topo_cb.setChecked("topo" in corrections)
|
|
self.corr_brdf_cb.setChecked("brdf" in corrections)
|
|
self.corr_glint_cb.setChecked("glint" in corrections)
|
|
|
|
# topo
|
|
topo = config.get("topo", {})
|
|
self.topo_type.setCurrentText(topo.get("type", "scs+c"))
|
|
self.c_fit_type.setCurrentText(topo.get("c_fit_type", "nnls"))
|
|
self.topo_calc_mask_list.set_conditions(topo.get("calc_mask", []))
|
|
self.topo_apply_mask_list.set_conditions(topo.get("apply_mask", []))
|
|
|
|
# brdf
|
|
brdf = config.get("brdf", {})
|
|
self.brdf_type.setCurrentText(brdf.get("type", "flex"))
|
|
self.brdf_grouped.setChecked(brdf.get("grouped", True))
|
|
self.geometric_kernel.setCurrentText(brdf.get("geometric", "li_dense_r"))
|
|
self.volume_kernel.setCurrentText(brdf.get("volume", "ross_thick"))
|
|
self.br_b_ratio.setValue(brdf.get("b/r", 2.5))
|
|
self.hb_ratio.setValue(brdf.get("h/b", 2.0))
|
|
self.sample_perc.setValue(brdf.get("sample_perc", 0.1))
|
|
self.interp_kind.setCurrentText(brdf.get("interp_kind", "linear"))
|
|
self.brdf_calc_mask_list.set_conditions(brdf.get("calc_mask", []))
|
|
self.brdf_apply_mask_list.set_conditions(brdf.get("apply_mask", []))
|
|
self.bin_type.setCurrentText(brdf.get("bin_type", "dynamic"))
|
|
self.num_bins.setValue(brdf.get("num_bins", 18))
|
|
self.ndvi_bin_min.setValue(brdf.get("ndvi_bin_min", 0.05))
|
|
self.ndvi_bin_max.setValue(brdf.get("ndvi_bin_max", 1.0))
|
|
self.ndvi_perc_min.setValue(brdf.get("ndvi_perc_min", 10))
|
|
self.ndvi_perc_max.setValue(brdf.get("ndvi_perc_max", 95))
|
|
self.solar_zn_type.setCurrentText(brdf.get("solar_zn_type", "scene"))
|
|
|
|
# glint
|
|
glint = config.get("glint", {})
|
|
self.glint_type.setCurrentText(glint.get("type", "hochberg"))
|
|
self.correction_wave.setValue(glint.get("correction_wave", 2150))
|
|
self.glint_apply_mask_list.set_conditions(glint.get("apply_mask", []))
|
|
|
|
# export
|
|
export = config.get("export", {})
|
|
self.output_dir_edit.setText(export.get("output_dir", ""))
|
|
self.suffix_edit.setText(export.get("suffix", "topo_brdf_glint"))
|
|
self.coeffs_cb.setChecked(export.get("coeffs", False))
|
|
self.image_cb.setChecked(export.get("image", True))
|
|
self.masks_cb.setChecked(export.get("masks", True))
|
|
subset = export.get("subset_waves", [])
|
|
self.subset_waves_edit.setText(",".join(str(w) for w in subset))
|
|
self.resample_cb.setChecked(config.get("resample", False))
|
|
|
|
QMessageBox.information(self, "成功", f"已加载配置: {file_path}")
|
|
|
|
def save_config(self):
|
|
if not self.file_pairs:
|
|
QMessageBox.warning(self, "警告", "没有有效的文件对,请先匹配文件。")
|
|
return
|
|
|
|
config = {}
|
|
config["input_files"] = [rfl for rfl, _ in self.file_pairs]
|
|
config["file_type"] = "envi"
|
|
config["bad_bands"] = []
|
|
config["num_cpus"] = 2
|
|
|
|
anc_files_dict = {}
|
|
for rfl_path, obs_path in self.file_pairs:
|
|
mapping = self.band_mappings.get(rfl_path, {})
|
|
anc_entry = {}
|
|
for param, spin in self.param_spinboxes.items():
|
|
band_idx = mapping.get(param, spin.value())
|
|
anc_entry[param] = [obs_path, band_idx]
|
|
anc_files_dict[rfl_path] = anc_entry
|
|
config["anc_files"] = anc_files_dict
|
|
|
|
corrections = []
|
|
if self.corr_topo_cb.isChecked():
|
|
corrections.append("topo")
|
|
if self.corr_brdf_cb.isChecked():
|
|
corrections.append("brdf")
|
|
if self.corr_glint_cb.isChecked():
|
|
corrections.append("glint")
|
|
config["corrections"] = corrections
|
|
|
|
config["topo"] = {
|
|
"type": self.topo_type.currentText(),
|
|
"calc_mask": self.topo_calc_mask_list.get_conditions(),
|
|
"apply_mask": self.topo_apply_mask_list.get_conditions(),
|
|
"c_fit_type": self.c_fit_type.currentText()
|
|
}
|
|
|
|
config["brdf"] = {
|
|
"type": self.brdf_type.currentText(),
|
|
"grouped": self.brdf_grouped.isChecked(),
|
|
"geometric": self.geometric_kernel.currentText(),
|
|
"volume": self.volume_kernel.currentText(),
|
|
"b/r": self.br_b_ratio.value(),
|
|
"h/b": self.hb_ratio.value(),
|
|
"sample_perc": self.sample_perc.value(),
|
|
"interp_kind": self.interp_kind.currentText(),
|
|
"calc_mask": self.brdf_calc_mask_list.get_conditions(),
|
|
"apply_mask": self.brdf_apply_mask_list.get_conditions(),
|
|
"bin_type": self.bin_type.currentText(),
|
|
"num_bins": self.num_bins.value(),
|
|
"ndvi_bin_min": self.ndvi_bin_min.value(),
|
|
"ndvi_bin_max": self.ndvi_bin_max.value(),
|
|
"ndvi_perc_min": self.ndvi_perc_min.value(),
|
|
"ndvi_perc_max": self.ndvi_perc_max.value(),
|
|
"solar_zn_type": self.solar_zn_type.currentText()
|
|
}
|
|
|
|
config["glint"] = {
|
|
"type": self.glint_type.currentText(),
|
|
"correction_wave": self.correction_wave.value(),
|
|
"apply_mask": self.glint_apply_mask_list.get_conditions()
|
|
}
|
|
|
|
subset_waves = []
|
|
if self.subset_waves_edit.text().strip():
|
|
try:
|
|
subset_waves = [int(x.strip()) for x in self.subset_waves_edit.text().split(",") if x.strip()]
|
|
except:
|
|
pass
|
|
config["export"] = {
|
|
"coeffs": self.coeffs_cb.isChecked(),
|
|
"image": self.image_cb.isChecked(),
|
|
"masks": self.masks_cb.isChecked(),
|
|
"subset_waves": subset_waves,
|
|
"output_dir": self.output_dir_edit.text(),
|
|
"suffix": self.suffix_edit.text()
|
|
}
|
|
config["resample"] = self.resample_cb.isChecked()
|
|
|
|
save_path, _ = QFileDialog.getSaveFileName(self, "保存 JSON 配置", "", "JSON Files (*.json)")
|
|
if not save_path:
|
|
return
|
|
try:
|
|
with open(save_path, 'w') as f:
|
|
json.dump(config, f, indent=3)
|
|
QMessageBox.information(self, "成功", f"配置已保存至: {save_path}")
|
|
except Exception as e:
|
|
QMessageBox.critical(self, "错误", f"保存失败: {e}")
|
|
|
|
|
|
def main():
|
|
app = QApplication(sys.argv)
|
|
window = MainWindow()
|
|
window.show()
|
|
sys.exit(app.exec_())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |