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

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

2
prosail_method/.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

2101
prosail_method/Refl_CAN.txt Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,54 @@
import numpy as np
def Jfunc1(k, l, t):
"""
Computes J1(k, l, t) = (exp(-lt) - exp(-kt)) / (k - l)
Uses Taylor expansion when k ≈ l.
k: scalar or array
l: array
t: scalar or array
"""
# 广播所有变量到相同 shape自动支持 k 是标量、l 是向量)
k, l, t_array = np.broadcast_arrays(k, l, t)
delta = (k - l) * t_array
Jout = np.zeros_like(delta)
mask_far = np.abs(delta) > 1e-3
mask_near = ~mask_far
# 正常计算
Jout[mask_far] = (
np.exp(-l[mask_far] * t_array[mask_far]) -
np.exp(-k[mask_far] * t_array[mask_far])
) / (k[mask_far] - l[mask_far])
# k ≈ l 的情况,使用二阶展开
d = delta[mask_near]
Jout[mask_near] = 0.5 * t_array[mask_near] * (
np.exp(-k[mask_near] * t_array[mask_near]) +
np.exp(-l[mask_near] * t_array[mask_near])
) * (1 - d * d / 12)
return Jout
def Jfunc2(k, l, t):
k, l, t_array = np.broadcast_arrays(k, l, t)
delta = (k + l) * t_array
Jout = np.zeros_like(k)
mask_far = np.abs(delta) > 1e-3
mask_near = ~mask_far
Jout[mask_far] = (1 - np.exp(-delta[mask_far])) / (k[mask_far] + l[mask_far])
d = delta[mask_near]
Jout[mask_near] = t_array[mask_near] * (1 - 0.5 * d + d ** 2 / 6)
return Jout
def Jfunc3(k, l, t):
return Jfunc2(k, l, t)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,46 @@
import numpy as np
def calctav(alfa, nr):
"""
Calculate average transmittance of isotropic light across a dielectric surface.
Translated from MATLAB version of calctav.m.
Parameters:
alfa : incidence angle in degrees (scalar or array)
nr : refractive index of second medium (scalar or array of same shape as alfa)
Returns:
tav : average transmittance (same shape as alfa)
"""
alfa = np.asarray(alfa)
nr = np.asarray(nr)
rd = np.pi / 180.0
n2 = nr ** 2
np_ = n2 + 1
nm = n2 - 1
a = ((nr + 1) ** 2) / 2
k = -((n2 - 1) ** 2) / 4
sa = np.sin(alfa * rd)
b1 = np.where(alfa != 90,
np.sqrt((sa ** 2 - np_ / 2) ** 2 + k),
0.0)
b2 = sa ** 2 - np_ / 2
b = b1 - b2
b3 = b ** 3
a3 = a ** 3
ts = (k ** 2 / (6 * b3) + k / b - b / 2) - (k ** 2 / (6 * a3) + k / a - a / 2)
tp1 = -2 * n2 * (b - a) / (np_ ** 2)
tp2 = -2 * n2 * np_ * np.log(b / a) / (nm ** 2)
tp3 = n2 * (1 / b - 1 / a) / 2
tp4 = 16 * n2 ** 2 * (n2 ** 2 + 1) * np.log((2 * np_ * b - nm ** 2) / (2 * np_ * a - nm ** 2)) / (np_ ** 3 * nm ** 2)
tp5 = 16 * n2 ** 3 * (1 / (2 * np_ * b - nm ** 2) - 1 / (2 * np_ * a - nm ** 2)) / (np_ ** 3)
tp = tp1 + tp2 + tp3 + tp4 + tp5
tav = (ts + tp) / (2 * sa ** 2)
return tav

View File

