0.1.13
Loading...
Searching...
No Matches
Leaf Optics Plugin Documentation

DependenciesNone
Python Importfrom pyhelios import LeafOptics
Main ClassLeafOptics

System Requirements

Dependencies None
Platforms Windows, Linux, macOS
GPU Not required

Quick Start

from pyhelios import Context, LeafOptics
from pyhelios.types import vec3, vec2
with Context() as context:
# Create leaf geometry
uuid = context.addPatch(center=vec3(0, 0, 1), size=vec2(0.1, 0.1))
# Use LeafOptics plugin
with LeafOptics(context) as leafoptics:
# Get properties from species library
props = leafoptics.getPropertiesFromLibrary("sunflower")
# Compute spectra and assign to geometry
leafoptics.run([uuid], props, "sunflower")
# Retrieve computed spectra
wavelengths, reflectance, transmittance = leafoptics.getLeafSpectra(props)
print(f"Spectral range: {wavelengths[0]}-{wavelengths[-1]} nm ({len(wavelengths)} points)")

Introduction

This plug-in computes leaf spectral reflectance and transmittance using the PROSPECT family of models. The implementation follows the PROSPECT–PRO formulation with eight absorbing constituents and a structural parameter \(N\) that represents the number of elementary layers in the leaf. Output spectra cover the range 400–2500 nm in 1 nm steps.

For each wavelength \(\lambda\) the absorption coefficient of a single layer is calculated as

\[ k(\lambda)=\frac{C_{ab}a_{ab}(\lambda)+C_{ar}a_{ar}(\lambda)+C_{an}a_{an}(\lambda)+C_{br}a_{br}(\lambda) +C_{w}a_{w}(\lambda)+C_{m}a_{m}(\lambda)+C_{p}a_{p}(\lambda)+C_{c}a_{c}(\lambda)}{N}, \]

where the \(C\) variables are the constituent masses per area and the \(a(\lambda)\) terms are the specific absorption coefficients loaded from the internal spectral library. Fresnel equations are used to compute surface reflectance and a radiative transfer solution gives the total leaf reflectance and transmittance.

LeafOptics Class Constructor

Constructors
LeafOptics

The constructor simply stores a pointer to the Helios context and loads the spectral library data required by the model.

LeafOpticsProperties Structure

The LeafOpticsProperties structure stores the biochemical inputs to the model.

MemberUnitsDescriptionDefault Value
numberlayersunitlessLeaf structure parameter \(N\)1.5
brownpigmentsunitlessMass of brown pigments0
chlorophyllcontent\(\mu\)g cm \(^{-2}\)Total chlorophyll30
carotenoidcontent\(\mu\)g cm \(^{-2}\)Total carotenoids7
anthocyancontent\(\mu\)g cm \(^{-2}\)Anthocyanins1
watermassg cm \(^{-2}\)Equivalent water thickness0.015
drymassg cm \(^{-2}\)Dry matter mass0.09
proteing cm \(^{-2}\)Protein mass0
carbonconstituentsg cm \(^{-2}\)Cellulose and other carbon compounds0

Using the LeafOptics Plug-in

The model can be run to produce global spectra and, optionally, assign those spectra and optical properties to a set of primitives.

from pyhelios import Context, LeafOptics
from pyhelios.LeafOptics import LeafOpticsProperties
from pyhelios.types import vec3, vec2
with Context() as context:
with LeafOptics(context) as leafoptics:
# Set custom properties
props = LeafOpticsProperties()
props.chlorophyllcontent = 40.0
props.watermass = 0.02
# Create leaf geometry
leafIDs = [context.addPatch(center=vec3(0, 0, 1), size=vec2(0.1, 0.1))]
# Run the model
leafoptics.run(leafIDs, props, "example")
High-level interface for PROSPECT leaf optical model.

This command creates global data labeled "leaf_reflectivity_example" and "leaf_transmissivity_example" containing the computed spectra. The spectra labels are also stored as primitive data for the specified UUIDs together with the biochemical property values.

Using the Built-in Species Library

The LeafOptics class includes a built-in species library that provides pre-configured optical properties for common plant species. This simplifies model usage by eliminating the need to manually specify biochemical parameters.

Basic Usage

