Skip to content

sylva.als: trees

The tree functions are re-exported from sylva.als (als.find_trees, als.segment_trees and so on); they live in sylva.als.trees.

sylva.als.trees

Individual trees from airborne lidar: tree tops, crowns and labelled points.

The functions are re-exported from sylva.als and follow the algorithms collected by Roussel et al. (2020), whose implementations are reproduced where the papers leave a detail open:

  • locate_trees: tree tops by the local maximum filter (lmf; Popescu and Wynne 2004) on a canopy height model or on points, with a fixed window or one that grows with height.
  • segment_crowns: crowns on a CHM from tree tops, by marker-controlled watershed (Meyer and Beucher 1990) or by the region growing of Dalponte and Coomes (2016).
  • li2012: the point-based segmentation of Li et al. (2012).
  • segment_trees: a whole segmentation of one cloud, giving each point its tree and each tree its top, crown outline and area.
  • find_trees: the same over a catalogue of tiles, in buffered chunks, with labelled tiles written back.
References

Dalponte, M. and Coomes, D. A. (2016). Tree-centric mapping of forest carbon density from airborne laser scanning and hyperspectral data. Methods in Ecology and Evolution 7, 1236-1245.

Duckham, M., Kulik, L., Worboys, M. and Galton, A. (2008). Efficient generation of simple polygons for characterizing the shape of a set of points in the plane. Pattern Recognition 41(10), 3224-3236.

Li, W., Guo, Q., Jakubowski, M. K. and Kelly, M. (2012). A new method for segmenting individual trees from the lidar point cloud. Photogrammetric Engineering & Remote Sensing 78(1), 75-84.

Meyer, F. and Beucher, S. (1990). Morphological segmentation. Journal of Visual Communication and Image Representation 1(1), 21-46.

Popescu, S. C. and Wynne, R. H. (2004). Seeing the trees in the forest: using lidar and multispectral data fusion with local filtering and variable window size for estimating tree height. Photogrammetric Engineering & Remote Sensing 70(5), 589-604.

Roussel, J.-R. et al. (2020). Collected tree detection and segmentation algorithms for airborne laser scanning (ALS) data. Remote Sensing of Environment 251, 112061.

LinearWindow dataclass

A window size that grows linearly with height: clip(intercept + slope * h, min, max) metres for a site h m high.

Calling it on heights gives the sizes, so it can stand wherever a window function is taken; the Rust core evaluates it directly, which keeps catalogue runs exact.

Parameters:

Name Type Description Default
intercept float

Size (m) at height 0 and its increase per metre of height.

required
slope float

Size (m) at height 0 and its increase per metre of height.

required
min float

Bounds on the size (m); 0 < min <= max.

1.0
max float

Bounds on the size (m); 0 < min <= max.

1.0

Examples:

A window of x * 0.1 + 3 m, bounded:

>>> w = LinearWindow(3.0, 0.1, min=3.0, max=8.0)
>>> float(w(20.0))
5.0

TreeTops dataclass

Tree tops from locate_trees.

Parameters:

Name Type Description Default
x ndarray

Position of each top (m): a CHM cell centre or a point.

required
y ndarray

Position of each top (m): a CHM cell centre or a point.

required
height ndarray

The CHM value or the point's height (m).

required
index ndarray

Row-major cell index in the CHM, or the point's index in the cloud.

required

xyz property

xyz: ndarray

(k, 3) array of x, y, height.

Trees dataclass

Trees found by segment_trees or find_trees.

Parameters:

Name Type Description Default
id ndarray

1, 2, ... in order of the tops' x, then y.

required
x ndarray

The top: a CHM cell centre and its value, or the highest point of the tree and its height (m).

required
y ndarray

The top: a CHM cell centre and its value, or the highest point of the tree and its height (m).

required
height ndarray

The top: a CHM cell centre and its value, or the highest point of the tree and its height (m).

required
crown_area ndarray

Area (m²) of the crown outline; NaN without a crown.

required
n_points ndarray

Points labelled with the tree.

required
crowns list

Crown outline of each tree, (k, 2) counter-clockwise, from its points.

list()
tree_id ndarray | None

For segment_trees, the tree of each point of the cloud (0 for none).

None
catalog Catalog | None

For find_trees with out, the labelled tiles written.

None
at_edge int

Crowns that reached the inner edge of their chunk's buffer; more than 0 means the buffer may be too narrow for them to be complete.

0
unmatched_points int

Points written with id 0 because the tree their chunk gave them was found by no chunk as its own; 0 with an adequate buffer.

0
crs str | None

Coordinate system of the positions.

