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); |
1.0
|
max
|
float
|
Bounds on the size (m); |
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 |
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, |
list()
|
tree_id
|
ndarray | None
|
For |
None
|
catalog
|
Catalog | None
|
For |
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 |
"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 |
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 |
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
( |
10.0
|
Returns:
| Type | Description |
|---|---|
Raster
|
The crown of each cell: |
Raises:
| Type | Description |
|---|---|
ValueError
|
For an unknown method, thresholds outside 0-1, or a non-positive
|
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 |
required |
dt1
|
float
|
Spacing thresholds (m) below and above |
1.5
|
dt2
|
float
|
Spacing thresholds (m) below and above |
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 |
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 |
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
|
|
required | |
hull
|
('convex', 'concave')
|
The convex hull, or the characteristic shape of Duckham et al.
(2008): from the Delaunay triangulation, outline edges longer than
|
"convex"
|
concavity
|
float
|
Edge length (m) of the concave outline; smaller follows the points more closely. |
2.0
|
Returns:
| Type | Description |
|---|---|
ndarray
|
|
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 |
required |
method
|
('dalponte2016', 'watershed', 'li2012', 'tops')
|
Segmentation. |
"dalponte2016"
|
resolution
|
float
|
CHM cell size (m). |
0.5
|
window
|
Tree-top detection, as for |
5.0
|
|
hmin
|
Tree-top detection, as for |
5.0
|
|
shape
|
Tree-top detection, as for |
5.0
|
|
tops_from
|
('chm', 'points')
|
Find tops on the CHM or on the points. |
"chm"
|
th_tree
|
float
|
Crown growing, as for |
2.0
|
th_seed
|
float
|
Crown growing, as for |
2.0
|
th_cr
|
float
|
Crown growing, as for |
2.0
|
max_cr
|
float
|
Crown growing, as for |
2.0
|
dt1
|
float
|
|
1.5
|
dt2
|
float
|
|
1.5
|
R
|
float
|
|
1.5
|
Zu
|
float
|
|
1.5
|
speed_up
|
float
|
|
1.5
|
hull
|
('convex', 'concave')
|
Crown outline from each tree's points, as for |
"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 |
0.5
|
smooth
|
int
|
Smooth the CHM with a mean filter over |
0
|
heights
|
Height of each point (array or attribute name); z if None (a normalised cloud). |
None
|
Returns:
| Type | Description |
|---|---|
Trees
|
With |
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 |
None
|
method
|
str
|
As for |
'dalponte2016'
|
resolution
|
str
|
As for |
'dalponte2016'
|
window
|
str
|
As for |
'dalponte2016'
|
hmin
|
str
|
As for |
'dalponte2016'
|
shape
|
str
|
As for |
'dalponte2016'
|
tops_from
|
str
|
As for |
'dalponte2016'
|
th_tree
|
float
|
As for |
2.0
|
th_seed
|
float
|
As for |
2.0
|
th_cr
|
float
|
As for |
2.0
|
max_cr
|
float
|
As for |
2.0
|
dt1
|
float
|
As for |
2.0
|
dt2
|
float
|
As for |
2.0
|
R
|
float
|
As for |
2.0
|
Zu
|
float
|
As for |
2.0
|
speed_up
|
float
|
As for |
2.0
|
hull
|
str
|
As for |
'convex'
|
concavity
|
str
|
As for |
'convex'
|
min_point_height
|
str
|
As for |
'convex'
|
smooth
|
str
|
As for |
'convex'
|
dtm
|
('auto', None or Raster)
|
Heights above ground: |
'auto'
|
dtm_resolution
|
float
|
Cell size (m) of the |
1.0
|
attribute
|
str
|
Name of the tree id attribute in the written tiles. |
'tree_id'
|
chunk_size
|
float | None
|
As for |
None
|
workers
|
float | None
|
As for |
None
|
format
|
float | None
|
As for |
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 |
30.0
|
Returns:
| Type | Description |
|---|---|
Trees
|
With |
Raises:
| Type | Description |
|---|---|
ValueError
|
As |