Skip to content

sylva.als.metrics

The same functions are available from sylva.als.

sylva.als.metrics

Area-based metrics of airborne lidar: grids, plots and single clouds.

The standard set follows the area-based metrics of Roussel et al. (2020): height statistics, quantiles, cumulative deciles and the normalised Shannon entropy of the heights, with intensity and return metrics, plus canopy cover and gap fraction from first returns. grid_metrics computes them on a raster over a whole catalogue, chunk by chunk, so that a cell on a tile edge has the value it would have in a single-tile run; plot_metrics computes them for circular or polygon plots, reading only the tiles each plot overlaps; cloud_metrics for one cloud in memory. A Python function of the points can replace the standard set, at the cost of speed.

These functions are also available from sylva.als.

Examples:

>>> from sylva import als
>>> cat = als.catalog("normalised/")
>>> m = als.grid_metrics(cat, 20.0, ["zmean", "zq95", "cover"], dtm=None)
>>> m["zq95"].to_geotiff("zq95.tif")
>>> plots = als.plot_metrics(cat, [(500, 200), (650, 340)], radius=11.28,
...                          dtm=None)

PlotMetrics dataclass

Metrics of plots, one row per plot, from plot_metrics.

Parameters:

Name Type Description Default
columns dict

The table, a dict of equal-length arrays: plot (the plot's position in the input, from 0), id when ids were given, then one column per metric. A plot without returns has n 0 and NaN elsewhere.

required
names list

The metric columns, in order.

list()

row

row(i: int) -> dict

The metrics of plot i as a dict.

Parameters:

Name Type Description Default
i int

Plot index.

required

as_dict

as_dict() -> dict

The table as a dict of arrays (a copy).

to_pandas

to_pandas()

The table as a pandas.DataFrame (needs pandas).

to_csv

to_csv(path: str | Path) -> None

Write the table as CSV (empty cells for NaN).

Parameters:

Name Type Description Default
path str | Path

Output file.

required

metric_names

metric_names(threshold: float = 2.0) -> list[str]

Names of the standard metrics, in the order they are computed.

Parameters:

Name Type Description Default
threshold float

The height of pzabove<threshold> (its name depends on it).

2.0

Returns:

Type Description
list of str

n, the height metrics zmax, zmean, zsd, zskew, zkurt, zentropy, pzabovezmean, pzabove2, zq5 .. zq95 and zpcum1 .. zpcum9; cover and gap_fraction; the intensity metrics itot, imax, imean, isd, iskew, ikurt, ipground and ipcumzq10 .. ipcumzq90; the return metrics p1th .. p5th and pground. See the guide for their definitions.

cloud_metrics

cloud_metrics(cloud: PointCloud, height=None, threshold: float = 2.0, entropy_bin: float = 1.0, cover_break: float = 2.0, min_height: float | None = None, drop_noise: bool = True, clamp_negative: bool = False) -> dict[str, float]

The standard metrics of one cloud.

Parameters:

Name Type Description Default
cloud PointCloud

Returns, normalised (z is height above ground) unless height says otherwise.

required
height

None to take z as the height, the name of an attribute holding it ("height", as sylva.als.normalize and sylva.ground.normalize_height write), or an array of one height per point.

None
threshold float

Height (m) of pzabove<threshold>.

2.0
entropy_bin float

Bin width (m) of zentropy.

1.0
cover_break float

Height (m) above which a first return counts as canopy for cover and gap_fraction.

2.0
min_height float | None

Returns lower than this are left out of every metric (heights below 0 are often dropped first, since zentropy is NaN with a negative height).

None
drop_noise bool

Leave out returns classified as noise (7 or 18).

True
clamp_negative bool

Set heights below 0 to 0 before anything else (the other usual habit). Ground returns a few centimetres below the DTM are in nearly every cell of a survey, and make zentropy NaN there; clamping keeps them, as ground, where min_height=0 would leave out the ones below it.

False

Returns:

Type Description
dict

Metric name to value. The intensity and return metrics are present when the cloud has intensity, return_number and classification. With no returns left, n is 0 and every other metric NaN.

Raises:

Type Description
ValueError

For an unknown attribute, heights of the wrong length or bad settings.

grid_metrics

grid_metrics(catalog, resolution: float = 20.0, metrics=None, func: Callable | None = None, dtm='auto', dtm_resolution: float = 1.0, threshold: float = 2.0, entropy_bin: float = 1.0, cover_break: float = 2.0, min_height: float | None = None, drop_noise: bool = True, clamp_negative: bool = False, chunk_size: float | None = None, buffer: float = 20.0, workers: int | None = None)

Rasters of area-based metrics over a whole catalogue.

Every return is assigned to the cell of the catalogue grid that holds it (cells are half-open, [x0, x0 + resolution)), and the metrics of each cell are computed from all of its returns, whichever tiles they come from. Each chunk computes the cells within about one cell of its core from its points and its buffer, so the result is the same, to the bit, for any chunk_size and any number of workers, and equals the metrics of the cells of all the tiles merged into one cloud.

Parameters:

Name Type Description Default
catalog

The tiles (a ~sylva.als.Catalog, or anything sylva.als.catalog accepts).

required
resolution float

Cell size (m); 10 to 30 m is usual for area-based models.

20.0
metrics

None for every standard metric (metric_names), a list of names, or one name. With func, the names of its results to keep.

None
func Callable | None

A function of one cell's returns, func(cloud), returning a dict of name to number (or one number, named "value"), in place of the standard metrics. cloud is a ~sylva.PointCloud whose z is the height above ground, with the tiles' attributes, in a fixed order (height, x, y, then attributes). It runs once per cell in Python, so it is far slower than the built-in set.

None
dtm ('auto', None, Raster or str)

Where heights come from, as for sylva.als.chm: "auto" (a DTM per chunk from its ground points at dtm_resolution), a DTM ~sylva.Raster, None (z is already the height, for tiles normalised with replace_z=True), or the name of an attribute holding the height ("height" for tiles written by sylva.als.normalize without replace_z).

'auto'
dtm_resolution float

Cell size (m) of the "auto" DTM.

1.0
threshold float

As for cloud_metrics.

2.0
entropy_bin float

As for cloud_metrics.

2.0
cover_break float

As for cloud_metrics.

2.0
min_height float

As for cloud_metrics.

2.0
drop_noise float

As for cloud_metrics.

2.0
clamp_negative float

As for cloud_metrics.

2.0
chunk_size float | None

As for sylva.als.apply. The buffer is raised to at least 1.5 cells; with dtm="auto" it should also exceed the widest gap in the ground.

None
buffer float | None

As for sylva.als.apply. The buffer is raised to at least 1.5 cells; with dtm="auto" it should also exceed the widest gap in the ground.

None
workers int | None

As for sylva.als.apply; not used with func, which runs one chunk at a time.

None

Returns:

Type Description
dict of str to Raster, or Raster

One ~sylva.Raster per metric on the catalogue grid (as sylva.als.chm returns), in the order asked, with the catalogue's CRS; a single Raster when metrics is one name. Cells without returns are NaN (n included).

Raises:

Type Description
ValueError

For an unknown metric, bad settings, a func result that is not a number or a dict of numbers, or tiles without ground for dtm="auto".

Examples:

>>> m = als.grid_metrics(cat, 20.0, ["zmean", "zq95", "cover"])
>>> top = als.grid_metrics(cat, 20.0, "zmax", dtm=None)
>>> def canopy(c):
...     z = c.z[c.z > 2]
...     return {"hmean": z.mean() if len(z) else np.nan, "npts": len(c)}
>>> mine = als.grid_metrics(cat, 20.0, func=canopy)

plot_metrics

plot_metrics(catalog, plots, radius=None, metrics=None, func: Callable | None = None, dtm='auto', dtm_resolution: float = 1.0, threshold: float = 2.0, entropy_bin: float = 1.0, cover_break: float = 2.0, min_height: float | None = None, drop_noise: bool = True, clamp_negative: bool = False, ids=None, buffer: float = 20.0, workers: int | None = None) -> PlotMetrics

Area-based metrics of field plots from a catalogue.

Plots are grouped by the tiles they overlap; each group reads only those tiles, and only the points in the group's box (grown by buffer for dtm="auto"). A plot on a tile edge gets its points from every tile it overlaps.

Parameters:

Name Type Description Default
catalog

The tiles.

required
plots

With radius: plot centres, an (N, 2) array of x, y (or one (x, y)). Without: polygons, in any form sylva.geo.masks accepts (a ~sylva.geo.masks.Polygons layer from sylva.geo.masks.read_polygons, a list of ~sylva.geo.masks.Polygon or (K, 2) vertex arrays), one plot per feature. Points on a plot's boundary are inside it.

required
radius

Radius (m) of circular plots, one for all or one per plot.

None
metrics

None for every standard metric, or a list of names.

None
func Callable | None

A function of one plot's returns, as for grid_metrics. A plot without returns is passed an empty cloud (with no attributes when it overlaps no tile).

None
dtm

Where heights come from, as for grid_metrics.

'auto'
dtm_resolution

Where heights come from, as for grid_metrics.

'auto'
threshold float

As for cloud_metrics.

2.0
entropy_bin float

As for cloud_metrics.

2.0
cover_break float

As for cloud_metrics.

2.0
min_height float

As for cloud_metrics.

2.0
drop_noise float

As for cloud_metrics.

2.0
clamp_negative float

As for cloud_metrics.

2.0
ids

Labels of the plots (one per plot), kept as an id column.

None
buffer float

With dtm="auto", the band (m) of points read around the plots to make their DTM.

20.0
workers int | None

Groups of plots read at once, as for sylva.als.apply.

None

Returns:

Type Description
PlotMetrics

One row per plot, in the order given. Plots outside every tile have n 0 and NaN elsewhere.

Raises:

Type Description
ValueError

For bad plots (a non-positive radius, a ring with fewer than three vertices), an unknown metric or bad settings.

Examples:

>>> t = als.plot_metrics(cat, [(520.0, 310.0), (580.5, 402.0)], radius=11.28)
>>> t["zq95"], t.to_pandas()
>>> layer = masks.read_polygons("plots.shp")
>>> names = [f.properties["name"] for f in layer]
>>> t = als.plot_metrics(cat, layer, ids=names)