Skip to content

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.

  1. An API key: my-api-key.

  2. A domain: your-domain-id.

  3. A completed inventory carrying dbh, crown_ratio, and fia_species_code — from TreeMap, a full upload, or GDAM allometry. Record its id: your-inventory-id.

The whole flow in one script:

Voxelize an inventory (create + polling)
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> completed
print(grid["georeference"]["shape"]) # -> [nz, ny, nx]

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.

POST grids/voxelize/inventory/tree
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
}'

Record the grid id: your-tree-grid-id. Poll until completed:

GET grid status
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'

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.

  • Export to QUIC-Fire — stack this canopy grid with surface fuel, terrain, and moisture into the .dat bundle. The QUIC-Fire tutorial walks the whole pipeline.
  • Inspect itfetch 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.
  • Inventory missing morphology columns. Voxelizing an inventory without dbh, crown_ratio, and fia_species_code returns a 422 naming 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_code as 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 carries fia_status_code — since allometry doesn’t impute it.

  • Source inventory not completed. Voxelization reads the inventory’s data — poll it to completed first.

  • Mismatched lattice at export. Every grid in a combined export must share one lattice. Pick a voxel resolution here 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 seed and each run draws a fresh realization. Set it for reproducibility.