← RTM-Suite home

Comparing PROSPECT-D and PRO with the packages originally developed by Féret

The R vs Python page checks that RTM-Suite's own two language implementations agree with each other. This page asks a different question: do RTM-Suite's R ports of PROSPECT-D, PROSPECT-PRO and PROSAIL (4SAIL) agree with Jean-Baptiste Féret's original reference R packagesprospect and prosail — that ToolsRTM's leaf and canopy models were originally ported from? 60 simulations, one shared randomly-sampled LUT (getLUT(inputsPROSAIL), seed 42), both packages run independently from the same inputs.

Comparison successful. Both PROSPECT leaf versions (D and PRO) and PROSAIL (4SAIL) match Jean-Baptiste Féret's original packages — PROSPECT-PRO and PROSAIL to exact machine precision, PROSPECT-D to R²>0.9999997. Verified in R, log below.
> source("compare_batch.R")   # 60 simulations, shared LUT, seed 42
Valid sims -- D: 60 / 60  PRO: 60 / 60  Canopy: 60 / 60
PROSPECT-D reflectance       n=126060   RMSE=8.0e-05  maxdiff=6.5e-04  R2=0.99999973
PROSPECT-PRO reflectance     n=126060   RMSE=0.000e+00  maxdiff=0.000e+00  R2=1.00000000
PROSPECT-PRO transmittance   n=126060   RMSE=0.000e+00  maxdiff=0.000e+00  R2=1.00000000
PROSAIL rsot (canopy)        n=126060   RMSE=0.000e+00  maxdiff=0.000e+00  R2=1.00000000
The LUT used PROSPECT-D PROSPECT-PRO PROSAIL (4SAIL) dataSpec_PDB & PRO contents Sensitivity check (Cab sweep) Reproduce this yourself

Same inputs, both packagesThe LUT used: 60 simulations, ranges and fixed values

One shared LUT, getLUT(inputs = ToolsRTM::inputsPROSAIL, nLUT = 60, setseed = 42) — every table above runs both packages from these same 60 rows. inputsPROSAIL allows wider nominal ranges for several parameters, but marks some of them "use default" instead of sampled; the ranges below are what the 60 rows actually realized, not the nominal spec.

ParameterRealized range (60 sims)Units
Cab (chlorophyll)9.5 – 72.5µg/cm²
Car (carotenoid)1.5 – 14.6µg/cm²
Anth (anthocyanin)0.02 – 4.33µg/cm²
Cbrown (brown pigment)0.08 – 0.96arbitrary units
EWT (equivalent water thickness)0.0010 – 0.0349cm
Prot (protein)0.0014 – 0.0291g/cm²
CBC (carbon-based constituents)0.0003 – 0.0298g/cm²
N (leaf structure)1.50 – 2.46
LIDFa (leaf angle distribution)30.5 – 69.9degree
LAI (leaf area index)2.02 – 4.95m²/m²
tto (view zenith angle)15.1 – 29.8degree
Held fixed (all 60 sims)ValueWhy
LMA (lumped dry matter)0PRO mode: dry matter comes from Prot+CBC instead
alpha (incidence solid angle)40°standard leaf-optics convention
LIDFb / TypeLidf0 / 2ellipsoidal LIDF (type 2), only LIDFa varies
hspot (hotspot parameter)0default
tts (sun zenith angle)default
psi (relative azimuth)default
R²=0.9999997
PROSPECT-D reflectance, 60 sims × 2101 wavelengths
R²=1.0000000
PROSPECT-PRO reflectance, 60 sims × 2101 wavelengths
R²=1.0000000
PROSAIL (PROSPECT-PRO+4SAIL) rsot, 60 sims
60 / 60
simulations valid on both sides, every comparison

vs. jbferet prospectPROSPECT-D

Leaf structure (N), chlorophyll, carotenoid, anthocyanin, brown pigment, water and lumped dry-matter (LMA) content — the classic PROSPECT-D parameterization, no protein/carbon split. ToolsRTM's prospect_DB() vs. jbferet's prospect() (with prot=cbc=0), same inputs, full 400–2500 nm reflectance and transmittance.

