Skip to content

sylva.als.tiles

Catalog, Chunk, catalog, apply, retile and write_tiles are the objects of sylva.als, documented there.

sylva.als.tiles

Point clouds of any size as tiles, with results identical to the whole cloud.

A plot scanned from a hundred positions holds hundreds of millions of points, more than fit in memory with room to process them. This module keeps such a plot as square LAS/LAZ tiles and processes it one tile at a time, each with a buffer of points from its neighbours, on the engine of sylva.als (Catalog, Chunk, apply, retile and write_tiles are the same objects here as there):

  1. from_scans builds the tiles from the scans, one scan at a time, thinning them on one global voxel grid and recording each point's scan_id. The tiles hold exactly what sylva.filters.voxel_downsample keeps of all the scans concatenated, on the same grid.
  2. voxel_downsample, statistical_outlier_removal, radius_outlier_removal, estimate_normals, planarity_linearity and detect_stems give, tile by tile, the result of their single-cloud counterparts on the whole plot. The neighbourhood operations are exact for any buffer: a point whose neighbours might lie beyond it is searched again in the tiles within its reach, one tile at a time.
  3. classify_ground, dtm and normalize are the sylva.als operations with settings for plots.

Tiles keep every attribute they carry (tree_id, wood, scan_id, height, ...): LAS dimensions by name, others as extra bytes. Each function that writes tiles returns the Catalog of what it wrote; last_run tells how many points were held at once.

Examples:

>>> from sylva.als import tiles
>>> cat = tiles.from_scans(sorted(Path("scans").glob("*.laz")), "tiles/",
...                        tile_size=10.0, voxel_size=0.02, transforms=sops)
>>> clean = tiles.statistical_outlier_removal(cat, "sor/", k=6, std_ratio=1.0)
>>> tiles.last_run().max_points

RunInfo dataclass

What a tiled run did, to check that memory stayed bounded.

Parameters:

Name Type Description Default
chunks int

Tiles processed (written, for from_scans).

required
max_points int

Most points held at once by one tile: the tile with its buffer, a tile read again for points reaching beyond it, the graph nodes of a tile and its buffer (segment_trees), or for from_scans the largest scan or tile being assembled. Memory is about 256 bytes times this (512 per graph node), per worker.

required
points_read int

Points read over the run, buffers and second reads included.

required
rereads int

Tiles searched a second time because some of their points had neighbours that could lie beyond the buffer (for segment_trees, tiles segmented again with a wider buffer).

required
widened_points int

Points evaluated from such a second search (graph nodes of the wider runs, for segment_trees).

required

TreeStore

Each tree's points of a segmented plot, one directory per tree.

Written by split_trees. A tree's points are those of every tile it touches, in catalogue order (the order the whole plot has them in), at full precision and with all their attributes. Per-point values of a tree (leaf / wood, say) can be kept beside it (set_values) and written back to the tiles (classify_leaf_wood does so).

Parameters:

Name Type Description Default
path str | Path

The store's directory.

required

Raises:

Type Description
ValueError

If path is not a tree store.

ids property

ids: list[int]

Tree ids, ascending.

n_points

n_points(tree_id: int) -> int

Points of one tree.

bounds

bounds(tree_id: int) -> tuple[float, ...]

(xmin, ymin, zmin, xmax, ymax, zmax) of one tree's points.

tiles_of

tiles_of(tree_id: int) -> list[str]

Names of the tiles holding points of one tree, in catalogue order.

read

read(tree_id: int) -> PointCloud

One tree's points.

Parameters:

Name Type Description Default
tree_id int

Which tree.

required

Returns:

Type Description
PointCloud

In the order the whole plot has them, with every attribute.

values

values(tree_id: int, name: str) -> np.ndarray | None

Values kept for one tree under name (None if there are none).

has_values

has_values(tree_id: int, name: str) -> bool

Whether values are kept for one tree under name.

set_values

set_values(tree_id: int, name: str, values) -> None

Keep a value per point of one tree (in read order) under name.

Raises:

Type Description
ValueError

For a count other than the tree's points, or a name that is not a plain word.

map

map(fn, ids=None, workers: int | None = None, label: str = 'trees') -> dict

fn(tree_id, cloud) for every tree, a few trees at a time.

