import laspy
import numpy as np
# -----------------------------------------------------------------------------
# draw_circles
# -----------------------------------------------------------------------------
[docs]
def generate_circles_cloud(
X_c,
Y_c,
R,
sections,
check_circle,
sector_perct,
n_points_in,
tree_vector,
outliers,
R_min=0.03,
R_max=0.5,
threshold=5,
n_sectors=16,
min_n_sectors=9,
circa_points=200,
):
"""This function generates points that comprise the circles computed by
fit_circle_check function, so sections can be visualized. The circles
points cloud along with their associated meta data are retuned as a Matrix
(numpy.ndarray)
Parameters
----------
X_c : numpy.ndarray
Matrix containing (x) coordinates of the center of the sections.
Y_c : numpy.ndarray
Matrix containing (y) coordinates of the center of the sections.
R : numpy.ndarray
Vector containing section radia.
sections : numpy.ndarray
Vector containing section heights (normalized heights).
section_perct : numpy.ndarray
Matrix containing the percentage of occupied sectors.
n_points_in : numpy.ndarray
Matrix containing the number of points in the inner circumferences.
tree_vector : numpy.ndarray
detected_trees output from individualize_trees.
outliers : numpy.ndarray
Vector containing the 'outlier probability' of each section.
R_min : float
Refer to fit_circle_check in 'sections' module. Defaults to 0.03.
R_max : float
Refer to fit_circle_check in 'sections' module. Defaults to 0.5.
threshold : float
Refer to fit_circle_check in 'sections' module. Defaults to 5.
n_sectors : int
Refer to fit_circle_check in 'sections' module. Defaults to 16.
min_n_sectors: int
Refer to fit_circle_check in sections module. Defaults to 9.
circa_points : int
Number of points used to draw each circle. Defaults to 200.
Returns
-------
coords : numpy.ndarray
Matrix containing the circles coordinates and their associated meta data
"""
# Empty vector to be filled. It has as many elements as the vector containing
# the center of the circles for a given tree.
tree_section = X_c.shape
# Empty array that will contain the information about each section, to then
# be used to complete the .LAS file data.
section_c_xyz = np.zeros([tree_section[0] * tree_section[1], 9])
# Auxiliar index indicating which section is in use.
section = 0
# Double for loop to iterate through each combination of coordinates
for i in range(tree_section[0]):
for j in range(tree_section[1]):
# If distance is within range (R_min, R_max), then proceed.
if R[i, j] >= R_min and R[i, j] <= R_max:
# Filling the array with the appropiate data
section_c_xyz[section, :] = [
X_c[i, j],
Y_c[i, j],
sections[j] + tree_vector[i, 7],
R[i, j],
check_circle[i, j],
sector_perct[i, j],
n_points_in[i, j],
sections[j],
outliers[i, j],
]
section = section + 1
# Just the centers of each filled section
centers = section_c_xyz[:section, :]
# Number of centers
n = centers.shape[0]
# Empty vector to be filled with the coordinates of each circle.
coords = np.zeros((circa_points * n, 11))
# User-create function to tranform polar coordinates to cartesian coordinates.
def polar_to_cart(theta, rho):
x = rho * np.cos(theta)
y = rho * np.sin(theta)
return x, y
# For loop to iterate over each circle and compute their (x, y) coordinates.
# (z) coordinates are already given by the user.
for i in range(n):
start = i * circa_points
end = (i + 1) * circa_points
angles = np.arange(0, 2 * np.pi, 2 * np.pi / circa_points)
radius = centers[i, 3]
(x, y) = polar_to_cart(angles, radius)
coords[start:end, 0] = x + centers[i, 0] # X
coords[start:end, 1] = y + centers[i, 1] # Y
coords[start:end, 2] = centers[i, 2] # check
coords[start:end, 3] = centers[i, 4] # Z0
coords[start:end, 4] = i # Tree ID
coords[start:end, 5] = centers[i, 5] # sector occupancy
coords[start:end, 6] = centers[i, 6] # points in inner circle
coords[start:end, 7] = centers[i, 7] # Z0
coords[start:end, 8] = centers[i, 3] * 2 # Diameter
coords[start:end, 9] = centers[i, 8] # outlier probability
if (
(centers[i, 5] < min_n_sectors / n_sectors * 100)
| (centers[i, 6] > threshold)
| (centers[i, 8] > 0.3)
| (centers[i, 3] < R_min)
| (centers[i, 3] > R_max)
): # only happens when which_dbh == 0 # which_valid_points should be used here
coords[start:end, 10] = 1 # does not pass quality checks
else:
coords[start:end, 10] = 0 # passes quality checks
return coords
[docs]
def draw_circles(
X_c,
Y_c,
R,
sections,
check_circle,
sector_perct,
n_points_in,
tree_vector,
outliers,
filename_las,
R_min=0.03,
R_max=0.5,
threshold=5,
n_sectors=16,
min_n_sectors=9,
circa_points=200,
):
"""This function generates points that comprise the circles computed by
fit_circle_check function, so sections can be visualized. The circles are
then saved in a LAS file, along some descriptive fields. Each circle
corresponds on a one-to-one basis to the sections described by the user.
Parameters
----------
X_c : numpy.ndarray
Matrix containing (x) coordinates of the center of the sections.
Y_c : numpy.ndarray
Matrix containing (y) coordinates of the center of the sections.
R : numpy.ndarray
Vector containing section radia.
sections : numpy.ndarray
Vector containing section heights (normalized heights).
section_perct : numpy.ndarray
Matrix containing the percentage of occupied sectors.
n_points_in : numpy.ndarray
Matrix containing the number of points in the inner circumferences.
tree_vector : numpy.ndarray
detected_trees output from individualize_trees.
outliers : numpy.ndarray
Vector containing the 'outlier probability' of each section.
filename_las : char
File name for the output file.
R_min : float
Refer to fit_circle_check in 'sections' module. Defaults to 0.03.
R_max : float
Refer to fit_circle_check in 'sections' module. Defaults to 0.5.
threshold : float
Refer to fit_circle_check in 'sections' module. Defaults to 5.
n_sectors : int
Refer to fit_circle_check in 'sections' module. Defaults to 16.
min_n_sectors: int
Refer to fit_circle_check in sections module. Defaults to 9.
circa_points : int
Number of points used to draw each circle. Defaults to 200.
"""
coords = generate_circles_cloud(
X_c,
Y_c,
R,
sections,
check_circle,
sector_perct,
n_points_in,
tree_vector,
outliers,
R_min,
R_max,
threshold,
n_sectors,
min_n_sectors,
circa_points,
)
# LAS file containing circle coordinates.
las_circ = laspy.create(point_format=2, file_version="1.2")
las_circ.x = coords[:, 0]
las_circ.y = coords[:, 1]
las_circ.z = coords[:, 2]
# All extra fields.
# las_circ.add_extra_dim(laspy.ExtraBytesParams(name = "check", type = np.int32))
# las_circ.check = coords[:, 3]
las_circ.add_extra_dim(laspy.ExtraBytesParams(name="tree_ID", type=np.int32))
las_circ.tree_ID = coords[:, 4]
las_circ.add_extra_dim(
laspy.ExtraBytesParams(name="sector_occupancy_percent", type=np.float64)
)
las_circ.sector_occupancy_percent = coords[:, 5]
las_circ.add_extra_dim(
laspy.ExtraBytesParams(name="pts_inner_circle", type=np.int32)
)
las_circ.pts_inner_circle = coords[:, 6]
las_circ.add_extra_dim(laspy.ExtraBytesParams(name="Z0", type=np.float64))
las_circ.Z0 = coords[:, 7]
las_circ.add_extra_dim(laspy.ExtraBytesParams(name="Diameter", type=np.float64))
las_circ.Diameter = coords[:, 8]
las_circ.add_extra_dim(laspy.ExtraBytesParams(name="outlier_prob", type=np.float64))
las_circ.outlier_prob = coords[:, 9]
las_circ.add_extra_dim(laspy.ExtraBytesParams(name="quality", type=np.int32))
las_circ.quality = coords[:, 10]
las_circ.write(filename_las)
# -----------------------------------------------------------------------------
# draw_axes
# -----------------------------------------------------------------------------
[docs]
def generate_axis_cloud(
tree_vector,
line_downstep=0.5,
line_upstep=10.0,
stripe_lower_limit=0.5,
stripe_upper_limit=2.5,
point_interval=0.01,
):
"""This function generates points that comprise the axes computed by
individualize_trees, so that they can be visualized. It output two
numpy.ndarray that describes the point cloud of the axis
and their associated tilt.
Parameters
----------
tree_vector : numpy.ndarray
detected_trees output from individualize_trees.
filename_las : char
File name for the output file
line_downstep : float
From the stripe centroid, how much (downwards direction) will the drawn
axes extend (units is meters). Defaults to 0.5.
line_upstep : float
From the stripe centroid, how much (upwards direction) will the drawn
axes extend (units is meters). Defaults to 10.0.
stripe_lower_limit : float
Lower (vertical) limit of the stripe (units is meters). Defaults to 0.7.
stripe_upper_limit : float
Upper (vertical) limit of the stripe (units is meters). Defaults to 3.5.
point_interval : float
Step value used to draw points (unit is meters). Defaults to 0.01.
Returns
--------
axes_point : numpy.ndarray
Matrix that describes the point cloud of the axes
tilt : numpy.ndarray
Matrix that describes the tilt of each axes
"""
stripe_centroid = (stripe_lower_limit + stripe_upper_limit) / 2
mean_descend = stripe_centroid + line_downstep
mean_rise = line_upstep - stripe_centroid
up_iter = np.round(mean_rise / point_interval)
down_iter = mean_descend / point_interval
axes_points = np.zeros((np.int_(tree_vector.shape[0] * (up_iter + down_iter)), 3))
tilt = np.zeros(np.int_(tree_vector.shape[0] * (up_iter + down_iter)))
ind = 0
for i in range(tree_vector.shape[0]):
if np.sum(np.exp2(tree_vector[i, 1:4])) > 0:
if tree_vector[i, 3] < 0:
vector = -tree_vector[i, 1:4]
else:
vector = tree_vector[i, 1:4]
axes_points[np.int_(ind) : np.int_(ind + up_iter + down_iter), :] = (
np.transpose(
[
np.arange(-down_iter, up_iter),
np.arange(-down_iter, up_iter),
np.arange(-down_iter, up_iter),
]
)
* vector
* point_interval
+ tree_vector[i, 4:7]
)
tilt[np.int_(ind) : np.int_(ind + up_iter + down_iter)] = tree_vector[i, 8]
ind = ind + up_iter + down_iter
axes_points = axes_points[: np.int_(ind), :]
return (axes_points, tilt)
[docs]
def draw_axes(
tree_vector,
filename_las,
line_downstep=0.5,
line_upstep=10.0,
stripe_lower_limit=0.5,
stripe_upper_limit=2.5,
point_interval=0.01,
):
"""This function generates points that comprise the axes computed by
individualize_trees, so that they can be visualized. The axes are then
saved in a LAS file, along some descriptive fields. Each axis corresponds
on a one-to-one basis to the individualized trees.
Parameters
----------
tree_vector : numpy.ndarray
detected_trees output from individualize_trees.
filename_las : char
File name for the output file
line_downstep : float
From the stripe centroid, how much (downwards direction) will the drawn
axes extend (units is meters). Defaults to 0.5.
line_upstep : float
From the stripe centroid, how much (upwards direction) will the drawn
axes extend (units is meters). Defaults to 10.0.
stripe_lower_limit : float
Lower (vertical) limit of the stripe (units is meters). Defaults to 0.5.
stripe_upper_limit : float
Upper (vertical) limit of the stripe (units is meters). Defaults to 2.5.
point_interval : float
Step value used to draw points (unit is meters). Defaults to 0.01..
"""
axes_points, tilt = generate_axis_cloud(
tree_vector,
line_downstep,
line_upstep,
stripe_lower_limit,
stripe_upper_limit,
point_interval,
)
las_axes = laspy.create(point_format=2, file_version="1.2")
las_axes.x = axes_points[:, 0]
las_axes.y = axes_points[:, 1]
las_axes.z = axes_points[:, 2]
las_axes.add_extra_dim(
laspy.ExtraBytesParams(name="tilting_degree", type=np.float64)
)
las_axes.tilting_degree = tilt
las_axes.write(filename_las)