The short answer
Cloud masking is the step that decides whether an optical time series is trustworthy. For Sentinel-2 the practical default is the Level-2A Scene Classification Layer (SCL): remove the classes that represent cloud, cirrus, and shadow, then dilate the mask by a few pixels to catch fuzzy edges. For higher accuracy, combine SCL with a probability model such as s2cloudless and add an explicit shadow projection step, because most cloud detectors do not see shadows at all. For Landsat, the equivalent is the QA_PIXEL / Fmask band shipped with Collection 2.
The two errors that wreck geological analysis are leaving thin cirrus in (it contaminates spectra and indices) and forgetting shadows (dark pixels masquerade as water, mafic rock, or vegetation stress). A good mask handles both, and labels what it could not handle.
Why masking is harder than "is it white"
Clouds are not a single class. An optical scene can contain opaque cumulus, semi-transparent cirrus, haze, cloud edges with mixed pixels, and the shadows those clouds cast on the ground. Each behaves differently:
- Opaque cloud is bright across visible and NIR and is the easy case.
- Cirrus is thin, cold, and partly transparent. It is detectable in the 1.38 micron band (Sentinel-2 B10, Landsat 8/9 B9) because water vapour absorbs that wavelength, so a bright 1.38 micron signal means high-altitude ice. But thin cirrus only partially attenuates the surface, so it slips past brightness thresholds.
- Shadow is dark, not bright, so cloud detectors miss it entirely. It must be inferred from geometry.
- Bright ground — snow, salt flats, white carbonate, bare evaporites — looks spectrally like cloud and is routinely over-masked, which matters for mineral exploration where those surfaces are the target.
This is why a single brightness threshold fails. Geological work in particular cannot afford either error: a cirrus-contaminated SWIR spectrum corrupts alteration-mineral mapping, and an unmasked shadow can read as a false low-reflectance anomaly.
Sentinel-2: the masking options
Work from Level-2A (surface reflectance) products, which already include the SCL.
SCL classes. The SCL assigns each pixel a class 0–11. The ones you typically exclude:
| Class | Meaning | Action |
|---|---|---|
| 3 | Cloud shadows | mask |
| 8 | Cloud, medium probability | mask |
| 9 | Cloud, high probability | mask |
| 10 | Thin cirrus | mask |
| 11 | Snow / ice | mask unless snow is the target |
Classes 4 (vegetation), 5 (bare soil), 6 (water) are kept. SCL is fast and adequate for many uses, but it is conservative at cloud edges and weak on thin cirrus.
MSK_CLDPRB / s2cloudless. For better edge behaviour, use a continuous cloud probability. The L2A product ships an MSK_CLDPRB layer, and the open s2cloudless model produces a 0–100 probability you threshold yourself (a value around 40–60 is a common starting point — lower catches more thin cloud at the cost of more false positives). Continuous probability lets you tune sensitivity to the scene instead of trusting a fixed classification.
Dilation (buffering). Whatever mask you build, grow it outward by a few pixels. Cloud edges contain mixed cloud-and-ground pixels that pass thresholds but are spectrally contaminated. A 3–5 pixel (roughly 30–50 m at 10 m resolution) morphological dilation removes most of them.
Shadows: the step everyone forgets
SCL class 3 gives an approximate shadow mask, but it is unreliable. The robust method is geometric projection:
- Take the detected cloud mask and the per-pixel cloud-top height (or assume a range of heights).
- Using the scene's solar azimuth and zenith angles (in the metadata), project where each cloud would cast a shadow on the ground.
- Intersect that projected zone with pixels that are genuinely dark in the NIR (B8), since shadowed ground reflects little NIR.
The intersection step is what avoids over-masking: dark NIR alone catches water and dark rock; the projection alone catches lit ground. Both together isolate true shadow. In Google Earth Engine this is the well-known approach behind community Sentinel-2 cloud-and-shadow functions, and it is worth replicating because shadow contamination is one of the most common sources of false geological anomalies.
Landsat: QA_PIXEL and Fmask
Landsat Collection 2 ships a QA_PIXEL bitmask produced by the Fmask (Function of mask) algorithm. Each bit flags cloud, cloud shadow, cirrus, snow, or fill. Decode the relevant bits and mask accordingly:
# Example: bitwise test of Landsat C2 QA_PIXEL for cloud and shadow
import numpy as np
cloud = (qa >> 3) & 1 # bit 3: cloud
cloud_shdw = (qa >> 4) & 1 # bit 4: cloud shadow
cirrus = (qa >> 2) & 1 # bit 2: cirrus
mask = ~((cloud | cloud_shdw | cirrus).astype(bool))
Fmask already integrates a thermal-band shadow test, so Landsat shadow handling is generally better out of the box than Sentinel-2's, where the thermal band is absent. (Always confirm the exact bit positions against the current Collection 2 product guide, as they are version-specific.)
A worked compositing example
For a regional geological baseline you usually want a clean, cloud-free composite rather than a single scene.
- Select all Sentinel-2 L2A scenes over the area for a season (to control for sun angle and moisture).
- For each scene, build the mask: SCL classes {3, 8, 9, 10, 11}, OR-combined with s2cloudless > 50, then dilate 4 pixels, then add the projected shadow mask.
- Apply the mask per scene.
- Reduce the stack to a per-pixel median (median is robust to residual cloud that one or two scenes missed).
- Reproject the composite to your project CRS, e.g.
gdalwarp -t_srs EPSG:32631 -r bilinear.
The median over a masked stack is the workhorse: even an imperfect per-scene mask rarely leaves the same pixel cloudy across many dates, so the median lands on clear ground.
Common mistakes and why they happen
- Trusting SCL alone. It is conservative at edges and leaks thin cirrus; pairing it with a probability band and dilation fixes most of that.
- Masking clouds but not shadows. Shadows are dark, so brightness-based detectors never flag them; they then read as false low-reflectance anomalies. Always add a shadow step.
- Over-masking bright targets. Snow, salt, carbonate and evaporites trip cloud thresholds. In exploration this masks the very surfaces you care about — tune thresholds against known clear pixels.
- Comparing dates without normalising conditions. Different sun angle, season, and atmospheric moisture change reflectance even on clear days; restrict composites to comparable seasons.
- Compositing without surface-reflectance correction. Mixing L1C top-of-atmosphere with L2A products produces spectral steps; stick to one processing level.
Validation
After masking, check the result rather than assuming it worked. Overlay the mask on the RGB scene and confirm cloud edges and shadows are covered without large holes over bright ground. Sample a few pixels of known clear surface and confirm they survived. For composites, inspect the scene-count layer (how many clear observations contributed per pixel) — areas with one or zero contributions are unreliable and should be flagged, not silently filled. Document the masking parameters (classes, threshold, dilation radius) so the result is reproducible.
Bathyl perspective
We treat the cloud mask as a documented part of the deliverable, not an invisible pre-step. The output should state which classes and thresholds were removed, where shadow projection was applied, and which pixels had too few clear observations to trust. That turns a pretty composite into evidence a geologist can actually defend.
Related reading
- Atmospheric Correction for Geological Imagery
- Spatial Resolution vs Spectral Resolution
- How GIS Is Used in Geology
- Remote sensing and Earth data