Trees are taken largest first, each read when a worker is free, so at most workers trees are in memory; workers is lowered when that many of the largest tree would not fit in the memory budget (sylva.util.limits, BYTES_PER_TREE_POINT per point). The work runs on threads: Sylva's computations release the interpreter, so threads run them in parallel.

Parameters:

Name Type Description Default
fn

Called with a tree's id and points; its result is kept.

required
ids

Trees to do; all if None.

None
workers int | None

Trees at once; the number of CPUs if None.

None
label str

For the progress bar.

'trees'

Returns:

Type Description
dict

{tree_id: fn(tree_id, cloud)}, in id order.

PlotRun dataclass

What run_plot made, and where.

Attributes:

Name Type Description
out Path

The run's directory.

trees list[Tree]

The trees, as segment_trees returns them.

table list[dict]

One row per tree: the tree, its crown metrics and its QSM's volume.

qsms PlotQSMs

The models.

catalogs dict[str, Catalog]

{stage: Catalog} of the tiles of each stage.

dtm Raster

The terrain model.

store TreeStore

The per-tree store.

timings dict[str, float]

Seconds per stage (0 for a stage taken from an earlier run).

memory dict[str, int]

Most points held at once per stage (RunInfo.max_points).

last_run

last_run() -> RunInfo | None

The RunInfo of the last tiled operation of this module.

Returns:

Type Description
RunInfo or None

None before any has run, and after classify_ground, dtm and normalize, which report nothing.

from_scans

from_scans(scans, out: str | Path, tile_size: float = 10.0, voxel_size: float | None = None, transforms=None, origin=(0.0, 0.0, 0.0), bounds=None, format: str = 'laz', point_format: int = 6, scale: float = 0.001, epsg: int | None = None, workers: int | None = None, tolerance: float = 1.0) -> Catalog

Build square tiles from scans, thinned on one global voxel grid.