Use the getPropertiesFromLibrary() method to return a LeafOpticsProperties structure with species-specific values:

from pyhelios import Context, LeafOptics
from pyhelios.types import vec3, vec2
with Context() as context:
with LeafOptics(context) as leafoptics:
# Get properties from library
props = leafoptics.getPropertiesFromLibrary("default")
# Create leaf geometry
leafIDs = [context.addPatch(center=vec3(0, 0, 1), size=vec2(0.1, 0.1))]
# Run the model
leafoptics.run(leafIDs, props, "example")

The method returns a LeafOpticsProperties structure with all nine parameters populated (numberlayers, chlorophyllcontent, carotenoidcontent, anthocyancontent, brownpigments, watermass, drymass, protein, carbonconstituents).

Available Species

The library contains PROSPECT-D parameters fitted to LOPEX93 spectral library samples using the fit_prospect_visrobust.py script with robust optimization. All species use PROSPECT-D mode (drymass > 0, protein = 0, carbonconstituents = 0).

Species LabelScientific NameNCab
(µg/cm²)
Car
(µg/cm²)
Ant
(µg/cm²)
CbrownCw
(g/cm²)
Cm
(g/cm²)
Source
"default"-1.5030.07.001.000.0000.01500.0900-Original Helios defaults
"garden_lettuce"Lactuca sativa L.2.0130.36.991.360.1070.02820.00530.993LOPEX93 sample 0021
"alfalfa"Medicago sativa L.2.0143.610.31.340.0000.01900.00470.994LOPEX93 sample 0036
"corn"Zea mays L.1.5922.93.970.000.7270.01500.00440.975LOPEX93 sample 0041
"sunflower"Helianthus annuus L.1.7654.112.91.750.0110.01860.00640.995LOPEX93 sample 0081
"english_walnut"Juglans regia L.1.5655.912.51.740.0000.01280.00580.994LOPEX93 sample 0091
"rice"Oryza sativa L.1.6737.210.00.000.0280.01010.00480.998LOPEX93 sample 0106
"soybean"Glycine max L.1.5446.412.10.650.0000.01010.00290.997LOPEX93 sample 0116
"wine_grape"Vitis vinifera L.1.4350.912.51.440.0800.01090.00600.997LOPEX93 sample 0276
"tomato"Lycopersicum esculentum1.4048.311.61.450.0000.01560.00260.997LOPEX93 sample 0316

Notes:

  • Species names are case-insensitive (e.g., "corn" and "CORN" are equivalent).
  • R² values computed as 1 - (RMSE² / variance), indicating goodness-of-fit to measured spectra.
  • All parameters were fitted without affine calibration using visible-robust optimization.
  • LOPEX93 dataset: Hosgood B. et al. (1994), Leaf Optical Properties Experiment 93 (LOPEX93), EUR 16095 EN.

Error Handling

If an unknown species name is provided, the method:

  1. Issues a warning message (if messages are enabled)
  2. Populates the properties structure with default values
  3. Does not throw an error

This ensures that code continues to run even if a species name is misspelled or not yet in the library.

Extending the Species Library

To add new species to the library, edit the species library data in the Helios C++ source code at plugins/leafoptics/src/LeafOptics.cpp. The library supports both PROSPECT-D mode (using drymass) and PROSPECT-PRO mode (using protein and carbonconstituents). Contact the PyHelios developers to request new species additions.

Retrieving PROSPECT Parameters from Spectra

The LeafOptics class maintains an internal mapping between spectrum labels and the PROSPECT parameters used to generate them. This allows users to retrieve the original model parameters from primitives that have been assigned LeafOptics-generated spectra.

Basic Usage

The getPropertiesFromSpectrum() method queries primitives for their "reflectivity_spectrum" primitive data and, if it matches a spectrum generated by the LeafOptics instance, assigns the corresponding PROSPECT parameters as primitive data:

