Examples

Every snippet on this page has actually been run against the real toolsrtm/scopeinpython packages – not hand-written pseudocode. These mirror the R tutorial series (ToolsRTM, SCOPEinR) topic-for-topic where a Python port exists; see each package’s own README.md for the full R-tutorial-to-Python-module bridge table, including the gaps called out at the bottom of this page.

Leaf + canopy (toolsrtm)

Mirrors R Tutorials 01-02 (prospect_d = leaf optics, foursail = canopy BRDF).

import numpy as np
from toolsrtm import prospect_d, foursail

leaf = prospect_d(N=1.5, Cab=40, Car=8, Anth=1, Cbrown=0, EWT=0.01, LMA=0.009, alpha=40)
print(leaf.lambda_[:3], leaf.refl[:3], leaf.tran[:3])

inputLUT = dict(
    N=1.5, Cab=40, Car=8, Anth=1, Cbrown=0, EWT=0.01, LMA=0.009, alpha=40,
    Prot=0.002, CBC=0.007,          # only used if leaf_model='PROSPECT-PRO'
    LIDFa=-0.35, LIDFb=-0.15, TypeLidf=1,
    LAI=3, hspot=0.01, tts=30, tto=0, psi=0,
)
rsoil = np.full(2101, 0.15)
sail = foursail(inputLUT, rsoil, leaf_model="PROSPECT-D", spectrum_all=True)
print("TOC bidirectional reflectance factor (rsot) at 550 nm:", sail.rsot[550 - 400])

Soil (BSM) + optical canopy BRDF (scopeinpython)

Mirrors SCOPEinR Tutorials 01-02 (soil model + canopy optics via rtmo).

import numpy as np
from toolsrtm import prospect_d
from toolsrtm.canopy import dladgen
from scopeinpython import SoilParams, WettingParams, get_bsm, CanopyStructure, get_spectra_scope, run_rtmo

spectral = get_spectra_scope()
rsoil = get_bsm(SoilParams(BSMBrightness=0.5, BSMlat=25, BSMlon=45),
                 WettingParams(SMp=15, SMC=25, film=0.015))

leaf = prospect_d(N=1.5, Cab=40, Car=8, Anth=1, Cbrown=0, EWT=0.01, LMA=0.009, alpha=40)
refl_leaf, tran_leaf = leaf.refl[:2001], leaf.tran[:2001]

lidf = dladgen(-0.35, -0.15).lidf
canopy = CanopyStructure(LAI=3, lidf=lidf, hot=0.1 / 2.0)

# Esun_/Esky_ must be supplied by the caller -- see python/README.md
result = run_rtmo(
    spectral=spectral, leaf_refl=refl_leaf, leaf_tran=tran_leaf,
    rho_thermal=0.01, tau_thermal=0.01, rsoil=rsoil, canopy=canopy,
    tts=30, tto=0, psi=0, Esun_=Esun_, Esky_=Esky_,
)
print("TOC reflectance at 550 nm:", result.refl[550 - 400])

Sensor convolution + vegetation indices (toolsrtm)

Mirrors R Tutorials 07-09: convolve a simulated hyperspectral canopy spectrum onto real Sentinel-2A band spectral response functions, then compute vegetation indices from the convolved bands.

import numpy as np
from toolsrtm import prospect_d, foursail
from toolsrtm.srf import srf_sentinel2a, spectral_convolution_srf
from toolsrtm.indices import get_indices

inputLUT = dict(
    N=1.5, Cab=40, Car=8, Anth=1, Cbrown=0, EWT=0.01, LMA=0.009, alpha=40,
    LIDFa=-0.35, LIDFb=-0.15, TypeLidf=1,
    LAI=3, hspot=0.01, tts=30, tto=0, psi=0,
)
rsoil = np.full(2101, 0.15)
sail = foursail(inputLUT, rsoil, leaf_model="PROSPECT-D", spectrum_all=True)
wave = np.arange(400, 2501)

s2a = srf_sentinel2a()
conv = spectral_convolution_srf(wave, sail.rsot, s2a)
print("Sentinel-2A bands:", s2a.band_names)
print("Convolved TOC reflectance:", np.round(conv.rfl, 4))

indices = get_indices(conv.wl, conv.rfl, spectral_domain="VNIR")
for name in ("NDVI", "MSAVI", "REP"):
    print(name, "=", round(float(indices[name][0]), 4))

Machine-learning trait inversion (toolsrtm)

Mirrors R Tutorials 11-12: build a small LUT, extract Sentinel-2-like bands, and invert Cab with a PLSR model (get_inversion dispatches to 12 algorithms in total – see toolsrtm.inversion.ALGORITHMS).

import numpy as np
import pandas as pd
from toolsrtm import prospect_d, foursail
from toolsrtm.inversion import get_inversion

