Voxelize an inventory into a 3D canopy grid
You are viewing in-progress documentation for v2 (Beta). Switch to the stable version for the current production release.
A physics-based fire model doesn’t run on a list of trees; it runs on a 3D grid
of fuel. Voxelization crosses that gap — it builds a crown for each tree and
discretizes it onto a regular voxel lattice, so each cell carries a canopy fuel
property such as live-foliage bulk density (kg/m**3). The result is a
grid you can inspect, combine with surface
and terrain grids, and export to QUIC-Fire.
Prerequisites
Section titled “Prerequisites”-
An API key: my-api-key.
-
A domain: your-domain-id.
-
A completed inventory carrying
dbh,crown_ratio, andfia_species_code— from TreeMap, a full upload, or GDAM allometry. Record its id: your-inventory-id.
Create the canopy grid
Section titled “Create the canopy grid”POST to grids/voxelize/inventory/tree. Point source_inventory_id at the
completed inventory, set the voxel resolution (horizontal x/y and vertical z,
in meters), and choose the output bands. Here we build a single
bulk_density.foliage.live band — the per-voxel bulk density of live foliage,
the API’s default band — with foliage mass estimated from nsvb allometric
equations. seed fixes the stochastic step that thins each crown into a porous
canopy, so the grid is reproducible.
curl -X 'POST' \ 'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/voxelize/inventory/tree' \ -H 'accept: application/json' \ -H 'api-key: my-api-key' \ -H 'Content-Type: application/json' \ -d '{ "name": "Canopy fuel grid", "source_inventory_id": "your-inventory-id", "resolution": { "horizontal": 2, "vertical": 1 }, "bands": ["bulk_density.foliage.live"], "biomass_source": { "type": "allometry", "equations": "nsvb", "components": ["foliage"], "component_states": { "foliage": { "live": 1.0, "dead": 0.0 } } }, "seed": 42}'import fastfuels_sdk.v2 as fffrom fastfuels_sdk.v2.client_library.models import ( AllometryBiomassSource, AllometryBiomassSourceComponentStates, BiomassComponent, BiomassEquations,)
ff.set_api_key("my-api-key")
# A completed inventory carrying dbh, crown_ratio, and fia_species_code.inventory = ff.get_inventory("your-domain-id", "your-inventory-id")
# Voxelization is a method on the completed Inventory. resolution is horizontal# (x/y) and vertical (z) in meters; seed fixes the stochastic crown thinning.grid = inventory.voxelize( horizontal_resolution_m=2, vertical_resolution_m=1, bands=["bulk_density.foliage.live"], biomass_source=AllometryBiomassSource( equations=BiomassEquations.NSVB, components=[BiomassComponent.FOLIAGE], component_states=AllometryBiomassSourceComponentStates.from_dict( {"foliage": {"live": 1.0, "dead": 0.0}} ), ), seed=42, name="Canopy fuel grid",){ "id": "your-tree-grid-id", "domain_id": "your-domain-id", "name": "Canopy fuel grid", "description": "", "status": "pending", "progress": null, "created_on": "2026-07-09T16:03:59.102451Z", "modified_on": "2026-07-09T16:03:59.102451Z", "source": { "operation": "voxelize", "input": "inventory", "entity": "tree", "source_inventory_id": "your-inventory-id", "resolution": { "horizontal": 2.0, "vertical": 1.0 }, "bands": ["bulk_density.foliage.live"], "crown_profile_model": "purves", "biomass_source": { "type": "allometry", "equations": "nsvb", "components": ["foliage"], "component_states": { "foliage": { "live": 1.0, "dead": 0.0 } } }, "max_crown_radius_source": { "type": "allometry" }, "seed": 42 }, "modifications": [], "bands": [ { "key": "bulk_density.foliage.live", "type": "continuous", "unit": "kg/m**3", "index": 0, "nodata": null } ], "georeference": null, "error": null, "tags": []}Record the grid id: your-tree-grid-id. Poll until completed:
curl -X 'GET' \ 'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/your-tree-grid-id' \ -H 'accept: application/json' \ -H 'api-key: my-api-key'grid.wait()
# grid.georeference.shape is [nz, ny, nx].{ "id": "your-tree-grid-id", "domain_id": "your-domain-id", "name": "Canopy fuel grid", "description": "", "status": "completed", "progress": { "percent": 100, "message": "Complete" }, "created_on": "2026-07-09T16:03:59.102451Z", "modified_on": "2026-07-09T16:04:10.600826Z", "source": { "operation": "voxelize", "input": "inventory", "entity": "tree", "source_inventory_id": "your-inventory-id", "resolution": { "horizontal": 2.0, "vertical": 1.0 }, "bands": ["bulk_density.foliage.live"], "crown_profile_model": "purves", "biomass_source": { "type": "allometry", "equations": "nsvb", "components": ["foliage"], "component_states": { "foliage": { "live": 1.0, "dead": 0.0 } } }, "max_crown_radius_source": { "type": "allometry" }, "seed": 42 }, "modifications": [], "bands": [ { "key": "bulk_density.foliage.live", "type": "continuous", "unit": "kg/m**3", "index": 0, "nodata": null } ], "georeference": { "crs": "EPSG:32611", "transform": [2.0, 0.0, 720226.0, 0.0, -2.0, 5190646.0], "shape": [37, 442, 654], "z_resolution": 1.0, "z_origin": 0.0 }, "error": null, "chunks": { "shape": [37, 442, 442], "count": 2, "count_by_axis": { "z": 1, "y": 1, "x": 2 } }, "tags": []}The completed grid’s georeference.shape is [37, 442, 654] — 37 vertical
layers of 1 m stacked over the 442 × 654 horizontal lattice (rows × columns).
The vertical extent is the tallest crown; the horizontal cell size and origin
come from the resolution and the domain. That’s the 3D canopy the export
writes as treesrhof.dat.
Next steps
Section titled “Next steps”- Export to QUIC-Fire — stack this canopy grid with surface fuel, terrain,
and moisture into the
.datbundle. The QUIC-Fire tutorial walks the whole pipeline. - Derive a canopy-conditioned surface pattern — build a DUET-compatible tree grid with the three-band recipe above, then run DUET and optionally calibrate its output.
- Inspect it — fetch and stream the grid data to read bulk density voxel by voxel.
- Align it — if your other grids sit on a different lattice, resample or reproject before a combined export.
Common pitfalls
Section titled “Common pitfalls”-
Inventory missing morphology columns. Voxelizing an inventory without
dbh,crown_ratio, andfia_species_codereturns a422naming the missing columns and pointing you at the allometry endpoint:{"detail": "Inventory 'your-inventory-id' is missing column(s) ['crown_ratio', 'dbh', 'fia_species_code'] required for voxelization. Impute ['crown_ratio', 'dbh', 'fia_species_code'] with the allometry endpoint (POST /domains/your-domain-id/inventories/tree/allometry/gdam with source_tree_inventory_id='your-inventory-id'), then voxelize the resulting inventory."}Complete the inventory with GDAM allometry, then voxelize the resulting inventory.
-
No tree status. Voxelization treats a tree with no
fia_status_codeas live — so a GDAM-completed inventory (species imputed, status absent) voxelizes correctly. To represent dead stems explicitly, the status must come from the inventory’s source — a TreeMap inventory or an upload that carriesfia_status_code— since allometry doesn’t impute it. -
Source inventory not
completed. Voxelization reads the inventory’s data — poll it tocompletedfirst. -
Mismatched lattice at export. Every grid in a combined export must share one lattice. Pick a voxel
resolutionhere that matches your surface and terrain grids (2 m horizontal is the QUIC-Fire default), or resample them to agree. -
Expecting identical voxels without a seed. The step that samples which sub-voxels a crown fills is stochastic; omit
seedand each run draws a fresh realization. Set it for reproducibility.