@ -0,0 +1,53 @@
import numpy as np
def campbell(ala):
"""
Campbell's leaf angle distribution function (ellipsoidal).
Parameters:
ala : average leaf angle in degrees
Returns:
freq : relative frequency for 13 inclination bins
litab: bin centers (degrees)
"""
tx1 = np.array([10., 20., 30., 40., 50., 60., 70., 80., 82., 84., 86., 88., 90.])
tx2 = np.array([0., 10., 20., 30., 40., 50., 60., 70., 80., 82., 84., 86., 88.])
litab = (tx1 + tx2) / 2
n = len(litab)
tl1 = np.radians(tx1)
tl2 = np.radians(tx2)
excent = np.exp(-1.6184e-5 * ala**3 + 2.1145e-3 * ala**2 - 0.12390 * ala + 3.2491)
freq = np.zeros(n)
for i in range(n):
x1 = excent / np.sqrt(1 + (excent**2) * np.tan(tl1[i])**2)
x2 = excent / np.sqrt(1 + (excent**2) * np.tan(tl2[i])**2)
if excent == 1:
freq[i] = abs(np.cos(tl1[i]) - np.cos(tl2[i]))
else:
alpha = excent / np.sqrt(abs(1 - excent**2))
alpha2 = alpha**2
x1_sq = x1**2
x2_sq = x2**2
if excent > 1:
alpx1 = np.sqrt(alpha2 + x1_sq)
alpx2 = np.sqrt(alpha2 + x2_sq)
dum1 = x1 * alpx1 + alpha2 * np.log(x1 + alpx1)
dum2 = x2 * alpx2 + alpha2 * np.log(x2 + alpx2)
freq[i] = abs(dum1 - dum2)
else:
almx1 = np.sqrt(alpha2 - x1_sq)
almx2 = np.sqrt(alpha2 - x2_sq)
dum1 = x1 * almx1 + alpha2 * np.arcsin(x1 / alpha)
dum2 = x2 * almx2 + alpha2 * np.arcsin(x2 / alpha)
freq[i] = abs(dum1 - dum2)
freq_sum = np.sum(freq)
freq_normalized = freq / freq_sum if freq_sum != 0 else freq
return freq_normalized, litab

View File

@ -0,0 +1,29 @@
import numpy as np
def dcum(a, b, t):
"""
计算 PROSAIL 中使用的累积分布函数 dcum
Parameters:
- a: float
- b: float
- t: float, in degrees
Returns:
- f: float
"""
rd = np.pi / 180 # degree to radian
if a >= 1:
f = 1 - np.cos(rd * t)
else:
eps = 1e-8
x = 2 * rd * t
p = x
delx = 1.0
while delx >= eps:
y = a * np.sin(x) + 0.5 * b * np.sin(2 * x)
dx = 0.5 * (y - x + p)
x += dx
delx = abs(dx)
f = (2 * y + p) / np.pi
return f

View File

@ -0,0 +1,35 @@
import numpy as np
from modules.dcum import dcum # 确保你有这个函数
def dladgen(a, b):
"""
Generate Leaf Inclination Distribution Function (LIDF)
using a two-parameter beta distribution.
Parameters:
a, b : shape parameters of beta distribution
Returns:
freq : frequency (probability) per inclination class (length 13)
litab : central inclination angles (degrees)
"""
litab = np.array([5., 15., 25., 35., 45., 55., 65., 75., 81., 83., 85., 87., 89.])
freq = np.zeros_like(litab)
# First 8 bins: 10° steps
for i in range(8):
t = (i + 1) * 10
freq[i] = dcum(a, b, t)
# Next 4 bins: finer steps from 80° to 89°
for i in range(8, 12):
t = 80.0 + (i - 8 + 1) * 2.0
freq[i] = dcum(a, b, t)
freq[12] = 1.0 # total cumulative = 1
# Convert cumulative to class probabilities
for i in range(12, 0, -1):
freq[i] -= freq[i - 1]
return freq, litab

View File

@ -0,0 +1,32 @@
import numpy as np
def load_coefficients(filepath):
"""
Load spectral coefficients from a full-format PROSAIL data file (e.g., dataSpec_PDB.txt).
Returns:
wl : ndarray, wavelength (nm)
nr : ndarray, refractive index of leaf material
Kab : ndarray, absorption coefficient of chlorophyll (a+b)
Kcar : ndarray, absorption coefficient of carotenoids
Kant : ndarray, absorption coefficient of anthocyanins
Kbrown : ndarray, absorption coefficient of brown pigments
Kw : ndarray, absorption coefficient of water
Km : ndarray, absorption coefficient of dry matter
Rsoil1 : ndarray, dry soil reflectance
Rsoil2 : ndarray, wet soil reflectance
"""
data = np.loadtxt(filepath, comments='%', delimiter=None)
wl = data[:, 0]
nr = data[:, 1]
Kab = data[:, 2]
Kcar = data[:, 3]
Kant = data[:, 4]
Kbrown = data[:, 5]
Kw = data[:, 6]
Km = data[:, 7]
Rsoil1 = data[:, 10]
Rsoil2 = data[:, 11]
return wl, nr, Kab, Kcar, Kant, Kbrown, Kw, Km, Rsoil1, Rsoil2

View File

@ -0,0 +1,23 @@
from modules.prospect_d import prospect_d
from modules.sail import sail
def prosail(N, Cab, Car, Ant, Cbrown, Cw, Cm,
LIDFa, LIDFb, TypeLidf, lai, hspot,
tts, tto, psi, rsoil,
wl, nr, Kab, Kcar, Kant, Kbrown, Kw, Km):
"""
PROSAIL simulator using PROSPECT-D + 4SAIL.
Returns:
refl: canopy directional reflectance
LRT : leaf reflectance/transmittance spectra
"""
# Step 1: simulate leaf optical properties using 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]
# Step 2: simulate canopy reflectance using 4SAIL
refl = sail(rho, tau, LIDFa, LIDFb, TypeLidf, lai, hspot, tts, tto, psi, rsoil, wl)
return wl, refl, LRT

