Finding the closest point from Point Cloud Data to point (0,0,0)

Question:

I made a program to plot a figure from point cloud data (x,y,z coordinates shown as list x_list, y_list, z_list). Now, I have to find the closest point to (0,0,0). Anybody has an idea to do that? Here is the program:

import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import math
from mpl_toolkits.mplot3d import axes3d


cloud = np.loadtxt('c1.txt')
rows = len(cloud)
columns = int(len(cloud[0])/5)

x_list = []
y_list = []
z_list = []

for i in range(rows):
    for j in range(columns):
    x_list.append(cloud[i][j])
    y_list.append(cloud[i][j+columns])
    z_list.append(cloud[i][j+2*columns])

#x_list = x_list[~pd.isnull(x_list)]


X = x_list
Y = y_list
Z = z_list

#Eliminating 'nan' values 
newlist_x = [X for X in x_list if math.isnan(X) == False]
newlist_y = [Y for Y in y_list if math.isnan(Y) == False]
newlist_z = [Z for Z in z_list if math.isnan(Z) == False]


display(newlist_x, newlist_y, newlist_z)


fig = plt.figure()
ax = fig.add_subplot(projection='3d')

ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')


ax.scatter(newlist_x, newlist_y, newlist_z, c=newlist_z, cmap='plasma', linewidth=0.01) 
#3D plotting of points
plt.rcParams["figure.figsize"] = (12,15) #making plot more viewable

plt.show()
Asked By: mika hirvonen

||

Answers:

This should do the trick.

def closest(X, Y, Z, P):
    """
        X: x-axis series
        Y: y-axis series
        Z: z-axis series
        P: centre point to measure distance from
        
        Returns: tuple(closest point, distance between P and closest point)
    """ 
    # Here we combine the X, Y, Z series to get a data struct. with all our points (similar to using the builtin's zip())
    points = np.column_stack((X, Y, Z))
    # Now we compute the distances between each of the points and the desired point
    distances = [np.linalg.norm(p) for p in (points - P)]
    # Finally we get the index of the smalles value in the array so that we can determine which point is closest and return it
    idx_of_min = np.argmin(distances)
    return (points[idx_of_min], distances[idx_of_min])

You can call closest as such:

X = [-1, -6, 8, 1, -2, -5, -1, 5, -3, -10, 6, 3, -4, 9, -5, -2, 4, -1, 3, 0]
Y = [-2, -1, -9, -6, -8, 7, 9, -2, -9, -9, 0, -2, -2, -3, 6, -5, -1, 3, 8, -5]
Z = [-1, 2, 3, 2, 6, -2, 9, 3, -10, 4, -6, 9, 8, 3, 3, -6, 4, 1, -10, 1]
closest_to_point(X, Y, Z, (0, 0, -100))

An example output looks like this:
(array([ 3, 8, -10]), 90.40464589831653)

Answered By: Mieszko

This should do the trick quite a lot faster

def closest_pythagoras(X, Y, Z, P):
    """
        X: x-axis series
        Y: y-axis series
        Z: z-axis series
        P: centre point to measure distance from
        
        Returns: tuple(closest point, distance between P and closest point)
    """ 
    # Compute the distances between each of the points and the desired point using Pythagoras' theorem
    distances = np.sqrt((X - P[0]) ** 2 + (Y - P[1]) ** 2 + (Z - P[2]) ** 2)
    # Get the index of the smallest value in the array to determine which point is closest and return it
    idx_of_min = np.argmin(distances)
    return (np.array([X[idx_of_min], Y[idx_of_min], Z[idx_of_min]]), distances[idx_of_min])

If performance is critical in your application, consider the above changes to Mieszko’s answer.
This code will run several orders of magnitude faster for large X Y Z arrays.

The original answer contains a vanilla python loop that quickly ruins your performance when the size of points increases. Removing the loop and letting numpy do the heavy lifting lets the cost increase linearly with the input size allowing it to scale much better. On top of this, using Pythagoras’ theorem directly roughly doubles the performance over np.linalg.norm, not because it’s any faster, but because syntactically it can omit the np.column_stack step and the connected cost.

  • Red: Mieszko’s answer / [np.linalg.norm(p) for p in (points - P)]
  • Orange: Optimized version of Mieszko’s answer / np.linalg.norm(points - P, axis=1)
  • Green: Good old Pythagoras / np.sqrt((X - P[0]) ** 2 + (Y - P[1]) ** 2 + (Z - P[2]) ** 2)

runtimes corresponding to input size

All measurements were taken with timeit, taking the lowest of 5 runs at any given input size.
Input sizes started at 10 and were increased by an order of magnitude until function execution time exceeded 1 second. (In some places intermediate values were added to keep the graph tidy)
Measurements were taken using one thread of a Ryzen 9 5900X.

Answered By: NeonApe
Categories: questions Tags: , , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.