增加模块;增加主调用命令

This commit is contained in:
2026-01-07 16:36:47 +08:00
commit 2d4b170a45
109 changed files with 55763 additions and 0 deletions

View File

@ -0,0 +1,440 @@
import sys
import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QGroupBox, QLabel, QDoubleSpinBox, QRadioButton, QPushButton,
QButtonGroup, QSplitter, QFrame, QFileDialog, QMessageBox,
QCheckBox, QProgressBar, QStatusBar)
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from PyQt5.QtGui import QFont, QIcon
# 添加当前目录到Python路径以便导入本地模块
current_dir = os.path.dirname(os.path.abspath(__file__))
if current_dir not in sys.path:
sys.path.insert(0, current_dir)
from modules.load_coefficients import load_coefficients
from modules.prospect_d import prospect_d
from modules.sail import sail
class SimulationWorker(QThread):
"""Worker thread for running PROSAIL simulation"""
finished = pyqtSignal(object) # Signal emitted when simulation is complete
error = pyqtSignal(str) # Signal emitted when error occurs
def __init__(self, params):
super().__init__()
self.params = params
def run(self):
try:
# Extract parameters
N, Cab, Car, Ant, Cbrown, Cw, Cm = self.params['leaf_params']
LIDFa, LIDFb, TypeLidf, LAI, q = self.params['canopy_params']
tts, tto, psi = self.params['angle_params']
rsoil = self.params['soil_param']
# Load coefficients
data_path = os.path.join(current_dir, "data", "dataSpec_PDB.txt")
wl, nr, Kab, Kcar, Kant, Kbrown, Kw, Km, Rsoil1, Rsoil2 = load_coefficients(data_path)
# Run PROSPECT-D
LRT = prospect_d(N, Cab, Car, Ant, Cbrown, Cw, Cm, wl, nr, Kab, Kcar, Kant, Kbrown, Kw, Km)
rho = LRT[:, 1]
tau = LRT[:, 2]
# Run SAIL
refl = sail(rho, tau, LIDFa, LIDFb, TypeLidf, LAI, q, tts, tto, psi, rsoil, wl)
rdot = refl[:, 1]
rsot = refl[:, 2]
# Calculate RESV
data_all = np.loadtxt(data_path, comments='%', delimiter=None)
Es = data_all[:, 8]
Ed = data_all[:, 9]
rd = np.pi / 180
skyl = 0.847 - 1.61 * np.sin((90 - tts) * rd) + 1.04 * np.sin((90 - tts) * rd) ** 2
PARdiro = (1 - skyl) * Es
PARdifo = skyl * Ed
denominator = PARdiro + PARdifo
denominator[denominator == 0] = 1e-6
resv = (rdot * PARdifo + rsot * PARdiro) / denominator
# Create DataFrame
df = pd.DataFrame({"wavelength": wl, "resv": resv})
self.finished.emit(df)
except Exception as e:
self.error.emit(str(e))
class PROSAILSimulator(QMainWindow):
def __init__(self):
super().__init__()
self.results = [] # Store simulation results as (params, df) tuples
self.worker = None
self.initUI()
def initUI(self):
self.setWindowTitle("PROSAIL Simulator")
self.setGeometry(100, 100, 1200, 800)
# Create central widget and main layout
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QHBoxLayout(central_widget)
# Create splitter for resizable panels
splitter = QSplitter(Qt.Horizontal)
main_layout.addWidget(splitter)
# Left panel - Parameters
self.create_parameter_panel()
splitter.addWidget(self.parameter_panel)
# Right panel - Plot
self.create_plot_panel()
splitter.addWidget(self.plot_panel)
# Set splitter proportions
splitter.setSizes([400, 800])
# Status bar
self.status_bar = self.statusBar()
self.status_bar.showMessage("就绪")
# Progress bar for simulation
self.progress_bar = QProgressBar()
self.progress_bar.setVisible(False)
self.status_bar.addPermanentWidget(self.progress_bar)
def create_parameter_panel(self):
self.parameter_panel = QWidget()
layout = QVBoxLayout(self.parameter_panel)
# Control buttons
control_group = QGroupBox("控制选项")
control_layout = QHBoxLayout(control_group)
self.keep_checkbox = QCheckBox(" 保留")
self.simulate_btn = QPushButton(" 模拟")
self.simulate_btn.clicked.connect(self.run_simulation)
self.clear_btn = QPushButton(" 清空")
self.clear_btn.clicked.connect(self.clear_results)
control_layout.addWidget(self.keep_checkbox)
control_layout.addWidget(self.simulate_btn)
control_layout.addWidget(self.clear_btn)
layout.addWidget(control_group)
# Parameters section
params_scroll = QWidget()
params_layout = QVBoxLayout(params_scroll)
# Canopy parameters
self.create_canopy_params(params_layout)
# Angle parameters
self.create_angle_params(params_layout)
# Leaf parameters
self.create_leaf_params(params_layout)
# Soil parameters
self.create_soil_params(params_layout)
layout.addWidget(params_scroll)
# Save button
self.save_btn = QPushButton(" 保存为 CSV")
self.save_btn.clicked.connect(self.save_results)
self.save_btn.setEnabled(False)
layout.addWidget(self.save_btn)
def create_canopy_params(self, parent_layout):
canopy_group = QGroupBox("1. 冠层参数")
layout = QVBoxLayout(canopy_group)
# LIDF Type
lidf_layout = QHBoxLayout()
lidf_layout.addWidget(QLabel("LIDF 类型:"))
self.lidf_group = QButtonGroup()
self.lidf_1 = QRadioButton("双参数分布")
self.lidf_2 = QRadioButton("Campbell 分布")
self.lidf_1.setChecked(True)
self.lidf_group.addButton(self.lidf_1, 1)
self.lidf_group.addButton(self.lidf_2, 2)
lidf_layout.addWidget(self.lidf_1)
lidf_layout.addWidget(self.lidf_2)
layout.addLayout(lidf_layout)
# Parameters
params_layout = QHBoxLayout()
left_col = QVBoxLayout()
self.lai_input = self.create_spinbox("LAI (叶面积指数)", 3.0, 0.0, 8.0, 0.1)
left_col.addWidget(self.lai_input)
self.lidfb_input = self.create_spinbox("LIDFb", 0.0, -1.0, 1.0, 0.05)
left_col.addWidget(self.lidfb_input)
right_col = QVBoxLayout()
self.lidfa_input = self.create_spinbox("LIDFa", 0.0, -1.0, 1.0, 0.05)
right_col.addWidget(self.lidfa_input)
self.q_input = self.create_spinbox("hotspot q", 0.05, 0.0, 1.0, 0.01)
right_col.addWidget(self.q_input)
params_layout.addLayout(left_col)
params_layout.addLayout(right_col)
layout.addLayout(params_layout)
# Connect LIDF type change
self.lidf_group.buttonClicked.connect(self.on_lidf_changed)
parent_layout.addWidget(canopy_group)
def create_angle_params(self, parent_layout):
angle_group = QGroupBox("2. 观测与光照角度")
layout = QHBoxLayout(angle_group)
left_col = QVBoxLayout()
self.tts_input = self.create_spinbox("tts (太阳天顶角)", 30.0, 0.0, 90.0, 1.0)
left_col.addWidget(self.tts_input)
self.psi_input = self.create_spinbox("psi (方位角差)", 0.0, 0.0, 180.0, 5.0)
left_col.addWidget(self.psi_input)
right_col = QVBoxLayout()
self.tto_input = self.create_spinbox("tto (观测天顶角)", 20.0, 0.0, 90.0, 1.0)
right_col.addWidget(self.tto_input)
layout.addLayout(left_col)
layout.addLayout(right_col)
parent_layout.addWidget(angle_group)
def create_leaf_params(self, parent_layout):
leaf_group = QGroupBox("3. 叶片参数")
layout = QHBoxLayout(leaf_group)
left_col = QVBoxLayout()
self.n_input = self.create_spinbox("N (叶结构参数)", 1.5, 1.0, 3.0, 0.1)
left_col.addWidget(self.n_input)
self.car_input = self.create_spinbox("Car (类胡萝卜素)", 8.0, 0.0, 30.0, 1.0)
left_col.addWidget(self.car_input)
self.cm_input = self.create_spinbox("Cm (干物质含量)", 0.009, 0.0, 0.05, 0.001)
left_col.addWidget(self.cm_input)
self.ant_input = self.create_spinbox("Ant (花青素)", 0.0, 0.0, 5.0, 0.1)
left_col.addWidget(self.ant_input)
right_col = QVBoxLayout()
self.cab_input = self.create_spinbox("Cab (叶绿素含量)", 40.0, 0.0, 100.0, 1.0)
right_col.addWidget(self.cab_input)
self.cw_input = self.create_spinbox("Cw (叶片含水量)", 0.015, 0.0, 0.05, 0.001)
right_col.addWidget(self.cw_input)
self.cbrown_input = self.create_spinbox("Cbrown (棕色素)", 0.0, 0.0, 1.0, 0.05)
right_col.addWidget(self.cbrown_input)
layout.addLayout(left_col)
layout.addLayout(right_col)
parent_layout.addWidget(leaf_group)
def create_soil_params(self, parent_layout):
soil_group = QGroupBox("4. 土壤湿度")
layout = QVBoxLayout(soil_group)
self.rsoil_input = self.create_spinbox("rsoil (土壤反射率)", 0.2, 0.0, 1.0, 0.01)
layout.addWidget(self.rsoil_input)
parent_layout.addWidget(soil_group)
def create_spinbox(self, label, default, min_val, max_val, step):
widget = QWidget()
layout = QVBoxLayout(widget)
layout.addWidget(QLabel(label))
spinbox = QDoubleSpinBox()
spinbox.setRange(min_val, max_val)
spinbox.setValue(default)
spinbox.setSingleStep(step)
spinbox.setDecimals(3)
layout.addWidget(spinbox)
return widget
def create_plot_panel(self):
self.plot_panel = QWidget()
layout = QVBoxLayout(self.plot_panel)
# Create matplotlib figure
self.figure = plt.Figure(figsize=(10, 6))
self.canvas = FigureCanvas(self.figure)
self.toolbar = NavigationToolbar(self.canvas, self)
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
# Initial empty plot
self.update_plot()
def on_lidf_changed(self):
lidf_type = self.lidf_group.checkedId()
if lidf_type == 1: # 双参数分布
self.lidfa_input.findChild(QDoubleSpinBox).setRange(-1.0, 1.0)
self.lidfa_input.findChild(QDoubleSpinBox).setValue(0.0)
else: # Campbell 分布
self.lidfa_input.findChild(QDoubleSpinBox).setRange(0.0, 90.0)
self.lidfa_input.findChild(QDoubleSpinBox).setValue(30.0)
def get_params(self):
leaf_params = (
self.n_input.findChild(QDoubleSpinBox).value(),
self.cab_input.findChild(QDoubleSpinBox).value(),
self.car_input.findChild(QDoubleSpinBox).value(),
self.ant_input.findChild(QDoubleSpinBox).value(),
self.cbrown_input.findChild(QDoubleSpinBox).value(),
self.cw_input.findChild(QDoubleSpinBox).value(),
self.cm_input.findChild(QDoubleSpinBox).value()
)
canopy_params = (
self.lidfa_input.findChild(QDoubleSpinBox).value(),
self.lidfb_input.findChild(QDoubleSpinBox).value(),
self.lidf_group.checkedId(),
self.lai_input.findChild(QDoubleSpinBox).value(),
self.q_input.findChild(QDoubleSpinBox).value()
)
angle_params = (
self.tts_input.findChild(QDoubleSpinBox).value(),
self.tto_input.findChild(QDoubleSpinBox).value(),
self.psi_input.findChild(QDoubleSpinBox).value()
)
soil_param = self.rsoil_input.findChild(QDoubleSpinBox).value()
return {
'leaf_params': leaf_params,
'canopy_params': canopy_params,
'angle_params': angle_params,
'soil_param': soil_param
}
def run_simulation(self):
if self.worker and self.worker.isRunning():
return
self.simulate_btn.setEnabled(False)
self.progress_bar.setVisible(True)
self.progress_bar.setRange(0, 0) # Indeterminate progress
self.status_bar.showMessage("正在运行 PROSAIL 模拟...")
params = self.get_params()
self.worker = SimulationWorker(params)
self.worker.finished.connect(self.on_simulation_finished)
self.worker.error.connect(self.on_simulation_error)
self.worker.start()
def on_simulation_finished(self, df):
self.progress_bar.setVisible(False)
self.simulate_btn.setEnabled(True)
self.status_bar.showMessage("模拟完成")
# Get current parameters at simulation time
current_params = self.get_params()
if self.keep_checkbox.isChecked():
self.results.append((current_params, df))
else:
self.results = [(current_params, df)]
self.save_btn.setEnabled(True)
self.update_plot()
def on_simulation_error(self, error_msg):
self.progress_bar.setVisible(False)
self.simulate_btn.setEnabled(True)
self.status_bar.showMessage("模拟失败")
QMessageBox.critical(self, "错误", f"模拟过程中出现错误:\n{error_msg}")
def clear_results(self):
self.results = []
self.save_btn.setEnabled(False)
self.update_plot()
self.status_bar.showMessage("结果已清空")
def update_plot(self):
self.figure.clear()
ax = self.figure.add_subplot(111)
if self.results:
for i, (params, df_plot) in enumerate(self.results):
ax.plot(df_plot["wavelength"], df_plot["resv"], label=f"refl #{i+1}")
ax.set_ylabel("Reflectance")
ax.legend()
else:
ax.text(0.5, 0.5, "无模拟结果", fontsize=16, ha='center', va='center',
alpha=0.5, transform=ax.transAxes)
ax.set_xlabel("Wavelength (nm)")
ax.grid(True)
self.canvas.draw()
def save_results(self):
if not self.results:
return
filename, _ = QFileDialog.getSaveFileName(
self, "保存结果", "prosail_resv.csv",
"CSV 文件 (*.csv);;所有文件 (*)"
)
if filename:
try:
# Prepare data for CSV - each row represents one sample
all_data = []
for i, (params, df) in enumerate(self.results):
# Create one row per sample with parameters + reflectance spectrum
data_row = {
'sample_id': i + 1,
'N': params['leaf_params'][0],
'Cab': params['leaf_params'][1],
'Car': params['leaf_params'][2],
'Ant': params['leaf_params'][3],
'Cbrown': params['leaf_params'][4],
'Cw': params['leaf_params'][5],
'Cm': params['leaf_params'][6],
'LIDFa': params['canopy_params'][0],
'LIDFb': params['canopy_params'][1],
'TypeLidf': params['canopy_params'][2],
'LAI': params['canopy_params'][3],
'q': params['canopy_params'][4],
'tts': params['angle_params'][0],
'tto': params['angle_params'][1],
'psi': params['angle_params'][2],
'rsoil': params['soil_param']
}
# Add reflectance values for each wavelength
for _, row in df.iterrows():
wavelength = int(row['wavelength'])
data_row[f'refl_{wavelength}'] = row['resv']
all_data.append(data_row)
# Create DataFrame and save
result_df = pd.DataFrame(all_data)
result_df.to_csv(filename, index=False)
self.status_bar.showMessage(f"结果已保存到 {filename} (共 {len(all_data)} 个样本)")
except Exception as e:
QMessageBox.critical(self, "错误", f"保存文件时出现错误:\n{str(e)}")
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setStyle('Fusion') # Modern style
window = PROSAILSimulator()
window.show()
sys.exit(app.exec_())