Check whether a point cloud is fit to build on
You are viewing in-progress documentation for v2 (Beta). Switch to the stable version for the current production release.
A completed point cloud is not automatically a usable point cloud. Four fields
on GET /domains/{domain_id}/pointclouds/{point_cloud_id} decide whether it is
worth building on, and reading them takes one request.
Every downstream disappointment in this pipeline is diagnosable here — before you spend a grid job, a detection run, and an afternoon on it.
Prerequisites
Section titled “Prerequisites”-
An API key: my-api-key.
-
A completed point cloud: your-point-cloud-id in your-domain-id.
curl -X 'GET' \ 'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/pointclouds/your-point-cloud-id' \ -H 'accept: application/json' \ -H 'api-key: my-api-key'import fastfuels_sdk.v2 as ff
ff.set_api_key("my-api-key")
point_cloud = ff.get_point_cloud("your-domain-id", "your-point-cloud-id")
# The four fields that matter are attributes on the returned object:# point_cloud.status# point_cloud.summary.point_count / .density / .point_classes# point_cloud.georeference.bounds / .crs# point_cloud.error (when status is "failed"){ "id": "your-point-cloud-id", "domain_id": "your-domain-id", "type": "als", "status": "completed", "source": { "requested_datasets": ["MT_Statewide_P3_4_B21"], "coverage_fraction": 1.0, "datasets": ["MT_Statewide_P3_4_B21"], "catalog_fetched_on": "2026-08-27T12:40:06.141456+00:00", "name": "3dep" }, "name": "Blackfoot ALS (pinned)", "description": "Pinned to a single 3DEP acquisition so the fetch is reproducible.", "progress": { "message": "Complete", "percent": 100 }, "created_on": "2026-08-27T12:59:51.511829+00:00", "modified_on": "2026-08-27T13:00:00.865856+00:00", "checksum": "4d6f58ccd9e74f51965edd22ee044521", "georeference": { "crs": "EPSG:32612", "bounds": [ 294095.0, 5198982.0, 1026.3600000000001, 294785.0, 5199750.0, 1274.19 ] }, "summary": { "point_count": 10512255, "point_classes": [1, 2, 7, 9, 18, 20], "density": 19.837437726449274 }, "error": null, "tags": ["blackfoot", "3dep"]}All four checks in one script:
"""Decide whether a completed point cloud is worth building on."""
import fastfuels_sdk.v2 as ff
ff.set_api_key("my-api-key")
domain = ff.Domain.from_id("your-domain-id")point_cloud = ff.get_point_cloud("your-domain-id", "your-point-cloud-id")
if point_cloud.status != "completed": raise SystemExit(f"{point_cloud.status}: {point_cloud.error}")
summary = point_cloud.summary
# 1. Does the cloud actually cover the domain? coverage_fraction is computed# from catalog boundaries; these bounds come from the points themselves.min_x, min_y, _, max_x, max_y, _ = point_cloud.georeference.boundsdxmin, dymin, dxmax, dymax = domain.bboxcloud_area = (max_x - min_x) * (max_y - min_y)domain_area = (dxmax - dxmin) * (dymax - dymin)print(f"Cloud extent covers ~{cloud_area / domain_area:.0%} of the domain bbox")
# 2. Did it return roughly what the pre-flight estimated?print(f"Points: {summary.point_count:,}")
# 3. Is it dense enough for the cell size you intend to rasterize at?print(f"Density: {summary.density:.1f} pts/m2")
# 4. Will a derived CHM get measured ground, or inferred ground?has_ground = 2 in summary.point_classesprint(f"Classes: {summary.point_classes}")print(f"Ground class present: {has_ground} -> CHM ground will be " f"{'classification' if has_ground else 'derived'}")1. Does it cover the domain?
Section titled “1. Does it cover the domain?”Compare georeference.bounds — [min_x, min_y, min_z, max_x, max_y, max_z] in
the domain’s CRS — against the domain’s own bbox.
This is the check that coverage_fraction cannot do for you. Coverage is
computed from the catalog’s published boundary polygons; bounds comes from
the points themselves. A cloud that reports coverage_fraction: 1.0 and arrives
with a gap is a documented failure mode
(API #481).
2. Did you get roughly what was estimated?
Section titled “2. Did you get roughly what was estimated?”Compare summary.point_count against the estimated_point_count from the
coverage pre-flight:
{ "available": true, "coverage_fraction": 1.0, "datasets": [ { "name": "MT_Statewide_P3_4_B21", "url": "https://s3-us-west-2.amazonaws.com/usgs-lidar-public/MT_Statewide_P3_4_B21/ept.json", "contribution_fraction": 1.0, "estimated_density": 14.811981421134373, "estimated_points": 7847291 } ], "estimated_point_count": 7847291}
A third more than estimated, which is normal — the catalog averages density over an acquisition’s full published extent, including parts holding no points. Treat a large shortfall as the signal, not a modest surplus.
3. Is it dense enough for the cell size you want?
Section titled “3. Is it dense enough for the cell size you want?”summary.density is points per square metre. Multiply by your intended cell
area to get the returns per cell:
| Density | at 1 m cells | at 0.5 m cells |
|---|---|---|
| 20 pts/m² | ~20 per cell | ~5 per cell |
| 8 pts/m² | ~8 per cell | ~2 per cell |
| 2 pts/m² | ~2 per cell | ~0.5 per cell — mostly empty |
A cell receiving one or two returns is described by luck rather than by measurement. Note that a point-cloud CHM cannot go below 1 m anyway.
4. Will a derived CHM get measured or inferred ground?
Section titled “4. Will a derived CHM get measured or inferred ground?”summary.point_classes lists the ASPRS classes present. Look for 2 —
ground. Its presence decides whether a canopy height model built from this cloud
measures the ground beneath the canopy or infers it.
The cloud above reports [1, 2, 7, 9, 18, 20]. Two things to read from that:
- Class
2is present, so a derived CHM will reportground_source: "classification". - There are no vegetation classes (
3,4,5) at all. This is normal and not a problem: many acquisitions classify only ground and leave vegetation in class1. It does not mean the cloud has no vegetation.
Here is what that looks like once the CHM is built — the reported ground statistics beside the cells that actually received a canopy return:

A CHM from this cloud: 93.1% of cells carry a canopy return, and the pale band is the Blackfoot River — water absorbs the pulse, so it returns nothing. Nodata is not automatically a coverage gap; here it is the river doing what rivers do.
Reading a failed cloud
Section titled “Reading a failed cloud”When status is failed, error carries a code, a message, and usually a
suggestion:
{ "id": "your-point-cloud-id", "domain_id": "your-domain-id", "type": "als", "name": "Scan with no CRS", "description": "", "status": "failed", "progress": { "percent": 100, "message": "Failed" }, "created_on": "2026-08-03T19:51:16.253376Z", "modified_on": "2026-08-03T19:51:18.051700Z", "checksum": "2b0bea9318fc4a4dba360bbdf764c0d7", "source": { "name": "upload", "object_name": "pointclouds/your-point-cloud-id/upload" }, "georeference": null, "summary": null, "error": { "code": "MISSING_CRS", "message": "The point cloud has no coordinate reference system. Assign a CRS before uploading.", "suggestion": "Set the CRS in your processing software, e.g. `pdal translate in.laz out.laz --writers.las.a_srs=EPSG:<code>`." }, "tags": []}georeference and summary stay null. A failed cloud still occupies a slot
against your total quota until you delete it.
Next steps
Section titled “Next steps”- Build a CHM grid from it
— where
source.groundreports what check 4 predicts. - Manage your point clouds — delete the ones that failed these checks.
- About point clouds — why ground quality bounds canopy height, and what the classes mean.
Common pitfalls
Section titled “Common pitfalls”- Reading
summarybeforestatusiscompleted. It isnulluntil then, and so isgeoreference. - Trusting
densityas evidence of completeness. It is measured only where points exist. - Reading a missing class
3/4/5as missing vegetation. It usually means the vendor did not label it. - Skipping this page and discovering the problem after detection. A sparse or badly grounded cloud produces a plausible-looking CHM and an inventory full of trees that are not there.