Summary
irradiance.surface.relative grids fail in treevox whenever the supplied terrain/DEM grid does not have the exact same pixel dimensions as the source leaf-area-density (LAD) grid. The surface path assumes the DEM and LAD grids are already cell-for-cell aligned and passes the raw DEM array straight into leaflux, which rejects the shape mismatch.
Error (from treevox-v2-prod logs)
ValueError: Terrain dimensions must match leaf area grid dimensions.
Leaf area is (715, 703) and terrain is (173, 141)
Raised in leaflux/environment.py:318 (Environment.__init__ → Terrain(...)), reached via:
treevox/handlers/leaflux.py:442 run_leaflux -> _write_tiles
treevox/handlers/leaflux.py:331 _write_tiles -> _write_tile
treevox/handlers/leaflux.py:301 _write_tile -> _surface_irradiance
treevox/handlers/leaflux.py:232 _surface_irradiance -> Environment(..., terrain=Terrain(terrain), ...)
Root cause
_write_tile (want_surface=True) calls _read_terrain, which reads the DEM tile directly:
elev = dem_ds[ELEVATION_KEY][tile.pad_yx].values.astype(np.float32)
return (elev - job.z_origin) / job.vr
tile.pad_yx indexes the DEM with the LAD tile's padded (y, x) window, so the returned terrain only has the LAD's shape when the two grids share a resolution/extent. When they don't (the common case — e.g. a 2 m LAD grid voxelized from an inventory paired with a coarser topography grid), the DEM slice comes back at the DEM's own resolution and leaflux's Environment raises.
The canopy-only path (irradiance.canopy.relative, no terrain) is unaffected because it never touches _read_terrain/_surface_irradiance.
Suggested fix
In _read_terrain (or upstream in run_leaflux), resample/reproject the DEM onto the LAD grid so terrain is returned on the LAD grid's transform and shape before Terrain(...) is built — rather than assuming matching dimensions. Add a treevox integration test that pairs a fine LAD grid with a coarser DEM.
Repro
- Voxelize a tree inventory to a
leaf_area_density grid at 2 m horizontal resolution.
- Create a topography grid in the same domain at a different resolution (e.g. 30 m native).
- Request a leaflux irradiance grid with
bands=["irradiance.surface.relative"] and that terrain grid as the source terrain.
- The grid job fails with
UNEXPECTED_FAILURE; treevox logs show the ValueError above.
Impact
Blocks end-to-end validation of the Fosberg 1-hr dead fuel moisture grid (#540), which consumes an irradiance.surface.relative grid. Tracked downstream in the SDK by silvxlabs/fastfuels-sdk-python#197 (held pending this fix).
Summary
irradiance.surface.relativegrids fail in treevox whenever the supplied terrain/DEM grid does not have the exact same pixel dimensions as the source leaf-area-density (LAD) grid. The surface path assumes the DEM and LAD grids are already cell-for-cell aligned and passes the raw DEM array straight intoleaflux, which rejects the shape mismatch.Error (from
treevox-v2-prodlogs)Raised in
leaflux/environment.py:318(Environment.__init__→Terrain(...)), reached via:Root cause
_write_tile(want_surface=True) calls_read_terrain, which reads the DEM tile directly:tile.pad_yxindexes the DEM with the LAD tile's padded (y, x) window, so the returned terrain only has the LAD's shape when the two grids share a resolution/extent. When they don't (the common case — e.g. a 2 m LAD grid voxelized from an inventory paired with a coarser topography grid), the DEM slice comes back at the DEM's own resolution andleaflux'sEnvironmentraises.The canopy-only path (
irradiance.canopy.relative, no terrain) is unaffected because it never touches_read_terrain/_surface_irradiance.Suggested fix
In
_read_terrain(or upstream inrun_leaflux), resample/reproject the DEM onto the LAD grid so terrain is returned on the LAD grid's transform and shape beforeTerrain(...)is built — rather than assuming matching dimensions. Add a treevox integration test that pairs a fine LAD grid with a coarser DEM.Repro
leaf_area_densitygrid at 2 m horizontal resolution.bands=["irradiance.surface.relative"]and that terrain grid as the source terrain.UNEXPECTED_FAILURE; treevox logs show the ValueError above.Impact
Blocks end-to-end validation of the Fosberg 1-hr dead fuel moisture grid (#540), which consumes an
irradiance.surface.relativegrid. Tracked downstream in the SDK by silvxlabs/fastfuels-sdk-python#197 (held pending this fix).