1. Read the pointcloud
  2. For every point in the pointcloud
    1. Identify the neighboring points
    2. Run PCA on the points
    3. Identify the orientaiton : eigen vector and eigen values
    4. keep data of all the points and their eigen vector and values
  3. Define eigen vector as feature
  4. Use K-Means clustering with eigen vector and eigenvalues as feature
  5. Visualize the clustered Pointcloud

import open3d as o3d
import os
import numpy as np
import cv2
from scipy.spatial import KDTree, cKDTree

# function : Calculation PCA Function of
# Input :
# data: Point cloud ,NX3 Matrix
# correlation: distinguish np Of cov and corrcoef, When it is not input, it defaults to False
# sort: Eigenvalue sorting , Sorting is for the convenience of other functions , When it is not input, it defaults to True
# Output :
# eigenvalues: The eigenvalue
# eigenvectors: Eigenvector

def PCA(data, correlation=False, sort=True):
    # Homework 1
    # # Shielding starts
    # # axis = 0, So the output matrix is 1 That's ok , Find the average of each column ( Average each line );
    # # axis = 1, The output matrix is 1 Column , Find the average of each line ( Average according to each column ).
    # # It's understandable ,axis How many , That shows which dimensions add up to average .
    # n*3
    data = data.transpose()
    # Subtract the mean of each dimension X
    data = data - data.mean(axis = 1,keepdims=True)
    #X^T
    data_T = data.transpose()
    H = np.matmul(data,data_T)
    # u The size is (M, M),s The size is (M, N),v The size is (N, N).
    # A = u * s * v
    eigenvectors,eigenvalues,_ = np.linalg.svd(H,full_matrices=True)
    # End of shielding
    if sort:
        # argsort It's in descending order [::-1] It's from big to small
        sort = eigenvalues.argsort()[::-1]
        # Eigenvalues arranged from large to small
        eigenvalues = eigenvalues[sort]
        # Eigenvectors corresponding to large to small eigenvalues
        eigenvectors = eigenvectors[:, sort]
    return eigenvalues, eigenvectors