View File

@ -0,0 +1,80 @@
import numpy as np
from scipy.special import exp1
from modules.calctav import calctav # 你自己已有的函数
def prospect_d(N, Cab, Car, Ant, Brown, Cw, Cm, wl, nr, Kab, Kcar, Kant, Kbrown, Kw, Km):
"""
Accurate PROSPECT-D implementation matching the original MATLAB model.
Parameters:
N : Structure parameter
Cab : Chlorophyll content (µg/cm²)
Car : Carotenoid content (µg/cm²)
Ant : Anthocyanins content (µg/cm²)
Brown : Brown pigment content (a.u.)
Cw : Equivalent water thickness (cm)
Cm : Dry matter content (g/cm²)
wl : Wavelength array (nm)
nr : Real refractive index
Kab,... : Absorption coefficients for components (same length as wl)
Returns:
LRT : ndarray of shape (n, 3) → [wavelength, reflectance, transmittance]
"""
# 1. Compute total absorption coefficient
Kall = (Cab * Kab + Car * Kcar + Ant * Kant + Brown * Kbrown + Cw * Kw + Cm * Km) / N
# 2. Compute tau (internal transmittance) with expint for K > 0
t1 = (1 - Kall) * np.exp(-Kall)
t2 = (Kall ** 2) * exp1(Kall)
tau = np.ones_like(Kall)
j = Kall > 0
tau[j] = t1[j] + t2[j]
# 3. Fresnel interface properties
talf = calctav(40, nr)
ralf = 1 - talf
t12 = calctav(90, nr)
r12 = 1 - t12
t21 = t12 / (nr ** 2)
r21 = 1 - t21
# 4. Top surface
denom = 1 - (r21 ** 2) * (tau ** 2)
Ta = talf * tau * t21 / denom
Ra = ralf + r21 * tau * Ta
# 5. Bottom surface
t = t12 * tau * t21 / denom
r = r12 + r21 * tau * t
# 6. Stokes multilayer reflectance and transmittance
D = np.sqrt((1 + r + t) * (1 + r - t) * (1 - r + t) * (1 - r - t))
rq = r ** 2
tq = t ** 2
a = (1 + rq - tq + D) / (2 * r)
b = (1 - rq + tq + D) / (2 * t)
bNm1 = b ** (N - 1)
bN2 = bNm1 ** 2
a2 = a ** 2
denom2 = a2 * bN2 - 1
Rsub = a * (bN2 - 1) / denom2
Tsub = bNm1 * (a2 - 1) / denom2
# 7. Zero absorption edge-case correction
j0 = (r + t >= 1)
Tsub[j0] = t[j0] / (t[j0] + (1 - t[j0]) * (N - 1))
Rsub[j0] = 1 - Tsub[j0]
# 8. Final reflectance and transmittance
denom3 = 1 - Rsub * r
tran = Ta * Tsub / denom3
refl = Ra + Ta * Rsub * t / denom3
# 9. Clip values to [0, 1]
refl = np.clip(refl, 0, 1)
tran = np.clip(tran, 0, 1)
return np.column_stack((wl, refl, tran))

View File

