Create a crown Feature from a CHM
You are viewing in-progress documentation for v2 (Beta). Switch to the stable version for the current production release.
This guide shows you how to outline each tree’s crown on a canopy height model
(CHM) and store the outlines as a crown Feature. A crown Feature holds one
polygon per tree, matched to the tree by tree_id. Its main use is to
voxelize an inventory with measured crown shapes.
For what measured crowns change in a voxel canopy, see
Where crown geometry comes from.
Prerequisites
Section titled “Prerequisites”-
An API key: my-api-key.
-
A domain: your-domain-id.
-
A completed CHM grid with a
chmband in meters, at 2 m resolution or finer: your-chm-grid-id. NAIP, Meta, and point-cloud CHMs all qualify. See Generate a tree inventory from a CHM. -
A completed tree inventory detected from that CHM grid with
POST /inventories/tree/chm: your-chm-inventory-id. See Step 2 of the CHM guide.
The responses and figures on this page come from a 400 m × 400 m domain in an
open conifer stand north of the Blackfoot River, Montana. The CHM is
rasterized from 3DEP lidar on the 0.6 m lattice of a NAIP CHM grid, and
2,253 trees were detected on it by vwf.
Create the crown Feature
Section titled “Create the crown Feature”POST to features/crown/chm with the inventory and the CHM grid. Each tree
seeds one crown in the CHM cell under its x, y, and the crown grows outward
until it reaches the crown’s edge. Crowns never overlap.
curl -X 'POST' \ 'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/features/crown/chm' \ -H 'accept: application/json' \ -H 'api-key: my-api-key' \ -H 'Content-Type: application/json' \ -d '{ "name": "Crowns from CHM", "source_inventory_id": "your-chm-inventory-id", "source_chm_grid_id": "your-chm-grid-id"}'import fastfuels_sdk.v2 as ff
ff.set_api_key("my-api-key")
domain = ff.Domain.from_id("your-domain-id")chm = ff.get_grid(domain, "your-chm-grid-id")trees = ff.get_inventory(domain, "your-chm-inventory-id")
# The inventory must be the one detected from this CHM grid.crowns = ff.features.create_crown_feature_from_chm( domain, trees, chm, name="Crowns from CHM"){ "id": "your-crown-feature-id", "domain_id": "your-domain-id", "type": "crown", "name": "Crowns from CHM", "description": "", "status": "pending", "progress": null, "created_on": "2026-10-13T14:41:24.717741", "modified_on": "2026-10-13T14:41:24.717741", "source": { "product": "chm", "source_inventory_id": "your-chm-inventory-id", "source_inventory_checksum": "c63e67a5b6e74b5d88be3b7d7b71825d", "source_chm_grid_id": "your-chm-grid-id", "source_chm_grid_checksum": "4f5d125e88154aa3b56973cb00b8a326", "crown_segmentation": { "method": "dalponte2016", "min_relative_height": 0.45, "min_relative_crown_height": 0.55, "max_crown_radius": 10.0, "min_height": 2.0, "max_height": 120.0 } }, "georeference": null, "error": null, "tags": []}Record the Feature id: your-crown-feature-id.
The defaults of crown_segmentation are in the response. To change how far a
crown can grow, set fields such as max_crown_radius (m) or min_height (m)
in crown_segmentation. The
API reference lists every
field.
Wait for it to complete
Section titled “Wait for it to complete”Poll until the status is completed:
curl -X 'GET' \ 'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/features/your-crown-feature-id' \ -H 'accept: application/json' \ -H 'api-key: my-api-key'crowns.wait(){ "id": "your-crown-feature-id", "domain_id": "your-domain-id", "type": "crown", "name": "Crowns from CHM", "description": "", "status": "completed", "progress": { "percent": 100, "message": "Complete" }, "created_on": "2026-10-13T14:41:24.717741Z", "modified_on": "2026-10-13T14:41:50.261173Z", "source": { "product": "chm", "source_chm_grid_checksum": "4f5d125e88154aa3b56973cb00b8a326", "crown_segmentation": { "method": "dalponte2016", "min_relative_crown_height": 0.55, "min_height": 2.0, "max_height": 120.0, "max_crown_radius": 10.0, "min_relative_height": 0.45 }, "source_inventory_checksum": "c63e67a5b6e74b5d88be3b7d7b71825d", "source_chm_grid_id": "your-chm-grid-id", "source_inventory_id": "your-chm-inventory-id" }, "georeference": { "crs": "EPSG:32612", "bounds": [294150.0, 5199350.0, 294550.0, 5199750.0] }, "error": null, "tags": []}Check the crowns
Section titled “Check the crowns”Each crown is a polygon traced along CHM cell edges. Look for one outline around each tree top, following the edge of the crown in the CHM.

