Removing Noise Classes Before Processing

TL;DR: Put {"type": "filters.range", "limits": "Classification![7:7],Classification![18:18]"} directly after the reader in every processing pipeline, so low noise (class 7) and high noise (class 18) never reach SMRF, HAG, covariance features or rasterization. If the data has no noise classification yet, flag outliers first with filters.outlier or filters.elm, then exclude them the same way. When noise must stay in the delivered file, exclude it with where clauses instead of removing it.

# Context and Motivation

This guide is part of Pipeline Filtering Logic. Noise points are rare — a fraction of a percent of a typical tile — and disproportionately destructive. A single low-noise return 30 m below the terrain, from a multipath reflection or a sensor artefact, becomes the lowest point in its SMRF cell and drags the ground surface down into a pit. A high-noise return from a bird or a cloud edge sets a DSM cell 200 m above the canopy and ruins any hillshade or canopy height model built from it. Neighbourhood features computed near either are distorted too.

ASPRS reserves class 7 for low points (noise) and, in LAS 1.4, class 18 for high noise. Vendors routinely classify both, and removing them first is the single cheapest quality improvement available in almost every pipeline.

One point, one pit A terrain profile with ground points along a gentle slope. One low noise return sits 30 metres below the surface. The ground surface interpolated with the noise point included dips sharply into a pit at that location. With class 7 removed first, the surface follows the real ground smoothly. class 7: 30 m below ground solid: surface with noise removed first dashed: surface with the noise point included

# Prerequisites and Assumptions

  • PDAL 2.x.
  • Data classified to LAS 1.4 conventions: class 7 low noise, class 18 high noise. Older LAS 1.2 deliveries use class 7 for all noise and have no class 18.
  • A decision about the delivered file: are noise points removed, or kept and labelled?

# Step-by-Step Implementation

# Step 1 — Check what noise classes exist

pdal info --stats --enumerate Classification tile.laz lists the classes present. If 7 and 18 are absent, the data has not been noise-classified and Step 4 applies.

# Step 2 — Drop noise immediately after the reader

filters.range with negated ranges keeps everything except the listed classes:

json
{ "type": "filters.range", "limits": "Classification![7:7],Classification![18:18]" }

The equivalent expression is Classification != 7 && Classification != 18.

# Step 3 — Keep withheld and overlap handling consistent

Points flagged withheld should usually be excluded in the same stage. Overlap points are a separate decision; see flagging overlap and withheld points.

# Step 4 — Classify noise when the vendor did not

Run filters.outlier (statistical) and filters.elm (extended local minimum, for low points) and let them write class 7, then exclude class 7 as in Step 2.

# Step 5 — Exclude instead of remove when delivering

If the output must contain every point, add where clauses to the processing stages — "where": "Classification != 7 && Classification != 18" — so noise is skipped but written.

# Complete Working Example

A pipeline that classifies noise when missing, excludes it from SMRF and HAG, and still writes every point:

json
{
  "pipeline": [
    { "type": "readers.las", "filename": "tiles/t_0431.laz" },
    { "type": "filters.elm", "cell": 10.0, "threshold": 1.0, "class": 7 },
    { "type": "filters.outlier", "method": "statistical", "mean_k": 12, "multiplier": 3.0,
      "class": 18, "where": "Classification != 7" },
    { "type": "filters.smrf", "slope": 0.15, "window": 18, "threshold": 0.5,
      "where": "Classification != 7 && Classification != 18" },
    { "type": "filters.hag_nn", "count": 2,
      "where": "Classification != 7 && Classification != 18" },
    { "type": "writers.las", "filename": "out/t_0431_classified.laz",
      "minor_version": 4, "dataformat_id": 6, "forward": "all",
      "extra_dims": "HeightAboveGround=float" }
  ]
}

