CSF vs SMRF for Forested Ground
TL;DR: Under forest canopy, both filters work from the sparse last returns that reach the ground. CSF tends to produce smoother, more continuous ground on gentle to moderate slopes with fewer parameters to tune; tuned SMRF tends to hold steep gully walls and ridge crests better. Run both on a reference tile, compare type I and type II errors and a DTM difference map, and choose per terrain — or combine them by accepting agreed ground and reviewing disagreements.
# Context and Motivation
This guide is part of CSF Cloth Simulation Ground Filtering. Forest is where ground filters earn their keep. Ground returns may be a small fraction of all returns, unevenly spread, and mixed with understorey only a metre above the soil. Two failure modes dominate: accepting low vegetation as ground, which lifts the DTM into lumpy mounds, and rejecting real ground on slopes and at breaks, which smooths away gullies and stream banks that matter for hydrology and forestry roads. The comparison below measures both for SMRF and CSF on the same data, so the choice is made on evidence.
# Prerequisites and Assumptions
- A forested reference tile with trusted ground, ideally including slopes and a gully or stream.
- PDAL 2.1+ with both filters; Python with NumPy, pandas and rasterio.
- Each filter tuned reasonably for the tile, not left at defaults — see tuning CSF cloth resolution and rigidness and tuning SMRF for forested terrain.
# Step-by-Step Implementation
# Step 1 — Freeze the reference classes
Copy the reference classification into RefClass so both runs compare against it in one array.
# Step 2 — Run both filters with identical preprocessing
Same noise removal, same reset of old ground, same input returns.
# Step 3 — Score both
Type I and type II error overall, and separately on steep cells (slope over 20°) where the methods usually differ most.
# Step 4 — Build both DTMs and difference them
Rasterize each method’s ground at the same grid, subtract, and map cells where they differ by more than 0.5 m.
# Step 5 — Decide
Pick the method with lower errors on the terrain that matters for the deliverable, or combine: agreed ground, then review disagreement areas.
# Complete Working Example
"""Score CSF and SMRF against a forest reference and difference their DTMs."""
from __future__ import annotations
import json
import numpy as np
import pandas as pd
import pdal
import rasterio
REF = "reference/forest_ref_0822.laz"
PRE = [
REF,
{"type": "filters.range", "limits": "Classification![7:7],Classification![18:18]"},
{"type": "filters.ferry", "dimensions": "Classification=>RefClass"},
{"type": "filters.assign", "value": ["Classification = 1"]},
]
METHODS = {
"csf": {"type": "filters.csf", "resolution": 1.0, "rigidness": 2, "threshold": 0.5, "smooth": True},
"smrf": {"type": "filters.smrf", "cell": 1.0, "slope": 0.25, "window": 16, "threshold": 0.45, "scalar": 1.25},
}
def run(name: str, stage: dict) -> np.ndarray:
spec = {"pipeline": PRE + [
stage, {"type": "filters.hag_nn", "count": 2},
{"type": "writers.gdal", "filename": f"out/{name}_dtm.tif", "resolution": 1.0,
"output_type": "idw", "window_size": 6, "data_type": "float32",
"where": "Classification == 2"},
]}
p = pdal.Pipeline(json.dumps(spec))
p.execute()
return p.arrays[0]
def errors(a: np.ndarray) -> dict:
ref, got = a["RefClass"] == 2, a["Classification"] == 2
return {"type1": float((ref & ~got).sum() / ref.sum()),
"type2": float((~ref & got).sum() / (~ref).sum()),
"ground_pts": int(got.sum())}
if __name__ == "__main__":
rows = {name: errors(run(name, stage)) for name, stage in METHODS.items()}
print(pd.DataFrame(rows).T.round(4))
with rasterio.open("out/csf_dtm.tif") as c, rasterio.open("out/smrf_dtm.tif") as s:
d = c.read(1, masked=True) - s.read(1, masked=True)
big = np.ma.abs(d) > 0.5
print(f"CSF − SMRF median {np.ma.median(d):+.3f} m; |Δ| > 0.5 m on {big.mean():.1%} of cells")Illustrative results for a mixed conifer tile with a stream gully:
type1 type2 ground_pts
csf 0.0374 0.0071 1204113
smrf 0.0288 0.0112 1231907
CSF − SMRF median -0.004 m; |Δ| > 0.5 m on 1.8% of cellsThe two methods agree within half a metre on 98 percent of cells; the disagreement is concentrated in the gully (SMRF closer to reference) and in patches of dense understorey (CSF closer).
# Key Parameter Table
| Aspect | CSF | SMRF |
|---|---|---|
| Main parameters | resolution, rigidness, threshold | cell, slope, window, threshold, scalar |
| Tuning effort | low | moderate |
| Gentle forested slopes | smooth, continuous ground | good, occasional understorey bumps |
| Steep gullies and banks | tends to round off | holds breaks better when slope is tuned |
| Dense understorey | rejects well with rigidness 2–3 | needs a low threshold |
| Run time on 1 km² tile | similar order; depends on cloth size | similar order; depends on window |
# Verification
- Same preprocessing. Confirm both runs saw the same number of input points; differences in noise handling make the comparison meaningless.
- Subset scores. Report errors on steep and gentle cells separately, as above; overall rates hide the trade-off.
- Visual check of disagreements. Overlay the |Δ| > 0.5 m mask on a hillshade and inspect a dozen patches in a point cloud viewer to see which method was right.
# Gotchas and Edge Cases
Untuned comparisons. Comparing tuned SMRF with default CSF, or the reverse, measures tuning effort rather than method. Give both a fair sweep.
Leaf-on versus leaf-off. Ground penetration differs hugely between seasons in deciduous forest. A comparison on leaf-off data does not transfer to leaf-on collections.
Reference derived from one method. If the reference was produced by editing SMRF output, it inherits SMRF’s style, and SMRF will score better. Prefer references edited from scratch or from checkpoint surveys.
Combining methods. An intersection of the two ground sets is conservative — few type II errors, more type I. Resolve disagreements with a rule such as “SMRF on steep cells, CSF elsewhere”, computed from a slope raster, rather than a blanket union.
# Frequently Asked Questions
Is CSF or SMRF better for forests?
Neither universally. In typical comparisons CSF gives smoother, continuous ground on gentle to moderate forested slopes with less tuning, while a well-tuned SMRF keeps steep gullies and banks sharper. Test both on a reference tile from your project.
Why do both filters struggle under dense canopy?
Few pulses reach the ground, so both work from sparse, unevenly spaced ground returns mixed with understorey near the soil. Any filter has to interpolate across gaps, and low vegetation is easily mistaken for ground.
Can I use both filters together?
Yes. Accept points both call ground, then resolve disagreements by review or by a rule based on slope or land cover. The difference raster between their DTMs shows where that effort is needed.
How much does the choice matter for the DTM?
On most cells the two agree within a few centimetres. The choice matters in a small share of the area — gullies, banks, dense understorey — which is often exactly where the DTM is used for hydrology or engineering.
# Related
- CSF Cloth Simulation Ground Filtering — how CSF works
- Tuning SMRF for Forested Terrain — getting SMRF right in forest
- Tuning SMRF for Steep Terrain — the steep-slope case
- SMRF vs PMF for Dense Urban LiDAR — the urban comparison
- Measuring Ground Point Density Under Canopy — how much ground there is to work with