The scans are taken one at a time, in order. Each is moved by its transform, cropped to bounds, and keeps the first point of each voxel it has; those points go, tile by tile, to scratch files in out at full precision. Each tile is then assembled keeping the first point per voxel in scan order, and written with a scan_id attribute (the scan's position in scans). Tiles are cut along voxel boundaries, so no voxel is split between two tiles.

The tiles hold exactly the points that filters.voxel_downsample(concatenated, voxel_size, origin=origin) keeps of all the (transformed, cropped) scans concatenated in order, with their attributes; only their coordinates are rounded to scale. Only one scan, and one tile per worker, is in memory at a time.

Parameters:

Name Type Description Default
scans

Paths of point cloud files (any format sylva.read reads), ~sylva.PointCloud objects, or a mix, in scan order.

required
out str | Path

Directory for the tiles, named <xmin>_<ymin>.<format>.

required
tile_size float

Side of the tiles (m); a whole number of voxels.

10.0
voxel_size float | None

Voxel size (m) of the thinning; None keeps every point.

None
transforms

One 4x4 matrix (or None) per scan, applied before anything else (a scan's SOP, say), as sylva.PointCloud.transform applies it.

None
origin

(x, y, z) of a corner of the voxel grid and of the tile grid; (0, 0, 0) puts voxels and tiles at multiples of their size.

(0.0, 0.0, 0.0)
bounds

(xmin, ymin, xmax, ymax): keep only the points inside (closed), a plot and its buffer, say.

None
format str

"las" or "laz".

'laz'
point_format int

LAS point data record format of the tiles.

6
scale float

Coordinate quantisation of the tiles (m).

0.001
epsg int | None

EPSG code recorded in each tile.

None
workers int | None

Tiles assembled at once; the number of CPUs if None, fewer if memory is short.

None
tolerance float

For the returned catalogue (see Catalog).

1.0

Returns:

Type Description
Catalog

The tiles, south to north and west to east.

Raises:

Type Description
ValueError

For a tile size that is not a whole number of voxels, bad bounds, a transform that is not 4x4, or non-finite coordinates.

OSError

For a scan that cannot be read.

Notes

The scratch files hold every scan's thinned points (about 40 bytes each plus the attributes) until the tiles are written; they are removed afterwards, and after an error.

voxel_downsample

voxel_downsample(catalog: Catalog, out: str | Path, voxel_size: float, origin=(0.0, 0.0, 0.0), workers: int | None = None, format: str | None = None) -> Catalog

Thin tiles to the first point of each voxel of one global grid.

Points are taken in catalogue order (tile by tile, each in file order), so the result is filters.voxel_downsample(cat.read(), voxel_size, origin=origin): a voxel that straddles two tiles keeps one point, from the first tile that has one. Each tile is read with a buffer of one voxel, which is all this needs.

Parameters:

Name Type Description Default
catalog Catalog

The tiles (a Catalog, or anything catalog takes).

required
out str | Path

Directory for the thinned tiles, one per input tile with its name.

required
voxel_size float

Voxel size (m).

required
origin

(x, y, z) of a corner of the voxel grid.

(0.0, 0.0, 0.0)
workers int | None

Tiles processed at once; the number of CPUs if None, fewer if memory is short. The result does not depend on it.

None
format str | None

"las" or "laz"; that of each input tile if None.

None

Returns:

Type Description
Catalog

The written tiles.

Raises:

Type Description
ValueError

For a non-positive voxel size or an output that would overwrite an input tile.

statistical_outlier_removal

statistical_outlier_removal(catalog: Catalog, out: str | Path, k: int = 8, std_ratio: float = 2.0, classify: bool = False, buffer: float = 1.0, workers: int | None = None, format: str | None = None) -> Catalog

Statistical outlier removal over all the tiles, as on the whole cloud.

CloudCompare's SOR (sylva.filters.statistical_outlier_removal) compares each point's mean distance to its k nearest neighbours with mean + std_ratio * std of that distance over the whole cloud. Tiled, it takes two passes: the per-point distances tile by tile (each with its buffer, and a wider second read for any point whose neighbours could lie beyond it), kept in scratch files beside the output; then the mean and standard deviation over every point, summed in catalogue order as the whole-cloud filter sums them; then the tiles are written with the threshold applied. The points kept are exactly those filters.statistical_outlier_removal(cat.read(), k, std_ratio) keeps.

Parameters:

Name Type Description Default
catalog Catalog

The tiles.

required
out str | Path

Directory for the filtered tiles.

required
k int

Neighbours per point.

8
std_ratio float

Threshold in standard deviations; lower removes more.

2.0
classify bool

Keep every point and set classification 7 (low noise) on the outliers instead of removing them.

False
buffer float

Width (m) of the band of neighbouring points read around each tile. The result does not depend on it; wider than the k-th neighbour distance of almost every point, it saves second reads.

1.0
workers int | None

Tiles processed at once; see voxel_downsample.

None
format str | None

"las" or "laz"; that of each input tile if None.

None

Returns:

Type Description
Catalog

The written tiles.

Raises:

Type Description
ValueError

For k below 1, a non-finite std_ratio or a negative buffer.

radius_outlier_removal

radius_outlier_removal(catalog: Catalog, out: str | Path, radius: float, min_neighbors: int = 4, classify: bool = False, buffer: float | None = None, workers: int | None = None, format: str | None = None) -> Catalog

Radius outlier removal over all the tiles, as on the whole cloud.

Points with fewer than min_neighbors others within radius are removed, exactly as sylva.filters.radius_outlier_removal removes them from the whole cloud.

Parameters:

Name Type Description Default
catalog Catalog

The tiles.

required
out str | Path

Directory for the filtered tiles.

required
radius float

Search radius (m).

required
min_neighbors int

Points with fewer other points within radius are outliers.

4
classify bool

Keep every point and set classification 7 on the outliers.

False
buffer float | None

Band read around each tile (m); 1 cm more than radius if None, which is all this needs. A narrower one gives the same result, with second reads.

None
workers int | None

Tiles processed at once; see voxel_downsample.

None
format str | None

"las" or "laz"; that of each input tile if None.

None

Returns:

Type Description
Catalog

The written tiles.

Raises:

Type Description
ValueError

For a non-positive radius or a negative buffer.

estimate_normals

estimate_normals(catalog: Catalog, out: str | Path, k: int = 12, buffer: float = 1.0, workers: int | None = None, format: str | None = None) -> Catalog

Add surface normals to every point of the tiles.

Each point gets normal_x, normal_y and normal_z: the values sylva.filters.estimate_normals gives it in the whole cloud (the eigenvector of the smallest eigenvalue of its k neighbours' covariance; the sign is arbitrary).

Parameters:

Name Type Description Default
catalog Catalog

The tiles.

required
out str | Path

Directory for the tiles with normals.

required
k int

Neighbours per point (at least 3 are used).

12
buffer float

Band read around each tile (m); the result does not depend on it.

1.0
workers int | None

Tiles processed at once; see voxel_downsample.

None
format str | None

"las" or "laz"; that of each input tile if None.

None

Returns:

Type Description
Catalog

The written tiles.

Raises:

Type Description
ValueError

For k below 1 or a negative buffer.

Notes

Neighbours at equal distances are ordered by the search tree, whose layout differs between a tile and the whole cloud; where such a tie falls at the k-th neighbour (common with quantised coordinates), a point's value can come from another neighbour. On the Tumbarumba plot this affected 0.02 per cent of the points.

planarity_linearity

planarity_linearity(catalog: Catalog, out: str | Path, k: int = 20, buffer: float = 1.0, workers: int | None = None, format: str | None = None) -> Catalog

Add planarity and linearity to every point of the tiles.

Each point gets planarity and linearity, the values sylva.filters.planarity_linearity gives it in the whole cloud.

Parameters:

Name Type Description Default
catalog Catalog

The tiles.

required
out str | Path

Directory for the tiles with the features.

required
k int

Neighbours per point (at least 3 are used).

20
buffer float

Band read around each tile (m); the result does not depend on it.

1.0
workers int | None

Tiles processed at once; see voxel_downsample.

None
format str | None

"las" or "laz"; that of each input tile if None.

None

Returns:

Type Description
Catalog

The written tiles.

Raises:

Type Description
ValueError

For k below 1 or a negative buffer.

Notes

Neighbours at equal distances are ordered by the search tree, whose layout differs between a tile and the whole cloud; where such a tie falls at the k-th neighbour (common with quantised coordinates), a point's value can come from another neighbour. On the Tumbarumba plot this affected 0.02 per cent of the points.

classify_ground

classify_ground(catalog: Catalog, out: str | Path, method: str = 'csf', cloth_resolution: float = 0.5, rigidness: int = 2, class_threshold: float = 0.3, iterations: int = 500, time_step: float = 0.65, cell_size: float = 0.5, max_window: float = 10.0, slope: float = 0.3, initial_distance: float = 0.15, max_distance: float = 2.0, buffer: float = 10.0, workers: int | None = None, format: str | None = None) -> Catalog

Classify ground in every tile (sylva.als.classify_ground, one chunk per tile).

Each tile's own points are written with classification 2 (ground) or 1 (other); points classified 7 or 18 (noise, e.g. by statistical_outlier_removal with classify) take no part and keep their class.

With method="pmf" the result is that of sylva.ground.classify_ground_pmf on the whole cloud once the buffer covers how far the filter looks: its grid is anchored at multiples of cell_size in every tile, and each opening of half-width w cells reaches 2 w cells, so buffer >= 2 * cell_size * (1 + 2 + 4 + ... + W) (W the largest half-width, max_window / cell_size or less) plus the widest gap in the ground is sufficient; narrower buffers agree everywhere but near the tile edges in practice. method="csf" is not exact: the cloth is one sheet whose settling depends on the whole of it, so points near a tile edge can differ from the whole-cloud classification (rarely, with a buffer of a few metres).

Parameters:

Name Type Description Default
catalog Catalog

The tiles.

required
out str | Path

Directory for the classified tiles.

required
method ('csf', 'pmf')

Cloth simulation or progressive morphological filter.

"csf"
cloth_resolution float

CSF parameters (sylva.ground.classify_ground_csf).

0.5
rigidness float

CSF parameters (sylva.ground.classify_ground_csf).

0.5
class_threshold float

CSF parameters (sylva.ground.classify_ground_csf).

0.5
iterations float

CSF parameters (sylva.ground.classify_ground_csf).

0.5
time_step float

CSF parameters (sylva.ground.classify_ground_csf).

0.5
cell_size float

PMF parameters (sylva.ground.classify_ground_pmf).

0.5
max_window float

PMF parameters (sylva.ground.classify_ground_pmf).

0.5
slope float

PMF parameters (sylva.ground.classify_ground_pmf).

0.5
initial_distance float

PMF parameters (sylva.ground.classify_ground_pmf).

0.5
max_distance float

PMF parameters (sylva.ground.classify_ground_pmf).

0.5
buffer float

Band read around each tile (m).

10.0
workers int | None

Tiles processed at once; see voxel_downsample.

None
format str | None

"las" or "laz"; that of each input tile if None.

None

Returns:

Type Description
Catalog

The written tiles.

Raises:

Type Description
ValueError

For an unknown method or bad parameters.

dtm

dtm(catalog: Catalog, resolution: float = 0.5, method: str = 'lowest', buffer: float = 10.0, workers: int | None = None) -> Raster

Digital terrain model of all the tiles (sylva.als.dtm, one chunk per tile).

Away from the outer edge of the plot, and with a buffer wider than the widest gap in the ground, each cell is that of sylva.ground.make_dtm on the ground points of the whole cloud.

Parameters:

Name Type Description Default
catalog Catalog

Tiles with ground classified (classify_ground).

required
resolution float

Cell size (m).

0.5
method ('lowest', 'tin', 'natural', 'idw')

As in sylva.als.dtm.

"lowest"
buffer float

Band read around each tile (m).

10.0
workers int | None

Tiles processed at once; see voxel_downsample.

None

Returns:

Type Description
Raster

On a grid anchored at multiples of resolution.

Raises:

Type Description
ValueError

For an unknown method, or tiles without ground points.

normalize

normalize(catalog: Catalog, out: str | Path, dtm='auto', dtm_resolution: float = 0.5, replace_z: bool = False, buffer: float = 10.0, workers: int | None = None, format: str | None = None) -> Catalog

Height above ground for every point of the tiles (sylva.als.normalize).

With dtm="auto" each tile makes its DTM from its own and its buffer's ground points at dtm_resolution, as dtm would, so the heights are those of sylva.ground.normalize_height with the whole-cloud sylva.ground.make_dtm (to rounding of the cell positions, below a nanometre).

Parameters:

Name Type Description Default
catalog Catalog

Tiles with ground classified.

required
out str | Path

Directory for the normalised tiles.

required
dtm 'auto' or Raster

Make the DTM per tile, or use this one (from dtm, say).

'auto'
dtm_resolution float

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

0.5
replace_z bool

Replace z by the height (keeping the elevation in elevation) rather than adding a height attribute.

False
buffer float

Band read around each tile (m); wider than the widest gap in the ground for "auto".

10.0
workers int | None

Tiles processed at once; see voxel_downsample.

None
format str | None

"las" or "laz"; that of each input tile if None.

None

Returns:

Type Description
Catalog

The written tiles.

Raises:

Type Description
ValueError

For a bad dtm, or a tile with fewer than 3 ground points within its buffer with "auto".

detect_stems

detect_stems(catalog: Catalog, height_attr: str = 'height', buffer: float = 2.0, workers: int | None = None, **params) -> list[Tree]

Detect stems over all the tiles, as in the whole cloud.

sylva.trees.detect_stems runs on each tile with its buffer, and each tile keeps the stems whose position (at the reference height) lies in it, or nearest to it. RANSAC draws its samples from a random stream per cluster (cluster_seeds=True), so a stem depends only on the points around it: with a buffer wider than the clusters and circles of the stems at a tile edge (max_cluster_extent, 2 m, is enough), the stems are those trees.detect_stems(cat.read(), cluster_seeds=True, **params) finds, with the same ids.

Parameters:

Name Type Description Default
catalog Catalog

Height-normalised tiles (normalize).

required
height_attr str

Attribute holding heights; z is used if the tiles do not have it.

'height'
buffer float

Band read around each tile (m).

2.0
workers int | None

Tiles processed at once; see voxel_downsample.

None
**params

Keyword parameters of sylva.trees.detect_stems.

{}

Returns:

Type Description
list of Tree

Sorted by quality with ids 1..n.

Raises:

Type Description
ValueError

For an unknown parameter or a negative buffer.

segment_trees

segment_trees(catalog: Catalog, stems: list[Tree], out: str | Path, height_attr: str = 'height', buffer: float = 20.0, max_buffer: float = 60.0, merge=True, percentile: float = 100.0, prune=True, attribute: str = 'tree_id', voxel_origin=(0.0, 0.0, 0.0), edge_margin: float = 2.0, workers: int | None = None, format: str | None = None, **params) -> tuple[list[Tree], Catalog]

Segment the trees of height-normalised tiles, as on the whole plot.

The whole-plot sequence is sylva.trees.merge_branches, sylva.trees.segment_trees, sylva.trees.tree_heights and sylva.trees.prune_trees. Here each tree is segmented by the tile whose core holds its stem, from the graph nodes of that tile and of a buffer around it, and every point gets the label of its nearest node; the tiles are written to out with each point's tree id in attribute (-1 for none). Tree ids are those the whole-plot sequence gives, unique over the plot.

The graph is built on the points above cut_above_ground thinned to one per voxel of a grid with a corner at voxel_origin, found tile by tile exactly as in the whole cloud. With a buffer wider than the largest crown (every tree that competes with a tile's trees for points then has its stem, seeds and crown within the buffer), the labels, heights and trees are those of the whole-plot sequence with the same voxel_origin, up to ties in the nearest-neighbour searches. A tile whose trees reach the edge of its buffer (a node within edge_margin of it, where the plot goes on) is read again with a buffer twice as wide, up to max_buffer; trees still at the edge are named in a warning and flagged crown_at_edge in their extra.

Parameters:

Name Type Description Default
catalog Catalog

Height-normalised tiles (sylva.als.tiles.normalize).

required
stems list[Tree]

Stems from sylva.als.tiles.detect_stems (or any trees with unique ids, positions and DBH).

required
out str | Path

Directory for the segmented tiles.

required
height_attr str

Attribute holding heights; z is used if the tiles do not have it.

'height'
buffer float

Band of neighbouring nodes read around each tile (m); wider than the largest crown.

20.0
max_buffer float

Widest band a tile is read again with (m).

60.0
merge

Merge branches into their stems first (sylva.trees.merge_branches): True with its defaults, False to skip, or a dict of its keywords (voxel_size, k, search_radius, ...).

True
percentile float

Height percentile of sylva.trees.tree_heights.

100.0
prune

Prune afterwards (sylva.trees.prune_trees): True with its defaults, False to skip, or a dict of its keywords (min_height, min_slenderness, ...).

True
attribute str

Name of the label attribute written to the tiles (int32).

'tree_id'
voxel_origin

Corner of the voxel grid of the graphs.

(0.0, 0.0, 0.0)
edge_margin float

How close to the edge of the buffer (m) a tree's nodes may come before its tile is read again.

2.0
workers int | None

Tiles processed at once; the number of CPUs if None, fewer if memory is short. The result does not depend on it.

None
format str | None

"las" or "laz"; that of each input tile if None.

None
**params

Keywords of sylva.trees.segment_trees (voxel_size, k, power, ...).

{}

Returns:

Name Type Description
trees list of Tree

The trees as sylva.trees.prune_trees returns them (sorted by DBH with ids 1..n; without pruning, the stems that survived merging with their own ids), with heights and point counts. Each keeps the extra of the stem it came from.

catalog Catalog

The segmented tiles.

Raises:

Type Description
ValueError

For bad parameters, stems without unique ids, or a plot that does not fit in memory one tile at a time.

Warns:

Type Description
UserWarning

When crowns reach the edge of the widest buffer, or nodes were claimed by two tiles.

split_trees

split_trees(catalog: Catalog, out: str | Path, attribute: str = 'tree_id', workers: int | None = None) -> TreeStore

Write every tree's points to a TreeStore, one tile at a time.

Parameters:

Name Type Description Default
catalog Catalog

Segmented tiles (segment_trees).

required
out str | Path

Directory of the store; trees already in it are replaced.

required
attribute str

Attribute holding each point's tree id; below 0 is no tree.

'tree_id'
workers int | None

Tiles at once; see sylva.als.tiles.voxel_downsample.

None

Returns:

Type Description
TreeStore

Raises:

Type Description
ValueError

For tiles without attribute.

read_tree

read_tree(source, tree_id: int, attribute: str = 'tree_id', bounds=None) -> PointCloud

One tree's points, without loading the plot.

Parameters:

Name Type Description Default
source

A TreeStore (or its directory), which reads only the tree's own files; or segmented tiles (a Catalog), where every tile meeting bounds (all of them if None) is read and filtered.

required
tree_id int

Which tree.

required
attribute str

Label attribute of the tiles.

'tree_id'
bounds

(xmin, ymin, xmax, ymax) known to hold the tree, for tiles.

None

Returns:

Type Description
PointCloud

The tree's points in catalogue order, with their attributes.

classify_leaf_wood

classify_leaf_wood(store, catalog: Catalog, out: str | Path, min_points: int = 100, voxel_size: float = 0.02, method: str = 'gbs', attribute: str = 'tree_id', name: str = 'wood', resume: bool = False, workers: int | None = None, format: str | None = None, **wood_params) -> Catalog

Leaf / wood of every tree, written to the tiles as wood.

Each tree of min_points points or more is classified on its own points (sylva.leaves.classify_leaf_wood, which is defined on one tree), a few trees at a time; the tiles are then written to out with name (int8): 1 wood, 0 leaf, -1 for points of no tree or of a tree too small. The values are those of classifying plot[labels == tree_id] of the whole plot.

Parameters:

Name Type Description Default
store

The trees (split_trees).

required
catalog Catalog

The segmented tiles the store was split from.

required
out str | Path

Directory for the tiles with name.

required
min_points int

Smaller trees are left at -1.

100
voxel_size float

As in sylva.leaves.classify_leaf_wood.

0.02
method float

As in sylva.leaves.classify_leaf_wood.

0.02
**wood_params float

As in sylva.leaves.classify_leaf_wood.

0.02
attribute str

Label attribute of the tiles.

'tree_id'
name str

Attribute written, and the name the values are kept under in the store.

'wood'
resume bool

Keep values already in the store rather than classifying again.

False
workers int | None

Trees, then tiles, at once.

None
format str | None

"las" or "laz"; that of each input tile if None.

None

Returns:

Type Description
Catalog

The written tiles.

build_qsms

build_qsms(store, stems=None, voxel_size: float = 0.01, wood: bool = True, buttress: bool = False, min_points: int = 2000, height_attr: str = 'height', resume: bool = False, ids=None, workers: int | None = None, **params) -> qsm.PlotQSMs

A QSM for every tree of a store, a few trees at a time.

Each tree is modelled from its own points as sylva.qsm.build_plot models it in the whole plot (thinning, the wood filter, the stem centre and DBH anchor from stems, a buttress if asked for), so the models are those of build_plot(plot, labels, stems, ...). Trees run in parallel, each one's fit parallel inside; at most workers trees are in memory.

Parameters:

Name Type Description Default
store

The trees (split_trees).

required
stems

The trees of segment_trees, for each model's stem centre and DBH anchor.

None
voxel_size float

As in sylva.qsm.build_plot.

0.01
wood float

As in sylva.qsm.build_plot.

0.01
buttress float

As in sylva.qsm.build_plot.

0.01
min_points float

As in sylva.qsm.build_plot.

0.01
height_attr float

As in sylva.qsm.build_plot.

0.01
resume bool

Keep models already saved in the store (qsm.pkl beside each tree) rather than fitting them again.

False
ids

Trees to model; every tree of the store if None.

None
workers int | None

Trees at once; the number of CPUs if None, fewer if memory is short.

None
**params

Passed to sylva.qsm.build_qsm (stem_radius_cap, ...).

{}

Returns:

Type Description
PlotQSMs

As sylva.qsm.build_plot returns it.

crown_metrics

crown_metrics(store, height_attr: str = 'height', crown_base_fraction: float = 0.1, workers: int | None = None) -> dict[int, dict]

sylva.trees.crown_metrics for every tree of a store.

Parameters:

Name Type Description Default
store

The trees (split_trees).

required
height_attr str

Attribute holding heights.

'height'
crown_base_fraction float

Density threshold for the crown base.

0.1
workers int | None

Trees at once.

None

Returns:

Type Description
dict

{tree_id: metrics} as sylva.trees.crown_metrics_all gives it; trees with fewer than 4 points are missing.

run_plot

run_plot(scans, out: str | Path, transforms=None, use=None, bounds=None, plot=None, tile_size: float = 10.0, voxel_size: float = 0.02, read_options: dict | None = None, sor: dict | None = None, ground: dict | None = None, dtm_resolution: float = 0.5, ground_voxel: float = 0.05, stems: dict | None = None, merge=None, segment: dict | None = None, percentile: float = 99.0, prune=None, buffer: float = 20.0, max_buffer: float = 60.0, leaf_wood: dict | None = None, qsm_options: dict | None = None, qsm_files: bool = False, scale: float = 0.001, workers: int | None = None, log=print) -> PlotRun

The plot workflow from registered scans to trees and QSMs, in tiles.

Stages, each saved in its own directory of out and skipped when a rerun finds it complete (so an interrupted run resumes; per-tree stages resume tree by tree):

====================== ============================================================== scans/ (RiSCAN projects only) each scan read, moved by its SOP, cropped to bounds and thinned tiles/ sylva.als.tiles.from_scans: tiles thinned to voxel_size on one grid sor/ sylva.als.tiles.statistical_outlier_removal ground/ sylva.als.tiles.classify_ground on a ground_voxel thinning; the DTM in dtm.npz (full precision, used for the heights) and dtm.asc heights/ sylva.als.tiles.normalize with that DTM stems.pkl sylva.als.tiles.detect_stems on the tiles inside plot segmented/ segment_trees, with trees.pkl trees/ split_trees, with per-tree leaf / wood and QSMs wood/ classify_leaf_wood qsm_table.csv build_qsms, with qsm_models.pkl trees.csv the trees with their crown metrics and QSM volumes ====================== ==============================================================

Memory is bounded by the tile size, the buffers and workers, not by the plot: no stage holds more than a few tiles with their buffers, or a few trees.

Parameters:

Name Type Description Default
scans

Registered scan files or point clouds in scan order (as sylva.als.tiles.from_scans takes them), or a RiSCAN project (its directory, or a sylva.riscan.RiscanProject), whose scans are read with their SOPs.

required
out str | Path

Directory of the run.

required
transforms

One 4x4 matrix (or None) per scan, applied first: the registration of the files, or for a project the corrections applied after each SOP.

None
use

One flag per scan; scans flagged False are left out.

None
bounds

(xmin, ymin, xmax, ymax) of everything kept: the plot and a margin for ground and buffers.

None
plot

(xmin, ymin, xmax, ymax) of the plot whose trees are wanted, on the tile grid (tiles inside it are used); all tiles if None.

None
tile_size float

Of sylva.als.tiles.from_scans.

10.0
voxel_size float

Of sylva.als.tiles.from_scans.

10.0
scale float

Of sylva.als.tiles.from_scans.

10.0
read_options dict | None

For a project: options of sylva.riscan.ScanPosition.read (shot_stride, ...).

None
sor dict | None

Keywords of sylva.als.tiles.statistical_outlier_removal (default k=6, std_ratio=1); False skips it.

None
ground dict | None

Keywords of sylva.als.tiles.classify_ground (default CSF with cloth_resolution=0.5, rigidness=2, a 10 m buffer).

None
dtm_resolution float

Cell size of the DTM (m).

0.5
ground_voxel float

Thinning of the points ground is classified on (m).

0.05
stems dict | None

Keywords of sylva.als.tiles.detect_stems (default min_arc_deg=130).

None
merge

As in segment_trees (defaults {"voxel_size": 0.1} and {"min_height": 2.0}).

None
prune

As in segment_trees (defaults {"voxel_size": 0.1} and {"min_height": 2.0}).

None
segment dict | None

Keywords of sylva.trees.segment_trees (default voxel_size=0.1).

None
percentile float

Height percentile of the trees.

99.0
buffer float

Of segment_trees.

20.0
max_buffer float

Of segment_trees.

20.0
leaf_wood dict | None

Keywords of classify_leaf_wood; False skips it.

None
qsm_options dict | None

Keywords of build_qsms (stem_radius_cap, ...); False skips it.

None
qsm_files bool

Also write each model's cylinders and mesh to qsm/.

False
workers int | None

Tiles or trees at once.

None
log

Called with a line of text per stage; None for silence.

print

Returns:

Type Description
PlotRun