@ -0,0 +1,141 @@
import numpy as np
from modules.volscatt import volscatt
from modules.dladgen import dladgen
from modules.campbell import campbell
from modules.Jfunc import Jfunc1, Jfunc2, Jfunc3
def sail(rho, tau, LIDFa, LIDFb, TypeLidf, lai, q, tts, tto, psi, rsoil,wl):
rd = np.pi / 180
cts = np.cos(rd * tts)
cto = np.cos(rd * tto)
ctscto = cts * cto
tants = np.tan(rd * tts)
tanto = np.tan(rd * tto)
cospsi = np.cos(rd * psi)
dso = np.sqrt(tants**2 + tanto**2 - 2 * tants * tanto * cospsi)
# Generate LIDF
if TypeLidf == 1:
lidf, litab = dladgen(LIDFa, LIDFb)
elif TypeLidf == 2:
lidf, litab = campbell(LIDFa)
# Initialize geometric coefficients
ks = ko = bf = sob = sof = 0
na = len(litab)
for i in range(na):
ttl = litab[i]
ctl = np.cos(rd * ttl)
chi_s, chi_o, frho, ftau = volscatt(tts, tto, psi, ttl)
ksli = chi_s / cts
koli = chi_o / cto
sobli = frho * np.pi / ctscto
sofli = ftau * np.pi / ctscto
bfli = ctl ** 2
ks += ksli * lidf[i]
ko += koli * lidf[i]
bf += bfli * lidf[i]
sob += sobli * lidf[i]
sof += sofli * lidf[i]
# Structure factors
sdb = 0.5 * (ks + bf)
sdf = 0.5 * (ks - bf)
dob = 0.5 * (ko + bf)
dof = 0.5 * (ko - bf)
ddb = 0.5 * (1 + bf)
ddf = 0.5 * (1 - bf)
sigb = ddb * rho + ddf * tau
sigf = ddf * rho + ddb * tau
att = 1 - sigf
m2 = (att + sigb) * (att - sigb)
m2[m2 <= 0] = 0
m = np.sqrt(m2)
sb = sdb * rho + sdf * tau
sf = sdf * rho + sdb * tau
vb = dob * rho + dof * tau
vf = dof * rho + dob * tau
w = sob * rho + sof * tau
if lai <= 0:
return rsoil, rsoil, rsoil, rsoil
e1 = np.exp(-m * lai)
e2 = e1**2
rinf = (att - m) / sigb
rinf2 = rinf**2
re = rinf * e1
denom = 1 - rinf2 * e2
J1ks = Jfunc1(ks, m, lai)
J2ks = Jfunc2(ks, m, lai)
J1ko = Jfunc1(ko, m, lai)
J2ko = Jfunc2(ko, m, lai)
Ps = (sf + sb * rinf) * J1ks
Qs = (sf * rinf + sb) * J2ks
Pv = (vf + vb * rinf) * J1ko
Qv = (vf * rinf + vb) * J2ko
rdd = rinf * (1 - e2) / denom
tdd = (1 - rinf2) * e1 / denom
tsd = (Ps - re * Qs) / denom
rsd = (Qs - re * Ps) / denom
tdo = (Pv - re * Qv) / denom
rdo = (Qv - re * Pv) / denom
tss = np.exp(-ks * lai)
too = np.exp(-ko * lai)
z = Jfunc3(ks, ko, lai)
g1 = (z - J1ks * too) / (ko + m)
g2 = (z - J1ko * tss) / (ks + m)
Tv1 = (vf * rinf + vb) * g1
Tv2 = (vf + vb * rinf) * g2
T1 = Tv1 * (sf + sb * rinf)
T2 = Tv2 * (sf * rinf + sb)
T3 = (rdo * Qs + tdo * Ps) * rinf
rsod = (T1 + T2 - T3) / (1 - rinf2)
# Hotspot effect
if q <= 0:
alf = 1e6
else:
alf = min((dso / q) * 2 / (ks + ko), 200)
if alf == 0:
tsstoo = tss
sumint = (1 - tss) / (ks * lai)
else:
fhot = lai * np.sqrt(ko * ks)
fint = (1 - np.exp(-alf)) * 0.05
sumint = 0
x1 = y1 = 0
f1 = 1
for i in range(20):
x2 = -np.log(1 - i * fint) / alf if i < 19 else 1
y2 = -(ko + ks) * lai * x2 + fhot * (1 - np.exp(-alf * x2)) / alf
f2 = np.exp(y2)
if y2 != y1:
sumint += (f2 - f1) * (x2 - x1) / (y2 - y1)
x1, y1, f1 = x2, y2, f2
tsstoo = f1
rsos = w * lai * sumint
rso = rsos + rsod
# Canopysoil interaction
dn = 1 - rsoil * rdd
rddt = rdd + tdd * rsoil * tdd / dn
rsdt = rsd + (tsd + tss) * rsoil * tdd / dn
rdot = rdo + tdd * rsoil * (tdo + too) / dn
rsodt = rsod + ((tss + tsd) * tdo + (tsd + tss * rsoil * rdd) * too) * rsoil / dn
rsost = rsos + tsstoo * rsoil
rsot = rsost + rsodt
return np.column_stack((wl, rdot, rsot, rddt, rsdt))

View File