None

table

table() -> dict[str, np.ndarray]

The per-tree columns: id, x, y, height, crown_area and n_points.

to_csv

to_csv(path: str | Path) -> None

Write table as CSV, one row per tree.

Parameters:

Name Type Description Default
path str | Path

Output file.

required

to_geojson

to_geojson(path: str | Path, geometry: str = 'crowns') -> None

Write the trees as a GeoJSON FeatureCollection.

Parameters:

Name Type Description Default
path str | Path

Output file.

required
geometry ('crowns', 'tops')

Crown polygons (trees without a crown of 3 vertices are left out) or top points; the table columns are the properties.

"crowns"

locate_trees

locate_trees(source, window=5.0, hmin: float = 2.0, shape: str = 'circular', heights=None) -> TreeTops

Tree tops by the local maximum filter (Popescu and Wynne 2004).

A CHM cell (its centre, with the cell's value) or a point is a tree top when it is at least hmin high and nothing within its window is higher. The window is a disc of diameter ws (or a square of side ws) centred on the site, and ws may depend on the site's height. Of equal-height maxima within each other's windows only the first, in order of x then y, is kept.

Parameters:

Name Type Description Default
source Raster or PointCloud

A canopy height model (NaN cells take no part), or a cloud of heights above ground.

required
window (float, LinearWindow or callable)

Window size ws (m): a number; a LinearWindow; or a function taking an array of heights and returning the sizes, e.g. lambda h: 0.1 * h + 3 (evaluated at every site).

5.0
hmin float

Lowest tree top (m).

2.0
shape ('circular', 'square')

Window shape.

"circular"
heights

For a cloud: the height of each point, as an array or the name of an attribute; z if None (a normalised cloud).

None

Returns:

Type Description
TreeTops

In order of x, then y.

Raises:

Type Description
ValueError

For a non-positive or non-finite window size, an unknown shape, or heights that do not match the cloud.

segment_crowns

segment_crowns(chm: Raster, tops, method: str = 'dalponte2016', th_tree: float = 2.0, th_seed: float = 0.45, th_cr: float = 0.55, max_cr: float = 10.0) -> Raster

Crowns on a canopy height model, grown from tree tops.

"dalponte2016" is the region growing of Dalponte and Coomes (2016) (th_tree, th_seed, th_cr, max_cr): each crown adds, sweep after sweep, the 4-neighbours of its cells that are higher than th_tree, higher than th_seed times the top's CHM value and than th_cr times the crown's mean height, at most 5 % above the top, and fewer than max_cr cells from it in x and in y. "watershed" is a marker-controlled watershed (Meyer and Beucher 1990): the CHM is flooded from the tops, highest cells first, over 8-connected cells higher than th_tree, each cell taking the crown that reaches it first; tops on cells not higher than th_tree grow nothing.

Parameters:

Name Type Description Default
chm Raster

Canopy height model.

required
tops TreeTops or array(k, 3)

Tree tops (x, y, height), e.g. from locate_trees. A top seeds the cell it falls in; of several in one cell the highest is kept.

required
method ('dalponte2016', 'watershed')

How crowns grow.

"dalponte2016"
th_tree float

Cells not higher than this (m) are in no crown.

2.0
th_seed float

Growing thresholds of Dalponte and Coomes (0-1).

0.45
th_cr float

Growing thresholds of Dalponte and Coomes (0-1).

0.45
max_cr float

Largest crown extent, in cells from the top in x and in y ("dalponte2016").

10.0

Returns:

Type Description
Raster

The crown of each cell: k + 1 for tops[k], NaN for none.

Raises:

Type Description
ValueError

For an unknown method, thresholds outside 0-1, or a non-positive max_cr.

li2012

li2012(cloud: PointCloud, dt1: float = 1.5, dt2: float = 2.0, R: float = 2.0, Zu: float = 15.0, hmin: float = 2.0, speed_up: float = 10.0, heights=None) -> np.ndarray

Point-based tree segmentation of Li et al. (2012).

Points are taken from the highest down. The highest point left starts a tree (its set P, with an empty set N); every point left within speed_up of that top, from the highest down, joins P or N by its smallest horizontal distances d1 to P and d2 to N. A local maximum (highest within a disc of diameter R) joins N if d1 > dt or d2 < d1 < dt, and P otherwise, where dt is dt2 for points higher than Zu and dt1 below; any other point joins P if d1 <= d2. P is the tree; N stays for the next ones. It stops when the highest point left is lower than hmin. Equal heights are taken in order of x, then y.

Parameters:

Name Type Description Default
cloud PointCloud

Points; see heights.

required
dt1 float

Spacing thresholds (m) below and above Zu.

1.5
dt2 float

Spacing thresholds (m) below and above Zu.

1.5
R float

Diameter (m) of the local maximum window; 0 makes every point a local maximum.

2.0
Zu float

Height (m) above which dt2 applies.

15.0
hmin float

Lowest tree top (m).

2.0
speed_up float

Largest crown radius (m) considered; it only saves time when larger than any crown.

10.0
heights

Height of each point (array or attribute name); z if None.

None

Returns:

Type Description
ndarray

Tree of each point, 1, 2, ... in the order found (tallest first), 0 for none.

Raises:

Type Description
ValueError

For non-positive thresholds or a negative R.

crown_hull

crown_hull(xy, hull: str = 'convex', concavity: float = 2.0) -> np.ndarray

Outline of a crown's points seen from above.

Parameters:

Name Type Description Default
xy

(N, 2) or (N, 3) coordinates.

required
hull ('convex', 'concave')

The convex hull, or the characteristic shape of Duckham et al. (2008): from the Delaunay triangulation, outline edges longer than concavity are removed, longest first, while the outline stays a simple polygon.

"convex"
concavity float

Edge length (m) of the concave outline; smaller follows the points more closely.

2.0

Returns:

Type Description
ndarray

(k, 2) vertices, counter-clockwise, the first not repeated. Fewer than 3 distinct points (or collinear ones) give their convex hull.

segment_trees

segment_trees(cloud: PointCloud, method: str = 'dalponte2016', resolution: float = 0.5, window=5.0, hmin: float = 2.0, shape: str = 'circular', tops_from: str = 'chm', th_tree: float = 2.0, th_seed: float = 0.45, th_cr: float = 0.55, max_cr: float = 10.0, dt1: float = 1.5, dt2: float = 2.0, R: float = 2.0, Zu: float = 15.0, speed_up: float = 10.0, hull: str = 'convex', concavity: float = 2.0, min_point_height: float = 0.5, smooth: int = 0, heights=None) -> Trees

Find the trees of one cloud: tops, crowns and each point's tree.

For "dalponte2016" and "watershed" a CHM is made at resolution (the highest point per cell, as sylva.ground.make_chm, on a grid aligned with multiples of the resolution and two empty cells wider than the cloud), tops are found by locate_trees on it (or on the points with tops_from= "points"), crowns are grown by segment_crowns, and each point takes the crown of its cell. "li2012" labels the points directly (li2012) and a tree's top is its highest point. "tops" finds tops only. find_trees runs the same on a catalogue.

Parameters:

Name Type Description Default
cloud PointCloud

Points; see heights.

required
method ('dalponte2016', 'watershed', 'li2012', 'tops')

Segmentation.

"dalponte2016"
resolution float

CHM cell size (m).

0.5
window

Tree-top detection, as for locate_trees; a function of height is sampled every centimetre and interpolated linearly.

5.0
hmin

Tree-top detection, as for locate_trees; a function of height is sampled every centimetre and interpolated linearly.

5.0
shape

Tree-top detection, as for locate_trees; a function of height is sampled every centimetre and interpolated linearly.

5.0
tops_from ('chm', 'points')

Find tops on the CHM or on the points.

"chm"
th_tree float

Crown growing, as for segment_crowns (max_cr in cells).

2.0
th_seed float

Crown growing, as for segment_crowns (max_cr in cells).

2.0
th_cr float

Crown growing, as for segment_crowns (max_cr in cells).

2.0
max_cr float

Crown growing, as for segment_crowns (max_cr in cells).

2.0
dt1 float

li2012 settings; its hmin is hmin.

1.5
dt2 float

li2012 settings; its hmin is hmin.

1.5
R float

li2012 settings; its hmin is hmin.

1.5
Zu float

li2012 settings; its hmin is hmin.

1.5
speed_up float

li2012 settings; its hmin is hmin.

1.5
hull ('convex', 'concave')

Crown outline from each tree's points, as for crown_hull.

"convex"
concavity float

Edge length (m) of a concave outline.

2.0
min_point_height float

Points lower than this (m) are in no tree and outline no crown. (For "li2012" this cannot change the labels of the higher points, which are taken first.)

0.5
smooth int

Smooth the CHM with a mean filter over (2 * smooth + 1) squared cells before finding tops and growing crowns (smooth=1 is a 3 x 3 focal mean); 0 for none. A CHM top's height is still the unsmoothed cell value.

0
heights

Height of each point (array or attribute name); z if None (a normalised cloud).

None

Returns:

Type Description
Trees

With tree_id giving each point's tree (0 for none).

Raises:

Type Description
ValueError

For an unknown method, hull or shape, or a bad setting.

Examples:

>>> from sylva import als
>>> w = als.LinearWindow(2, 0.1, 3, 8)
>>> trees = als.segment_trees(normalised, window=w)
>>> trees.to_geojson("crowns.geojson")

find_trees

find_trees(catalog, out: str | Path | None = None, method: str = 'dalponte2016', resolution: float = 0.5, dtm='auto', dtm_resolution: float = 1.0, window=5.0, hmin: float = 2.0, shape: str = 'circular', tops_from: str = 'chm', th_tree: float = 2.0, th_seed: float = 0.45, th_cr: float = 0.55, max_cr: float = 10.0, dt1: float = 1.5, dt2: float = 2.0, R: float = 2.0, Zu: float = 15.0, speed_up: float = 10.0, hull: str = 'convex', concavity: float = 2.0, min_point_height: float = 0.5, smooth: int = 0, attribute: str = 'tree_id', chunk_size: float | None = None, buffer: float = 30.0, workers: int | None = None, format: str | None = None) -> Trees

Find the trees of a whole catalogue, and optionally write every tile with each point's tree id.

Each chunk is segmented with its buffer as segment_trees does it, on the CHM grid shared by the whole catalogue. A tree belongs to the chunk that holds its top (a CHM-cell top to the chunk whose core is nearest the cell centre, as raster outputs are joined; a point top to the chunk whose core holds the point) and is reported once, with the crown that chunk finds for it. Ids are 1, 2, ... in order of the tops' x, then y, over the whole catalogue, so they do not depend on the chunks or the number of workers; with a wide enough buffer the trees are those segment_trees finds in all the tiles merged.

Parameters:

Name Type Description Default
catalog

The tiles.

required
out str | Path | None

Directory for labelled tiles (one per input tile, or per chunk on a grid): every point with its tree id in attribute (0 for none). A point is written by the chunk whose core holds it and takes the id of the tree whose crown holds it, wherever that tree's top is. Needs crowns (not method="tops").

None
method str

As for segment_trees. A window function is sampled every centimetre from 0 to the catalogue's height range.

'dalponte2016'
resolution str

As for segment_trees. A window function is sampled every centimetre from 0 to the catalogue's height range.

'dalponte2016'
window str

As for segment_trees. A window function is sampled every centimetre from 0 to the catalogue's height range.

'dalponte2016'
hmin str

As for segment_trees. A window function is sampled every centimetre from 0 to the catalogue's height range.

'dalponte2016'
shape str

As for segment_trees. A window function is sampled every centimetre from 0 to the catalogue's height range.

'dalponte2016'
tops_from str

As for segment_trees. A window function is sampled every centimetre from 0 to the catalogue's height range.

'dalponte2016'
th_tree float

As for segment_trees.

2.0
th_seed float

As for segment_trees.

2.0
th_cr float

As for segment_trees.

2.0
max_cr float

As for segment_trees.

2.0
dt1 float

As for segment_trees.

2.0
dt2 float

As for segment_trees.

2.0
R float

As for segment_trees.

2.0
Zu float

As for segment_trees.

2.0
speed_up float

As for segment_trees.

2.0
hull str

As for segment_trees.

'convex'
concavity str

As for segment_trees.

'convex'
min_point_height str

As for segment_trees.

'convex'
smooth str

As for segment_trees.

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

Heights above ground: "auto" makes one DTM of the whole catalogue from its ground points first (as sylva.als.dtm with method="lowest" at dtm_resolution), so that a point has the same height in every chunk that reads it; a ~sylva.Raster is subtracted; None takes z as height (normalised tiles).

'auto'
dtm_resolution float

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

1.0
attribute str

Name of the tree id attribute in the written tiles.

'tree_id'
chunk_size float | None

As for sylva.als.apply.

None
workers float | None

As for sylva.als.apply.

None
format float | None

As for sylva.als.apply.

None
buffer float

Width (m) of the band read around each chunk. For a crown near a chunk edge to be complete and the same as in one run, the buffer must hold the crown and the crowns and tops that compete with it: at least half the largest window plus two crown diameters for the CHM methods, twice speed_up for "li2012".

30.0

Returns:

Type Description
Trees

With catalog (the labelled tiles) when out is given. A warning is issued when crowns reach the edge of a buffer or points could not be matched to a tree, both signs of a narrow buffer.

Raises:

Type Description
ValueError

As segment_trees, for out with method="tops", or for a catalogue without ground points with dtm="auto".