Tuning CSF Cloth Resolution and Rigidness
TL;DR: Sweep resolution (0.5–2.0 m), rigidness (1, 2, 3) and threshold (0.3–0.7 m) on a tile with a trusted reference classification, score each run by type I error (reference ground rejected) and type II error (reference non-ground accepted), and choose the setting with the lowest total error — then confirm it on a second tile of the same landscape before using it project-wide.
# Context and Motivation
This guide is part of CSF Cloth Simulation Ground Filtering. CSF has few parameters, which tempts people to guess them. The trouble is that its two main knobs interact: a fine cloth with low rigidness follows every dip, including gaps between trees; a coarse, stiff cloth rejects vegetation well but floats over ditches and banks. The best combination depends on point density, terrain and land cover, and the only reliable way to find it is to measure against something you trust.
A sweep of three parameters at a few values each is 20–60 runs on one tile — minutes of compute — and turns guessing into a documented choice.
# Prerequisites and Assumptions
- A reference tile per landscape type with trusted ground classification — manually edited, or a vendor classification that passed QA.
- PDAL 2.1+ with Python bindings; pandas.
- Enough cores to run the sweep in parallel; each run is single-threaded.
- The same noise handling as production, applied before every run.
# Step-by-Step Implementation
# Step 1 — Freeze the reference
Store the reference classification in a separate dimension (for example with filters.ferry to RefClass) so every run can be compared point by point in one array.
# Step 2 — Define the grid
Resolution 0.5, 0.75, 1.0, 1.5, 2.0 m; rigidness 1, 2, 3; threshold 0.3, 0.5, 0.7 m. Forty-five runs.
# Step 3 — Run and score
For each combination, run CSF on the reference tile and compute type I = reference ground not classified as ground ÷ reference ground, type II = reference non-ground classified as ground ÷ reference non-ground.
# Step 4 — Pick by total error, check the balance
Minimize type I + type II, but look at both: a DTM tolerates small type I errors (missing a few ground points) far better than type II errors (bumps from vegetation or buildings).
# Step 5 — Validate on a held-out tile
Run the chosen setting on a second reference tile of the same landscape. If errors rise sharply, the first tile was not representative.
# Complete Working Example
"""Grid sweep of filters.csf parameters against a reference classification."""
from __future__ import annotations
import itertools
import json
from concurrent.futures import ProcessPoolExecutor
import numpy as np
import pandas as pd
REF_TILE = "reference/forest_ref_0822.laz" # classification is the trusted reference
GRID = {"resolution": [0.5, 0.75, 1.0, 1.5, 2.0], "rigidness": [1, 2, 3], "threshold": [0.3, 0.5, 0.7]}
def score(params: dict) -> dict:
import pdal
spec = {"pipeline": [
REF_TILE,
{"type": "filters.range", "limits": "Classification![7:7],Classification![18:18]"},
{"type": "filters.ferry", "dimensions": "Classification=>RefClass"},
{"type": "filters.assign", "value": ["Classification = 1"]},
{"type": "filters.csf", "smooth": True, **params},
]}
p = pdal.Pipeline(json.dumps(spec))
p.execute()
a = p.arrays[0]
ref_g = a["RefClass"] == 2
got_g = a["Classification"] == 2
t1 = float((ref_g & ~got_g).sum() / max(ref_g.sum(), 1))
t2 = float((~ref_g & got_g).sum() / max((~ref_g).sum(), 1))
return {**params, "type1": round(t1, 4), "type2": round(t2, 4), "total": round(t1 + t2, 4)}
if __name__ == "__main__":
combos = [dict(zip(GRID, v)) for v in itertools.product(*GRID.values())]
with ProcessPoolExecutor() as pool:
results = pd.DataFrame(list(pool.map(score, combos)))
results = results.sort_values("total")
results.to_csv("tuning/csf_sweep_forest.csv", index=False)
print(results.head(8).to_string(index=False))
best = results.iloc[0].to_dict()
print("best:", {k: best[k] for k in ("resolution", "rigidness", "threshold")})Illustrative top of the table for a mixed forest tile at about 12 pts/m²:
resolution rigidness threshold type1 type2 total
1.00 2 0.5 0.0312 0.0088 0.0400
1.00 2 0.3 0.0421 0.0051 0.0472
0.75 2 0.5 0.0247 0.0241 0.0488
1.50 2 0.5 0.0493 0.0063 0.0556
1.00 1 0.5 0.0206 0.0379 0.0585# Key Parameter Table
| Parameter | Sweep values | What it trades |
|---|---|---|
resolution |
0.5–2.0 m | Following terrain detail vs bridging vegetation gaps |
rigidness |
1, 2, 3 | Following slopes vs rejecting low objects |
threshold |
0.3–0.7 m | Accepting rough ground vs accepting low vegetation |
smooth |
on (fixed) | Slope post-processing; turn off only on flat land |
iterations |
500 (fixed) | Increase only if the cloth has not settled |
# Verification
- Held-out tile. The chosen setting’s total error on a second reference tile should be within about 20 percent of the first.
- Hillshade. Visual inspection of the DTM from the chosen setting catches error types the rates average away, such as a single large building left in the ground.
- Stability. The top few settings in the sweep should be neighbours in parameter space. A best result isolated among poor ones is likely noise.
# Gotchas and Edge Cases
Reference quality bounds your tuning. A vendor reference with its own ground errors trains CSF to reproduce them. Spot-check the reference, especially around bridges, dense shrubs and steep banks, before sweeping.
Error rates depend on landscape mix. A tile that is 90 percent forest and 10 percent town weights errors accordingly. Tune per landscape type — flat farmland, rolling woodland, mountains, towns — and select settings per tile by land cover.
Density changes the optimum. Settings tuned on 12 pts/m² data do not transfer to 4 or 40 pts/m². Retune when density differs by more than a factor of about two.
Overfitting the threshold. Very small thresholds reduce type II errors on the reference tile by being strict, and fail on rougher terrain elsewhere. Prefer settings that are good across several tiles over the best on one.
# Frequently Asked Questions
How do I tune the cloth simulation filter?
Run filters.csf over a small grid of resolution, rigidness and threshold values on a tile with trusted ground classification, score each run by the share of ground missed and non-ground accepted, and pick the setting with the lowest total error. Confirm it on a second tile.
What are type I and type II errors in ground filtering?
Type I errors are true ground points rejected as non-ground; type II errors are non-ground points, such as vegetation or buildings, accepted as ground. Type II errors are usually more harmful to a DTM because they create bumps.
How many runs does a tuning sweep need?
A grid of five resolutions, three rigidness values and three thresholds is 45 runs, which takes minutes on one tile with a few cores. Coarse grids followed by a finer grid around the best result work well.
Should I use one setting for the whole project?
Only if the project is homogeneous. For mixed landscapes, tune per landscape type and choose the setting per tile based on its dominant land cover.
# Related
- CSF Cloth Simulation Ground Filtering — the method and options
- Classifying Ground with filters.csf — a single run end to end
- CSF vs SMRF for Forested Ground — the same scoring applied to two methods
- Benchmarking SMRF Against Reference Ground Points — the equivalent for SMRF
- Threads vs Processes for PDAL Workloads — running sweeps in parallel