@ -0,0 +1,141 @@
import numpy as np
from modules.volscatt import volscatt
from modules.dladgen import dladgen
from modules.campbell import campbell
from modules.Jfunc import Jfunc1, Jfunc2, Jfunc3
def sail(rho, tau, LIDFa, LIDFb, TypeLidf, lai, q, tts, tto, psi, rsoil):
rd = np.pi / 180
cts = np.cos(rd * tts)
cto = np.cos(rd * tto)
ctscto = cts * cto
tants = np.tan(rd * tts)
tanto = np.tan(rd * tto)
cospsi = np.cos(rd * psi)
dso = np.sqrt(tants**2 + tanto**2 - 2 * tants * tanto * cospsi)
# Generate LIDF
if TypeLidf == 1:
lidf, litab = dladgen(LIDFa, LIDFb)
elif TypeLidf == 2:
lidf, litab = campbell(LIDFa)
# Initialize geometric coefficients
ks = ko = bf = sob = sof = 0
na = len(litab)
for i in range(na):
ttl = litab[i]
ctl = np.cos(rd * ttl)
chi_s, chi_o, frho, ftau = volscatt(tts, tto, psi, ttl)
ksli = chi_s / cts
koli = chi_o / cto
sobli = frho * np.pi / ctscto
sofli = ftau * np.pi / ctscto
bfli = ctl ** 2
ks += ksli * lidf[i]
ko += koli * lidf[i]
bf += bfli * lidf[i]
sob += sobli * lidf[i]
sof += sofli * lidf[i]
# Structure factors
sdb = 0.5 * (ks + bf)
sdf = 0.5 * (ks - bf)
dob = 0.5 * (ko + bf)
dof = 0.5 * (ko - bf)
ddb = 0.5 * (1 + bf)
ddf = 0.5 * (1 - bf)
sigb = ddb * rho + ddf * tau
sigf = ddf * rho + ddb * tau
att = 1 - sigf
m2 = (att + sigb) * (att - sigb)
m2[m2 <= 0] = 0
m = np.sqrt(m2)
sb = sdb * rho + sdf * tau
sf = sdf * rho + sdb * tau
vb = dob * rho + dof * tau
vf = dof * rho + dob * tau
w = sob * rho + sof * tau
if lai <= 0:
return rsoil, rsoil, rsoil, rsoil
e1 = np.exp(-m * lai)
e2 = e1**2
rinf = (att - m) / sigb
rinf2 = rinf**2
re = rinf * e1
denom = 1 - rinf2 * e2
J1ks = Jfunc1(ks, m, lai)
J2ks = Jfunc2(ks, m, lai)
J1ko = Jfunc1(ko, m, lai)
J2ko = Jfunc2(ko, m, lai)
Ps = (sf + sb * rinf) * J1ks
Qs = (sf * rinf + sb) * J2ks
Pv = (vf + vb * rinf) * J1ko
Qv = (vf * rinf + vb) * J2ko
rdd = rinf * (1 - e2) / denom
tdd = (1 - rinf2) * e1 / denom
tsd = (Ps - re * Qs) / denom
rsd = (Qs - re * Ps) / denom
tdo = (Pv - re * Qv) / denom
rdo = (Qv - re * Pv) / denom
tss = np.exp(-ks * lai)
too = np.exp(-ko * lai)
z = Jfunc3(ks, ko, lai)
g1 = (z - J1ks * too) / (ko + m)
g2 = (z - J1ko * tss) / (ks + m)
Tv1 = (vf * rinf + vb) * g1
Tv2 = (vf + vb * rinf) * g2
T1 = Tv1 * (sf + sb * rinf)
T2 = Tv2 * (sf * rinf + sb)
T3 = (rdo * Qs + tdo * Ps) * rinf
rsod = (T1 + T2 - T3) / (1 - rinf2)
# Hotspot effect
if q <= 0:
alf = 1e6
else:
alf = min((dso / q) * 2 / (ks + ko), 200)
if alf == 0:
tsstoo = tss
sumint = (1 - tss) / (ks * lai)
else:
fhot = lai * np.sqrt(ko * ks)
fint = (1 - np.exp(-alf)) * 0.05
sumint = 0
x1 = y1 = 0
f1 = 1
for i in range(20):
x2 = -np.log(1 - i * fint) / alf if i < 19 else 1
y2 = -(ko + ks) * lai * x2 + fhot * (1 - np.exp(-alf * x2)) / alf
f2 = np.exp(y2)
if y2 != y1:
sumint += (f2 - f1) * (x2 - x1) / (y2 - y1)
x1, y1, f1 = x2, y2, f2
tsstoo = f1
rsos = w * lai * sumint
rso = rsos + rsod
# Canopysoil interaction
dn = 1 - rsoil * rdd
rddt = rdd + tdd * rsoil * tdd / dn
rsdt = rsd + (tsd + tss) * rsoil * tdd / dn
rdot = rdo + tdd * rsoil * (tdo + too) / dn
rsodt = rsod + ((tss + tsd) * tdo + (tsd + tss * rsoil * rdd) * too) * rsoil / dn
rsost = rsos + tsstoo * rsoil
rsot = rsost + rsodt
return np.column_stack((rdot, rsot, rddt, rsdt))

View File

@ -0,0 +1,23 @@
from modules.prospect_d import prospect_d
from modules.sail import sail
def prosail(N, Cab, Car, Ant, Cbrown, Cw, Cm,
LIDFa, LIDFb, TypeLidf, lai, hspot,
tts, tto, psi, rsoil,
wl, nr, Kab, Kcar, Kant, Kbrown, Kw, Km):
"""
PROSAIL simulator using PROSPECT-D + 4SAIL.
Returns:
refl: canopy directional reflectance
LRT : leaf reflectance/transmittance spectra
"""
# Step 1: simulate leaf optical properties using 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]
# Step 2: simulate canopy reflectance using 4SAIL
refl = sail(rho, tau, LIDFa, LIDFb, TypeLidf, lai, hspot, tts, tto, psi, rsoil, wl)
return wl, refl, LRT