rng = np.random.default_rng(1)
rows = []
for _ in range(200):
    Cab, LAI = rng.uniform(10, 80), rng.uniform(0.5, 6)
    inputLUT = dict(
        N=1.5, Cab=Cab, Car=8, Anth=1, Cbrown=0, EWT=0.01, LMA=0.009, alpha=40,
        LIDFa=-0.35, LIDFb=-0.15, TypeLidf=1,
        LAI=LAI, hspot=0.01, tts=30, tto=0, psi=0,
    )
    sail = foursail(inputLUT, np.full(2101, 0.15), leaf_model="PROSPECT-D", spectrum_all=True)
    row = {"Cab": Cab, "LAI": LAI}
    for wl in (490, 560, 665, 705, 740, 783, 842, 865, 1610, 2190):
        row[f"R{wl}"] = sail.rsot[wl - 400]
    rows.append(row)

df = pd.DataFrame(rows)
band_cols = [c for c in df.columns if c.startswith("R")]

result = get_inversion(df, dep_var="Cab", inputs=band_cols, algorithm="PLSR", n_samples=200, seed=1)
print("Test R2:", round(result.statistics["test"]["r2"], 3))
print("Test RMSE:", round(result.statistics["test"]["rmse"], 3))

MARMIT soil moisture model (toolsrtm)

Mirrors R Tutorial 16: build a wetted soil spectrum from a dry reference and couple it into a canopy simulation.

from toolsrtm.marmit import get_marmit_rsoil
from toolsrtm import prospect_d, foursail

soil = get_marmit_rsoil(soil_id=3, L=0.05, eps=0.4, version="marmit1")
print("SMC (soil moisture content):", round(float(soil.smc), 4))
for wl in (550, 850, 1600):
    i = wl - 400
    print(f"{wl}nm: dry={soil.rsoil_dry[i]:.4f}  wet={soil.rsoil_wet[i]:.4f}")

inputLUT = dict(
    N=1.5, Cab=40, Car=8, Anth=1, Cbrown=0, EWT=0.01, LMA=0.009, alpha=40,
    LIDFa=-0.35, LIDFb=-0.15, TypeLidf=1,
    LAI=1.5, hspot=0.01, tts=30, tto=0, psi=0,
)
sail = foursail(inputLUT, soil.rsoil_wet, leaf_model="PROSPECT-D", spectrum_all=True)
print("Canopy TOC reflectance at 850nm with wet MARMIT soil:", round(float(sail.rsot[850 - 400]), 4))

Full SCOPE run: energy balance + fluorescence (scopeinpython)

Mirrors SCOPEinR Tutorials 03-04: one full get_scope() call – optics, energy balance, photosynthesis and fluorescence together – against the same bundled example LUT row SCOPEinR’s own test suite and R vignettes use (SCOPEinR/inst/input/LUT_input.csv).

import csv
from pathlib import Path
from scopeinpython import ScopeOptions, get_scope

with open("SCOPEinR/inst/input/LUT_input.csv", newline="") as f:
    row = next(csv.DictReader(f))

res = get_scope(row, options=ScopeOptions(k_maxit=100, maxEBer=1.0))
print("Canopy layers:", res.nlayers)
print("TOC reflectance at 550/700/850 nm:",
      round(float(res.rtmo.refl[550 - 400]), 4),
      round(float(res.rtmo.refl[700 - 400]), 4),
      round(float(res.rtmo.refl[850 - 400]), 4))
print("Net radiation, total (Rntot):", round(float(res.ebal.Rntot), 2), "W/m2")
print("Total photosynthesis (Actot):", round(float(res.ebal.Actot), 2), "umol CO2/m2/s")
if res.rtmf is not None:
    print("Emitted fluorescence (EoutF):", round(float(res.rtmf.EoutF), 4), "W/m2/sr")

Full simulate -> indices -> ML-invert pipeline

A complete, actually-executed pipeline (100 samples, spectral indices, scikit-learn trait inversion, real R² 0.7-0.9) lives outside this package as plain scripts + a Jupyter notebook, kept separate from the R scripts:

Scripts/Python/ForPROSAIL_fourSAIL/1_simulate_lut.py2_spectral_indices.py3_inversion_ml.py, plus pipeline.ipynb. See Scripts/Python/README.md for how to run it.

What isn’t ported yet

Two tutorial-level gaps not covered by any example above, on top of the finer-grained implementation gaps in Known limitations:

  • Sobol/Johnson sensitivity analysis (toolsrtm, R Tutorial 10’s get.sobol.indices()/get.spectral.sensitivity()) and LUT correlation-distribution helpers (get_distributionLUT()/getCor(), R Tutorial 05) have no Python module yet.

  • The real-Sentinel-2/STAC capstone (SCOPEinR Tutorial 11 – Actot retrieved from a real satellite time series and mapped spatially; ToolsRTM Tutorial 18 – a spatial index map from a real STAC-retrieved image) has no scopeinpython/toolsrtm equivalent yet, even though toolsrtm.satellite already has the STAC retrieval machinery a Python port of it would reuse.

See each package’s own README.md for the full R-tutorial-to-Python-module bridge table.