Cloud mask
There are two separate questions: which scenes to use, and which pixels inside a scene to trust.
Available now
Section titled “Available now”Scene filter: cloud cover
Section titled “Scene filter: cloud cover”Every item has eo:cloud_cover, the percentage of the whole scene flagged as cloud by the producer.
- In search, filter with the STAC
queryextension:"query": {"eo:cloud_cover": {"lte": 20}}. CQL2filteris not available in lean v1. - On tiles and TileJSON, use
maxCloudCoverage=20. Tiles take the least cloudy scenes first. - In
POST /v1/process, useinput.data[].dataFilter.maxCloudCoverage.
Scene cloud cover says nothing about your area: a scene at 5% cloud can be cloudy exactly where you look. For that you need a pixel mask.
Pixel mask: the source layers
Section titled “Pixel mask: the source layers”Lean v1 has no unified mask yet: tiles are not pixel-masked, and processing.cloudMask in a process request is rejected with 400 invalid-body. Request the mission’s own classification layer as a band and mask with it yourself. Both are passed through exactly as stored.
Sentinel-2: SCL (scene classification, 20 m, one class value per pixel)
| SCL value | Meaning | Treat as |
|---|---|---|
| 0 | No data | nodata |
| 1 | Saturated or defective | bad |
| 2 | Dark area or terrain shadow | shadow |
| 3 | Cloud shadow | cloud shadow |
| 4 | Vegetation | clear |
| 5 | Not vegetated | clear |
| 6 | Water | water |
| 7 | Unclassified | your choice |
| 8 | Cloud, medium probability | cloud |
| 9 | Cloud, high probability | cloud |
| 10 | Thin cirrus | cirrus |
| 11 | Snow or ice | snow |
Class 7 is ambiguous: SentiWiki calls it unclassified, while Sentinel Hub and Earth Engine treat it as cloud. Decide what suits your analysis.
Landsat 8-9: QA_PIXEL (a bit field, 30 m)
| Bit | Meaning when set |
|---|---|
| 0 | Fill (no data) |
| 1 | Dilated cloud |
| 2 | Cirrus |
| 3 | Cloud |
| 4 | Cloud shadow |
| 5 | Snow |
| 6 | Clear |
| 7 | Water |
| 8-9 | Cloud confidence |
| 14-15 | Cirrus confidence |
Example: mask a Sentinel-2 download
Section titled “Example: mask a Sentinel-2 download”Download red, nir08 and SCL together, with process or the download API, then keep only clear pixels. Process names the band scl in the file:
import numpy as npimport rasterio
with rasterio.open("scene.tif") as src: names = list(src.descriptions) # band names are stored in the file data = {n: src.read(i + 1) for i, n in enumerate(names)}
scl = data[[n for n in names if n.lower() == "scl"][0]]clear = np.isin(scl, [4, 5, 6]) # vegetation, not vegetated, waterSentinel-2 has no buffer around clouds of its own; consider growing the cloud and shadow areas by a few pixels before trusting the edges.
Coming later
Section titled “Coming later”Schema v1 defines one unified mask with 11 classes for every optical collection, whatever the source calls its flags:
| Bit and class | Class id | Meaning |
|---|---|---|
| 0 | nodata |
No valid observation |
| 1 | saturated_defective |
Saturated or defective detector |
| 2 | cloud |
Opaque cloud; on HLS includes cirrus |
| 3 | cloud_shadow |
Shadow cast by cloud |
| 4 | cirrus |
Thin cirrus, detectable only on Sentinel-2 and Landsat 8-9 |
| 5 | cloud_adjacent |
Buffer around cloud and shadow |
| 6 | snow_ice |
Snow or ice |
| 7 | water |
Water |
| 8 | dark_terrain_shadow |
Dark area or terrain shadow |
| 9 | unclassified |
The source could not classify the pixel |
| 10 | clear |
Valid, with no other flag |
It will be exposed three ways: mask (a uint16 bit field), mask_class (one class per pixel) and dataMask (1 for valid and not excluded, 0 elsewhere, as in Sentinel Hub). None of them will count towards units.
Requests will choose classes to drop with cloudMask.exclude, and three settings resolve the known disagreements:
scl7: whether SCL class 7 isunclassified(the default),cloudorclear.dilate_m: 0 to 600 m of buffer around Sentinel-2 cloud and shadow; 150 m matches HLS.cloudConfidence: the Landsat QA_PIXEL confidence that counts as cloud,mediumby default.
Each item will carry sdn:mask_source (scl, qa_pixel, …) and sdn:mask_capabilities, the classes that source can detect. A missing class means “not detectable”, never “absent”.