Derive canopy fuel from a tree inventory
You are viewing in-progress documentation for v2 (Beta). Switch to the stable version for the current production release.
This guide starts with a completed tree inventory and ends with the four 2D
canopy fuel bands a landscape file needs, cbd, cbh, chm, and cc. The
endpoint estimates each tree’s available canopy fuel, distributes it through
the crown, attributes it to output cells, and reduces each cell’s vertical
profile to those four bands. It does this directly from the inventory, without
first building a 3D voxel grid.
Prerequisites
Section titled “Prerequisites”-
An API key. my-api-key.
-
A domain. your-domain-id.
-
A completed tree inventory. your-inventory-id. The inventory may come from any supported inventory workflow, but it must carry
x,y,height,dbh,crown_ratio, andfia_species_code.
The live responses and figures on this page use the completed inventory from From lidar to a tree inventory. That point-cloud lineage makes the fixture reproducible. It is not a requirement of the canopy endpoint.
The complete minimal flow of create, poll, export, and download is available as one script.
import fastfuels_sdk.v2 as ff
ff.set_api_key("my-api-key")
# Any completed tree inventory with x, y, height, dbh, crown_ratio, fia_species_code.inventory = ff.get_inventory("your-domain-id", "your-inventory-id")
grid = ff.grids.create_canopy_fuel_grid_from_inventory( inventory, name="Canopy fuel from inventory",)grid.wait()
# Export every band to one GeoTIFF, then download it.export = grid.export(format="geotiff")export.wait()export.to_file("canopy_fuel.tif")Create the four canopy bands
Section titled “Create the four canopy bands”The only required request field is the completed inventory id. The endpoint defaults to four bands on a 30 m, domain-anchored lattice.
curl -X 'POST' \ 'https://api-v2-prod-782971006568.us-west1.run.app/domains/your-domain-id/grids/canopy/inventory' \ -H 'accept: application/json' \ -H 'api-key: my-api-key' \ -H 'Content-Type: application/json' \ -d '{ "source_inventory_id": "your-inventory-id", "name": "Canopy fuel from inventory"}'{ "id": "your-canopy-grid-id", "domain_id": "your-domain-id", "name": "Canopy fuel from inventory", "description": "", "status": "pending", "progress": null, "created_on": "{{CREATED_ON}}", "modified_on": "{{MODIFIED_ON}}", "checksum": "bc89105a3d364a5484174f6b23706d1f", "source": { "biomass_source": { "type": "allometry", "equations": "nsvb" }, "available_fuel": { "foliage_fraction": 1.0, "branchwood": { "size_partition": "none", "fraction": 0.075 } }, "species_inclusion": "all_species", "crown_class_adjustment": { "method": "none" }, "min_tree_height": 0.0, "vertical_distribution": "reinhardt_2006", "layer_depth": 0.3048, "horizontal_distribution": "crown_projected", "max_crown_radius_source": { "type": "allometry", "equations": "purves" }, "cbd": { "method": "maximum_running_mean", "window": 3.0, "edge": "ground_clamped" }, "cbh": { "method": "bulk_density_threshold", "threshold": 0.012, "relative_threshold_fraction": 0.1, "smoothing_window": null, "smoothing_edge": "ground_clamped" }, "chm": { "method": "bulk_density_threshold", "threshold": 0.012, "relative_threshold_fraction": 0.1, "smoothing_window": null, "smoothing_edge": "ground_clamped" }, "cc": { "method": "crown_union" }, "name": "canopy", "product": "inventory", "description": "Canopy fuel metrics computed from a tree inventory", "extent_buffer_cells": 0, "alignment": { "target": "domain", "resolution": 30.0, "method": null }, "source_inventory_id": "your-inventory-id", "source_inventory_checksum": null, "bands": ["cbd", "cbh", "chm", "cc"] }, "modifications": [], "bands": [ { "key": "cbd", "name": "Canopy Bulk Density", "description": "Mass of available canopy fuel per unit canopy volume.", "type": "continuous", "unit": "kg/m**3", "index": 0, "nodata": null, "summary": null }, { "key": "cbh", "name": "Canopy Base Height", "description": "Height above ground of the base of the canopy fuel layer.", "type": "continuous", "unit": "m", "index": 1, "nodata": null, "summary": null }, { "key": "chm", "name": "Canopy Height", "description": "Height above ground of the canopy top.", "type": "continuous", "unit": "m", "index": 2, "nodata": null, "summary": null }, { "key": "cc", "name": "Canopy Cover", "description": "Fraction of ground covered by tree canopy (%).", "type": "continuous", "unit": "%", "index": 3, "nodata": null, "summary": null } ], "georeference": null, "error": null, "chunks": { "shape": [512, 512], "count": null, "count_by_axis": null }, "tags": []}Record the id as your-canopy-grid-id. The response’s source records
every omitted choice. The defaults are NSVB allometry, all foliage plus 7.5
percent of total branchwood, all species, Reinhardt vertical profiles,
crown-projected cell attribution, a 3 m CBD running mean, threshold-derived CBH
and CHM, and geometric crown-union cover. The source inventory id, and its
checksum when the inventory supplies one, preserve the input lineage. The
inputs that build the profile
explanation covers what each default means.
Poll until the job completes.
curl -X 'GET' \ 'https://api-v2-prod-782971006568.us-west1.run.app/domains/your-domain-id/grids/your-canopy-grid-id' \ -H 'accept: application/json' \ -H 'api-key: my-api-key'{ "id": "your-canopy-grid-id", "domain_id": "your-domain-id", "name": "Canopy fuel from inventory", "description": "", "status": "completed", "progress": { "percent": 100, "message": "Complete" }, "created_on": "{{CREATED_ON}}", "modified_on": "{{MODIFIED_ON}}", "checksum": "bc89105a3d364a5484174f6b23706d1f", "source": { "chm": { "smoothing_edge": "ground_clamped", "threshold": 0.012, "smoothing_window": null, "relative_threshold_fraction": 0.1, "method": "bulk_density_threshold" }, "cc": { "method": "crown_union" }, "cbh": { "smoothing_edge": "ground_clamped", "threshold": 0.012, "smoothing_window": null, "relative_threshold_fraction": 0.1, "method": "bulk_density_threshold" }, "bands": ["cbd", "cbh", "chm", "cc"], "min_tree_height": 0.0, "source_inventory_id": "your-inventory-id", "max_crown_radius_source": { "type": "allometry", "equations": "purves" }, "crown_class_adjustment": { "method": "none" }, "source_inventory_checksum": null, "layer_depth": 0.3048, "product": "inventory", "species_inclusion": "all_species", "name": "canopy", "biomass_source": { "type": "allometry", "equations": "nsvb" }, "cbd": { "edge": "ground_clamped", "window": 3.0, "method": "maximum_running_mean" }, "extent_buffer_cells": 0, "available_fuel": { "foliage_fraction": 1.0, "branchwood": { "fraction": 0.075, "size_partition": "none" } }, "vertical_distribution": "reinhardt_2006", "horizontal_distribution": "crown_projected", "alignment": { "resolution": 30.0, "target": "domain", "method": null }, "description": "Canopy fuel metrics computed from a tree inventory" }, "modifications": [], "bands": [ { "key": "cbd", "name": "Canopy Bulk Density", "description": "Mass of available canopy fuel per unit canopy volume.", "type": "continuous", "unit": "kg/m**3", "index": 0, "nodata": null, "summary": { "type": "continuous", "count": 598, "nodata_count": 0, "min": 0.0, "max": 0.16052499413490295, "mean": 0.023289420163010895, "std": 0.016715286996701526 } }, { "key": "cbh", "name": "Canopy Base Height", "description": "Height above ground of the base of the canopy fuel layer.", "type": "continuous", "unit": "m", "index": 1, "nodata": null, "summary": { "type": "continuous", "count": 598, "nodata_count": 0, "min": 0.0, "max": 17.983200073242188, "mean": 2.4547103786946938, "std": 2.3756085678678645 } }, { "key": "chm", "name": "Canopy Height", "description": "Height above ground of the canopy top.", "type": "continuous", "unit": "m", "index": 2, "nodata": null, "summary": { "type": "continuous", "count": 598, "nodata_count": 0, "min": 0.0, "max": 37.49039840698242, "mean": 18.777820848302298, "std": 8.026292311913336 } }, { "key": "cc", "name": "Canopy Cover", "description": "Fraction of ground covered by tree canopy (%).", "type": "continuous", "unit": "%", "index": 3, "nodata": null, "summary": { "type": "continuous", "count": 598, "nodata_count": 0, "min": 0.0, "max": 45.69444274902344, "mean": 17.579756612412904, "std": 10.796532476029185 } } ], "georeference": { "crs": "EPSG:32612", "transform": [30.0, 0.0, 294095.0, 0.0, -30.0, 5199762.0], "shape": [26, 23] }, "error": null, "chunks": { "shape": [512, 512], "count": 1, "count_by_axis": { "x": 1, "y": 1 } }, "tags": []}The completed grid contains these bands.
| Band | Unit | Role |
|---|---|---|
cbd | kg/m**3 | canopy bulk density |
cbh | m | base of the canopy fuel layer |
chm | m | canopy-top height |
cc | % | canopy cover |