filters.outlier marks outliers with the class you give it — class 7 by default — without removing them; setting class: 18 here labels statistical outliers as high noise. Strictly, statistical outliers can be below the surface too; if your specification distinguishes, run a second pass that relabels class-18 points below the ground surface as class 7.

A quick Python check of noise share per tile across a batch:

python
import json
from pathlib import Path

import numpy as np
import pdal

for tile in sorted(Path("tiles").glob("*.laz")):
    p = pdal.Pipeline(json.dumps({"pipeline": [str(tile)]}))
    p.execute()
    c = p.arrays[0]["Classification"]
    share = np.isin(c, [7, 18]).mean()
    flag = "  <-- check" if share > 0.01 else ""
    print(f"{tile.name:<24} noise {share:6.3%}{flag}")
Noise out before anything looks at neighbours A pipeline row: reader, then the noise range filter highlighted, then SMRF, HAG and covariance features grouped as neighbourhood stages, then writers. A second row shows the same pipeline with noise removal placed after SMRF, marked wrong because SMRF has already used the noise points. right reader drop 7, 18 smrf hag_nn writers wrong reader smrf drop 7, 18 hag_nn writers in the wrong order SMRF has already built its minimum surface from the noise points; removing them afterwards cannot undo the pits it classified around them

# Key Parameter Table

Stage Option Typical value Purpose
filters.range limits Classification![7:7],Classification![18:18] Remove classified noise
filters.elm cell 10.0 m Search cell for isolated low points
filters.elm threshold 1.0 m Height gap marking a point as low noise
filters.outlier mean_k 8–16 Neighbours for the statistical test
filters.outlier multiplier 2.5–3.0 Standard deviations beyond which a point is an outlier
filters.outlier class 7 (default) or 18 Class written to outliers

# Verification

  • No noise downstream. Add a check after the noise stage in development runs: pdal info --stats --enumerate Classification on an intermediate output should list neither 7 nor 18.
  • DTM minimum. The minimum of the DTM should be within a few metres of the lowest ground you expect. A minimum far below it points to a noise point that slipped through.
  • Noise share per tile. Typical shares are 0.01–0.5 percent. A tile with several percent noise may have a sensor problem worth reporting.

# Gotchas and Edge Cases

LAS 1.2 noise. Older files have no class 18; all noise is class 7, and some vendors put high noise in class 7 too. The same range filter handles both.

Unclassified outliers. Many deliveries classify only ground and leave noise as class 1. Excluding classes 7 and 18 then removes nothing. Check the class histogram before assuming noise has been handled.

Removing too much. Statistical outlier removal with a small multiplier on sparse or edge areas removes legitimate points — isolated poles, wires, tree tops. Use conservative settings and inspect what was flagged before trusting it.

Not every isolated point is noise A scene with three isolated elevated features: the top of a lone tree, a wire span and a lamp post. With an aggressive statistical multiplier of 1.5, all three are flagged as noise, shown in red. With a multiplier of 3.0, only a genuine bird return high above is flagged. tree top wire span lamp post bird: real noise

Removing noise from the deliverable. Some specifications require noise to remain in the file, classified. Use where clauses on processing stages rather than removing points when the output is a deliverable, as the example does.

# Frequently Asked Questions

What are ASPRS classes 7 and 18?

Class 7 is low point (noise), for returns below the true surface from multipath and sensor artefacts. Class 18, added in LAS 1.4, is high noise, for returns well above the surface from birds, clouds or atmospheric scatter.

Where in the pipeline should noise be removed?

Immediately after the reader, before any stage that uses neighbourhoods or builds surfaces. Removing it later cannot undo the effect it already had on ground classification or features.

How do I remove noise but keep it in the output file?

Instead of a range filter, add a where clause excluding classes 7 and 18 to each processing stage. The stages skip noise points while the writer still receives and writes them.

Which PDAL filter finds low noise points?

filters.elm, the extended local minimum filter, is designed for isolated low points and writes class 7 by default. filters.outlier catches statistical outliers in any direction.