PROSPECT-D example spectrum: reflectance (lower curve, labeled) and 1-minus-transmittance (upper curve, labeled) -- ToolsRTM black solid vs jbferet original red dashed, fully overlapping -- plus a scatter plot of all 60 simulations tight on the 1:1 line
OutputValid simsRMSEMax abs diff
Reflectance60/608.0 × 10-56.5 × 10-40.99999973
Transmittance60/608.0 × 10-56.4 × 10-40.99999962

vs. jbferet prospectPROSPECT-PRO

Same leaf model, but dry matter split into protein (Prot) and non-protein carbon-based constituents (CBC) instead of one lumped LMA term — ToolsRTM's prospect_PRO() vs. jbferet's prospect() (with lma=0, prot/cbc from the same LUT columns).

PROSPECT-PRO example spectrum: reflectance (lower curve, labeled) and 1-minus-transmittance (upper curve, labeled) -- ToolsRTM black solid vs jbferet original red dashed, fully overlapping -- plus a scatter plot of all 60 simulations tight on the 1:1 line
OutputValid simsRMSEMax abs diff
Reflectance60/600.00.01.00000000
Transmittance60/600.00.01.00000000

Exact machine-precision agreement across all 60 simulations.

vs. jbferet prosailPROSAIL — PROSPECT-PRO + 4SAIL

The full canopy chain: PROSPECT-PRO leaf optics feeding ToolsRTM's foursail() vs. jbferet's prosail() wrapper (which couples the same leaf model to its own fourSAIL()). Same LIDF, LAI, hotspot, sun/view geometry and a flat soil reflectance (0.15) on both sides; bi-directional reflectance factor (rsot) compared.

PROSAIL example spectrum overlay, fully overlapping, and scatter plot of all 60 simulations tight on the 1:1 line
OutputValid simsRMSEMax abs diff
rsot (bi-directional reflectance factor)60/600.00.01.00000000

Exact agreement across the full canopy chain, all 60 simulations.

What's inside ToolsRTM's two coefficient tables: dataSpec_PDB vs. dataSpec_PRO

ToolsRTM ships two separate coefficient tables, sourced independently rather than one derived from the other — dataSpec_PDB backs PROSPECT-D (and also bundles solar-irradiance and reference-soil spectra, used elsewhere), while dataSpec_PRO backs PROSPECT-PRO. Their parameter sets only partly overlap:

dataSpec_PDB columnWhat it holds
wavelength, Refrac_leafmWavelength grid, leaf refractive index
SC_chl, SC_car, SC_Anth, SC_BrwonChlorophyll, carotenoid, anthocyanin, brown-pigment coefficients
SC_Cw, SC_CmWater and lumped dry-matter coefficients (PROSPECT-D style)
direct_light, diffuse_lightSolar irradiance spectra (not a leaf-optics coefficient)
dry_soil, wet_soilReference soil reflectance spectra (not a leaf-optics coefficient)
dataSpec_PRO columnWhat it holds
wave, RI_leafmaterialWavelength grid, leaf refractive index
spA_Cab, spA_Car, spA_Atn, spA_BrChlorophyll, carotenoid, anthocyanin, brown-pigment coefficients
spA_CwWater coefficient
spA_CmLegacy lumped dry-matter coefficient (kept for reference, unused in PRO mode)
spA_Protein, spA_nonProSplit dry-matter coefficients (PROSPECT-PRO style: protein + carbon-based constituents)

The pigment and refractive-index columns that both tables carry (refractive index, Cab, Car, Anth, Brown, EWT) agree to within ≈5×10-4 or better — consistent with two independently-exported historical tables, not a shared error. The dry-matter side isn't a like-for-like comparison: PROSPECT-D's single Cm and PROSPECT-PRO's split Protein/nonPro are two different published parameterizations of the same physical absorption, not expected to match wavelength-by-wavelength.

Beyond one example pointSensitivity check: sweeping chlorophyll (Cab)

The tables above compare 60 random simulations, but it's easy to wonder whether agreement holds only near the specific values those draws happened to land on. This sweeps chlorophyll content (Cab = 10, 25, 40, 55, 70 μg/cm²) through PROSAIL end to end, everything else held fixed, ToolsRTM vs. jbferet's original at every value.

PROSAIL sensitivity to chlorophyll (Cab), five values swept, each colored differently: ToolsRTM (solid) vs jbferet original (dashed, same color per Cab) exactly overlapping in a zoomed visible-NIR edge panel and across the full spectrum

