Open-source project
jianboqi/CSF avatar
jianboqi/CSF

CSF strips ground from airborne LiDAR by dropping a cloth over the point cloud

LiDAR point cloud ground filtering / segmentation (bare earth extraction) method based on cloth simulation

651 stars152 forksC++Apache-2.0

At a glance

What is it?
CSF is a C++ implementation of a bare earth extraction method that simulates a cloth settling over a point cloud and reads the terrain shape off the cloth, published in Remote Sensing in 2016. It suits LiDAR processing pipelines that need a ground classifier they can call from Python, R, Matlab or a C++ build, and it is a research code that has drifted from its published form.
Who is it for?
Teams building a LiDAR processing chain that need a ground classifier with a C++ core and a scriptable interface, especially those already working with laspy or lidR, are the people CSF is for, and the fact that the same algorithm is exposed through Python, R, Matlab, CMake and a CloudCompare plugin is what makes it usable in an existing pipeline.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 42 days ago.
What is it written in?
Mainly C++, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The ground is read off a simulated cloth, not a fitted plane

Most ground filters approach the problem as curve or surface fitting, and they inherit the weakness of that framing on terrain that is not smooth. CSF takes a different route. The method simulates a cloth grid over the point cloud and lets it fall under gravity until it rests on the terrain, and the points that end up close to that resting surface are the ground. Because the cloth is a grid that can bend, it follows ridges and slopes instead of averaging them away, which is why the README links the method to applications such as digital terrain model and canopy height model generation. The provenance is specific. The code is the implementation of the paper by Zhang, Qi, Wan, Wang, Xie, Wang and Yan titled An Easy-to-Use Airborne LiDAR Data Filtering Method Based on Cloth Simulation, published in Remote Sensing volume 8 number 6 page 501 in 2016. The people this serves are LiDAR processing engineers who need a ground classifier they can drop into an existing toolchain rather than a research artefact to reimplement.

The Python binding is the entry point most people use

The project grew a Python interface through SWIG, and the README is explicit that this was added so the method can be embedded in a larger project, naming laspy as the motivating example. The workflow is short. You read a point cloud into an array of coordinates, create a CSF object, set the parameters, hand over the cloud, and receive two index lists back, one for ground points and one for non-ground points, which you then use to slice your array and write a new file. The example below shows that path end to end with laspy.

python
# coding: utf-8
import laspy
import CSF
import numpy as np

inFile = laspy.read(r"in.las") # read a las file
points = inFile.points
xyz = np.vstack((inFile.x, inFile.y, inFile.z)).transpose() # extract x, y, z and put into a list

csf = CSF.CSF()

# prameter settings
csf.params.bSloopSmooth = False
csf.params.cloth_resolution = 0.5
# more details about parameter: http://ramm.bnu.edu.cn/projects/CSF/download/

csf.setPointCloud(xyz)
ground = CSF.VecInt()  # a list to indicate the index of ground points after calculation
non_ground = CSF.VecInt() # a list to indicate the index of non-ground points after calculation
csf.do_filtering(ground, non_ground) # do actual filtering.

outFile = laspy.LasData(inFile.header)
outFile.points = points[np.array(ground)] # extract ground points, and save it to a las file.
out_file.write(r"out.las")

The two VecInt outputs are index lists into your own input array, which is the detail that makes the binding useful: you keep your own point attributes and only borrow the classification.

Plain text point files skip the array machinery entirely

If the LiDAR data is already a text file with x, y and z on each line, the API gets simpler still, because the file can be read directly and the results written back as text. The example below is the whole flow, and it is the one to reach for if you are not working with laspy.

python
import CSF

csf = CSF.CSF()
csf.readPointsFromFile('samp52.txt')

csf.params.bSloopSmooth = False
csf.params.cloth_resolution = 0.5

ground = CSF.VecInt()  # a list to indicate the index of ground points after calculation
non_ground = CSF.VecInt() # a list to indicate the index of non-ground points after calculation
csf.do_filtering(ground, non_ground) # do actual filtering.
csf.savePoints(ground,"ground.txt")

The two parameters visible here are the ones you will always touch. bSloopSmooth turns the slope smoothing off, and cloth_resolution sets the grid spacing of the simulated cloth, which is the parameter that decides how finely the terrain shape can be captured. The documentation points to the project download page for the full parameter list rather than repeating it, and it is worth reading before you accept the demo values, since a cloth resolution that is too coarse for your point spacing will cut through ridges rather than sit on them.

One pip package, and an R wrapper that talks to lidR

Installation is a single pip command, and the README credits the contribution that made it possible.

bash
pip install cloth-simulation-filter

Beyond Python, the method is reachable from several other places, and this is where the project's value shows. There is a Matlab path, with the details in the demo_mex.m file under the matlab folder, which is how the original publication's audience would use it. There is an R package, RCSF, built by Jean-Romain through collaboration and designed to work with the lidR package, where the ground classification is a single call on a LAS object.

r
library(lidR)
las  <- readLAS("file.las")
las  <- lasground(las, csf())

