sylva.quality¶
sylva.quality ¶
Point-cloud quality measured on tree stems.
Between 1 and 3 m a stem is the one surface in a forest scan whose shape is known well enough to measure the scanner against. Each stem is cut into thin slices and a circle fitted to every slice from all scans together; every point's radial residual is then read per scan position::
from sylva import quality
q = quality.stem_noise(cloud, scan_id=quality.scan_ids_from_origins(origins))
q.summary() # sigma_total, sigma_within, sigma_local, registration, tails
q.scans # per scan: tx, ty (horizontal offset), sigma_within, sigma_local
What each spread contains:
sigma_total
all points about the shared circle, as the cloud stands: range noise,
bark, the stem's departure from a circle and misregistration together.
A whole-stem circle cannot get below a few millimetres here even for a
perfect scanner (about 7 mm on synthetic stems).
sigma_within
one scan's points about their own median in the slice: range noise,
bark and stem shape over the arc that scan saw; misregistration removed.
sigma_local
what remains after removing a smooth curve along that arc (constant,
first and second harmonics of the angle): range noise and bark only, the
measure closest to the scanner's own noise.
tx, ty
the horizontal offset of each scan, from sum (r - t . n)^2 over its
points on every stem (n the outward normal; Huber 1964 weights),
refined by moving the scans back and refitting; relative to the mean of
all scans, since a common shift is invisible. Vertical misregistration
does not show on vertical stems.
StemNoise
dataclass
¶
Result of stem_noise; tables are dicts of equal-length arrays.
Attributes:
| Name | Type | Description |
|---|---|---|
slices |
dict
|
Per stem slice: |
scan_slices |
dict
|
Per scan and slice: |
scans |
dict
|
Per scan: |
residual |
ndarray
|
Radial residual of each input point (m); NaN where not used. |
summary ¶
summary(min_scan_slices: int = 1) -> dict
Plot-level quality figures, in metres.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
min_scan_slices
|
int
|
Scans measured in fewer stem slices than this are left out of the
registration figures. A scan that saw only a few stem points (an
outer position looking away from the plot) gets an offset fitted
to almost nothing, which would otherwise set |
1
|
Returns:
| Type | Description |
|---|---|
dict
|
|
Notes
On the TERN CUP 2022 plot sigma_local was 6.7 mm and
registration_rms 4.7 mm; two halves of the stems agreed to
1.1 mm. Compare scans and dates with the same settings, not with
a fixed threshold.
scan_ids_from_origins ¶
scan_ids_from_origins(origins, tolerance: float = 0.05) -> np.ndarray
Scan position of each point, from its sensor origin.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
origins
|
|
required | |
tolerance
|
float
|
Origins on the same |
0.05
|
Returns:
| Type | Description |
|---|---|
ndarray
|
int64 id per point, |
stem_noise ¶
stem_noise(cloud: PointCloud, scan_id=None, stems=None, height_attr: str = 'height', iterations: int = 3, **params) -> StemNoise
Stem-based noise and registration of cloud.
scan_id: per-point scan position (array, the name of an attribute, or
None for a single scan; see scan_ids_from_origins).
stems: stem positions as (n, 2) xy, a list of
sylva.trees.Tree, or None to detect them. Heights come from
height_attr if the cloud has it, else from the lowest point.
params: height_min=1, height_max=3, step=0.25,
thickness=0.1 (slices, m above ground), min_radius=0.05,
max_radius=1, min_arc=270 (deg a slice's circle must see),
min_inlier_fraction=0.5, cut_min=0.05 and cut_fraction=0.3
(points further than max(cut_min, cut_fraction * r) from the circle
are not stem), min_scan_points=30. iterations of offset refinement.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cloud
|
PointCloud
|
Registered multi-scan cloud (or one scan) covering 1-3 m of the stems. |
required |
scan_id
|
Scan position per point, as above. |
None
|
|
stems
|
Stem positions, as above. |
None
|
|
height_attr
|
str
|
Attribute holding height above ground. |
'height'
|
iterations
|
int
|
Rounds of moving the scans back and refitting. |
3
|
**params
|
The slice and fit settings above. |
{}
|
Returns:
| Type | Description |
|---|---|
StemNoise
|
Call |
Notes
Needs stems that are roughly round and seen from most sides: at least
min_arc degrees of each slice, so single scans of isolated trees
usually qualify only with a lower min_arc.