12 - Statistical vegetation¶
When a tree has hundreds of thousands of leaves, or when the subject is a large canopy, modelling every leaf is neither practical nor necessary. Statistical vegetation only describes the space the canopy occupies; the leaves inside are described statistically by leaf area density, leaf angle distribution and leaf optics. Rays are intercepted, scattered and absorbed inside the canopy according to these statistics.
| Representation | Use |
|---|---|
| Triangle mesh | When leaf shape, clumping and other geometric detail matter |
| Statistical vegetation | Very many leaves; interest in canopy-scale radiation and physiology |
| Both together | Mesh trunks and branches, statistical foliage |
Canopy boundary¶
The extent of statistical vegetation is given by a closed triangle mesh, usually the outline of the crown:
The boundary mesh must:
- enclose a closed, non-self-intersecting volume;
- have consistently oriented faces, with no openings, duplicate faces or zero-area triangles.
The boundary itself neither reflects nor absorbs; it only marks where the statistical leaves are.
Leaves inside the canopy¶
OpticalVegetation describes the leaves inside the boundary:
vegetation = less.OpticalVegetation(
leaf=less.Prospect(cab=40, car=10),
leaf_area_density=1.5, # m² leaf per m³ canopy
leaf_angle_distribution="spherical",
leaf_size=(0.08, 0.04), # leaf length and width (m)
leaf_shape="ellipse",
)
| Parameter | Meaning |
|---|---|
leaf |
Optical property of the representative leaf: Prospect, Fluspect, MeasuredSpectrum … |
leaf_area_density |
One-sided leaf area per unit canopy volume (m² m⁻³) |
leaf_angle_distribution |
Leaf angle distribution |
leaf_size, leaf_shape |
Actual leaf size and shape ("ellipse" or "rectangle"), which set the width of the hotspot |
Leaf angle distributions:
| Name | Character |
|---|---|
spherical |
Leaf normals evenly distributed over all directions; common when no measurements exist |
uniform |
Inclination uniform between 0° and 90° |
planophile |
Mostly horizontal leaves |
erectophile |
Mostly vertical leaves |
plagiophile |
Mostly intermediate inclinations |
extremophile |
Many horizontal and many vertical leaves |
Without leaf_size the leaves are treated as infinitely small and there is no hotspot.
A statistical crown on a mesh trunk¶
tutorial_tree.obj contains a closed crown crown and a trunk trunk. The crown becomes statistical vegetation and the trunk stays a mesh:
tree = less.Object("tree")
tree.add_component("crown", crown)
tree.add_component("trunk", less.Mesh(less.asset_path("tutorial_tree.obj"), group="trunk"))
tree.set_property("crown", vegetation)
tree.set_property("trunk", less.Lambertian(reflectance=0.12))
scene = less.Scene()
scene.size = (20.0, 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.SimpleSpectralAtmosphere(turbidity=2.5))
scene.add(tree, positions=[(5, 5, 0), (10, 12, 0), (15, 6, 0)])
image = scene.simulate(less.OpticalImager(
less.Orthographic(image_size=128), bands=[550, 680, 850], quality=64))
brf = image.to_brf()
brf.save("turbid_brf.tif")
A single ray interacts with trunk surfaces, statistical leaves and the ground in turn, and multiple scattering is handled in one simulation. Statistical vegetation works with every sensor and process in this tutorial, exactly like meshes.
Leaf area¶
The leaf area of statistical vegetation is the volume inside the boundary times the leaf area density:
print(scene.measure(less.LAIMeasurement(group_by="component")))
print(scene.measure(less.LAIMeasurement(property_filter=less.Prospect)))
property_filter=less.Prospect matches both mesh leaves using Prospect and statistical vegetation whose representative leaf is Prospect. Scaling a boundary up increases its volume and hence its leaf area. Parts of a boundary outside the scene are not counted; in a periodic scene, parts crossing the edge are wrapped back into the scene.
A whole canopy layer¶
A box-shaped boundary covering the whole scene, combined with periodic boundaries, gives a horizontally uniform, infinite canopy, the same assumption as in one-dimensional canopy models:
from pathlib import Path
def write_box(path, width, height):
"""Write a width × width × height box in OBJ coordinates (Y up)."""
v = [(0, 0, 0), (-width, 0, 0), (-width, height, 0), (0, height, 0),
(0, 0, -width), (-width, 0, -width), (-width, height, -width), (0, height, -width)]
faces = [(1, 4, 3), (1, 3, 2), (5, 6, 7), (5, 7, 8), (1, 5, 8), (1, 8, 4),
(2, 3, 7), (2, 7, 6), (1, 2, 6), (1, 6, 5), (4, 8, 7), (4, 7, 3)]
Path(path).write_text("g canopy\n" + "".join(f"v {x} {y} {z}\n" for x, y, z in v)
+ "".join("f %d %d %d\n" % f for f in faces))
write_box("canopy_box.obj", 1.0, 2.0)
canopy = less.Object("canopy")
canopy.add_component("leaves", less.TurbidBoundary("canopy_box.obj", group="canopy"))
canopy.set_property("leaves", less.OpticalVegetation(
leaf=less.Prospect(cab=40), leaf_area_density=1.5, leaf_angle_distribution="spherical"))
field = less.Scene()
field.size = (1.0, 1.0)
field.repetitive = True
field.terrain = less.Terrain(property=less.Lambertian(reflectance=0.15))
field.illumination = less.Illumination(
source=less.Sun(zenith=30, azimuth=180), atmosphere=less.NoAtmosphere())
field.add(canopy, positions=[(0.0, 0.0, 0.0)])
print("LAI:", field.measure(less.LAIMeasurement())) # 1.5 × 2 = 3
field_brf = field.simulate(less.BRFSensor(
bands=[650, 850], directions=[(0, 0), (30, 180), (30, 0)], num_photons=500_000))
print(field_brf.brf)
The canopy is 2 m tall with a leaf area density of 1.5 m² m⁻³, so the LAI is 3.
Sampling settings¶
Optical imaging only needs the number of rays, controlled by quality, num_photons and similar parameters. The radiation field, energy balance, photosynthesis and fluorescence additionally place statistical leaf samples inside the canopy to record leaf absorption, temperature and physiological state. The number and distribution of these samples are set by TurbidSampling:
crown = less.TurbidBoundary(
less.asset_path("tutorial_tree.obj"),
group="crown",
sampling=less.TurbidSampling(quality="standard", seed=7),
)
| Parameter | Role |
|---|---|
quality |
"preview", "standard" or "high" |
sample_budget |
Target number of samples per instance; defaults to the quality preset |
seed |
Random seed; the same seed and settings give the same samples |
orientation_theta_bins, orientation_phi_bins |
Number of discrete leaf orientations |
max_samples_per_leaf_cell |
Upper limit of samples per spatial cell |
Check a scene with "preview" first, then compute final results with "standard" or "high" and compare the two to confirm that the results are stable. These processes are covered in chapters 31–33.