08 - Properties¶
Geometry decides where an object is and what it looks like; properties decide how it interacts with radiation and its environment. This chapter covers property domains, how properties are assigned, and how spectral properties are matched to sensor bands.
Property domains¶
Each property belongs to one physical domain:
| Domain | Describes | Properties | Covered in |
|---|---|---|---|
| Optical | Shortwave reflection, transmission and absorption | Lambertian, RPV, WaterSurface, Prospect, Fluspect, MeasuredSpectrum, SpectrumDB, OpticalVegetation |
Chapters 9, 10, 12 |
| Thermal | Temperature and longwave emissivity | ThermalProperty |
Chapter 26 |
| Microwave | Permittivity and scatterers | MicrowaveSoil, MicrowaveVegetation, MicrowaveTrunk and others |
Chapter 28 |
| Biophysical | Sensible and latent heat exchange with the air, soil heat flux | BiophysicalProperty |
Chapter 32 |
| Physiological | Photosynthesis and stomatal conductance | Farquhar |
Chapter 33 |
A component can carry properties from several domains; set them one by one with set_property(). LESS works out the domain from the property type, so setting a new property only replaces the existing property of that domain:
import less
tree = less.Object("ash", mesh=less.asset_path("FREX.obj"))
tree.set_property("leaves", less.Prospect(cab=40, car=10)) # optical
tree.set_property("leaves", less.ThermalProperty(temperature=303.15, emissivity=0.98)) # thermal
tree.set_property("leaves", less.BiophysicalProperty(leaf_width=0.05)) # biophysical
tree.set_property("leaves", less.Farquhar(Vcmax25=60)) # physiological
tree.set_property("stem_branch", less.Lambertian(reflectance=0.1))
tree.set_property("leaves", less.Prospect(cab=20)) # replaces only the optical property
If an object has a single component, or all components should share a property, the component name can be left out:
maize = less.Object("maize", mesh=less.asset_path("maize.obj"))
maize.set_property(less.Prospect(cab=45))
tree.components lists the component names found in the OBJ file (the OBJ group names):
Components do not need properties from every domain, only those required by the simulations they take part in (see the table in chapter 4).
Spectral properties and bands¶
An optical property can be a single number (the same at all wavelengths) or a spectrum:
soil = less.Lambertian(reflectance=0.15)
soil_spectrum = less.Lambertian(
wavelengths=[400, 550, 670, 800, 1600], # nm
reflectance=[0.10, 0.14, 0.18, 0.22, 0.30],
)
During a simulation the spectrum is linearly interpolated to the sensor's actual wavelengths. The same property therefore works for RGB, multispectral and hyperspectral sensors without fixing the bands in advance.
For numeric Lambertian inputs, the three forms have different meanings:
| Input | Use during simulation |
|---|---|
A scalar, such as reflectance=0.15 |
The same value at every simulation wavelength. |
An array with wavelengths |
Linear interpolation from the supplied wavelength coordinates to the sensor's simulation wavelengths. |
An array without wavelengths |
One value per simulation wavelength, in the same order; the lengths must match. Wavelength coordinates are neither inferred nor interpolated. |
For example, reflectances of 0.1, 0.3 and 0.2 at 500, 600 and 700 nm give an interpolated reflectance of 0.2 when the sensor requests 550 nm:
soil = less.Lambertian(
wavelengths=[500, 600, 700],
reflectance=[0.1, 0.3, 0.2],
)
print(soil.evaluate([550])["reflectance"]) # [0.2]
A numeric array with only one element is also treated as a constant. Supply wavelengths when reusing a spectrum with multiple samples across different sensors. Interpolation estimates values between existing samples; it cannot recover narrow absorption features absent from the source sampling.
Valid wavelength range¶
Accepting a requested wavelength does not guarantee reliable property data at that wavelength. Keep every simulation wavelength within the valid ranges of the property data and physical models. Currently, wavelength-indexed numeric Lambertian arrays and Prospect.evaluate() use the nearest endpoint value outside their spectral range. This is not a physically justified prediction beyond that range. Other properties may reject out-of-range requests; endpoint extension is not a universal rule.
Leaf models such as PROSPECT compute the spectrum from 400 to 2500 nm from biochemical parameters. evaluate() returns a property's values at given wavelengths:
leaf = less.Prospect(cab=40, car=10)
values = leaf.evaluate([450, 550, 650, 850])
print(values["reflectance"], values["transmittance"])
Discrete wavelengths and broad bands¶
A sensor setting of bands=[550, 850] computes two discrete wavelengths, 550 and 850 nm, rather than broad-band averages centered on them. Hyperspectral also generates discrete simulation wavelengths: its step is a wavelength sampling interval, not an instrument response width.
For an instrument's broad-band response, use SRFBands or a corresponding sensor band definition. LESS generates simulation wavelengths across the spectral response function (SRF), interpolates property spectra to those wavelengths, simulates each wavelength, then integrates the results with SRF weights. It does not average reflectance first and simulate only the center wavelength. Property spectra must therefore cover the entire SRF range, not just its center.
spectral_resolution sets the wavelength sampling interval for SRF integration, in nm. For quantitative analysis, reduce this interval until the result is stable; more photon samples cannot compensate for overly coarse spectral sampling. See chapter 18 and chapter 19 for sensor examples.
Reflectance plus transmittance may not exceed 1; otherwise creating the property raises an error.
Changing properties in a scene¶
Properties can be changed at any time; the next simulation uses the new values without rebuilding the geometry:
scene = less.Scene()
scene.size = 20.0
scene.terrain = less.Terrain(property=less.Lambertian(reflectance=0.15))
scene.illumination = less.Illumination(
source=less.Sun(zenith=30, azimuth=150), atmosphere=less.NoAtmosphere())
handle = scene.add(tree, positions=[[5, 5, 0], [10, 10, 0], [15, 15, 0]])
sensor = less.OpticalImager(less.Orthographic(image_size=64),
bands=[650, 550, 450], quality=16)
summer = scene.simulate(sensor)
tree.set_property("leaves", less.Prospect(cab=8, car=12, cbrown=0.3))
scene.terrain.set_property(less.Lambertian(reflectance=0.25))
autumn = scene.simulate(sensor)
A property can also be changed by its "object.component" path:
Properties for individual instances¶
All instances of an object share its properties by default. The handle returned by scene.add() can replace the property of a component for selected instances, leaving the others unchanged:
# one property per instance; None keeps the object's property
handle.set_property("leaves", [None,
less.Prospect(cab=20),
less.Prospect(cab=10, cbrown=0.2)])
# select instances by index
handle.set_property("stem_branch", less.Lambertian(reflectance=0.2), instances=[0, 2])
# generate properties from the instance index
handle.set_property("leaves", lambda i: less.Prospect(cab=20 + 10 * i))
# back to the object's property
handle.clear_property("leaves", "optical")
mixed = scene.simulate(sensor)
Per-instance properties apply to triangle-mesh components; a statistical-vegetation component has one property per component.
The built-in spectral library¶
LESS includes a set of measured spectra for common leaves, soils and man-made materials:
library = less.SpectrumLibrary()
names = library.list_spectra()
print(len(names), names[:5])
data = library.get("birch_leaf_green")
print(data["wavelength"][:3], data["reflectance"][:3])
scene.terrain.set_property(less.SpectrumDB("loam_brown"))
less.SpectrumDB(name) turns a library spectrum directly into an optical property.