├── ALS_CHM.tif ├── ALS_pointclouds.txt ├── Data_seg_ALS_pointclouds.csv ├── Parameter_ALS_pointclouds.csv ├── README.docx ├── README.md ├── VNSC.py ├── reference_tree.csv └── segmentation.py /ALS_CHM.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/limingado/NSC/300e17540a0ff75df1531b671e07f79ec1ded135/ALS_CHM.tif -------------------------------------------------------------------------------- /Parameter_ALS_pointclouds.csv: -------------------------------------------------------------------------------- 1 | TreeID,Position_X,Position_Y,Crown,Height 2 | 0.0,526014.002,4694134.065,2.9622499998658895,18.103 3 | 1.0,526032.889,4694135.477,9.931749999988824,17.272 4 | 2.0,526038.366,4694104.327,3.412000000011176,17.596 5 | 3.0,526029.52,4694111.593,3.6870000001508743,14.957999999999998 6 | 4.0,526024.557,4694127.977,2.0007500001229346,14.545000000000002 7 | 5.0,526046.108,4694141.132,2.7744999998249114,21.029 8 | 6.0,526047.301,4694131.109,2.6365000000223517,19.822 9 | 7.0,526021.835,4694118.218,2.9355000001378357,13.152999999999999 10 | 8.0,526024.769,4694108.556,2.8387500001117587,16.5 11 | 9.0,526036.097,4694128.648,2.7167500001378357,19.512 12 | 10.0,526035.932,4694122.538,2.578749999869615,16.459 13 | 11.0,526034.266,4694136.606,3.326249999925494,19.36 14 | 12.0,526042.177,4694135.235,2.96349999983795,19.459 15 | 13.0,526003.326,4694111.501,2.9262500000186265,16.715 16 | 14.0,526039.429,4694140.963,3.0834999999497086,18.78 17 | 15.0,526042.939,4694109.757,2.8760000001639128,16.778 18 | 16.0,526031.469,4694125.505,2.9299999999348074,16.598 19 | 17.0,526031.632,4694148.247,2.956000000005588,23.253 20 | 18.0,526037.055,4694145.543,3.4007499997969717,21.709 21 | 19.0,526017.549,4694112.592,2.9329999999608845,16.851 22 | 20.0,526003.875,4694141.901,2.9592499998398125,18.709 23 | 21.0,526033.667,4694137.211,3.425000000046566,20.914 24 | 22.0,526003.567,4694120.214,3.0494999999646097,17.52 25 | 23.0,526008.574,4694149.924,3.0190000000875443,20.666 26 | 24.0,526018.923,4694147.587,2.6114999998826534,21.396 27 | 25.0,526015.681,4694140.342,2.8885000001173466,18.869 28 | 26.0,526003.262,4694100.875,2.760749999899417,19.029 29 | 27.0,526045.131,4694145.098,2.569999999832362,22.0 30 | 28.0,526021.841,4694135.788,2.456000000005588,19.264 31 | 29.0,526047.211,4694131.121,3.1447499999776483,19.628 32 | 30.0,526048.758,4694121.414,2.602750000078231,19.295 33 | 31.0,526002.252,4694120.857,2.887749999994412,20.168 34 | 32.0,526009.68,4694103.096,2.8395000000018626,18.962 35 | 33.0,526019.326,4694132.973,2.141249999869615,18.713 36 | 34.0,526025.757,4694103.621,2.8634999999776483,18.086 37 | 35.0,526002.461,4694129.471,3.3707500000018626,16.991 38 | 36.0,526049.286,4694125.353,2.8327500000596046,21.086 39 | 37.0,526043.965,4694124.797,2.2992499999236315,19.103 40 | 38.0,526002.149,4694120.639,2.518750000046566,20.414 41 | 39.0,526019.863,4694123.097,3.5324999999720603,13.81 42 | 40.0,526024.83,4694147.052,2.462999999988824,18.76 43 | 41.0,526006.797,4694144.307,2.807750000152737,20.834 44 | 42.0,526027.256,4694147.913,1.443500000052154,20.237 45 | 43.0,526020.313,4694100.743,2.2832499998621643,17.726 46 | 44.0,526009.354,4694110.205,2.945750000188127,16.528 47 | 45.0,526017.137,4694129.369,2.777500000083819,16.341 48 | 46.0,526035.004,4694110.153,2.349500000011176,16.865 49 | 47.0,526030.252,4694111.344,2.504499999806285,16.428 50 | 48.0,526013.795,4694106.927,2.5052499999292195,16.113 51 | 49.0,526019.778,4694145.788,2.6792500000447035,18.596 52 | 50.0,526015.483,4694119.569,2.38149999990128,14.117 53 | 51.0,526036.442,4694122.33,2.208000000100583,17.093 54 | 52.0,526049.833,4694140.654,2.3364999999757856,21.105 55 | 53.0,526030.741,4694106.713,2.326249999925494,16.769 56 | 54.0,526017.706,4694112.894,2.0360000000800937,16.634 57 | 55.0,526025.665,4694104.676,2.1365000000223517,17.199 58 | 56.0,526029.877,4694130.767,3.0095000001601875,20.773 59 | 57.0,526014.189,4694146.555,3.763499999884516,22.616 60 | 58.0,526024.822,4694143.362,2.147499999962747,21.299 61 | 59.0,526032.845,4694117.429,2.0012499999720603,15.806 62 | 60.0,526018.933,4694133.221,2.8247499999124557,18.446 63 | 61.0,526031.87,4694117.856,2.2804999998770654,14.184 64 | 62.0,526033.065,4694103.5,2.6674999999813735,16.61 65 | 63.0,526033.119,4694144.915,2.444499999983236,8.876 66 | 64.0,526003.805,4694115.808,2.2350000001024455,16.604 67 | 65.0,526018.127,4694139.931,2.6527499998919666,18.234 68 | 66.0,526042.293,4694134.625,2.325999999884516,18.776 69 | 67.0,526006.96,4694134.408,2.2242499999701977,18.305 70 | 68.0,526042.359,4694133.212,2.638000000268221,14.727 71 | 69.0,526001.141,4694105.616,2.4122499998193234,18.601 72 | 70.0,526000.54,4694149.502,1.8065000001806766,19.378 73 | 71.0,526020.552,4694106.888,1.983500000089407,16.225 74 | 72.0,526022.613,4694111.518,1.796749999979511,16.362 75 | 73.0,526040.071,4694141.167,1.8359999998938292,19.683 76 | 74.0,526004.064,4694116.266,1.819249999942258,16.062 77 | 75.0,526001.033,4694138.104,2.1867499998770654,19.259 78 | 76.0,526015.815,4694134.705,1.9880000001285225,18.109 79 | 77.0,526036.346,4694129.079,2.6365000000223517,20.075 80 | 78.0,526029.895,4694131.277,3.1222499997820705,21.265 81 | 79.0,526043.767,4694125.809,2.655500000109896,18.232 82 | 80.0,526030.727,4694106.413,1.7390000000596046,16.487 83 | 81.0,526003.115,4694110.901,2.0294999999459833,16.521 84 | 82.0,526037.599,4694104.806,2.0049999998882413,16.116 85 | 83.0,526000.065,4694134.237,1.911749999737367,17.537 86 | 84.0,526021.926,4694117.24,2.522249999921769,10.725 87 | 85.0,526010.272,4694114.565,3.0844999998807907,16.785 88 | 86.0,526021.934,4694135.465,1.8725000000558794,19.116 89 | 87.0,526025.313,4694142.435,2.314500000094995,20.329 90 | 88.0,526007.398,4694133.84,2.3899999998975545,17.208 91 | 89.0,526049.101,4694121.175,1.8360000001266599,18.727 92 | 90.0,526049.972,4694134.442,1.5719999999273568,19.644 93 | 91.0,526036.598,4694145.365,2.066499999957159,22.193 94 | 92.0,526045.733,4694140.875,1.9837499998975545,21.875 95 | 93.0,526048.712,4694103.524,1.9420000002719462,16.767 96 | 94.0,526032.658,4694147.701,2.018750000046566,20.785 97 | 95.0,526024.491,4694132.071,2.345750000094995,19.194 98 | 96.0,526024.022,4694116.58,1.8152499999850988,12.55 99 | 97.0,526032.322,4694131.048,1.9705000000540167,15.356000000000002 100 | 98.0,526003.547,4694142.012,1.8442499998491257,19.016 101 | 99.0,526030.886,4694121.465,1.8489999999292195,14.867 102 | 100.0,526009.472,4694103.986,1.903999999864027,18.469 103 | 101.0,526043.228,4694134.909,2.2175000002607703,18.054 104 | 102.0,526010.316,4694140.637,1.8015000000596046,19.413 105 | 103.0,526014.564,4694134.712,1.6094999997876585,19.737 106 | 104.0,526046.458,4694117.033,1.4849999998696148,16.306 107 | 105.0,526049.805,4694149.986,2.218499999959022,20.373 108 | 106.0,526028.071,4694100.089,2.1252499998081475,19.093 109 | 107.0,526013.898,4694100.008,2.3182500002440065,17.597 110 | 108.0,526009.1,4694149.99,1.3734999999869615,21.111 111 | 109.0,526011.52,4694130.135,1.481499999994412,15.378 112 | 110.0,526044.819,4694145.657,1.4259999999776483,21.221 113 | 111.0,526039.138,4694149.855,1.821500000078231,18.224 114 | 112.0,526048.007,4694116.008,3.1019999999552965,13.2 115 | 113.0,526009.14,4694110.165,1.3082500000018626,16.604 116 | 114.0,526010.929,4694116.134,1.4592500003054738,14.341 117 | 115.0,526030.412,4694126.309,1.11324999993667,14.925 118 | 116.0,526025.845,4694123.552,1.1642499999143183,6.443 119 | 117.0,526022.395,4694113.129,1.4042500001378357,13.054000000000002 120 | 118.0,526003.994,4694100.318,1.7227500001899898,18.42 121 | 119.0,526026.214,4694147.188,2.213749999878928,22.817 122 | 120.0,526015.014,4694106.697,1.0474999998696148,14.719000000000001 123 | 121.0,526013.511,4694146.015,2.663249999983236,23.488 124 | -------------------------------------------------------------------------------- /README.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/limingado/NSC/300e17540a0ff75df1531b671e07f79ec1ded135/README.docx -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nystrӧm-based spectral clustering 2 | The code is an implementation of the Nystrӧm-based spectral clustering with the K-nearest neighbour-based sampling (KNNS) method (Pang et al. 2021). It is aimed for individual tree segmentation using airborne LiDAR point cloud data. 3 | 4 | # When using the code, please cite as: 5 | Yong Pang, Weiwei Wang, Liming Du, Zhongjun Zhang, Xiaojun Liang, Yongning Li, Zuyuan Wang (2021) Nystrӧm-based spectral clustering using airborne LiDAR point cloud data for individual tree segmentation, International Journal of Digital Earth 6 | 7 | # Code files: 8 | ‘segmentation.py’: the main function, including deriving local maximum from Canopy Height Model (CHM); 9 | ‘VNSC.py’: other functions for the algorithm, including mean-shift voxelization, similarity graph construction, KNNS sampling, eigendecomposition, k-means clustering, as well as the computation and writing of individual tree parameters. 10 | 11 | # Key parameters: 12 | When using the code, users can adjust the values of local maximum window, gap (the upper limit of the number of final clusters), knn (the number of k-nearest neighbours in the similarity graph) and quantile in meanshift method based specific data characteristics. Currently, the value of local maximum window is 3m ×3m, the value of gap is defined as the 1.5 times of the local maximum detected from CHM. Parameter knn can be defined as a constant value (40 in the code) based on the data characteristics, or be determined through the relationship between it and the number of voxels. The default setting of quantile in meanshift method is the average density of point clouds. More details can be found in Pang et al. (2021). 13 | 14 | # Test data: 15 | ‘ALS_pointclouds.txt’: point cloud data; 16 | ‘ALS_CHM.tif’: CHM of the point cloud data; 17 | ‘Reference_tree.csv’: field measurements for algorithm validation. The position was measured using differential GNSS. The tree height of each tree in this file is obtained by regression estimation. 18 | 19 | # Outputs: 20 | ‘Data_seg.csv’: coordinate of each point (x, y, z) as well as its cluster label after segmentation; 21 | ‘Parameter.csv’: individual tree parameters (TreeID, Position_X, Position_Y, Crown, Height) based on the calculation described in Pang et al. (2021). 22 | 23 | -------------------------------------------------------------------------------- /VNSC.py: -------------------------------------------------------------------------------- 1 | from sklearn.cluster import KMeans,MeanShift,estimate_bandwidth 2 | from sklearn.preprocessing import normalize 3 | from sklearn.neighbors import NearestNeighbors 4 | import numpy as np 5 | from numpy import linalg as LA 6 | import gc 7 | import csv 8 | 9 | ############################## Nystrom-Sprectral Clustering ######################################## 10 | def SpectralNystrom(X,g): 11 | nXx=np.shape(X)[0] 12 | std_dev=10 13 | knn=40 14 | nbrs = NearestNeighbors(n_neighbors=knn, algorithm="kd_tree").fit(np.array(X[:,:2])) 15 | distances, indices = nbrs.kneighbors(np.array(X[:,:2])) 16 | 17 | distances0=np.zeros((knn,)) 18 | for jj in range(nXx): 19 | l1=X[jj,2] 20 | l2=X[indices[jj,:],2] 21 | deta_l=abs(l1-l2) 22 | distances0=np.vstack((distances0,deta_l)) 23 | distances0=distances0[1:(nXx+1),:] 24 | 25 | # similarity graph 26 | for j in range(knn): 27 | distances[:,j]=distances[:,j]*distances[:,j]*X[:,3]*X[indices[:,j],3] 28 | distances0[:,j]=distances0[:,j]*distances0[:,j]*X[:,3]*X[indices[:,j],3] 29 | 30 | distances=np.exp(-distances/std_dev)*np.exp(-distances0/std_dev) 31 | del X 32 | gc.collect() 33 | #******************************* KNNS sampling *******************************************# 34 | sumw=np.sum(distances,axis=1) 35 | sumw=np.vstack((sumw,[0 for i in range(nXx)])).T 36 | idsumw=np.lexsort([-sumw[:,0]]) 37 | X1,X2,AA,BB=[],[],[],[] 38 | for ii in range(nXx): 39 | i=idsumw[ii] 40 | if sumw[i,1]==0: 41 | X1.extend([i]) 42 | sumw[i,1]=-1 43 | sim1=[0 for k1 in range(len(X1))] 44 | sim2=[0 for k2 in range(len(X2)+knn)] 45 | for j in range(knn): 46 | if indices[i,j] in X1: 47 | id1=X1.index(indices[i,j]) 48 | sim1[id1]=distances[i,j] 49 | sim2.pop() 50 | elif indices[i,j] in X2: 51 | id2=X2.index(indices[i,j]) 52 | sim2[id2]=distances[i,j] 53 | sim2.pop() 54 | else: 55 | X2.extend([indices[i,j]]) 56 | sim2[len(X2)-1]=distances[i,j] 57 | sumw[indices[i,j],1]=-1 58 | AA.append(sim1) 59 | BB.append(sim2) 60 | 61 | del distances,indices 62 | gc.collect() 63 | samples=len(X1) 64 | remains=len(X2) 65 | A=np.eye(samples) 66 | B=np.zeros((samples,remains)) 67 | for i in range(samples): 68 | A[i,:(i+1)]=AA[i] 69 | B[i,:len(BB[i])]=BB[i] 70 | del AA,BB 71 | gc.collect() 72 | #********************************** Eigendecomposition *************************************# 73 | idx=np.hstack((X1,X2)) 74 | sumw=sumw[idx,0] 75 | d=np.power(sumw,-0.5) 76 | dd=np.dot(d.reshape((len(d),1)),d.reshape((1,len(d)))) 77 | A=A*dd[:samples,:samples] 78 | B=B*dd[:samples,samples:] 79 | detA=LA.det(np.sqrt(A)) 80 | if detA>0: 81 | Asi=LA.inv(np.sqrt(A)) 82 | else: 83 | Asi=LA.pinv(np.sqrt(A)) 84 | Q=A+np.dot(np.dot(np.dot(Asi,B),B.T),Asi) 85 | eigvals, eigvecs = LA.eig(Q) 86 | Lamda=np.diag(np.power(eigvals, -0.5)) 87 | V=np.dot(np.dot(np.dot(np.vstack((A,B.T)),Asi),eigvecs),Lamda) 88 | 89 | #***************************** Clustering segementation *************************************# 90 | eeigval=sorted(eigvals) 91 | if g==1 or len(eeigval)-1ws) 11 | img *=threshed 12 | bc=np.ones((int(ws/d),int(ws/d))) 13 | maxima=mahotas.morph.regmax(img,Bc=bc) 14 | spots,n_spots=mahotas.label(maxima) 15 | return n_spots 16 | 17 | ############################################ main ############################################## 18 | path=os.getcwd() 19 | isExists=os.path.exists(path+'\\results') 20 | if not isExists: 21 | os.mkdir('results') 22 | 23 | for root,dirs,files in os.walk(path): 24 | for file in files: 25 | if os.path.splitext(file)[1]=='.tif': 26 | CHM_name=file 27 | break 28 | for file in files: 29 | if os.path.splitext(file)[1]=='.txt': 30 | print('Start:',file) 31 | X=np.loadtxt(file) 32 | Z=X[:,2] 33 | id0=[] 34 | for ii in range(0,len(Z)): 35 | if Z[ii]>2.5: 36 | id0.extend([ii]) 37 | X=X[id0,:] 38 | ###################################################### 39 | 40 | #read and process CHM 41 | img=gdal.Open(CHM_name) 42 | im_width=img.RasterXSize 43 | im_height=img.RasterYSize 44 | im_geotrans=img.GetGeoTransform() 45 | x0=im_geotrans[0] 46 | y1=im_geotrans[3] 47 | d=im_geotrans[1] 48 | xi=int((X[np.lexsort([X[:,0]])[0],0]-x0)/d) 49 | xj=int((X[np.lexsort([-X[:,0]])[0],0]-x0)/d) 50 | dx=xj-xi 51 | yi=int((y1-X[np.lexsort([X[:,1]])[0],1])/d) 52 | yj=int((y1-X[np.lexsort([-X[:,1]])[0],1])/d) 53 | dy=yj-yi 54 | xl=max(xi,0) 55 | xr=max(min(xj,im_width),0) 56 | yt=max(yj,0) 57 | yb=max(min(yi,im_height),0) 58 | im_data=img.ReadAsArray(xl,yt,xr-xl,yb-yt) 59 | nmax=localmaxima(im_data,d,3) 60 | del img,im_data 61 | gc.collect() 62 | 63 | gap=nmax*1.5 64 | XX=X[:,:3] 65 | VoxelNystromSC(XX,file.split('.')[0],int(gap),path) 66 | 67 | --------------------------------------------------------------------------------