View File

@ -0,0 +1,141 @@
import numpy as np
from modules.volscatt import volscatt
from modules.dladgen import dladgen
from modules.campbell import campbell
from modules.Jfunc import Jfunc1, Jfunc2, Jfunc3
def sail(rho, tau, LIDFa, LIDFb, TypeLidf, lai, q, tts, tto, psi, rsoil,wl):
rd = np.pi / 180
cts = np.cos(rd * tts)
cto = np.cos(rd * tto)
ctscto = cts * cto
tants = np.tan(rd * tts)
tanto = np.tan(rd * tto)
cospsi = np.cos(rd * psi)
dso = np.sqrt(tants**2 + tanto**2 - 2 * tants * tanto * cospsi)
# Generate LIDF
if TypeLidf == 1:
lidf, litab = dladgen(LIDFa, LIDFb)
elif TypeLidf == 2:
lidf, litab = campbell(LIDFa)
# Initialize geometric coefficients
ks = ko = bf = sob = sof = 0
na = len(litab)
for i in range(na):
ttl = litab[i]
ctl = np.cos(rd * ttl)
chi_s, chi_o, frho, ftau = volscatt(tts, tto, psi, ttl)
ksli = chi_s / cts
koli = chi_o / cto
sobli = frho * np.pi / ctscto
sofli = ftau * np.pi / ctscto
bfli = ctl ** 2
ks += ksli * lidf[i]
ko += koli * lidf[i]
bf += bfli * lidf[i]
sob += sobli * lidf[i]
sof += sofli * lidf[i]
# Structure factors
sdb = 0.5 * (ks + bf)
sdf = 0.5 * (ks - bf)
dob = 0.5 * (ko + bf)
dof = 0.5 * (ko - bf)
ddb = 0.5 * (1 + bf)
ddf = 0.5 * (1 - bf)
sigb = ddb * rho + ddf * tau
sigf = ddf * rho + ddb * tau
att = 1 - sigf
m2 = (att + sigb) * (att - sigb)
m2[m2 <= 0] = 0
m = np.sqrt(m2)
sb = sdb * rho + sdf * tau
sf = sdf * rho + sdb * tau
vb = dob * rho + dof * tau
vf = dof * rho + dob * tau
w = sob * rho + sof * tau
if lai <= 0:
return rsoil, rsoil, rsoil, rsoil
e1 = np.exp(-m * lai)
e2 = e1**2
rinf = (att - m) / sigb
rinf2 = rinf**2
re = rinf * e1
denom = 1 - rinf2 * e2
J1ks = Jfunc1(ks, m, lai)
J2ks = Jfunc2(ks, m, lai)
J1ko = Jfunc1(ko, m, lai)
J2ko = Jfunc2(ko, m, lai)
Ps = (sf + sb * rinf) * J1ks
Qs = (sf * rinf + sb) * J2ks
Pv = (vf + vb * rinf) * J1ko
Qv = (vf * rinf + vb) * J2ko
rdd = rinf * (1 - e2) / denom
tdd = (1 - rinf2) * e1 / denom
tsd = (Ps - re * Qs) / denom
rsd = (Qs - re * Ps) / denom
tdo = (Pv - re * Qv) / denom
rdo = (Qv - re * Pv) / denom
tss = np.exp(-ks * lai)
too = np.exp(-ko * lai)
z = Jfunc3(ks, ko, lai)
g1 = (z - J1ks * too) / (ko + m)
g2 = (z - J1ko * tss) / (ks + m)
Tv1 = (vf * rinf + vb) * g1
Tv2 = (vf + vb * rinf) * g2
T1 = Tv1 * (sf + sb * rinf)
T2 = Tv2 * (sf * rinf + sb)
T3 = (rdo * Qs + tdo * Ps) * rinf
rsod = (T1 + T2 - T3) / (1 - rinf2)
# Hotspot effect
if q <= 0:
alf = 1e6
else:
alf = min((dso / q) * 2 / (ks + ko), 200)
if alf == 0:
tsstoo = tss
sumint = (1 - tss) / (ks * lai)
else:
fhot = lai * np.sqrt(ko * ks)
fint = (1 - np.exp(-alf)) * 0.05
sumint = 0
x1 = y1 = 0
f1 = 1
for i in range(20):
x2 = -np.log(1 - i * fint) / alf if i < 19 else 1
y2 = -(ko + ks) * lai * x2 + fhot * (1 - np.exp(-alf * x2)) / alf
f2 = np.exp(y2)
if y2 != y1:
sumint += (f2 - f1) * (x2 - x1) / (y2 - y1)
x1, y1, f1 = x2, y2, f2
tsstoo = f1
rsos = w * lai * sumint
rso = rsos + rsod
# Canopysoil interaction
dn = 1 - rsoil * rdd
rddt = rdd + tdd * rsoil * tdd / dn
rsdt = rsd + (tsd + tss) * rsoil * tdd / dn
rdot = rdo + tdd * rsoil * (tdo + too) / dn
rsodt = rsod + ((tss + tsd) * tdo + (tsd + tss * rsoil * rdd) * too) * rsoil / dn
rsost = rsos + tsstoo * rsoil
rsot = rsost + rsodt
return np.column_stack((wl,rdot, rsot, rddt, rsdt))