def main():
   # Load the original point cloud
    # point_cloud_o3d = o3d.io.read_point_cloud('21.ply')
    point_cloud_o3d = o3d.io.read_point_cloud('/media/achyut/Extreme SSD/Olsen_farms_Martin_Pointclouds/20220119-131337.ply')
    # point_cloud_o3d = point_cloud_o3d.uniform_down_sample(every_k_points=10)

    o3d.visualization.draw_geometries([point_cloud_o3d]) # Displays the original point cloud

    # pt_cloud_xyz = np.array(point_cloud_o3d.points)
    # color_xyz = np.array(point_cloud_o3d.colors)

    # asd =  pt_cloud_xyz[:,2]>0.1

    # asd = [np.bitwise_and(asd ,pt_cloud_xyz[:,0]<0.8 )]

    # asd[0] =np.squeeze(asd[0])

    # xyz1 = pt_cloud_xyz[asd[0]]
    # xyz2 = xyz1.reshape(-1,3)

    # c1 = color_xyz[asd[0]]
    # c2 = c1.reshape(-1,3)

    # point_cloud_o3d = o3d.geometry.PointCloud()
    # point_cloud_o3d.points = o3d.utility.Vector3dVector(xyz2)
    # point_cloud_o3d.colors = o3d.utility.Vector3dVector(c2)
    # Get points from the point cloud , Only points are processed
    points = np.asarray(point_cloud_o3d.points)

    #n×3
    print('total points number is:', points.shape[0])
    # PCA Analyze the main direction of the point cloud
    #w The eigenvalue , Eigenvector
    # w, v = PCA(points)
    # point_cloud_vector = v[:, 1] # The vector corresponding to the main direction of the point cloud
    # print('the main orientation of this pointcloud is: ', point_cloud_vector)
    
    # # Three dimensional to two dimensional
    # projected_points = np.dot(points, v[:, :2])
    # ##np.vstack(): Stack in the vertical direction
    # # np.hstack(): Tile horizontally
    # projected_points = np.hstack([projected_points,np.zeros((projected_points.shape[0],1))])
    # # Draw a point cloud image
    # projected_point_cloud_o3d = o3d.geometry.PointCloud()
    # projected_point_cloud_o3d.points = o3d.utility.Vector3dVector(projected_points)
    # projected_point_cloud_o3d.colors = o3d.utility.Vector3dVector(np.asarray(point_cloud_o3d.colors))
    
    # o3d.visualization.draw_geometries([projected_point_cloud_o3d])

    # PCA Analysis for normal vector estimation
    # Because nearest neighbor search is the content of Chapter 2 , Therefore, it is allowed to directly call open3d The function in
    # Circularly calculate the normal vector of each point

    pcd_tree = o3d.geometry.KDTreeFlann(point_cloud_o3d)

    # tree = KDTree(points)

    normals = []
    eigv = []

    eigm = []
    # One dimension maximum minus one dimension minimum
    cloud_range = points.max(axis=0)-points.min(axis=0)
    # dadius: set to 5% of the cloud's max range
    radius = cloud_range.max() * 0.05

    for point in point_cloud_o3d.points:
        # Number , Indexes , Distance to this point

        cnt,idxs,dists = pcd_tree.search_radius_vector_3d(point,radius)
        
        # ds,idxs = tree.query(point,500)

        # print("count",cnt)
        # print("dists",len(dists))
        # v:3*3 matrix
        # Find the eigenvalue and direction of these points
        w,v = PCA(points[idxs])
        #v[:,-1]:3*1 matrix
        # The eigenvector corresponding to the minimum eigenvalue
        normal = v[:,0]
        n = v.flatten()
        normals.append(normal)
        w_normal = np.linalg.norm(w)
        w_n = np.divide(w, w_normal, out=np.zeros_like(w), where=w_normal!=0)
        # eigv.append(np.hstack((n,w_n)))

        eigv.append(np.hstack((w,n)))

        i = np.argmax(w_n)
        eigm.append(v[:,i])

    normals = np.array(eigm, dtype=np.float64)
    print("normals",normals)

    # TODO: Here the normal vector is stored in normals in
    point_cloud_o3d.normals = o3d.utility.Vector3dVector(normals)
    point_cloud_o3d.orient_normals_consistent_tangent_plane(50)

    # etc1 = np.array(point_cloud_o3d.normals)

    # o3d.visualization.draw_geometries([point_cloud_o3d])
    o3d.visualization.draw_geometries([point_cloud_o3d], "Open3D normal estimation", width=800, height=600, left=50, top=50,
    point_show_normal=True, mesh_show_wireframe=False,
    mesh_show_back_face=False)
    # # Visualize the points of the normal vector , And store the normal vector points to the file
    # normal_point1 = o3d.utility.Vector3dVector(point_cloud_o3d.normals)
    # normals1 = o3d.geometry.PointCloud()
    # normals1.points = normal_point1
    # normals1.paint_uniform_color((0, 1, 0)) # The points of the point cloud normal vector are displayed in green
    # o3d.visualization.draw_geometries([point_cloud_o3d, normals1], "Open3D noramls points", width=800, height=600, left=50, top=50,
    # point_show_normal=False, mesh_show_wireframe=False,
    # mesh_show_back_face=False)

    eig = np.array(eigv)
    # eig = np.array(etc1)

    # crit = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 1000, 0.2)
    # gr = 3
    # _, labels, (centers) = cv2.kmeans(eig, gr, None, crit, 100, cv2.KMEANS_PP_CENTERS)

    from sklearn.cluster import KMeans
    import matplotlib.pyplot as plt

    kmeans = KMeans(n_clusters=2, random_state=0).fit(eig)
    labels = kmeans.labels_
    centers = kmeans.cluster_centers_

    # centers = np.uint8(centers)/360

    # centers = matplotlib.colors.hsv_to_rgb(centers)

    # flatten the labels array
    labels = labels.flatten()
    max_label = len(np.unique(labels))

    col = plt.get_cmap("tab10")(labels / (max_label if max_label > 0 else 1))
    col1 = col[:,:3]

    col2 = centers[labels.flatten()]

    pcd2 = o3d.geometry.PointCloud()
    pcd2.colors = o3d.utility.Vector3dVector(col1)
    pcd2.points = o3d.utility.Vector3dVector(np.asarray(point_cloud_o3d.points))

    o3d.visualization.draw_geometries_with_editing([pcd2])

if __name__ == '__main__':
    main()