18 - Spatial textures and property fields¶
Use UV images, georeferenced rasters, measured spectra, RGB imagery, temperature, and emissivity as spatially varying Property inputs.
π What a texture means in LESS¶
A LESS texture is a spatial source for one named physical parameter. It is not
a complete material by itself. Assign the texture to a Property parameter,
then assign that property to a terrain or object component:
roof = less.Lambertian(
reflectance=less.RGBTexture("roof_albedo.png"))
building.set_property("roof", roof)
This keeps the physical meaning explicit. The same image declaration can be a reflectance source, but it cannot silently become temperature, emissivity, or transmittance.
| Input | Public type | Typical parameter |
|---|---|---|
| One physical value per pixel | ScalarTexture |
temperature |
| Measured spectral samples | SpectralTexture |
reflectance, transmittance, emissivity |
| Three-channel image | RGBTexture |
empirical visible reflectance reconstruction |
Constants and textures use the same Property definitions. A component can therefore change from a constant reflectance to a spatial reflectance without changing the sensor or simulation workflow.
π Choose the spatial mapping¶
| Mapping | Use it for | Required information |
|---|---|---|
mapping="uv" |
OBJ models, buildings, vehicles, photogrammetry meshes | OBJ vt coordinates |
mapping=less.WorldXY() |
GeoTIFF, DEM-aligned products, land-cover and LST rasters | raster CRS/affine and scene.georeference |
UV textures default to wrap="repeat". World-XY textures default to
wrap="clamp" and require an explicit policy when the scene extends outside
the raster.
β οΈ Do not use
mapping="uv"for terrain. Terrain has no imported OBJ UV atlas. LESS reports this as an error instead of sampling an arbitrary point.
π Use measured hyperspectral reflectance¶
The raster stores linear surface reflectance with shape
(height, width, bands). Wavelength samples may be irregularly spaced.
import less
leaf = less.Lambertian(
reflectance=0.10,
back_reflectance=0.12,
transmittance=0.05,
).with_fields(
reflectance=less.SpectralTexture(
"leaf_front.tif",
wavelengths="metadata",
mapping="uv",
filter="trilinear",
nodata="component_default",
),
back_reflectance=less.SpectralTexture(
"leaf_back.tif",
wavelengths="metadata",
mapping="uv",
),
transmittance=less.SpectralTexture(
"leaf_transmittance.tif",
wavelengths="metadata",
mapping="uv",
),
)
tree.set_property("leaves", leaf)
GeoTIFF wavelength metadata can come from per-band wavelength tags, band
descriptions, or ENVI wavelength tags. For .npy, pass wavelengths explicitly;
for .npz, store arrays named data and wavelengths.
soil = less.SpectralTexture(
"soil_reflectance.npy",
wavelengths=(450.0, 550.0, 670.0, 865.0, 1610.0),
mapping="uv",
)
Values with units="fraction" must be linear physical fractions in [0, 1].
Do not apply sRGB gamma to measured reflectance.
π Use an RGB image¶
RGBTexture converts sRGB to linear RGB and reconstructs an empirical smooth
visible spectrum with spectral_reconstruction="jh19":
facade = less.Lambertian(
reflectance=less.RGBTexture(
"facade.png",
color_space="srgb",
spectral_reconstruction="jh19",
mapping="uv",
))
building.set_property("facade", facade)
β οΈ RGB reconstruction is an estimate, not measured hyperspectral reflectance. A photograph or satellite RGB composite may already contain illumination, shadows, atmosphere, tone mapping, and compression. Use a surface-reflectance product for quantitative simulation whenever possible.
The JH19 reconstruction is defined only in the visible domain used by the
implementation. A sensor wavelength outside that domain raises an error unless
you configure an explicit fractional outside_spectral_domain value. An RGB
texture should therefore not be treated as a 905 nm or 1064 nm LiDAR spectrum.
π Place a georeferenced raster¶
For world-XY mapping, the scene and raster must use the same projected CRS in
metres. The scene origin is the world coordinate of its local (0, 0) corner.
import less
scene = less.Scene()
scene.size = (1000.0, 1000.0)
scene.georeference = {
"crs": "EPSG:32650",
"origin": (500000.0, 4000000.0),
"units": "m",
}
terrain_reflectance = less.SpectralTexture(
"surface_reflectance.tif",
wavelengths="metadata",
mapping=less.WorldXY(outside="error"),
filter="bilinear",
nodata="component_default",
)
terrain_property = less.Lambertian(reflectance=0.15).with_field(
"reflectance", terrain_reflectance)
scene.terrain = less.Terrain(property=terrain_property)
LESS reads GeoTIFF band-first storage, applies valid scale/offset metadata, uses raster masks as nodata. A CRS mismatch fails closed; LESS does not reproject a scientific raster implicitly.
Land-cover products contain class codes rather than reflectance. Use
class_values to map each class explicitly to a physical scalar or spectrum;
unlisted classes follow the selected nodata policy:
worldcover = less.SpectralTexture(
"worldcover.tif",
wavelengths=(550.0, 850.0),
class_values={
10: (0.08, 0.45), # example only; use your study's spectral library
20: (0.12, 0.38),
80: (0.04, 0.02),
},
mapping=less.WorldXY(),
filter="nearest",
nodata="component_default",
)
Categorical lookup requires explicit wavelengths and cannot also apply a GeoTIFF scale/offset. LESS does not ship an βauthoritative WorldCover spectrumβ: reflectance for one class varies with location, season, and water content.
Statistical TurbidBoundary leaves have no OBJ UV coordinates. They may use a
world-XY Field when that workflow is available, but cannot use a surface UV
atlas.
π Map temperature and emissivity¶
Temperature is a scalar field in kelvin. Emissivity may be constant or spectral:
thermal = less.ThermalProperty(
temperature=300.0,
emissivity=0.95,
).with_fields(
temperature=less.ScalarTexture(
"lst.tif",
units="K",
mapping=less.WorldXY(outside="error"),
nodata="component_default",
),
emissivity=less.SpectralTexture(
"emissivity.tif",
wavelengths=(10600.0, 12000.0),
mapping=less.WorldXY(outside="error"),
nodata="component_default",
),
)
scene.terrain.set_property(thermal)
Use land-surface temperature, not top-of-atmosphere brightness temperature.
Missing pixels with nodata="component_default" use the constant value on the
Property. Keep the constant in the Property constructor and attach the texture
with with_field() or with_fields(), as shown above.
π Reuse one asset with multiple sensors¶
Texture wavelengths describe the material data, not output bands. Each sensor selects its own wavelengths or SRFs; LESS evaluates the property at the required spectral samples and integrates the requested response.
rgb = scene.simulate(less.OpticalImager(
less.Orthographic(image_size=512),
bands=[450.0, 550.0, 650.0],
))
hyperspectral = scene.simulate(less.OpticalImager(
less.Orthographic(image_size=512),
bands=list(range(400, 1001, 5)),
))
The scene is persistent: unchanged geometry and texture resources are reused. Changing sensor bands does not require editing the OBJ or duplicating the texture.
πΎ Control filtering, nodata, and residency¶
| Option | Values | Meaning |
|---|---|---|
filter |
nearest, bilinear, trilinear |
Spatial reconstruction and MIP filtering |
wrap |
clamp, repeat |
Sampling beyond the UV extent |
nodata |
error, component_default, number |
Missing-pixel policy |
missing_uv |
error, component_default |
Missing mesh-UV policy |
trilinear is currently supported for optical and thermal imaging, where LESS
can select a texture level from the area covered by each image pixel. Use
nearest or bilinear for BRF, RadiationField, irradiance, LiDAR,
energy-balance, photosynthesis, and SIF workflows. LESS reports an unsupported
combination before starting the simulation and does not silently choose a
different filter.
You can query the complete scene/request combination before simulation:
if not scene.can_use(sensor, explain=True):
raise RuntimeError("This simulation and spatial-texture combination is unavailable")
image = scene.simulate(sensor)
Unresolved turbid boundaries have no surface UV coordinate. Their spatial
Fields must use WorldXY; ordinary mesh surfaces in the same scene may still
use UV textures.
| outside_spectral_domain | error, fraction | Wavelength-domain policy |
| raster_bands | all, one-based band indices | Select GeoTIFF bands |
| scale, offset | metadata, scalar, or per-band values | Convert integer remote-sensing DN to physical values |
| class_values | {class code: scalar or spectrum} | Convert a categorical raster to a physical Field |
Large world-XY GeoTIFFs use a scene-footprint window. Set a hard resident budget and tile alignment on the scene:
If the complete raster area needed by the scene cannot fit the budget, LESS
raises MemoryError; it does not silently downsample the raster.
πΎ Save and move a textured scene¶
Reference saves store absolute texture paths. Packed saves copy textures into
the .less archive with content-derived names, so the archive remains valid
after the original raster is moved or deleted:
Packing also distinguishes different source files with the same filename and deduplicates repeated references to the same source.
π§ Troubleshooting¶
spectral texture wavelength metadata is missing¶
The file has no readable wavelength tags. Pass an explicit increasing
wavelengths=(...) sequence, or add per-band/ENVI wavelength metadata.
world_xy geospatial textures require scene.georeference¶
Set scene.georeference with matching projected crs, origin, and metre
units before building the scene.
texture contains nodata while nodata='error'¶
Use nodata="component_default" when the Property has a scientifically valid
constant default, or provide an explicit finite fallback value.
requested wavelength is outside the texture spectral domain¶
Use measured data covering the sensor domain. Configure a numeric fallback only when that value has a defensible physical meaning.
Related API¶
less.Texture2Dless.SpectralTextureless.ScalarTextureless.UVMappingless.WorldXYMappingless.Property.with_field()andless.Property.with_fields()