There is also a CloudCompare plugin, so a point cloud can be filtered inside the point cloud viewer, and a separate tool, CSFTools, that is based on CSF and adds digital elevation model and canopy height model generation along with normalisation. The variety of front ends is the point. One algorithm, exposed through whichever language your pipeline already uses, rather than a reference implementation you have to port.

Building the C++ library with CMake, and the OpenMP asymmetry

The native build is a CMake project that produces a static library other C++ programs can link against. On Linux the documented sequence creates a build directory, configures with cmake, compiles, and installs.

bash
mkdir build #or other name
cd build
cmake ..
make
sudo make install

Turning on the demo executable takes one extra flag, and the README notes that it is optional.

bash
mkdir build #or other name
cd build
cmake -DBUILD_DEMO=ON ..
make
sudo make install

On Windows the documentation points at the CMake GUI to generate a Visual Studio solution instead. The packaging file is where the platform differences become concrete. The build defines a CSF_USE_OPENMP macro on Windows, with the /openmp flag and the /std:c++11 standard, and on Linux with -fopenmp and the same C++11 standard, while macOS is explicitly handled as the case where clang does not ship with OpenMP, so it gets the C++11 standard with no OpenMP flags and no macro. The practical effect is that a macOS build runs single threaded, and anyone benchmarking or processing large clouds on a Mac should know that before they compare timings against Linux.

The code has moved on from the 2016 paper

The README carries a warning that is easy to skim past and important not to. It states that the code has been changed a lot since the publication of the corresponding paper, that a lot of optimizations have been made, that the authors are still working on it, and that they wish it could be better. That sentence tells you the version you pull is not the version the 2016 evaluation was run against. If you are reproducing results from the paper, you need to check which changes matter for your terrain, and if you are comparing CSF against another filter, the comparison is against the current code and not necessarily against the published numbers. The release history shows the same pattern in the version names, with v1.3.6 and v1.3.5 both labelled as compatible with Numpy 2.0 and v1.2.6 titled as adding a correct way to input a numpy array, which suggests the binding has been the source of the churn rather than the algorithm itself.

Where CSF is the wrong tool

The first case is terrain where a cloth is the wrong physical model, which in practice means very steep slopes, cliffs, tall buildings with flat roofs that sit close to the ground, and dense vegetation that the cloth cannot fall through. On a flat site with clean returns the method is unremarkable, and on a forest with understory it is a choice you should make deliberately rather than by default. The second case is a production pipeline that needs a stable contract, since the project states it is still being worked on and publishes releases without a documented support policy, so you should expect to pin a version and own the upgrades. The third is a team that needs the ground classification to be reproducible across machines, because the OpenMP asymmetry between Linux and macOS means the same input can produce the same classification but at very different speeds, and the parameter defaults are tuned for a demo point density rather than yours.

Editorial conclusion

Teams building a LiDAR processing chain that need a ground classifier with a C++ core and a scriptable interface, especially those already working with laspy or lidR, are the people CSF is for, and the fact that the same algorithm is exposed through Python, R, Matlab, CMake and a CloudCompare plugin is what makes it usable in an existing pipeline. It is the wrong choice for a production pipeline that needs a stable, versioned API, because the README states plainly that the code has changed a lot since the paper and that work on it continues, and the last push was on 2026-09-11 with the newest tagged release at v1.3.6 from 2025-08-13. Check three things before you commit: that the numeric results still match what the 2016 paper describes for your terrain, that the cloth resolution and iteration parameters are tuned for your point density rather than the demo values, and that the C++ build works on your platform, since OpenMP is enabled on Windows and Linux but not on macOS. The licence is Apache 2.0, the package installs from pip as cloth-simulation-filter, and the project is maintained by Jianbo Qi.

Frequently asked questions

How do I install CSF for Python?

The README says you can install it from pip with pip install cloth-simulation-filter, thanks to a contribution that wrapped the C++ code with SWIG. The package builds a _CSF extension from the sources in the src directory and exposes the CSF module.

What does CSF return when it filters a point cloud?

Two index lists, one for ground points and one for non-ground points, both of the CSF.VecInt type. You pass them to do_filtering, then use them to index your own point array, which is how the laspy example extracts the ground points into a new las file.

How do I use CSF from R with lidR?

The R package is RCSF, built by Jean-Romain, and it works with the lidR package. The README example reads a LAS file with readLAS and calls lasground with the csf() function.

Can I use CSF inside CloudCompare?

Yes. The README says the method has been implemented as a CloudCompare plugin, with a pointer to the CloudCompare trunk repository. A related project, CSFTools, is based on CSF and adds digital elevation model and canopy height model generation and normalisation.

Does CSF use OpenMP on macOS?

No. The packaging file enables OpenMP on Windows and Linux and handles macOS as the case where clang does not come with OpenMP, so the macOS build compiles with the C++11 standard and no OpenMP flags or macro.

Official sources

  1. jianboqi/CSF on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/jianboqi-csf.svg)](https://hysenlabs.com/projects/jianboqi-csf)

Was this useful?