The four default bands from the live fixture inventory, calculated on the same 30 m lattice. Non-forest cells are zero. Each band has its own physical unit and color scale. Matching spatial outlines do not make their values interchangeable.
To also retain total available canopy fuel per ground area, add cfl to
bands. It is reported in kg/m**2, but it is not a landscape-file role.
Download the grid
Section titled “Download the grid”Create a GeoTIFF export after the grid reaches completed, poll the returned
export id, then download its signed_url.
curl -X 'POST' \ 'https://api-v2-prod-782971006568.us-west1.run.app/domains/your-domain-id/grids/your-canopy-grid-id/exports/geotiff' \ -H 'accept: application/json' \ -H 'api-key: my-api-key' \ -H 'Content-Type: application/json' \ -d '{}'
# Record the response id as your-export-id, then poll it:curl \ 'https://api-v2-prod-782971006568.us-west1.run.app/exports/your-export-id' \ -H 'accept: application/json' \ -H 'api-key: my-api-key'
# When status is completed, download from the response's signed_url:curl 'paste-signed-download-url' --output canopy_fuel.tifReproduce a named convention
Section titled “Reproduce a named convention”Use a preset when your result must be comparable to an existing tool. Each curl
body below is complete and copyable; the Python SDK blocks continue from the
one-script flow above, reusing the imported ff module and the inventory
object, and show only the parameters and typed method objects that change. Keep
the inventory and cell lattice fixed when comparing presets so the method is the
only changing input. For why matching a convention keeps a downstream model
valid, see
reproducing a convention or deviating from it.
FuelCalc 1.7
Section titled “FuelCalc 1.7”This is the endpoint’s fuelcalc_comparison OpenAPI example. It switches from
the FastFuels national defaults to Brown (1978) biomass, FuelCalc species and
crown-class handling, stem-cell attribution, 5 ft running means,
Crookston-Stage crown widths, and random-overlap cover.
curl -X 'POST' \ 'https://api-v2-prod-782971006568.us-west1.run.app/domains/your-domain-id/grids/canopy/inventory' \ -H 'api-key: my-api-key' \ -H 'Content-Type: application/json' \ -d '{ "source_inventory_id": "your-inventory-id", "name": "FuelCalc 1.7-comparable canopy fuel", "biomass_source": {"type": "allometry", "equations": "brown_1978"}, "species_inclusion": "fuelcalc_default", "crown_class_adjustment": { "method": "fuelcalc_table", "missing_crown_class": "other_none" }, "horizontal_distribution": "stem", "max_crown_radius_source": { "type": "allometry", "equations": "crookston_stage" }, "cbd": { "method": "maximum_running_mean", "window": 1.524, "edge": "ground_clamped" }, "cbh": { "method": "bulk_density_threshold", "smoothing_window": 1.524, "smoothing_edge": "ground_clamped" }, "chm": { "method": "bulk_density_threshold", "smoothing_window": 1.524, "smoothing_edge": "ground_clamped" }, "cc": {"method": "crown_overlap"}}'from fastfuels_sdk.v2.client_library.models import ( CanopyCbdRunningMean, CanopyProfileThreshold, CanopyRunningMeanEdge,)
grid = ff.grids.create_canopy_fuel_grid_from_inventory( inventory, name="FuelCalc 1.7-comparable canopy fuel", biomass_equations="brown_1978", species_inclusion="fuelcalc_default", crown_class_adjustment="fuelcalc_table", horizontal_distribution="stem", max_crown_radius_equations="crookston_stage", cbd=CanopyCbdRunningMean(window=1.524, edge=CanopyRunningMeanEdge.GROUND_CLAMPED), cbh=CanopyProfileThreshold(smoothing_window=1.524, smoothing_edge=CanopyRunningMeanEdge.GROUND_CLAMPED), chm=CanopyProfileThreshold(smoothing_window=1.524, smoothing_edge=CanopyRunningMeanEdge.GROUND_CLAMPED), cc="crown_overlap",)If the inventory does not carry fia_crown_class_code,
missing_crown_class: "other_none" applies FuelCalc’s documented fallback to
those trees.
Original FuelCalc method (RMRS-P-41)
Section titled “Original FuelCalc method (RMRS-P-41)”Starting from the FuelCalc comparison body, use a 15 ft (4.572 m) window for
CBD and threshold crossings and exclude trees below 6 ft (1.83 m).
curl -X 'POST' \ 'https://api-v2-prod-782971006568.us-west1.run.app/domains/your-domain-id/grids/canopy/inventory' \ -H 'api-key: my-api-key' \ -H 'Content-Type: application/json' \ -d '{ "source_inventory_id": "your-inventory-id", "name": "Original FuelCalc method (RMRS-P-41)", "biomass_source": {"type": "allometry", "equations": "brown_1978"}, "species_inclusion": "fuelcalc_default", "crown_class_adjustment": { "method": "fuelcalc_table", "missing_crown_class": "other_none" }, "min_tree_height": 1.83, "horizontal_distribution": "stem", "max_crown_radius_source": { "type": "allometry", "equations": "crookston_stage" }, "cbd": { "method": "maximum_running_mean", "window": 4.572, "edge": "ground_clamped" }, "cbh": { "method": "bulk_density_threshold", "smoothing_window": 4.572, "smoothing_edge": "ground_clamped" }, "chm": { "method": "bulk_density_threshold", "smoothing_window": 4.572, "smoothing_edge": "ground_clamped" }, "cc": {"method": "crown_overlap"}}'from fastfuels_sdk.v2.client_library.models import ( CanopyCbdRunningMean, CanopyProfileThreshold, CanopyRunningMeanEdge,)
grid = ff.grids.create_canopy_fuel_grid_from_inventory( inventory, name="Original FuelCalc method (RMRS-P-41)", biomass_equations="brown_1978", species_inclusion="fuelcalc_default", crown_class_adjustment="fuelcalc_table", min_tree_height=1.83, horizontal_distribution="stem", max_crown_radius_equations="crookston_stage", cbd=CanopyCbdRunningMean(window=4.572, edge=CanopyRunningMeanEdge.GROUND_CLAMPED), cbh=CanopyProfileThreshold(smoothing_window=4.572, smoothing_edge=CanopyRunningMeanEdge.GROUND_CLAMPED), chm=CanopyProfileThreshold(smoothing_window=4.572, smoothing_edge=CanopyRunningMeanEdge.GROUND_CLAMPED), cc="crown_overlap",)FFE-FVS
Section titled “FFE-FVS”Use uniform fuel through each crown, ignore trees below 6 ft, reduce CBD over a
13 ft window, and find CBH/CHM by crossing a 3 ft smoothed profile at a flat
0.011 kg/m**3. truncated reproduces FFE-FVS’s treatment of running means at
both profile ends.
curl -X 'POST' \ 'https://api-v2-prod-782971006568.us-west1.run.app/domains/your-domain-id/grids/canopy/inventory' \ -H 'api-key: my-api-key' \ -H 'Content-Type: application/json' \ -d '{ "source_inventory_id": "your-inventory-id", "name": "FFE-FVS-comparable canopy fuel", "biomass_source": {"type": "allometry", "equations": "brown_1978"}, "vertical_distribution": "uniform", "min_tree_height": 1.83, "cbd": { "method": "maximum_running_mean", "window": 3.9624, "edge": "truncated" }, "cbh": { "method": "bulk_density_threshold", "threshold": 0.011, "relative_threshold_fraction": null, "smoothing_window": 0.9144, "smoothing_edge": "truncated" }, "chm": { "method": "bulk_density_threshold", "threshold": 0.011, "relative_threshold_fraction": null, "smoothing_window": 0.9144, "smoothing_edge": "truncated" }}'from fastfuels_sdk.v2.client_library.models import ( CanopyCbdRunningMean, CanopyProfileThreshold, CanopyRunningMeanEdge,)
grid = ff.grids.create_canopy_fuel_grid_from_inventory( inventory, name="FFE-FVS-comparable canopy fuel", biomass_equations="brown_1978", vertical_distribution="uniform", min_tree_height=1.83, cbd=CanopyCbdRunningMean(window=3.9624, edge=CanopyRunningMeanEdge.TRUNCATED), cbh=CanopyProfileThreshold( threshold=0.011, relative_threshold_fraction=None, smoothing_window=0.9144, smoothing_edge=CanopyRunningMeanEdge.TRUNCATED, ), chm=CanopyProfileThreshold( threshold=0.011, relative_threshold_fraction=None, smoothing_window=0.9144, smoothing_edge=CanopyRunningMeanEdge.TRUNCATED, ),)Brown & Johnston (1976), the FFE-FVS biomass family, is not available. Brown (1978) is the closest offered family, so expect comparable rather than identical values.
LANDFIRE comparison basis
Section titled “LANDFIRE comparison basis”For an inventory-derived counterpart built on the canopy allometries behind the FuelCalc/LANDFIRE method, select Brown (1978) biomass and Crookston-Stage crown widths while leaving the FastFuels spatial defaults in place.
curl -X 'POST' \ 'https://api-v2-prod-782971006568.us-west1.run.app/domains/your-domain-id/grids/canopy/inventory' \ -H 'api-key: my-api-key' \ -H 'Content-Type: application/json' \ -d '{ "source_inventory_id": "your-inventory-id", "name": "LANDFIRE-comparison canopy fuel", "biomass_source": {"type": "allometry", "equations": "brown_1978"}, "max_crown_radius_source": { "type": "allometry", "equations": "crookston_stage" }}'grid = ff.grids.create_canopy_fuel_grid_from_inventory( inventory, name="LANDFIRE-comparison canopy fuel", biomass_equations="brown_1978", max_crown_radius_equations="crookston_stage",)This does not recreate a published LANDFIRE raster. LANDFIRE also includes mapping, imputation, and product-level adjustments that are outside this per-cell inventory calculation.
Deviate on purpose
Section titled “Deviate on purpose”
Method choice changes the reported value, even though the inventory and lattice stay fixed. Each row shares one color scale. The top row is default threshold CBH versus 20th-percentile CBH, and the bottom row is default effective CBD versus Cruz-style load-over-depth CBD. Zero-valued non-forest cells are shown in light grey.
Lower-tail CBH for conservative screening
Section titled “Lower-tail CBH for conservative screening”Use the 20th percentile of per-tree crown bases when low ladder fuel should control the result rather than the average tree. It can land higher or lower than a threshold-crossing CBH, so compare the two on the same inventory before choosing. The canopy base height explanation covers when the lower tail is the right choice.
curl -X 'POST' \ 'https://api-v2-prod-782971006568.us-west1.run.app/domains/your-domain-id/grids/canopy/inventory' \ -H 'api-key: my-api-key' \ -H 'Content-Type: application/json' \ -d '{ "source_inventory_id": "your-inventory-id", "name": "Conservative CBH screening", "cbh": {"method": "percentile", "percentile": 20}}'from fastfuels_sdk.v2.client_library.models import CanopyCbhPercentile
grid = ff.grids.create_canopy_fuel_grid_from_inventory( inventory, name="Conservative CBH screening", cbh=CanopyCbhPercentile(percentile=20),)For the most risk-averse screen, replace the cbh object with
{"method": "minimum"} so any low crown base can set the cell’s CBH.
Cruz-style load-over-depth CBD
Section titled “Cruz-style load-over-depth CBD”Use foliage only and divide canopy fuel load by mean crown length. This reports an average density over the depth, which reads lower than the default effective CBD. The canopy bulk density explanation covers why.
curl -X 'POST' \ 'https://api-v2-prod-782971006568.us-west1.run.app/domains/your-domain-id/grids/canopy/inventory' \ -H 'api-key: my-api-key' \ -H 'Content-Type: application/json' \ -d '{ "source_inventory_id": "your-inventory-id", "name": "Cruz-style load-over-depth CBD", "available_fuel": { "foliage_fraction": 1.0, "branchwood": {"size_partition": "none", "fraction": 0.0} }, "cbd": {"method": "load_over_depth", "depth": "mean_crown_length"}}'from fastfuels_sdk.v2.client_library.models import CanopyCbdLoadOverDepth, CanopyCbdDepth
grid = ff.grids.create_canopy_fuel_grid_from_inventory( inventory, name="Cruz-style load-over-depth CBD", foliage_fraction=1.0, branchwood_fraction=0.0, branchwood_size_partition="none", cbd=CanopyCbdLoadOverDepth(depth=CanopyCbdDepth.MEAN_CROWN_LENGTH),)Feed the canopy grid into a landscape export
Section titled “Feed the canopy grid into a landscape export”Point all four canopy_* roles at the completed inventory canopy grid. The
terrain and FBFM grids may be different resources, but every role must already
share one CRS, origin, cell size, and coverage.
curl -X 'POST' \ 'https://api-v2-prod-782971006568.us-west1.run.app/domains/your-domain-id/grids/exports/landscape' \ -H 'api-key: my-api-key' \ -H 'Content-Type: application/json' \ -d '{ "name": "Landscape with inventory-derived canopy fuel", "alignment": {"target": "grid", "grid_id": "your-canopy-grid-id"}, "fire_behavior_fuel_model": "fbfm40", "elevation": {"grid_id": "your-topography-grid-id", "band": "elevation"}, "slope": {"grid_id": "your-topography-grid-id", "band": "slope"}, "aspect": {"grid_id": "your-topography-grid-id", "band": "aspect"}, "fuel_model": {"grid_id": "your-fbfm40-grid-id", "band": "fbfm"}, "canopy_cover": {"grid_id": "your-canopy-grid-id", "band": "cc"}, "canopy_height": {"grid_id": "your-canopy-grid-id", "band": "chm"}, "canopy_base_height": {"grid_id": "your-canopy-grid-id", "band": "cbh"}, "canopy_bulk_density": {"grid_id": "your-canopy-grid-id", "band": "cbd"}}'# Align the export lattice to the canopy grid; pull each band from its grid.export = ff.exports.create_landscape_export( "your-domain-id", fire_behavior_fuel_model="fbfm40", align_to="your-canopy-grid-id", elevation=("your-topography-grid-id", "elevation"), slope=("your-topography-grid-id", "slope"), aspect=("your-topography-grid-id", "aspect"), fuel_model=("your-fbfm40-grid-id", "fbfm"), canopy_cover=("your-canopy-grid-id", "cc"), canopy_height=("your-canopy-grid-id", "chm"), canopy_base_height=("your-canopy-grid-id", "cbh"), canopy_bulk_density=("your-canopy-grid-id", "cbd"),)The example anchors the export to your-canopy-grid-id. Build or
resample the topography and
FBFM grids onto that exact lattice first. Alternatively, choose an existing
export grid up front and create the canopy grid with
{"alignment": {"target": "grid", "grid_id": "..."}}. The landscape
export crops aligned inputs but never resamples or reprojects them.
Common pitfalls
Section titled “Common pitfalls”- Passing a thin inventory. Complete
dbh,crown_ratio, andfia_species_codewith GDAM before deriving canopy fuel. - Using a pending inventory. Poll the GDAM inventory to
completedbefore this request. - Changing resolution during a comparison. A smaller cell changes which crowns contribute and what scale each value describes. Keep the lattice fixed when comparing methods.
- Calling a preset an exact reproduction. Match its method body and the input inventory, including crown class, before interpreting differences as implementation differences.
- Building export roles at native resolution. Native source cells often do not match a domain-anchored 30 m lattice. Use an explicit resolution or align every role to one existing grid.