Every function call below actually runs against the real, installed
SCOPEinR and ToolsRTM packages — nothing here
is illustrative pseudocode. Where the port has real rough edges (a soil
energy-balance warning, a non-exported helper, a folder-path quirk),
they are shown and explained as they actually occur, not hidden.
library(ToolsRTM)
library(SCOPEinR)
cat("ToolsRTM ", as.character(packageVersion("ToolsRTM")),
" | SCOPEinR ", as.character(packageVersion("SCOPEinR")), "\n", sep = "")## ToolsRTM 0.62.5 | SCOPEinR 0.46
SCOPE (Soil Canopy Observation, Photochemistry and Energy fluxes) is a coupled radiative-transfer/energy-balance model of a vegetated surface (Van der Tol et al. 2009; Yang et al. 2020, SCOPE 2.0). Given leaf biochemistry, canopy structure, viewing/illumination geometry and meteorology, it simultaneously solves for:
SCOPEinR is the R port of SCOPE 2.1 (originally
MATLAB). It does not reimplement leaf and canopy optics from scratch:
the radiative-transfer core (Fluspect leaf optics, 4SAIL canopy
geometry) is the same family of models exposed directly in
ToolsRTM (see the companion
ToolsRTM_PROSAIL_tutorial.Rmd). SCOPEinR calls into that
machinery and then adds everything PROSAIL/4SAIL alone does not do: the
iterative leaf/soil energy balance (get.ebal()), the
biochemical photosynthesis model (get.biochemical()), and
the fluorescence radiative transfer (get.RTMf()).
ToolsRTM is a hard dependency of SCOPEinR for
exactly this reason — you always need both packages loaded to run
SCOPE.
Both packages are distributed from public GitLab repositories and, in this environment, are already installed:
if (!requireNamespace("remotes", quietly = TRUE)) install.packages("remotes")
remotes::install_gitlab("caminoccg/toolsrtm", upgrade = "never")
remotes::install_gitlab("caminoccg/scopeinr", upgrade = "never")library(SCOPEinR) on its own is not enough — always load
ToolsRTM first (or alongside it), since SCOPEinR’s
get.SCOPE() dispatches leaf and canopy optics straight into
ToolsRTM functions.
SCOPE needs two kinds of input beyond the leaf/canopy traits ToolsRTM
already required for PROSAIL: simulation options (which
sub-models to switch on) and a parameter table with the
added biochemical, meteorological and geometric fields the energy
balance needs. Both ship inside the installed package under
inst/input/, resolved here with system.file()
so the paths work regardless of the current working directory:
## [1] "input_borders.csv" "input_data_default.csv" "inputs_SCOPE.csv"
## [4] "inputs_SCOPE.xlsx" "leafangles" "LUT_input.csv"
## [7] "mSCOPE.csv" "radiationdata" "setoptions.csv"
## [10] "soil_spectra"
setoptions.csv — simulation optionsscope_options <- read.table(file.path(path_input, "setoptions.csv"),
header = TRUE, sep = ",")
scope_options[, c("Options", "Value")]## Options Value
## 1 lite 1
## 2 calc_fluorescence 1
## 3 calc_spectrum_planck 1
## 4 calc_xanthophyllabs 1
## 5 soilspectrum 0
## 6 Fluorescence_model 0
## 7 applTcorr 1
## 8 verify 1
## 9 mSCOPE 0
## 10 simulation 0
## 11 calc_directional 0
## 12 calc_vert_profiles 0
## 13 soil_heat_method 2
## 14 calc_rss_rbs 0
## 15 MoninObukhov 1
## 16 LIDF 0
## 17 irradiance 0
Each row switches a SCOPE sub-model or numerical behaviour on/off
(0/1) or picks a variant
(e.g. Fluorescence_model). The ones that matter most for
what follows: calc_fluorescence = 1 turns on the
Fluspect/RTMf fluorescence chain (without it,
data.rad$reflapp and the fluorescence_*
outputs are not produced); calc_spectrum_planck = 1 turns
on the thermal-emission spectrum; soil_heat_method = 2
picks how the soil heat flux G is estimated
(0.35 * Rn when not running a time series);
LIDF = 0 means the leaf-inclination distribution is built
from LIDFa/LIDFb in the LUT rather than from a
measured-angles file.
LUT_input.csv and
inputs_SCOPE.csvLUT_input.csv is a ready-to-run, single-row example
parameter set (one SCOPE simulation) with every column SCOPE expects —
leaf biochemistry (N, Cab, Car,
…), photosynthetic capacity (Vcmax25,
BallBerrySlope, …), canopy structure (LAI,
hc, LIDFa, LIDFb, …),
soil/meteorology (Ta, Rin, Rli,
p, ea, RH, u, …) and
viewing/illumination geometry (tts, tto,
psi):
LUT_default <- read.table(file.path(path_input, "LUT_input.csv"),
header = TRUE, sep = ",")
dim(LUT_default)## [1] 1 77
## N Cab Car LAI Vcmax25 tts tto Ta Rin
## 1 1.5 60 10 3 60 30 0 20 600
inputs_SCOPE.csv is the companion ranges table
used to generate new, randomized parameter sets (Section 6): one row per
variable, with a lower/upper bound, a sampling
Distribution (Uniform, Gaussian
or Fixed), and a default value used when the
variable is held fixed.
inputLUT <- read.table(file.path(path_input, "inputs_SCOPE.csv"),
header = TRUE, sep = ",")
inputLUT[inputLUT$variable %in% c("Cab","LAI","Vcmax25","EWT","LIDFa","tts"),
c("variable","lower","upper","Distribution","default")]## variable lower upper Distribution default
## 2 Cab 5.00 90.0 Gaussian 40.000
## 6 EWT 0.00 0.2 Uniform 0.009
## 15 Vcmax25 0.75 250.0 Uniform 70.000
## 40 LAI 0.10 7.0 Uniform 3.000
## 42 LIDFa -1.00 1.0 Uniform -0.350
## 74 tts 0.00 15.0 Uniform 30.000
get.SCOPE()The default LUT_input.csv row plus the default options
are enough to run one full simulation. optipar selects the
wavelength-dependent leaf optical coefficients for the chosen
leaf.model — optipar2021.Pro.CX pairs with
leaf.model = "fluspect-CX" (PROSPECT-PRO pigment set, plus
the Cx xanthophyll de-epoxidation state for PRI-type
simulations).
invisible(capture.output(
db.sim <- SCOPEinR::get.SCOPE(
LUT = LUT_default[1, ],
options.SCOPE = scope_options,
optipar = SCOPEinR::optipar2021.Pro.CX,
leaf.model = "fluspect-CX",
canopy.model = "fourSAIL",
get.outputs = "ALL",
get.plots = FALSE
)
))get.SCOPE() always returns a list of
simulations (one element per LUT row); for a single row that is
a list of length 1, and db.sim[[1]] is itself a list with
19 named elements:
## [1] 1
## [1] 19
## [1] "data.spectral" "data.angles" "data.rad" "atmo"
## [5] "resist_out" "data.fluxes" "data.soil" "data.leafopt"
## [9] "data.leafbio" "data.canopy" "data.profiles" "data.gap"
## [13] "data.meteo" "data.thermal" "data.bcu" "data.bch"
## [17] "data.directional" "iter.ebal" "data.opts"
The elements that matter for everyday use:
| Element | Contents |
|---|---|
data.rad |
Reflectance/radiance/irradiance spectra (rdd,
rdo, rsd, rso, refl,
reflapp, Lo_, Esun_,
Esky_, fluorescence spectra, absorbed-PAR terms, …) — 87
fields |
data.fluxes |
Scalar energy/carbon fluxes: Rnctot,
lEctot, Hctot, Actot,
Tcave, and the soil/total equivalents |
data.thermal |
Solved sunlit/shaded canopy and soil temperatures (Tcu,
Tch, Tsu, Tsh) |
data.bcu / data.bch |
Biochemical model state for sunlit (bcu) / shaded
(bch) leaves: A (assimilation),
Ja (electron transport), Kn (NPQ),
eta (fluorescence yield factor) |
data.canopy, data.leafbio,
data.soil, data.meteo,
data.angles |
Echo back the resolved canopy/leaf/soil/meteo/geometry inputs actually used |
iter.ebal |
Energy-balance solver diagnostics: iteration counter,
maxit, and the residual
maxEBercu/maxEBerch/maxEBers
errors (canopy sunlit/shaded/soil) |
data.opts, data.spectral,
data.gap, data.profiles,
data.directional, atmo,
resist_out |
Options actually used, wavelength grid, gap probabilities, vertical profiles, directional (BRDF) output, atmosphere, aerodynamic/surface resistances |
## $Rnctot
## [1] 381.7305
##
## $lEctot
## [1] 131.1323
##
## $Hctot
## [1] 250.724
##
## $Actot
## [1] 19.8942
##
## $Tcave
## [1] 22.04278
##
## $Rnstot
## [1] 114.9653
##
## $lEstot
## [1] 50.53012
##
## $Hstot
## [1] 24.25024
##
## $Gtot
## [1] 40.23785
##
## $Tsave
## [1] 26.7952
##
## $Rntot
## [1] 496.6957
##
## $lEtot
## [1] 181.6625
##
## $Htot
## [1] 274.9742
## $counter
## [1] 7
##
## $maxit
## [1] 100
##
## $maxEBercu
## [1] 0.0365275
##
## $maxEBerch
## [1] 0.08928034
##
## $maxEBers
## [1] 0.1082583
iter.ebal$counter is how many iterations the
energy-balance solver used to converge;
maxEBercu/maxEBerch/maxEBers are
its final canopy sunlit/shaded/soil residual errors.
rdd, rdo,
rsd, rso, refl,
reflappSCOPE separates canopy reflectance into four bidirectional components before summing them, plus the sensor-observed “apparent” reflectance:
rdd — diffuse-in / diffuse-out
reflectance. Sky light entering the canopy and leaving it also as
diffuse radiation; describes multiple scattering inside the canopy.rdo — diffuse-in / directional-out
reflectance. Diffuse incoming radiation (sky light) leaving the canopy
in the sensor’s specific viewing direction; relevant to BRDF effects
under overcast conditions.rsd — direct-in (sun beam) /
diffuse-out reflectance. Direct sunlight entering the canopy and
emerging as diffuse scattered light; shows sun–canopy interactions and
internal scattering.rso — direct-in (sun beam) /
directional-out reflectance. Direct sun illumination leaving the canopy
exactly toward the sensor; contains hot-spot effects and strong geometry
interactions.refl — total canopy reflectance,
rdd + rdo + rsd + rso; the “true” physical reflectance of
the canopy.reflapp — apparent reflectance,
derived from the outgoing radiance at sensor level (accounts for the
actual illumination geometry and, when fluorescence is on, differs from
refl in the fluorescence emission band because it is
radiance-derived). This is what a real sensor (e.g. Sentinel-2, PRISMA)
would observe.bands <- SCOPEinR::define.bands()
wlS <- bands$wlS[1:2001] # 400-2500 nm, shortwave-only part of data.rad's 2162-length vectors
rad <- db.sim[[1]]$data.rad
refl_components <- data.frame(
Wavelength = rep(wlS, 6),
Value = c(rad$rdd[1:2001], rad$rdo[1:2001], rad$rsd[1:2001],
rad$rso[1:2001], rad$refl[1:2001], rad$reflapp[1:2001]),
Component = rep(c("rdd","rdo","rsd","rso","refl","reflapp"), each = 2001)
)
library(ggplot2)
ggplot(refl_components, aes(x = Wavelength, y = Value, color = Component)) +
geom_line(linewidth = 0.5) +
theme_bw() + xlim(400, 2500) +
xlab("Wavelength (nm)") + ylab("Reflectance") +
ggtitle("Reflectance components for the default LUT_input.csv simulation")reflapp tracks refl closely outside the
fluorescence band but departs from it around 640-850nm — the signature
of SCOPE’s fluorescence emission riding on top of the reflected signal,
exactly what a real sensor would pick up in that region.
getLUT.SCOPE()getLUT.SCOPE(inputLUT, nLUT, setseed) samples
nLUT parameter sets from the ranges table read above:
Uniform rows are sampled with stats::runif(),
Fixed rows are repeated at their default, and
anything else (Gaussian) is sampled with
ToolsRTM::gauss_byMin_Max() (truncated Gaussian within
[lower, upper], centered at Mean_D with spread
Std_D).
## [1] 10 76
## Cab LAI Vcmax25
## Min. :22.22 Min. :1.020 Min. : 29.71
## 1st Qu.:44.34 1st Qu.:1.938 1st Qu.: 47.81
## Median :47.61 Median :3.754 Median : 96.58
## Mean :51.67 Mean :3.726 Mean : 97.08
## 3rd Qu.:60.26 3rd Qu.:5.494 3rd Qu.:137.01
## Max. :80.23 Max. :6.434 Max. :182.64
Real traits co-vary. ToolsRTM::getCor() (covered in
depth in the ToolsRTM manual) generates correlated pairs instead of
independent draws — this is exactly the pattern used in Carlos’s
production scripts
(Scripts/R/ForSCOPE/1-getSCOPE-v3_withChunck.R) to keep the
leaf-inclination parameters LIDFa/LIDFb
jointly plausible instead of independently uniform:
lidf_cor <- suppressMessages(ToolsRTM::getCor(
n_inputs = 2, setseed = 7, distribution = "Uniform",
nLUT = n_samples, rho = 0.20, Varnames = c("LIDFa", "LIDFb"),
MinRange = c(-0.5, -0.5), MaxRange = c(0.2, 0.2)
))
LUT$LIDFa <- lidf_cor$LUT$LIDFa
LUT$LIDFb <- lidf_cor$LUT$LIDFb
cat("Achieved correlation:", round(cor(LUT$LIDFa, LUT$LIDFb), 2), "\n")## Achieved correlation: -0.06
plot(LUT$LIDFa, LUT$LIDFb, xlab = "LIDFa", ylab = "LIDFb",
main = "Correlated leaf-angle-distribution parameters", pch = 19)The scatter leans along a diagonal instead of filling a square —
rho = 0.20 is a modest but real correlation, not
independent sampling.
get.SCOPE() accepts a multi-row LUT
directly and loops over it serially (n.LUT must match
nrow(LUT)). get.SCOPE.parallel() splits the
same LUT into n.cores chunks, runs them on a
parallel/doParallel PSOCK cluster, and
(optionally) writes CSV outputs itself. Timed back-to-back on the same
10-row LUT:
t0 <- Sys.time()
sims_serial <- SCOPEinR::get.SCOPE(
LUT = LUT, n.LUT = n_samples, options.SCOPE = scope_options,
optipar = SCOPEinR::optipar2021.Pro.CX, leaf.model = "fluspect-CX",
canopy.model = "fourSAIL", get.outputs = "ALL", get.plots = FALSE
)
t_serial <- as.numeric(Sys.time() - t0, units = "secs")
t0 <- Sys.time()
sims_parallel <- SCOPEinR::get.SCOPE.parallel(
LUT = LUT, options.SCOPE = scope_options, optipar = SCOPEinR::optipar2021.Pro.CX,
leaf.model = "fluspect-CX", canopy.model = "fourSAIL",
parallel = TRUE, n.cores = 4, get.outputs = "ALL",
get.plots = FALSE, get.csv = FALSE
)## Total simulations: 10
## Total execution time: 8.91504
t_parallel <- as.numeric(Sys.time() - t0, units = "secs")
cat("Serial (1 core): ", round(t_serial, 1), "s for", length(sims_serial), "sims\n")## Serial (1 core): 12.8 s for 10 sims
## Parallel (4 cores): 10.7 s for 10 sims
For a LUT this small the parallel path still pays a fixed cost —
spinning up a PSOCK cluster and exporting
SCOPEinR/ToolsRTM to every worker — so the
speed-up is modest here; it becomes worthwhile once nLUT is
in the hundreds/thousands, which is the realistic use case for
get.LUT() + inversion training data.
For large LUTs, Carlos’s real workflow
(Scripts/R/ForSCOPE/1-getSCOPE-v3_withChunck.R) splits the
LUT into chunks and calls get.SCOPE.parallel() once per
chunk with get.csv = TRUE, so intermediate results are
written to disk instead of being held in memory all at once:
# A dedicated, larger LUT for this section -- get.SCOPE.plots() (Section 8)
# needs at least 10 simulations per chunk to have something to bin, and the
# inversion demo below (Section 11) reuses this same data: get.inversion()
# uses 10-fold x 10-repeat cross-validation internally, which needs enough
# rows per fold to fit PLSR/RF reliably -- 20 total (14 in the 70% training
# split) is too few and makes caret::train() fail.
n_prod <- 60
LUT_prod <- getLUT.SCOPE(inputLUT = inputLUT, nLUT = n_prod, setseed = 43)
lidf_cor_prod <- suppressMessages(ToolsRTM::getCor(
n_inputs = 2, setseed = 8, distribution = "Uniform",
nLUT = n_prod, rho = 0.20, Varnames = c("LIDFa", "LIDFb"),
MinRange = c(-0.5, -0.5), MaxRange = c(0.2, 0.2)
))
LUT_prod$LIDFa <- lidf_cor_prod$LUT$LIDFa
LUT_prod$LIDFb <- lidf_cor_prod$LUT$LIDFb
out_root <- paste0(tempdir(), "/scope_outs/") # trailing slash required by path.out below
dir.create(out_root, showWarnings = FALSE, recursive = TRUE)
n_chunks <- 2
n_rows <- nrow(LUT_prod)
chunk_size <- n_rows / n_chunks
for (i in seq_len(n_chunks)) {
start_row <- ((i - 1) * chunk_size) + 1
end_row <- min(i * chunk_size, n_rows)
LUT_chunk <- LUT_prod[start_row:end_row, ]
SCOPEinR::get.SCOPE.parallel(
LUT = LUT_chunk,
options.SCOPE = scope_options,
optipar = SCOPEinR::optipar2021.Pro.CX,
leaf.model = "fluspect-CX",
canopy.model = "fourSAIL",
parallel = TRUE,
n.cores = 4,
get.outputs = "ALL",
get.plots = FALSE,
get.csv = TRUE,
path.out = out_root
)
}## [1] "2026-08-20_17-19-34" "2026-08-20_17-19-58"
get.SCOPE.parallel()/get.SCOPE.outputs()
build the timestamped output folder with
paste(path.out, current_time, sep = "") — plain string
concatenation, not file.path().
path.out must end in /, or
the “subfolder” ends up as a sibling directory with the timestamp glued
onto its name instead of nested inside it (an easy trap;
tempdir() never ends in a slash on its own).
Each chunk folder holds one CSV per output variable, plus a
Parameters/ subfolder with the exact LUT used:
## [1] "aPAR.csv" "Eout_spectrum.csv"
## [3] "Esky.csv" "Esun.csv"
## [5] "fluorescence.csv" "fluorescence_All_Leaves.csv"
## [7] "fluorescence_hemis.csv" "fluorescence_ReabsCorr.csv"
## [9] "fluorescence_scalar.csv" "fluorescence_scalar_units.csv"
## [11] "fluorescence_scattered.csv" "fluorescence_shaded.csv"
## [13] "fluorescence_soil.csv" "fluorescence_sunlit.csv"
## [15] "fluxes.csv" "fluxes_units.csv"
## [17] "Lo_spectrum.csv" "Lo_spectrum_includingF.csv"
## [19] "Parameters" "radiation.csv"
## [21] "radiation_units.csv" "rdd.csv"
## [23] "rdo.csv" "refl.csv"
## [25] "reflapp.csv" "resistance.csv"
## [27] "resistance_units.csv" "rsd.csv"
## [29] "rso.csv" "SigmaF.csv"
## [31] "vegatation.csv" "vegatation_units.csv"
## [1] "inputLUT.csv" "SCOPEversion.txt"
One CSV per output variable (fluxes.csv,
reflapp.csv, …), plus the exact input LUT that produced
them saved alongside for reproducibility.
Reading each chunk’s CSV directly and row-binding across chunk folders merges chunked output back into one table:
fluxes_all <- do.call(rbind, lapply(subdirs, function(d) read.csv(file.path(d, "fluxes.csv"))))
dim(fluxes_all)## [1] 60 14
## Rnctot Actot Tcave Rnstot Tsave
## 1 530.7934 22.7952724 22.14358 39.489187 23.42798
## 2 582.9905 20.2861382 22.46935 10.602271 25.42557
## 3 616.1376 26.2266501 22.12216 9.564002 25.13658
## 4 201.9446 0.9033706 21.84580 272.589532 26.79398
## 5 425.9027 27.2858073 22.87809 104.218383 25.02557
## 6 596.2558 26.0440659 21.19897 17.022793 23.27270
get.SCOPE.plots()get.SCOPE.plots(path.files, plant.trait, get.plots)
groups a chunk’s spectra by an input trait (into 8 bins) and saves
averaged spectra plots to path.files/Plots.checks/. It
needs at least 10 simulations per file to have something to bin. Called
with ::: since it’s an internal helper, not part of the
exported user-facing API:
SCOPEinR:::get.SCOPE.plots(path.files = last_subdir, plant.trait = c("Vcmax25", "Cab"),
get.plots = "reflectance")
SCOPEinR:::get.SCOPE.plots(path.files = last_subdir, plant.trait = c("Vcmax25", "Cab"),
get.plots = "fluorescence")
SCOPEinR:::get.SCOPE.plots(path.files = last_subdir, plant.trait = c("Vcmax25", "Cab"),
get.plots = "radiance")
png_files <- list.files(file.path(last_subdir, "Plots.checks"), full.names = TRUE)
basename(png_files)refl_png <- png_files[grepl("^1-reflectance_refl_by_Vcmax25", basename(png_files))]
knitr::include_graphics(refl_png)refl grouped by Vcmax25, produced by get.SCOPE.plots()
Reflectance averaged within each of the 8 Vcmax25 bins –
a quick way to see whether a trait leaves a visible signature on the
spectrum before running a full inversion.
The apparent reflectance (reflapp) SCOPE produces is
exactly the kind of “sensor-observed” spectrum inversion works from —
same idea as the PROSAIL inversion in the ToolsRTM manual, just
generated with a coupled energy-balance model instead of pure radiative
transfer. This section runs the short version of that workflow; see
ToolsRTM_PROSAIL_tutorial.Rmd for the full 12-algorithm
reference and the deep-learning (CNN/Hidden-layers) path.
A 10-row LUT is too small to train anything meaningful. Rather than
running a second, separate batch of simulations, this section reuses the
20 already produced by the chunked-parallel run above
(out_root, two 10-row chunks) — same effective LUT size,
without simulating twice:
inv_subdirs <- list.dirs(out_root, recursive = FALSE)
# A chunk can come back with no reflapp.csv if every simulation in it hit an
# unrecoverable error (e.g. a randomly-drawn LIDFa/LIDFb combination outside
# the physically valid range for fourSAIL) -- drop incomplete chunks rather
# than fail the whole comparison.
inv_subdirs <- inv_subdirs[file.exists(file.path(inv_subdirs, "reflapp.csv"))]
length(inv_subdirs)## [1] 2
lut_used <- do.call(rbind, lapply(inv_subdirs, function(d) {
read.csv(file.path(d, "Parameters", "inputLUT.csv"))
}))
reflapp <- do.call(rbind, lapply(inv_subdirs, function(d) {
read.csv(file.path(d, "reflapp.csv"))
}))
wlS <- SCOPEinR::define.bands()$wlS[1:2001]
colnames(reflapp) <- paste0("R.", wlS)
df.indices <- ToolsRTM::getIndices(reflapp, pattern.rfl = "R.", spectral.domain = "VNIR")## [1] "Estimating indices using VNIR domain..."
##
df.indices <- df.indices[, colSums(is.na(df.indices)) == 0]
target_wavelengths <- paste0("R.", seq(400, 800, by = 5))
dataset <- cbind(Cab = lut_used$Cab, reflapp[, target_wavelengths], df.indices)
dataset <- dataset[, !duplicated(names(dataset))]
dim(dataset)## [1] 60 1968
Two caret-backed algorithms via
ToolsRTM::get.inversion(), using the same interface the
ToolsRTM manual documents in full for all 12 options:
inputs_inv <- colnames(dataset)[-1]
inv_plsr <- get.inversion(data = dataset, depVar = "Cab", inputs = inputs_inv,
algorithm = "PLSR", n.samples = nrow(dataset), seed = 123)inv_rf <- get.inversion(data = dataset, depVar = "Cab", inputs = inputs_inv,
algorithm = "RF", n.samples = nrow(dataset), seed = 123)## PLSR trained. Object class: list
## RF trained. Object class: list
| Want to… | Use |
|---|---|
| Run one simulation | get.SCOPE(LUT[1, ], options.SCOPE, optipar, leaf.model, canopy.model, get.outputs, get.plots) |
| Sample a random parameter set | getLUT.SCOPE(inputLUT, nLUT, setseed) |
Correlate two traits
(e.g. LIDFa/LIDFb) |
ToolsRTM::getCor(...) |
| Run many simulations in parallel | get.SCOPE.parallel(LUT, ..., parallel = TRUE, n.cores = , get.csv = TRUE, path.out = "somewhere/") |
| Merge chunked CSV outputs | read.csv() + rbind() per chunk folder |
| Inspect reflectance/fluorescence/radiance by trait | SCOPEinR:::get.SCOPE.plots(path.files, plant.trait, get.plots) |
Invert a trait from simulated reflapp |
ToolsRTM::getIndices() +
ToolsRTM::get.inversion() — full detail in
ToolsRTM_PROSAIL_tutorial.Rmd |
Every code chunk above executed for real while writing this manual.