Skip to main content
1 of 3

I tried naively to use nearest neighbors to separate the ground and the trees. I iteratively set a point to be a tree, ground, or other type of point. A point is a tree or ground point if a percentage of the k=10 nearest neighbors are tree or ground points. The percentage decreases with each iteration. The initial guess of trees are points with intensity < 600 (I also tried points where the convex hull of the nearest neighbors has volume > 1e6, but this produces worse results).

import numpy as np
import pylas
from sklearn.neighbors import NearestNeighbors
import sys


def summary(i, istree):
    print("{0}: trees: {1}, ground: {2}, other: {3}"
          .format(i,
                  (istree == 1).sum() / len(istree),
                  (istree == -1).sum() / len(istree),
                  (istree == 0).sum() / len(istree)))


def main(fname1, fname2):
    with pylas.open(fname1) as f:
        outf = f.read()
    pts = outf.points
    data = np.asarray([pts[i] for i in ("X",
                                        "Y",
                                        "Z")]).transpose().copy()
    coords = data[:, :3]

    Nnbs = 10
    nbrs = NearestNeighbors(n_neighbors=Nnbs,
                            algorithm='ball_tree').fit(coords)
    distances, indices = nbrs.kneighbors(coords)
        
    tree_pts = np.logical_or(pts["intensity"] < 600, False)
    ground_pts = True ^ tree_pts
    
    istree = tree_pts * 1 + -1 * ground_pts
    
    for i in range(3):
        istree = solve(istree, indices,
                       bound=0.2)
        summary(i, istree)

    niter = 10
    for i in range(niter):
        istree = solve(istree, indices,
                       bound=0.2 * (niter - 1 - i) / (niter - 1))
        summary(i, istree)
            
    classification = np.zeros(len(istree), dtype=np.uint8)
    classification += np.uint8(3) * (istree == 1)
    classification += np.uint8(2) * (istree == -1)
    classification += np.uint8(1) * (istree == 0)
    
    outf.points["raw_classification"] = classification
    outf.write(fname2)

    
def solve(istree, indices, bound):
    n_istree = np.zeros(shape=(istree.shape[0],), dtype=np.int8)
    
    for pt, inds in enumerate(indices):
        s = istree[inds].sum() / len(inds)
        if s > bound:
            n_istree[pt] = 1
        elif s < -bound:
            n_istree[pt] = -1
        else:
            n_istree[pt] = 0
            
    return n_istree


if __name__ == "__main__":
    main(sys.argv[1], sys.argv[2])

Unfortunatley this code produces worse results than pdal.

The .las file for testing is available here.