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.

Two surfaces through the same canopy A profile of forest terrain with a gully. Tree crowns sit above sparse ground returns. The true ground runs down into the gully and back up. The CSF surface follows gentle slopes smoothly but rounds off the gully walls. The SMRF surface follows the gully more closely but has a small bump where understorey was accepted as ground. SMRF bump CSF rounds the gully grey: true ground · dashed: CSF · dotted: SMRF (illustrative)

# Prerequisites and Assumptions

# 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

python
"""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:

text
       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 cells

The 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).

Where each method wins Grouped bars of total error for CSF and SMRF on two subsets. On gentle terrain, CSF has 3.6 percent total error and SMRF 4.1 percent. On steep cells above 20 degrees, CSF has 9.8 percent and SMRF 6.9 percent. CSF wins on gentle ground, SMRF on steep ground. 3.6 %4.1 %6.9 % 9.8 % gentle terrain steep cells (> 20°) CSF SMRF

# 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.

Agree, then review Two overlapping sets of ground points from CSF and SMRF. Points both call ground are accepted automatically. Points only one method calls ground form two small crescents, sent for review or resolved by a rule such as preferring SMRF on steep cells and CSF elsewhere. both: accept CSF only SMRF only disagreements: review, or rule by slope

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.