View File

@ -0,0 +1,141 @@
import numpy as np
from modules.volscatt import volscatt
from modules.dladgen import dladgen
from modules.campbell import campbell
from modules.Jfunc import Jfunc1, Jfunc2, Jfunc3
def sail(rho, tau, LIDFa, LIDFb, TypeLidf, lai, q, tts, tto, psi, rsoil,wl):
rd = np.pi / 180
cts = np.cos(rd * tts)
cto = np.cos(rd * tto)
ctscto = cts * cto
tants = np.tan(rd * tts)
tanto = np.tan(rd * tto)
cospsi = np.cos(rd * psi)
dso = np.sqrt(tants**2 + tanto**2 - 2 * tants * tanto * cospsi)
# Generate LIDF
if TypeLidf == 1:
lidf, litab = dladgen(LIDFa, LIDFb)
elif TypeLidf == 2:
lidf, litab = campbell(LIDFa)
# Initialize geometric coefficients
ks = ko = bf = sob = sof = 0
na = len(litab)
for i in range(na):
ttl = litab[i]
ctl = np.cos(rd * ttl)
chi_s, chi_o, frho, ftau = volscatt(tts, tto, psi, ttl)
ksli = chi_s / cts
koli = chi_o / cto
sobli = frho * np.pi / ctscto
sofli = ftau * np.pi / ctscto
bfli = ctl ** 2
ks += ksli * lidf[i]
ko += koli * lidf[i]
bf += bfli * lidf[i]
sob += sobli * lidf[i]
sof += sofli * lidf[i]
# Structure factors
sdb = 0.5 * (ks + bf)
sdf = 0.5 * (ks - bf)
dob = 0.5 * (ko + bf)
dof = 0.5 * (ko - bf)
ddb = 0.5 * (1 + bf)
ddf = 0.5 * (1 - bf)
sigb = ddb * rho + ddf * tau
sigf = ddf * rho + ddb * tau
att = 1 - sigf
m2 = (att + sigb) * (att - sigb)
m2[m2 <= 0] = 0
m = np.sqrt(m2)
sb = sdb * rho + sdf * tau
sf = sdf * rho + sdb * tau
vb = dob * rho + dof * tau
vf = dof * rho + dob * tau
w = sob * rho + sof * tau
if lai <= 0:
return rsoil, rsoil, rsoil, rsoil
e1 = np.exp(-m * lai)
e2 = e1**2
rinf = (att - m) / sigb
rinf2 = rinf**2
re = rinf * e1
denom = 1 - rinf2 * e2
J1ks = Jfunc1(ks, m, lai)
J2ks = Jfunc2(ks, m, lai)
J1ko = Jfunc1(ko, m, lai)
J2ko = Jfunc2(ko, m, lai)
Ps = (sf + sb * rinf) * J1ks
Qs = (sf * rinf + sb) * J2ks
Pv = (vf + vb * rinf) * J1ko
Qv = (vf * rinf + vb) * J2ko
rdd = rinf * (1 - e2) / denom
tdd = (1 - rinf2) * e1 / denom
tsd = (Ps - re * Qs) / denom
rsd = (Qs - re * Ps) / denom
tdo = (Pv - re * Qv) / denom
rdo = (Qv - re * Pv) / denom
tss = np.exp(-ks * lai)
too = np.exp(-ko * lai)
z = Jfunc3(ks, ko, lai)
g1 = (z - J1ks * too) / (ko + m)
g2 = (z - J1ko * tss) / (ks + m)
Tv1 = (vf * rinf + vb) * g1
Tv2 = (vf + vb * rinf) * g2
T1 = Tv1 * (sf + sb * rinf)
T2 = Tv2 * (sf * rinf + sb)
T3 = (rdo * Qs + tdo * Ps) * rinf
rsod = (T1 + T2 - T3) / (1 - rinf2)
# Hotspot effect
if q <= 0:
alf = 1e6
else:
alf = min((dso / q) * 2 / (ks + ko), 200)
if alf == 0:
tsstoo = tss
sumint = (1 - tss) / (ks * lai)
else:
fhot = lai * np.sqrt(ko * ks)
fint = (1 - np.exp(-alf)) * 0.05
sumint = 0
x1 = y1 = 0
f1 = 1
for i in range(20):
x2 = -np.log(1 - i * fint) / alf if i < 19 else 1
y2 = -(ko + ks) * lai * x2 + fhot * (1 - np.exp(-alf * x2)) / alf
f2 = np.exp(y2)
if y2 != y1:
sumint += (f2 - f1) * (x2 - x1) / (y2 - y1)
x1, y1, f1 = x2, y2, f2
tsstoo = f1
rsos = w * lai * sumint
rso = rsos + rsod
# Canopysoil interaction
dn = 1 - rsoil * rdd
rddt = rdd + tdd * rsoil * tdd / dn
rsdt = rsd + (tsd + tss) * rsoil * tdd / dn
rdot = rdo + tdd * rsoil * (tdo + too) / dn
rsodt = rsod + ((tss + tsd) * tdo + (tsd + tss * rsoil * rdd) * too) * rsoil / dn
rsost = rsos + tsstoo * rsoil
rsot = rsost + rsodt
return np.column_stack((wl, rdot, rsot, rddt, rsdt))

