Skip to content

03 - First simulation

In this chapter we simulate a patch of flat bare soil, produce an RGB image looking straight down, and then put a maize plant on the ground.

The complete code

import less

# 1. Scene: 10 m × 10 m
scene = less.Scene()
scene.size = 10.0

# 2. Ground: Lambertian reflection with reflectance 0.3
scene.terrain = less.Terrain(property=less.Lambertian(reflectance=0.3))

# 3. Illumination: sun at 30° zenith and 150° azimuth (south-east), simple spectral atmosphere
scene.illumination = less.Illumination(
    source=less.Sun(zenith=30, azimuth=150),
    atmosphere=less.SimpleSpectralAtmosphere(turbidity=2.0),
)

# 4. Sensor: orthographic view, 256 × 256 pixels, red, green and blue bands
sensor = less.OpticalImager(
    less.Orthographic(image_size=256),
    bands=[650, 550, 450],
    quality=64,
)

# 5. Simulate and save
image = scene.simulate(sensor)
image.save("bare_soil_rgb.png")
print(image.data.shape)

Companion script: scripts/03_first_simulation.py

Step by step

Scene and ground

less.Scene() creates an empty scene and scene.size = 10.0 makes the ground 10 m × 10 m. less.Terrain is the ground; here it uses the simplest reflection model, Lambertian, which reflects equally in all directions. A reflectance of 0.3 means 30% of the incoming light is reflected.

Illumination

Illumination combines the sun and the atmosphere. Sun gives the sun's direction and the solar spectrum at the top of the atmosphere; SimpleSpectralAtmosphere computes the direct sunlight and the diffuse skylight reaching the ground, and a larger turbidity means a hazier sky. The other atmosphere models are described in Illumination and atmosphere.

Sensor

An OpticalImager combines a projection and a set of bands:

  • Orthographic(image_size=256): parallel rays looking straight down, 256 × 256 pixels, covering the whole scene by default;
  • bands=[650, 550, 450]: the band centres in nm; the bands in the result keep this order;
  • quality: the number of samples per pixel. Higher values give less noise and take longer.
quality Use
16–64 Quick previews
128–256 General analysis
512 and above Quantitative work, publication figures

In an orthographic projection all viewing rays are parallel, as if looking down from a very great height:

Orthographic projection

Simulating

The first call to scene.simulate(sensor) builds the scene and then traces rays. When you change the illumination, a property or the sensor and simulate again, the geometry that is already built is reused.

Looking at the result

image.data is a NumPy array of shape (rows, columns, bands) holding radiance in W m⁻² sr⁻¹ nm⁻¹:

data = image.data
print(data.dtype, data.min(), data.max())

The first row of the image is north and the first column is west. To save it:

image.save("bare_soil.png")   # 8-bit PNG for viewing
image.save("bare_soil.tif")   # keeps the radiance values

To convert radiance to bidirectional reflectance factor (BRF):

brf = image.to_brf()
print(brf.data.mean(axis=(0, 1)))   # close to 0.3 for flat ground

Changing parameters

To change the sun position or the ground reflectance, just simulate again:

scene.illumination = less.Illumination(
    source=less.Sun(zenith=60, azimuth=90),
    atmosphere=less.SimpleSpectralAtmosphere(turbidity=2.0),
)
image_low_sun = scene.simulate(sensor)

scene.terrain.set_property(less.Lambertian(reflectance=0.1))
image_dark = scene.simulate(sensor)

Adding a maize plant

LESS includes a maize plant model, maize.obj. Give its leaves optical properties with the PROSPECT leaf model and place it in the centre of the scene:

maize = less.Object("maize", mesh=less.asset_path("maize.obj"))
maize.set_property(less.Prospect(cab=40, car=8, cw=0.012, cm=0.006, N=1.5))
scene.add(maize, positions=[[5.0, 5.0, 0.0]])

image_maize = scene.simulate(sensor)
image_maize.save("one_maize.png")

After an object is added, the next simulation rebuilds the geometry automatically.

Next