# -*- python -*-
#
# Copyright INRIA - CIRAD - INRA
#
# Distributed under the Cecill-C License.
# See accompanying file LICENSE.txt or copy at
# http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html
#
# ==============================================================================
import math
import numpy
try:
import nx_cugraph as networkx
except ImportError:
import networkx
import scipy
# ==============================================================================
[docs]
def max_distance_in_points(points):
"""
Compute and return the maximal Euclidean distance between the two point the
most separate in points.
Parameters
----------
points: numpy.ndarray
array of 3d points position.
Returns
-------
max: int
The maximal distance
"""
if len(points) == 0:
return 0
result = scipy.spatial.distance.pdist(points, "euclidean")
if len(result) > 0:
return result.max()
return 0
[docs]
def max_distance_from_point_to_points(points, src_point):
"""
Compute and return the maximal Euclidean distance between src_point and
the most separate point from him in points.
Parameters
----------
src_point: tuple
3-tuple position of src_points: (x, y, z)
points: numpy.ndarray
An ndarray of 3d points position.
Returns
-------
max: int
The maximal distance
"""
if len(points) == 0:
return 0
result = scipy.spatial.distance.cdist(numpy.array([src_point]), points, "euclidean")
if len(result) > 0:
return result.max()
return 0
[docs]
def connected_points_with_point(points, points_graph, src_point):
"""
Return the connected points with src_point, based on points graph
connection.
src_point should be in the points_graph.
Parameters
----------
points: numpy.ndarray
Points positions x, y, z
src_point: numpy.ndarray
Point position x, y, z
points_graph: networkx.graph
The point graph
Returns
-------
connected_points: list
Return the connected points with src_point, based on points graph
connection.
"""
# Compute connected component in points in the ball
subgraph = points_graph.subgraph(points)
connected_components = networkx.connected_components(subgraph)
# Set points in ball by the connected points group with the current
# point of the path
for connected_points in connected_components:
if src_point in connected_points:
return connected_points
return [src_point]
[docs]
def connected_voxel_with_point(voxels_point, voxels_size, src_voxel_point):
"""
Return connected voxels point with src_voxel_point based on 26 neighboring
voxel grid, with a size of voxels_size.
.. warning:: WARNING ! Work only with connected voxel, and voxel grid
Parameters
----------
voxels_point: numpy.ndarray
array of voxel grid point
voxels_size: int
the size of each voxel.
src_voxel_point: tuple
position x, y, z of voxel point
Returns
-------
connected_voxels: list[tuple]
A list of the connected voxels with the point.
"""
closest_node, nodes = [], []
nodes.append(numpy.array(src_voxel_point))
while nodes:
node = nodes.pop()
rr = abs(voxels_point - node)
index = numpy.where(
(rr[:, 0] <= voxels_size)
& (rr[:, 1] <= voxels_size)
& (rr[:, 2] <= voxels_size)
)[0]
nodes += list(voxels_point[index])
closest_node += list(voxels_point[index])
voxels_point = numpy.delete(voxels_point, index, 0)
if voxels_point.size == 0:
break
return list(map(tuple, closest_node))
[docs]
def intercept_points_from_src_point_with_plane_equation(
points, src_point, plane_equation, distance_from_plane, distance_from_src_point=None
):
"""
Intercept the points who's the distance from the plane (generate by
plane_equation) are equal or inferior to the distance_from_plane. If
distance_from_src_point is not None, the intercept points are also the
points who's the distance from the src_point are equal or inferior to the
distance_from_src_point.
If points_graph is not None, points return are the points in the same
connected component that src_point.
Parameters
----------
points: numpy.ndarray
Array containing x,y, z position of each point.
src_point: tuple
Point source.
plane_equation: tuple
Element of an equation
distance_from_plane: float
The maximum distance from a plane.
distance_from_src_point: float
The distance from the source point
Returns
-------
closest_voxel: tuple
the closest voxels.
"""
res = abs(
points[:, 0] * plane_equation[0]
+ points[:, 1] * plane_equation[1]
+ points[:, 2] * plane_equation[2]
- plane_equation[3]
) / (
math.sqrt(
plane_equation[0] ** 2 + plane_equation[1] ** 2 + plane_equation[2] ** 2
)
)
index = numpy.where(res < distance_from_plane)[0]
closest_voxel = points[index]
if distance_from_src_point is not None:
res = numpy.linalg.norm(closest_voxel - src_point, axis=1)
index = numpy.where(res < distance_from_src_point)[0]
closest_voxel = closest_voxel[index]
return closest_voxel
[docs]
def compute_plane_equation(orientation_vector, src_point):
"""
Computation of plane equation
x, y, z = src_point
a, b, c, _ = k
Plane equation : - d = a * x + b * y + c * z
Parameters
----------
orientation_vector: tuple
The orientation vector.
src_point: tuple
The source point.
Returns
-------
plane_equation: tuple
The plane equation.
"""
d = (
orientation_vector[0] * src_point[0]
+ orientation_vector[1] * src_point[1]
+ orientation_vector[2] * src_point[2]
)
plane_equation = (
orientation_vector[0],
orientation_vector[1],
orientation_vector[2],
d,
)
return plane_equation
[docs]
def orientation_vector_of_point_in_polyline(polyline, index_point, windows_size):
"""
Compute and return orientation vector of point in polyline.
Parameters
----------
polyline: numpy.ndarray
The polyline.
index_point: int
The index of the point.
windows_size: int
The size of the search window.
Returns
-------
orientation_vector: tuple
The orientation vector of the input point.
"""
length_polyline = len(polyline)
vectors = []
for j in range(1, windows_size):
x1, y1, z1 = polyline[max(0, index_point - j)]
x2, y2, z2 = polyline[min(length_polyline - 1, index_point + j)]
vectors.append((x2 - x1, y2 - y1, z2 - z1))
orientation_vector = numpy.array(vectors).astype(float).mean(axis=0)
return orientation_vector
[docs]
def intercept_points_along_path_with_planes(
points,
polyline,
windows_size=8,
distance_from_plane=4,
points_graph=None,
without_connection=False,
voxels_size=4,
with_relative_distance=True,
fix_distance_from_src_point=None,
):
"""
Return a list of the intercept points along a path with a plane.
Parameters
----------
points: list
The list of points.
polyline: numpy.ndarray
Array of points.
windows_size: int
The size of the search window.
distance_from_plane: int
The maximum distance from the plane.
points_graph: networkx.graph
The point graph.
without_connection: bool
Whether the graph as connection or not.
voxels_size: int
The size of each voxel.
with_relative_distance: bool
fix_distance_from_src_point: float
(optional) use a fixed distance.
Returns
-------
out: tuple
a tuple with a list of intercepted points and the plane equation.
"""
length_polyline = len(polyline)
intercepted_points = [None] * length_polyline
planes_equation = [None] * length_polyline
distance_from_src_point = (1000,)
for i in range(length_polyline - 1, -1, -1):
point = tuple(polyline[i])
# ======================================================================
orientation_vector = orientation_vector_of_point_in_polyline(
polyline, i, windows_size
)
plane_equation = compute_plane_equation(orientation_vector, point)
if i < length_polyline - 1 and with_relative_distance:
nodes = intercepted_points[i + 1]
prev_radius_dist = max_distance_from_point_to_points(
numpy.array(list(nodes)), polyline[i + 1]
)
if prev_radius_dist == 0:
distance_from_src_point = 1000
else:
distance_from_src_point = min(
prev_radius_dist + 1 * voxels_size, 1000.0
)
if fix_distance_from_src_point is not None:
distance_from_src_point = fix_distance_from_src_point
# ======================================================================
pts = intercept_points_from_src_point_with_plane_equation(
points, point, plane_equation, distance_from_plane, distance_from_src_point
)
if without_connection:
pts = list(map(tuple, pts))
elif points_graph is not None:
pts = list(map(tuple, pts))
pts = connected_points_with_point(pts, points_graph, point)
else:
pts = connected_voxel_with_point(pts, voxels_size, point)
intercepted_points[i] = pts
planes_equation[i] = plane_equation
return intercepted_points, planes_equation
[docs]
def intercept_points_with_ball(points, ball_center, ball_radius):
"""
Return a list of the intercept points by a ball of radius ball_radius and
with center position ball_center.
Parameters
----------
points: numpy.ndarray
Array of the x,y,z position of points.
ball_center: tuple
ndarray - position x, y, z of center position of the ball.
Ball center position should be in the points graph.
ball_radius: float
Radius of the ball
Returns
-------
points: list
list of intercepted points
"""
# Compute points in the ball
points_distance_from_point = numpy.linalg.norm(points - ball_center, axis=1)
index = numpy.where(points_distance_from_point < ball_radius)
return points[index]
[docs]
def intercept_points_along_polyline_with_ball(points, graph, polyline, ball_radius=50):
"""
Return a list of intercept point along a polyline by a ball at each
point.
Parameters
----------
points: numpy.ndarray
Array of points
polyline: numpy.ndarray
Array of points
graph: networkx.graph
Graph of the points
ball_radius: float
The radius of the ball in mm
Returns
-------
intercepted_points: list
list of points intercepted by the ball [[(x, y, z), ...], ...].
"""
intercepted_points = []
for point in polyline:
points_in_ball = intercept_points_with_ball(points, point, ball_radius)
points_in_ball = list(map(tuple, points_in_ball))
points_in_ball = connected_points_with_point(
points_in_ball, graph, tuple(point)
)
intercepted_points.append(points_in_ball)
return intercepted_points