Skip to content

07 - Scenes and projects

This chapter shows how to save and reopen scenes, and how a project (less.Project) organises a complete experiment with several sensors.

Scene or project

less.Scene is what gets simulated. less.Project adds named sensors, file resources and experiment information on top of a scene.

Need Use
Interactive work, one-off calculations Scene
Saving or sharing a scene scene.save()
Saving a scene together with several sensors Project
Managing model files, keeping an experiment long term Project
Packaging an experiment to run on another machine Project.export_snapshot()

A project holds an ordinary Scene; all scene operations still go through project.scene.

Saving and opening scenes

First a small scene:

import numpy as np
import less

scene = less.Scene()
scene.size = (100.0, 100.0)
scene.terrain = less.Terrain(property=less.Lambertian(reflectance=0.18))
scene.illumination = less.Illumination(
    source=less.Sun(zenith=32, azimuth=145),
    atmosphere=less.SimpleSpectralAtmosphere(turbidity=2.2))

tree = less.Object("ash", mesh=less.asset_path("FREX.obj"))
tree.set_property("leaves", less.Prospect(cab=42))
tree.set_property("stem_branch", less.Lambertian(reflectance=0.1))
scene.add(tree, positions=np.array([[20, 20, 0], [40, 30, 0], [65, 25, 0],
                                    [30, 70, 0], [70, 65, 0]]))

There are three ways to save it:

scene.save("forest-scene.less")                    # one compressed file including the model files
scene.save("forest-scene-folder.less", pack=False) # an uncompressed folder, good for large models
scene.save("forest-scene.json")                    # only records the model file paths

A .less file packs the OBJ, DEM and spatial property files together and can be copied to another computer as is. A .json file only records paths, so the other computer needs the same files.

To open it again:

scene = less.Scene.load("forest-scene.less")
image = scene.simulate(less.OpticalImager(
    less.Orthographic(image_size=64), bands=[650, 550, 450], quality=16))

A scene file stores the scene's settings: terrain, objects, properties, illumination and microclimate. Temperatures solved by processes such as the energy balance are not included; solve again after reopening.

Creating a project

project = less.Project(
    scene=scene,
    title="Ash canopy experiment",
    author="LESS Lab",
    description="Canopy reflectance at nadir and oblique views",
)

You can also start from an empty project and set up the scene through project.scene:

empty = less.Project.create(title="New experiment", backend="auto")
empty.scene.size = 20.0

Adding sensors

Sensors in a project have names and are run by name:

project.add_sensor(
    less.OpticalImager(less.Orthographic(image_size=64),
                       bands=[450, 550, 650, 850], quality=16),
    name="Nadir multispectral")
project.add_sensor(
    less.OpticalImager(less.Orthographic(image_size=64, view_zenith=30, view_azimuth=90),
                       bands=[550, 650, 850], quality=16),
    name="30 degrees from east")

print([sensor.name for sensor in project.sensors])

Running simulations

project.build()
nadir = project.simulate("Nadir multispectral")

Without a name, all sensors run in turn and a dictionary keyed by sensor name is returned:

from pathlib import Path

Path("outputs").mkdir(exist_ok=True)
for name, product in project.simulate().items():
    product.save(f"outputs/{name}.tif")

All sensors share one scene, so the geometry is built only once.

After changing the illumination or properties, simply run again; nothing needs rebuilding:

project.set_illumination(less.Illumination(
    source=less.Sun(zenith=50, azimuth=210),
    atmosphere=less.SimpleSpectralAtmosphere(turbidity=2.5)))
project.scene.objects["ash"].set_property("leaves", less.Prospect(cab=25))
autumn = project.simulate("Nadir multispectral")

After adding or removing objects, replacing models or changing the terrain, call project.rebuild(). project.session_status shows the current state:

Status Meaning
unbuilt Not built yet
ready Ready to simulate
parameters_dirty Illumination or properties changed; updated automatically at the next run
geometry_dirty Geometry changed; a rebuild is needed

Managing model files

Files in a project come in two kinds:

  • Managed files are copied into the project and saved and shared with it;
  • External files are only referenced by path, which suits very large or shared data.
mesh = project.resources.import_file(less.asset_path("FREX.obj"), resource_type="mesh")
birch = less.Object("birch", mesh=project.resources.resolve(mesh.id))

terrain = project.resources.register_external(
    less.asset_path("tutorial_terrain.obj"), resource_type="terrain")

for resource in project.resources:
    print(resource.name, project.resources.verify(resource.id, checksum=True) or "OK")

Saving a project

project.save_directory("ash-experiment.less")      # an editable folder
project.pack("ash-experiment-portable.less")       # an additional single-file copy

A folder project suits day-to-day editing because saving does not recompress the whole scene; the single-file copy suits archiving and sharing. project.save("xxx.less") also saves a single file and makes it the current project path.

A folder project looks like this:

ash-experiment.less/
├── project.json
├── scene.json
├── sensors.json
├── resources.json
└── resources/

Opening and checking a project

project = less.Project.open("ash-experiment-portable.less")

report = project.validate(verify_checksums=True)
for issue in report.issues:
    print(issue.severity, issue.path, issue.message)
report.raise_for_errors()

result = project.simulate("Nadir multispectral")

validate() checks sensor names, whether model files exist and whether their content is intact.

Running on another machine

export_snapshot() exports the current project with selected sensors as a self-contained job:

Path("jobs").mkdir(exist_ok=True)
snapshot = project.export_snapshot("jobs/nadir-job.less", sensors=["Nadir multispectral"])
print(snapshot.revision, snapshot.sensors)

job = less.Project.open("jobs/nadir-job.less")
job.validate().raise_for_errors()
results = job.simulate()

job.close()
project.close()

A project opened from a single file is unpacked into a temporary folder; call close() when you are done to clean it up. External files are still referenced by path inside a job, so the machine that runs it needs access to them.

Importing LESS 2 projects

A LESS 2 project folder can be read into a scene with less.Scene.from_less(path). Observation settings are not converted; create the sensors again:

project = less.Project(scene=scene, title="Experiment imported from LESS 2")
project.add_sensor(
    less.OpticalImager(less.Orthographic(image_size=64), bands=[550, 650, 850]),
    name="Nadir")
project.save_directory("imported-experiment.less")

Next