from pyhelios import Context, LeafOptics
from pyhelios.LeafOptics import LeafOpticsProperties
from pyhelios.types import vec3, vec2
with Context() as context:
with LeafOptics(context) as leafoptics:
# Generate spectra for different leaf types
healthy_leaf = LeafOpticsProperties()
healthy_leaf.chlorophyllcontent = 45.0
healthy_leaf.carotenoidcontent = 12.0
stressed_leaf = LeafOpticsProperties()
stressed_leaf.chlorophyllcontent = 20.0
stressed_leaf.brownpigments = 0.3
# Create primitives
healthy_IDs = [context.addPatch(center=vec3(0, 0, 1), size=vec2(0.1, 0.1))]
stressed_IDs = [context.addPatch(center=vec3(0, 0, 2), size=vec2(0.1, 0.1))]
# Run model for each leaf type
leafoptics.run(healthy_IDs, healthy_leaf, "healthy")
leafoptics.run(stressed_IDs, stressed_leaf, "stressed")
# Later, retrieve parameters from primitives based on their assigned spectra
all_leaves = healthy_IDs + stressed_IDs
leafoptics.getPropertiesFromSpectrum(all_leaves)
# Each primitive now has parameter data matching its assigned spectrum
chl = context.getPrimitiveData(healthy_IDs[0], "chlorophyll")
# chl[0] = 45.0 (from healthy_leaf parameters)
chl = context.getPrimitiveData(stressed_IDs[0], "chlorophyll")
# chl[0] = 20.0 (from stressed_leaf parameters)

Method Behavior

For each UUID passed to getPropertiesFromSpectrum():

  1. The method queries the primitive data "reflectivity_spectrum"
  2. If the spectrum label starts with "leaf_reflectivity_", it extracts the user-provided label
  3. If that label matches a spectrum generated by this LeafOptics instance, the corresponding parameters are assigned as primitive data
  4. Primitives without matching spectra are silently skipped (no error is thrown)

Assigned Primitive Data Labels

The method assigns primitive data using the same labels as setProperties():

Primitive Data LabelParameterCondition
"chlorophyll"chlorophyllcontentAlways
"carotenoid"carotenoidcontentAlways
"anthocyanin"anthocyancontentAlways
"brown"brownpigmentsIf brownpigments > 0
"water"watermassAlways
"drymass"drymassIf drymass > 0 (PROSPECT-D mode)
"protein"proteinIf drymass = 0 (PROSPECT-PRO mode)
"cellulose"carbonconstituentsIf drymass = 0 (PROSPECT-PRO mode)

Important Notes

  • The parameter mapping is stored per LeafOptics instance. If you create a new LeafOptics object, it will not have access to spectra generated by a previous instance.
  • Only spectra generated using the run() methods are tracked. Manually created global data with "leaf_reflectivity_" prefixes will not match.
  • The method always overwrites existing primitive data for the parameters listed above.
  • Both overloads are available: pass a list of UUIDs for multiple primitives or a single UUID for one primitive.

Optional Output Primitive Data

By default, the LeafOptics plug-in writes all leaf constituent concentrations to primitive data. To selectively output only specific properties for improved performance, call optionalOutputPrimitiveData() with the desired labels before calling run().

from pyhelios import Context, LeafOptics
from pyhelios.LeafOptics import LeafOpticsProperties
from pyhelios.types import vec3, vec2
with Context() as context:
with LeafOptics(context) as leafoptics:
# Enable selective output for better performance
leafoptics.optionalOutputPrimitiveData("chlorophyll")
leafoptics.optionalOutputPrimitiveData("carotenoid")
# Run model
props = LeafOpticsProperties()
leafIDs = [context.addPatch(center=vec3(0, 0, 1), size=vec2(0.1, 0.1))]
leafoptics.run(leafIDs, props, "example")
Primitive Data Label Units Data Type Description
chlorophyll \(\mu\)g cm \(^{-2}\) float Total chlorophyll content
carotenoid \(\mu\)g cm \(^{-2}\) float Total carotenoid content
anthocyanin \(\mu\)g cm \(^{-2}\) float Anthocyanin content
brown unitless float Brown pigment content (only written if value > 0)
water g cm \(^{-2}\) float Equivalent water thickness
drymass g cm \(^{-2}\) float Dry matter mass (PROSPECT-D mode only, when drymass > 0)
protein g cm \(^{-2}\) float Protein mass (PROSPECT-PRO mode only, when drymass = 0)
cellulose g cm \(^{-2}\) float Cellulose and carbon compounds (PROSPECT-PRO mode only, when drymass = 0)

*/