16 - Scene 与 Project:保存、复用和任务快照¶
LESS 的模拟核心是 less.Scene。只要场景、光照和传感器已经在 Python
脚本中定义,就可以直接调用 scene.simulate(sensor) 完成全部模拟。
less.Project 是可选的工程管理层。它把 Scene、命名传感器、资源和工程元
数据组织在一起,适合长期保存、分享和远程任务。
本章使用 LESS Project Format v2,涵盖:
- 什么时候只用
Scene,什么时候使用Project; - 保存和加载独立场景;
- 创建完整工程并管理多个传感器;
- 构建一次 Scene Session,重复执行多个模拟;
- 修改光照或物理属性而不重建几何;
- 保存目录工程、导出便携包和生成远程任务快照;
- 从旧版 LESS 工程迁移。
1. 先选择:Scene 还是 Project¶
二者不是两套模拟系统。Project 内部仍然持有一个普通的 Scene:
less.Project
├── scene → 真正的模拟运行时和构建状态
├── sensors → 工程登记的命名传感器
├── resources → 文件资源注册表
└── metadata → 标题、作者、工程 ID 和修订号
按使用目的选择即可:
| 使用场景 | 推荐入口 |
|---|---|
| Notebook、单次实验、算法验证 | Scene |
| 传感器由脚本临时创建 | Scene |
| 复用或分享一个纯场景模板 | scene.save() |
| 保存场景和多个传感器 | Project |
| 管理本地资源和工程修订 | Project |
| 提交远程计算任务 | Project.export_snapshot() |
如果不需要工程管理和打包,直接使用 Scene 就足够:
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 会在第一次 simulate() 时自动构建。
image = scene.simulate(sensor)
2. 保存和加载独立 Scene¶
scene.save() 只保存场景本身,包括:
- 地形和 DEM;
- 对象与实例;
- 物理属性;
- 光照;
- Scene 的空间与后端配置。
它不保存工程传感器列表、资源注册表、工程 ID 或任务信息。
2.1 三种保存模式¶
默认得到一个自包含的 ZIP 文件,网格会被复制到包内。
得到同名的非压缩目录,适合本地调试和大型网格。
得到轻量 JSON 引用文件。网格保持原路径,不会复制,因此不一定可移植。
2.2 加载 Scene¶
scene = less.Scene.load("forest-scene.less")
sensor = less.OpticalImager(
less.Orthographic(image_size=512),
bands=[650, 550, 450],
)
image = scene.simulate(sensor)
加载后的 Scene 尚未构建;第一次 simulate() 会自动构建,无需额外准备。
建议给纯场景文件使用容易识别的文件名:
3. 创建一个完整 Project¶
3.1 从空工程开始¶
import numpy as np
import less
project = less.Project.create(
title="北方桦树林冠实验",
author="LESS Lab",
description="比较正射与多角度冠层反射率。",
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() 只是帮我们创建了工程和初始 Scene。Scene 的全部 API 仍然
可以通过 project.scene 使用。
3.2 从已有 Scene 创建¶
已有代码不需要重写:
这也是旧版 Project(scene=..., sensors=...) 调用的兼容入口。
4. 管理工程资源¶
Project Format 把资源分为 managed 和 external 两种存储策略。
4.1 托管资源¶
小型或必须随工程传递的文件应纳入工程:
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)
保存目录工程或便携包时,托管资源会复制到 resources/,并记录文件大小和
SHA-256 哈希。
资源注册不会自动创建 Object。它只负责文件身份、路径和存储策略,场景对象
仍然由 less.Object 创建。
4.2 外部资源¶
非常大的 DEM、点云或共享数据可以只保留链接:
dem_resource = project.resources.register_external(
"F:/remote-sensing/dem/large-area.tif",
resource_type="terrain",
)
外部资源不会复制到日常工程中,因此工程更小,但不再完全可移植。打开工程的 机器必须能访问相同路径或共享挂载点。
提交远程任务前应确保外部资源在服务器上可见,或者先将其改为托管资源。 当前
export_snapshot()会保留 external 路径,不会隐式复制超大文件。
4.3 检查资源¶
for resource in project.resources:
errors = project.resources.verify(
resource.id,
checksum=True,
)
print(resource.name, errors or "OK")
5. 添加多个命名传感器¶
nadir = less.OpticalImager(
less.Orthographic(
image_size=512,
view_zenith=0,
),
bands=[450, 550, 650, 850],
)
project.add_sensor(nadir, name="冠层多光谱(正射)")
oblique = less.OpticalImager(
less.Orthographic(
image_size=512,
view_zenith=30,
view_azimuth=90,
),
bands=[550, 650, 850],
)
project.add_sensor(oblique, name="冠层多角度(30°)")
查看和获取传感器:
移除传感器:
传感器配置不是场景几何。添加、删除或修改传感器不需要重建 Scene。
6. 构建一次,运行多个传感器¶
推荐在批量模拟中显式构建:
project.build()
image_nadir = project.simulate("冠层多光谱(正射)")
image_oblique = project.simulate("冠层多角度(30°)")
两个传感器共享同一个 project.scene,因此第二次模拟不会再创建几何加速结构。
不指定名称时,Project 会依次执行全部登记传感器:
results = project.simulate()
for sensor_name, product in results.items():
product.save(f"outputs/{sensor_name}.tif")
返回值是:
Project.simulate() 最终仍然调用同一个 Scene:
project.simulate("冠层多光谱(正射)")
# 等价于:
sensor = project.get_sensor("冠层多光谱(正射)")
project.scene.simulate(sensor)
Project 不会创建另一份 GPU Scene,也不会通过 HTTP 中转本地模拟。
7. 参数热更新与几何重建¶
7.1 修改光照:不重建¶
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("冠层多光谱(正射)")
下一次模拟前只会同步光照参数,已有几何加速结构继续复用。
7.2 修改物理属性:不重建¶
forest = project.scene.objects["birch"]
forest.set_property(
"leaves",
less.Prospect(cab=25),
)
image = project.simulate("冠层多光谱(正射)")
也可以使用路径式 API:
7.3 修改几何:需要重建¶
以下变化需要重新构建或更新几何结构:
- 增删对象;
- 更换网格;
- 改变地形几何;
- 改变大量实例位置、旋转或缩放。
快速判断当前状态:
session_status 的含义:
| 状态 | 含义 |
|---|---|
unbuilt |
尚未构建 |
ready |
可直接复用 |
parameters_dirty |
参数待同步,不需要重建 |
geometry_dirty |
几何变化,需要重建 |
8. 保存 Project¶
8.1 可编辑目录工程¶
需要频繁编辑时,推荐使用普通目录工程:
目录内容:
birch-experiment.less/
├── project.json # 格式版本、工程 ID、标题和修订号
├── metadata.json # 兼容元数据
├── scene.json # Scene 配置
├── sensors.json # 命名传感器
├── resources.json # 资源注册表和哈希
└── resources/
├── mesh/
├── terrain/
└── ...
第一次保存后,可以直接保存回当前位置:
目录模式不需要每次压缩整个大型场景,适合频繁编辑和版本管理。
8.2 单文件便携工程¶
默认生成一个 ZIP 格式的单文件 .less,同时把它设为当前工程路径。
如果只是另外导出一个便携副本,而不改变当前编辑工程:
二者的区别:
| API | 作用 |
|---|---|
save_directory() |
保存并继续编辑目录工程 |
save(path) |
保存并把该路径作为当前工程 |
pack(path) |
导出便携副本,不切换当前工程 |
旧调用仍然有效:
它等价于保存非压缩目录。
8.3 JSON 引用模式¶
JSON 模式主要用于兼容和轻量配置交换。资源保持路径引用,不适合作为完全可移植 的归档格式。
9. 打开、验证和关闭工程¶
目录和 ZIP 使用同一入口:
兼容入口也可继续使用:
运行前验证工程:
report = project.validate(verify_checksums=True)
for issue in report.issues:
print(issue.severity, issue.path, issue.message)
report.raise_for_errors()
验证会检查:
- Scene 是否有效;
- 传感器名称是否缺失或重复;
- 网格和资源是否存在;
- 文件大小与可选 SHA-256 是否一致。
从 ZIP 打开的工程会解压到临时目录。完成构建和模拟后可以清理:
不要在模拟前调用 close(),因为 Scene 中的网格可能仍指向解压目录。
10. 生成远程任务快照¶
远程任务不直接修改当前 Project 对象,而是从某个工程修订生成独立快照:
snapshot = project.export_snapshot(
"jobs/nadir-job.less",
sensors=["冠层多光谱(正射)"],
)
print(snapshot.project_id)
print(snapshot.revision)
print(snapshot.fingerprint)
print(snapshot.sensors)
快照中还会生成 snapshot.json:
服务器可以使用普通 Project API 打开快照:
job = less.Project.open("jobs/nadir-job.less")
job.validate().raise_for_errors()
job.build()
results = job.simulate()
本地模拟不需要快照,也不需要 HTTP:
11. Project API 速查¶
# 创建与打开
less.Project.create(...)
less.Project(scene=..., sensors=[...])
less.Project.open(path)
less.Project.load(path)
# 传感器
project.add_sensor(sensor, name="Nadir")
project.get_sensor("Nadir")
project.remove_sensor("Nadir")
# 会话与模拟
project.build()
project.rebuild()
project.simulate("Nadir")
project.simulate() # 全部传感器
project.set_illumination(...)
project.show()
# 验证与持久化
project.validate()
project.save_directory(path)
project.save(path)
project.pack(path)
project.export_snapshot(path, sensors=[...])
project.close()
常用状态:
project.path
project.is_dirty
project.is_built
project.session_status
project.project_id
project.revision
12. 从旧版 LESS 工程迁移¶
原版 LESS 的模拟目录可以先转换为 Scene:
from_less() 导入场景几何、地形、属性和光照,但不会自动把旧配置中的所有观测
参数变成新传感器。建议显式创建传感器并包装成 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")
新版本也可以读取由旧版 less.Project.save() 生成的 v1 .less 工程。首次保存
后会使用 v2 结构。
13. 推荐工作流¶
纯 Python 临时实验¶
可复现的本地工程¶
project = less.Project.create(title="Experiment")
# 配置 project.scene、resources 和 sensors
project.save_directory("experiment.less")
project.build()
results = project.simulate()
本地工程 + 远程服务¶
打开目录工程
→ 在 Python 中编辑 Scene 和传感器
→ 本地构建一次 Scene Session
→ 多个传感器重复模拟
→ 修改光照/属性时热更新
→ 需要远程计算时导出任务快照
→ lessd 打开快照并执行
关键原则只有一个:Scene 负责模拟,Project 负责组织和复现。
相关 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()