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.
The whole flow in one script:
import time
import requests
API_KEY = "my-api-key"DOMAIN_ID = "your-domain-id"# A completed inventory that carries dbh, crown_ratio, fia_species_code, and# fia_status_code — from TreeMap, a full upload, or GDAM allometry.INVENTORY_ID = "your-inventory-id"BASE = "https://api-v2-prod-nyvjyh5ywa-uw.a.run.app"HEADERS = {"api-key": API_KEY}
def poll(grid_id: str) -> dict: while True: r = requests.get( f"{BASE}/domains/{DOMAIN_ID}/grids/{grid_id}", headers=HEADERS ).json() if r["status"] in ("completed", "failed"): return r time.sleep(5)
grid = requests.post( f"{BASE}/domains/{DOMAIN_ID}/grids/voxelize/inventory/tree", headers=HEADERS, json={ "name": "Canopy fuel grid", "source_inventory_id": 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, },).json()grid = poll(grid["id"])print(grid["id"], grid["status"]) # -> <grid id> completedprint(grid["georeference"]["shape"]) # -> [nz, ny, nx]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}'{ "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'{ "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. - 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.