Each Cab value gets its own color for both the solid (ToolsRTM) and dashed (jbferet) line, so a true per-Cab mismatch would show as a visible gap in its own color — there isn't one anywhere, including at the chlorophyll absorption peak (≈550–680 nm) where Cab's effect is strongest and any porting drift would show up most clearly.

Reproduce this yourself

jbferet's original packages aren't on CRAN. prospect is installed for real (devtools::install_local()) since its ::-qualified internal calls need it on the search path; prosail is only source()'d directly — its forward-simulation functions (prosail(), fourSAIL()) don't touch the package's heavier inversion/hybrid-ML dependencies (raster, terra, sf, mlr...), so a full install of prosail isn't necessary just to compare simulations.

# Shell: get the source
git clone https://github.com/jbferet/prospect.git
git clone https://github.com/jbferet/prosail.git
\# R: install prospect for real (needed so its internal `prospect::` calls resolve)
install.packages("NlcOptim")  # only missing dependency; everything else Imports is on CRAN
devtools::install_local("prospect", dependencies = FALSE, upgrade = "never")

library(ToolsRTM)
library(prospect)

\# source prosail's R/ files directly -- no install needed for forward simulation
prosail_dir <- "prosail"  # path to the cloned repo
for (f in list.files(file.path(prosail_dir, "R"), pattern = "\\.R$", full.names = TRUE))
  source(f)

spec <- prospect::spec_prospect_full_range
lam  <- spec$lambda

\# one shared, randomly-sampled LUT -- same inputs feed both packages
LUT <- as.data.frame(getLUT(inputs = ToolsRTM::inputsPROSAIL, nLUT = 60, setseed = 42))
rsoil <- rep(0.15, length(lam))

row <- LUT[1, ]  # any row -- this repeats per simulation in the real comparison

\# ---- PROSPECT-PRO: ToolsRTM vs. jbferet's prospect() ----
rtm_leaf <- prospect_PRO(N = row$N, Cab = row$Cab, Car = row$Car, Anth = row$Anth,
                          Cbrown = row$Cbrown, EWT = row$EWT, LMA = 0,
                          alpha = row$alpha, Prot = row$Prot, CBC = row$CBC)
jb_leaf  <- prospect::prospect(spec_prospect = spec, n_struct = row$N, chl = row$Cab,
                          car = row$Car, ant = row$Anth, brown = row$Cbrown, ewt = row$EWT,
                          lma = 0, prot = row$Prot, cbc = row$CBC, alpha = row$alpha)
cat("Reflectance R2:", 1 - sum((rtm_leaf$refl - jb_leaf$reflectance)^2) /
      sum((rtm_leaf$refl - mean(rtm_leaf$refl))^2), "\n")

\# ---- PROSAIL: ToolsRTM's foursail() vs. jbferet's prosail() wrapper ----
lut_row <- row; lut_row$LMA <- 0
rtm_canopy <- foursail(rsoil = rsoil, inputLUT = lut_row,
                        LeafModel = "PROSPECT-PRO", spectrum.all = TRUE)
input_prospect_jb <- data.frame(n_struct = row$N, chl = row$Cab, car = row$Car,
                        ant = row$Anth, brown = row$Cbrown, ewt = row$EWT, lma = 0,
                        prot = row$Prot, cbc = row$CBC)
jb_canopy <- prosail(spec_sensor = spec, input_prospect = input_prospect_jb,
                        alpha = row$alpha, type_lidf = 2, lidf_a = row$LIDFa,
                        lai = row$LAI, hotspot = row$hspot, tts = row$tts,
                        tto = row$tto, psi = row$psi, rsoil = rsoil, SAILversion = "4SAIL")
plot(lam, rtm_canopy$rsot, type = "l", xlab = "Wavelength (nm)", ylab = "rsot")
lines(lam, jb_canopy$rsot, col = "red", lty = 2)  # ToolsRTM vs. jbferet original

Loop that over seq_len(nrow(LUT)) and accumulate rtm_*/jb_* into vectors to reproduce the full 60-simulation tables above, or slice by wavelength (lam >= 1300 & lam < 1450) to reproduce the per-wavelength SWIR breakdown.

See also the R Ecosystem section for direct links to prospect, prosail and biodivMapR, and the Contributors & Collaborators section for the collaboration with Jean-Baptiste Féret (UMR TETIS, INRAE).