Left: the lidar CHM with each crown outline (dark) and the tree top that seeded it (pink). Where two crowns touch, each CHM cell belongs to one of them. Right: the area of every crown in the Feature.
The map shows a 150 m square of the domain. Click a tree top for its height
and crown_radius.
Read the crowns
Section titled “Read the crowns”The crowns stream as GeoJSON in the domain’s CRS, in partitions. Each feature
has a tree_id property and a Polygon. In the SDK,
to_geodataframe() reads every partition.
# How many crowns, and in how many partitions.curl -X 'GET' \ 'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/features/your-crown-feature-id/data/metadata' \ -H 'accept: application/json' \ -H 'api-key: my-api-key'
# The crowns in partition 0, as GeoJSON in the domain CRS.curl -X 'GET' \ 'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/features/your-crown-feature-id/data/0' \ -H 'accept: application/geo+json' \ -H 'api-key: my-api-key'# One row per crown: tree_id and a Polygon in the domain CRS.gdf = crowns.to_geodataframe()gdf["area_m2"] = gdf.areaprint(len(gdf), gdf["area_m2"].median()){ "total_features": 2253, "partition_count": 3, "partitions": [ { "index": 0, "num_features": 1000 }, { "index": 1, "num_features": 1000 }, { "index": 2, "num_features": 253 } ]}{ "type": "FeatureCollection", "features": [ { "id": "0", "type": "Feature", "properties": { "tree_id": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [294157.79610194906, 5199750.000000001], [294157.79610194906, 5199748.800599701], [294157.1964017991, 5199748.800599701], [294157.1964017991, 5199747.601199402], [294158.39580209897, 5199747.601199402], [294158.39580209897, 5199747.001499251], [294160.19490254874, 5199747.001499251], [294160.19490254874, 5199747.601199402], [294160.79460269865, 5199747.601199402], [294160.79460269865, 5199748.200899552], [294161.3943028486, 5199748.200899552], [294161.3943028486, 5199747.601199402], [294161.9940029985, 5199747.601199402], [294161.9940029985, 5199746.401799101], [294163.1934032984, 5199746.401799101], [294163.1934032984, 5199747.001499251], [294163.7931034483, 5199747.001499251], [294163.7931034483, 5199747.601199402], [294165.59220389806, 5199747.601199402], [294165.59220389806, 5199748.200899552], [294164.99250374816, 5199748.200899552], [294164.99250374816, 5199749.400299851], [294165.59220389806, 5199749.400299851], [294165.59220389806, 5199750.000000001], [294157.79610194906, 5199750.000000001] ] ] } } ], "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::32612" } }}Here every tree got a crown: 2,253 crowns for 2,253 trees. A tree gets no crown
if it lies outside the CHM, sits on a no-data cell, or shares its CHM cell with
a taller tree. A tree below min_height gets a one-cell crown.
Next steps
Section titled “Next steps”- Voxelize with measured crowns. Complete the inventory with GDAM, then voxelize it with this Feature.
- Understand what changes. See Where crown geometry comes from.
- Only need a crown size per tree? Add a
crown_radiuscolumn at detection time withcrown_segmentation.
Common pitfalls
Section titled “Common pitfalls”- CHM coarser than 2 m. The CHM grid must be at 2 m resolution or finer, or
the request returns
422. A 30 m LANDFIRE canopy grid can’t outline crowns. - Grid or inventory not completed. Poll both to
completedfirst. - Deleting the CHM grid. Voxelizing with the Feature reads the crowns’ tops from its CHM grid, so keep the grid while you use the Feature.