View File

@ -0,0 +1,23 @@
from modules.prospect_d import prospect_d
from modules.sail import sail
def prosail(N, Cab, Car, Ant, Cbrown, Cw, Cm,
LIDFa, LIDFb, TypeLidf, lai, hspot,
tts, tto, psi, rsoil,
wl, nr, Kab, Kcar, Kant, Kbrown, Kw, Km):
"""
PROSAIL simulator using PROSPECT-D + 4SAIL.
Returns:
refl: canopy directional reflectance
LRT : leaf reflectance/transmittance spectra
"""
# Step 1: simulate leaf optical properties using 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]
# Step 2: simulate canopy reflectance using 4SAIL
refl = sail(rho, tau, LIDFa, LIDFb, TypeLidf, lai, hspot, tts, tto, psi, rsoil,wl)
return wl, refl, LRT

View File

@ -0,0 +1,96 @@
import numpy as np
def volscatt(tts, tto, psi, ttl):
"""
Volume scattering and interception functions for a given geometry.
Inputs:
tts : solar zenith angle (deg)
tto : observer zenith angle (deg)
psi : relative azimuth angle (deg)
ttl : leaf inclination angle (deg)
Returns:
chi_s : solar interception function
chi_o : observer interception function
frho : function to multiply with leaf reflectance
ftau : function to multiply with leaf transmittance
"""
rd = np.pi / 180.0
costs = np.cos(rd * tts)
costo = np.cos(rd * tto)
sints = np.sin(rd * tts)
sinto = np.sin(rd * tto)
cospsi = np.cos(rd * psi)
psir = rd * psi
costl = np.cos(rd * ttl)
sintl = np.sin(rd * ttl)
cs = costl * costs
co = costl * costo
ss = sintl * sints
so = sintl * sinto
# Solar side
if abs(ss) > 1e-6:
cosbts = -cs / ss
else:
cosbts = 5
if abs(so) > 1e-6:
cosbto = -co / so
else:
cosbto = 5
if abs(cosbts) < 1:
bts = np.arccos(cosbts)
ds = ss
else:
bts = np.pi
ds = cs
chi_s = 2 / np.pi * ((bts - 0.5 * np.pi) * cs + np.sin(bts) * ss)
if abs(cosbto) < 1:
bto = np.arccos(cosbto)
doo = so
elif tto < 90:
bto = np.pi
doo = co
else:
bto = 0
doo = -co
chi_o = 2 / np.pi * ((bto - 0.5 * np.pi) * co + np.sin(bto) * so)
# Bidirectional scattering coefficient
btran1 = abs(bts - bto)
btran2 = np.pi - abs(bts + bto - np.pi)
if psir <= btran1:
bt1 = psir
bt2 = btran1
bt3 = btran2
else:
bt1 = btran1
if psir <= btran2:
bt2 = psir
bt3 = btran2
else:
bt2 = btran2
bt3 = psir
t1 = 2 * cs * co + ss * so * cospsi
t2 = 0
if bt2 > 0:
t2 = np.sin(bt2) * (2 * ds * doo + ss * so * np.cos(bt1) * np.cos(bt3))
denom = 2 * np.pi * np.pi
frho = ((np.pi - bt2) * t1 + t2) / denom
ftau = (-bt2 * t1 + t2) / denom
frho = max(frho, 0)
ftau = max(ftau, 0)
return chi_s, chi_o, frho, ftau

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_())

File diff suppressed because one or more lines are too long