16 - Scene and Project: Saving, Reuse and Task Snapshots¶
The simulation core of LESS is less.Scene. As long as the scene, lights and sensors are already in Python
If defined in the script, you can directly call scene.simulate(sensor) to complete all simulations.
less.Project is an optional engineering management layer. It combines Scene, named sensors, resources and engineering elements
Data is organized for long-term preservation, sharing and remote tasks.
This chapter uses LESS Project Format v2 and covers:
- When to use only
Sceneand when to useProject; - Save and load independent scenes;
- Create complete projects and manage multiple sensors;
- Build a Scene Session and repeatedly execute multiple simulations;
- Modify lighting or physical properties without rebuilding geometry;
- Save directory projects, export portable packages and generate remote task snapshots;
- Migrate from legacy LESS project.
1. Choose first: Scene or Project¶
The two are not two simulation systems. Project still holds an ordinary Scene internally:
less.Project
├── scene → real simulation runtime and build status
├── sensors → Named sensors registered in the project
├── resources → File resource registry
└── metadata → title, author, project ID and revision number
Just choose according to the purpose of use:
| Usage scenarios | Recommended entrance |
|---|---|
| Notebook, single experiment, algorithm verification | Scene |
| The sensor is temporarily created by the script | Scene |
| Reuse or share a pure scene template | scene.save() |
| Save scenes and multiple sensors | Project |
| Manage local resources and project revisions | Project |
| Submit remote computing task | Project.export_snapshot() |
If project management and packaging are not required, it is enough to use Scene directly:
import less
scene = less.Scene()
scene.terrain = less.Terrain(
property=less.Lambertian(reflectance=0.2)
)
scene.illumination = less.Illumination(source=less.Sun(zenith=30, azimuth=150), atmosphere=less.HosekWilkieAtmosphere(turbidity=2.0))
sensor = less.OpticalImager(
less.Orthographic(image_size=512),
bands=[450, 550, 650, 850],
)
# Scene will be automatically built the first time simulate() is executed.
image = scene.simulate(sensor)
2. Save and load standalone Scene¶
scene.save() only saves the scene itself, including:
- Terrain and DEM;
- Objects and instances;
- physical properties;
- lighting;
- Space and backend configuration of Scene.
It does not save project sensor lists, resource registries, project IDs, or task information.
2.1 Three saving modes¶
By default you get a self-contained ZIP file and the mesh is copied into the package.
Get an uncompressed directory with the same name, suitable for local debugging and large grids.
Get the lightweight JSON reference file. The mesh maintains its original path and is not copied, so it is not necessarily portable.
2.2 Load Scene¶
scene = less.Scene.load("forest-scene.less")
sensor = less.OpticalImager(
less.Orthographic(image_size=512),
bands=[650, 550, 450],
)
image = scene.simulate(sensor)
The loaded Scene has not yet been built; simulate() will be built automatically for the first time without additional preparation.
It is recommended to use easily identifiable file names for pure scene files:
3. Create a complete Project¶
3.1 Start with an empty project¶
import numpy as np
import less
project = less.Project.create(
title="Northern Birch Canopy Experiment",
author="LESS Lab",
description="Comparing ortho and multi-angle canopy reflectance.",
backend="auto",
)
scene = project.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.HosekWilkieAtmosphere(turbidity=2.2))
tree = less.Object("birch", mesh="assets/birch.obj")
tree.set_property("leaves", less.Prospect(cab=42))
positions = np.array([
[20, 20, 0],
[40, 30, 0],
[65, 25, 0],
[30, 70, 0],
[70, 65, 0],
])
scene.add(tree, positions=positions)
Project.create() just helped us create the project and initial Scene. All of Scene API Still
Available via project.scene.
3.2 Create from existing Scene¶
Existing code does not need to be rewritten:
This is also a compatible entry point for the old Project(scene=..., sensors=...) call.
4. Manage engineering resources¶
Project Format divides resources into two storage strategies: managed and external.
4.1 Hosted Resources¶
Documents that are small or must be delivered with the project should be included in the project:
mesh_resource = project.resources.import_file(
"assets/birch.obj",
resource_type="mesh",
)
mesh_path = project.resources.resolve(mesh_resource.id)
tree = less.Object("birch", mesh=mesh_path)
When you save a catalog project or portable package, the managed resources are copied to resources/ and the file size and
SHA-256 hash.
Resource registration does not automatically create Object. It is only responsible for file identity, path and storage strategy, scene objects
Still created by less.Object.
4.2 External resources¶
Very large DEMs, point clouds or shared data can retain only links:
dem_resource = project.resources.register_external(
"F:/remote-sensing/dem/large-area.tif",
resource_type="terrain",
)
External resources are not copied into the daily project, so the project is smaller but no longer fully portable. Open the project The machine must have access to the same path or shared mount point.
Before submitting a remote task, ensure that the external resource is visible on the server, or change it to a managed resource first. Currently
export_snapshot()will retain the external path and will not implicitly copy very large files.
4.3 Check resources¶
for resource in project.resources:
errors = project.resources.verify(
resource.id,
checksum=True,
)
print(resource.name, errors or "OK")
5. Add multiple named sensors¶
nadir = less.OpticalImager(
less.Orthographic(
image_size=512,
view_zenith=0,
),
bands=[450, 550, 650, 850],
)
project.add_sensor(nadir, name="Canopy Multispectral (Orthophoto)")
oblique = less.OpticalImager(
less.Orthographic(
image_size=512,
view_zenith=30,
view_azimuth=90,
),
bands=[550, 650, 850],
)
project.add_sensor(oblique, name="Canopy multi-angle (30°)")
View and get sensors:
print([sensor.name for sensor in project.sensors])
sensor = project.get_sensor("Canopy Multispectral (Orthophoto)")
Remove the sensor:
Sensor configuration is not scene geometry. Adding, deleting or modifying sensors does not require rebuilding Scene.
6. Build once, run multiple sensors¶
It is recommended to build explicitly in batch simulations:
project.build()
image_nadir = project.simulate("Canopy Multispectral (Orthophoto)")
image_oblique = project.simulate("Canopy multi-angle (30°)")
Both sensors share the same project.scene, so the second simulation will no longer create the geometric acceleration structure.
When no name is specified, Project will execute all registration sensors in sequence:
results = project.simulate()
for sensor_name, product in results.items():
product.save(f"outputs/{sensor_name}.tif")
The return value is:
Project.simulate() still ends up calling the same Scene:
project.simulate("Canopy Multispectral (Orthophoto)")
# Equivalent to:
sensor = project.get_sensor("Canopy Multispectral (Orthophoto)")
project.scene.simulate(sensor)
Project will not create another copy of GPU Scene, nor will it relay the local simulation over HTTP.
7. Parameter hot update and geometric reconstruction¶
7.1 Modify lighting: do not rebuild¶
project.set_illumination(
less.Illumination(source=less.Sun(zenith=50, azimuth=210), atmosphere=less.HosekWilkieAtmosphere(turbidity=2.5))
)
print(project.session_status) # parameters_dirty
image = project.simulate("Canopy Multispectral (Orthophoto)")
Only lighting parameters will be synchronized before the next simulation, and existing geometric acceleration structures will continue to be reused.
7.2 Modify physical attributes: do not rebuild¶
forest = project.scene.objects["birch"]
forest.set_property(
"leaves",
less.Prospect(cab=25),
)
image = project.simulate("Canopy Multispectral (Orthophoto)")
You can also use path API:
7.3 Modify geometry: need to rebuild¶
The following changes require the geometry to be rebuilt or updated:
- Add and delete objects;
- Change the grid;
- Change terrain geometry;
- Change a large number of instance positions, rotations or scales.
# ... modify an object, instance, or terrain ...
project.rebuild()
image = project.simulate("Canopy Multispectral (Orthophoto)")
Quickly determine the current status:
The meaning of session_status:
| Status | Meaning |
|---|---|
unbuilt |
Not yet built |
ready |
Can be reused directly |
parameters_dirty |
Parameters to be synchronized, no need to rebuild |
geometry_dirty |
Geometry changes, need to rebuild |
8. Save Project¶
8.1 Editable directory project¶
When frequent editing is required, it is recommended to use a normal directory project:
Directory contents:
birch-experiment.less/
├── project.json # Format version, project ID, title, and revision number
├── metadata.json # Compatible metadata
├── scene.json # Scene configuration
├── sensors.json # Name the sensor
├── resources.json # Resource Registry and Hashes
└── resources/
├── mesh/
├── terrain/
└── ...
After saving for the first time, you can save directly back to the current location:
Directory mode does not require compressing the entire large scene each time, and is suitable for frequent editing and version management.
8.2 Single file portable project¶
By default, a single file .less in ZIP format is generated and set as the current project path.
To export just another portable copy without changing the current editing project:
The difference between the two:
| API | Function |
|---|---|
save_directory() |
Save and continue editing directory project |
save(path) |
Save and use this path as the current project |
pack(path) |
Export a portable copy without switching the current project |
The old call still works:
It is equivalent to saving an uncompressed directory.
8.3 JSON reference mode¶
JSON mode is mainly used for compatibility and lightweight configuration exchange. Resources remain path references and are not suitable as fully portable archive format.
9. Open, verify and close the project¶
Directory and ZIP use the same entry:
Compatible portals can also continue to be used:
Verify the project before running:
report = project.validate(verify_checksums=True)
for issue in report.issues:
print(issue.severity, issue.path, issue.message)
report.raise_for_errors()
Validation checks:
- Is Scene valid?
- Whether the sensor name is missing or duplicated;
- Whether the grid and resources exist;
- File size consistent with optional SHA-256.
Projects opened from ZIP will be extracted to a temporary directory. You can clean up after finishing building and simulating:
Do not call close() before simulation as the grid in Scene may still point to the unzipped directory.
10. Generate remote task snapshot¶
The remote task does not directly modify the current Project object, but generates an independent snapshot from a project revision:
snapshot = project.export_snapshot(
"jobs/nadir-job.less",
sensors=["Canopy multispectral (orthophoto)"],
)
print(snapshot.project_id)
print(snapshot.revision)
print(snapshot.fingerprint)
print(snapshot.sensors)
snapshot.json is also generated in the snapshot:
project_id project identity
revision project revision at submission
fingerprint configuration content fingerprint
sensors The sensors selected for this task
created_at snapshot creation time
The server can open the snapshot using normal Project API:
job = less.Project.open("jobs/nadir-job.less")
job.validate().raise_for_errors()
job.build()
results = job.simulate()
Local simulation does not require snapshots and does not require HTTP:
11. Project API Quick Check¶
# Create and open
less.Project.create(...)
less.Project(scene=..., sensors=[...])
less.Project.open(path)
less.Project.load(path)
# sensor
project.add_sensor(sensor, name="Nadir")
project.get_sensor("Nadir")
project.remove_sensor("Nadir")
# Sessions and Simulations
project.build()
project.rebuild()
project.simulate("Nadir")
project.simulate() # All sensors
project.set_illumination(...)
project.show()
# Validation and persistence
project.validate()
project.save_directory(path)
project.save(path)
project.pack(path)
project.export_snapshot(path, sensors=[...])
project.close()
Common status:
project.path
project.is_dirty
project.is_built
project.session_status
project.project_id
project.revision
12. Migrate from the old LESS project¶
The simulation directory of the original LESS can be converted to Scene first:
from_less() imports scene geometry, terrain, attributes and lighting, but does not automatically import all observations in the old configuration
Parameters become the new sensor. It is recommended to create the sensor explicitly and package it into a Project:
sensor = less.OpticalImager(
less.Orthographic(image_size=512),
bands=[550, 650, 850],
)
project = less.Project(
scene=scene,
title="Migrated LESS experiment",
)
project.add_sensor(sensor, name="Nadir multispectral")
project.save_directory("migrated-experiment.less")
The new version can also read the v1 .less project generated by the old version less.Project.save(). first save
The v2 structure will be used later.
13. Recommended workflow¶
Pure Python temporary experiment¶
Reproducible local projects¶
project = less.Project.create(title="Experiment")
# Configure project.scene, resources and sensors
project.save_directory("experiment.less")
project.build()
results = project.simulate()
Local project + remote service¶
Open directory project
→ Edit Scene and sensor in Python
→ Build a Scene Session locally
→ Repeat simulation for multiple sensors
→ Hot update when lighting/attributes are modified
→ Export task snapshot when remote computing is required
→ lessd opens the snapshot and executes
There is only one key principle: **Scene is responsible for simulation, and Project is responsible for organization and reproduction. **
Next chapter: 17 - Reconstruction of 3D scene from LiDAR point cloud
Related API¶
less.Scene.save()、less.Scene.load()less.Project.create()、less.Project.open()、less.Project.save()less.Project.add_sensor()、less.Project.simulate()less.Project.validate()、less.Project.snapshot()