跳转至

07 - 场景与工程

本章介绍如何保存和重新打开场景,以及如何用工程(less.Project)管理一个包含多个传感器的完整实验。

场景还是工程

less.Scene 是模拟的主体,less.Project 在场景之外再记录命名的传感器、文件资源和实验信息。

需求 使用
交互式实验、一次性计算 Scene
保存或分享一个场景 scene.save()
保存场景和多个传感器 Project
管理模型文件,长期保存实验 Project
把实验打包到另一台机器上运行 Project.export_snapshot()

工程内部持有一个普通的 Scene,所有场景操作仍然通过 project.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]]))

保存场景有三种方式:

scene.save("forest-scene.less")                    # 单个压缩文件,包含模型文件
scene.save("forest-scene-folder.less", pack=False) # 不压缩的目录,适合大型模型
scene.save("forest-scene.json")                    # 只记录模型文件路径

.less 文件把 OBJ、DEM 和空间属性文件一起打包,可以直接复制到其他电脑。.json 只记录路径,换一台电脑时需要同样的文件。

重新打开:

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

场景文件保存的是场景的设置,包括地形、物体、属性、光照和微气象。能量平衡等过程求得的温度不在其中,重新打开后需要再次求解。

创建工程

project = less.Project(
    scene=scene,
    title="白蜡林冠实验",
    author="LESS Lab",
    description="比较正射与斜视的冠层反射率",
)

也可以先创建空工程,再通过 project.scene 设置场景:

empty = less.Project.create(title="新实验", backend="auto")
empty.scene.size = 20.0

添加传感器

工程中的传感器有名字,之后按名字运行:

project.add_sensor(
    less.OpticalImager(less.Orthographic(image_size=64),
                       bands=[450, 550, 650, 850], quality=16),
    name="正射多光谱")
project.add_sensor(
    less.OpticalImager(less.Orthographic(image_size=64, view_zenith=30, view_azimuth=90),
                       bands=[550, 650, 850], quality=16),
    name="东侧30度斜视")

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

运行模拟

project.build()
nadir = project.simulate("正射多光谱")

不写名字时依次运行所有传感器,返回一个以传感器名为键的字典:

from pathlib import Path

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

所有传感器共用同一个场景,几何只构建一次。

修改光照或属性后直接再运行,不需要重建:

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("正射多光谱")

增删物体、更换模型或修改地形后,调用 project.rebuild()。project.session_status 显示当前状态:

状态 含义
unbuilt 尚未构建
ready 可以直接模拟
parameters_dirty 光照或属性已修改,下次模拟时自动更新
geometry_dirty 几何已修改,需要重建

管理模型文件

工程中的文件分为两类:

  • 托管文件:复制到工程目录中,随工程一起保存和分享;
  • 外部文件:只记录路径,适合很大的或多人共享的数据。
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")

保存工程

project.save_directory("ash-experiment.less")      # 可编辑的目录
project.pack("ash-experiment-portable.less")       # 另存一个单文件副本

目录工程适合日常编辑,每次保存不必压缩整个场景;单文件副本适合归档和分享。project.save("xxx.less") 也会保存为单文件,并把它作为当前工程路径。

目录工程的结构:

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

打开和检查工程

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("正射多光谱")

validate() 会检查传感器名称、模型文件是否存在以及文件内容是否完整。

打包到其他机器运行

export_snapshot() 把当前工程和选定的传感器导出为一个独立的任务包:

Path("jobs").mkdir(exist_ok=True)
snapshot = project.export_snapshot("jobs/nadir-job.less", sensors=["正射多光谱"])
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()

从单文件打开的工程会解压到临时目录,用完后调用 close() 清理。

外部文件在任务包中仍然只记录路径,运行任务的机器需要能访问这些文件。

导入 LESS 2 工程

LESS 2 的工程目录可以用 less.Scene.from_less(path) 读入为场景。观测设置不会自动转换,需要重新创建传感器:

project = less.Project(scene=scene, title="从 LESS 2 导入的实验")
project.add_sensor(
    less.OpticalImager(less.Orthographic(image_size=64), bands=[550, 650, 850]),
    name="正射")
project.save_directory("imported-experiment.less")

下一步