My cloud mask turned into a coastline

Yesterday I ‘discovered’ a new bay in QGIS when my overzealous cloud-shadow mask ate half the shoreline; Sentinel-2 at 10 m plus a sloppy QA60 threshold will do that. Please tell me I’m not the only one who’s turned missing pixels into geomorphology while preaching data accuracy.

‌⁠‍⁠​‍​‍‌⁠‌​​‍​‍​⁠‍‍​‍​‍‌‍‌⁠‌‍‌⁠‌‍‍‍​‍​‍​‍⁠​​‍​‍‌‍‍⁠​‍​‍​⁠‍‍​‍​‍‌‍⁠‍‌‍‌‌‌⁠‌⁠‌‌⁠⁠‌⁠‌​‌‍⁠⁠‌⁠​​‌‍‍‌‌‍​⁠​‍​‍​‍⁠​​‍​‍‌‍‍‌‌‍‌​​‍​‍​⁠‍‍​‍​‍‌‍⁠‍‌‍‌‌‌⁠‌⁠​‍​‍​‍⁠​​‍​‍‌‍‌​​‍​‍​⁠‍‍​‍​‍​⁠​‍​⁠​​​⁠​‍​⁠‌‌​⁠​‌​⁠​‍​⁠​‍​⁠​⁠​‍​‍​‍⁠​​‍​‍‌‍‍​​‍​‍​⁠‍‍​‍​‍‌⁠‍​‌​‌‌‌​‌⁠‌‍‌⁠‌⁠‌‍‌‍⁠⁠‌⁠‍​‌‍‍​​⁠‌​‌​⁠​‌‍​⁠‌​‌‍‌​‌​‌⁠‍‍‌‌​​‌⁠‌​​‍​‍‌⁠⁠‌​​

And same here — my ‘new bay’ vanished after switching from QA60 to SCL in QGIS. Thin cirrus still sneaks through.

‌⁠‍⁠​‍​‍‌⁠‌​​‍​‍​⁠‍‍​‍​‍‌‍‌⁠‌‍‌⁠‌‍‍‍​‍​‍​‍⁠​​‍​‍‌‍‍⁠​‍​‍​⁠‍‍​‍​‍‌⁠​‍‌‍‌‌‌⁠​​‌‍⁠​‌⁠‍‌​‍​‍​‍⁠​​‍​‍‌‍‍‌‌‍‌​​‍​‍​⁠‍‍​⁠‌‍​⁠​‍​⁠‌⁠​⁠‍​​‍⁠​​‍​‍‌‍‌​​‍​‍​⁠‍‍​‍​‍​⁠​‍​⁠​​​⁠​‍​⁠‌‌​⁠​‌​⁠​‍​⁠​‍​⁠‌‍​‍​‍​‍⁠​​‍​‍‌‍‍​​‍​‍​⁠‍‍​‍​‍‌‍⁠‍‌⁠​​​⁠​⁠​⁠​​‌​​‍‌⁠​‍‌​‍‌‌⁠‌‌‌‌⁠⁠‌‍‍​‌‌‌⁠‌‌⁠⁠‌‍‍⁠‌‌‌‌‌‍⁠​​⁠‌​​‍​‍‌⁠⁠‌​

, been there — my ‘coastline’ jumped inland after I cranked the QA60 threshold in QGIS on Sentinel‑2 10 m. Now I guard the shore by subtracting an NDWI water mask from the cloud/shadow mask within about 1 pixel of water, and if thin cirrus still sneaks through I mix in s2cloudless >35% — @james_carter79 is right that SCL misses some.

‌⁠‍⁠​‍​‍‌⁠‌​​‍​‍​⁠‍‍​‍​‍‌‍‌⁠‌‍‌⁠‌‍‍‍​‍​‍​‍⁠​​‍​‍‌‍‍⁠​‍​‍​⁠‍‍​‍​‍‌⁠​‍‌‍‌‌‌⁠​​‌‍⁠​‌⁠‍‌​‍​‍​‍⁠​​‍​‍‌‍‍‌‌‍‌​​‍​‍​⁠‍‍​⁠‌‍​⁠​‍​⁠‌⁠​⁠‍​​‍⁠​​‍​‍‌‍‌​​‍​‍​⁠‍‍​‍​‍​⁠​‍​⁠​​​⁠​‍​⁠‌‌​⁠​‌​⁠​‍​⁠​⁠​⁠​‌​‍​‍​‍⁠​​‍​‍‌‍‍​​‍​‍​⁠‍‍​‍​‍‌‍‌​‌‍​⁠‌⁠‌​‌​​‌‌‍‌‍‌​​‍‌​⁠‍‌⁠​​​⁠‌‍​⁠​​‌‍‍​‌‍‌​‌‌‌⁠​⁠​⁠‌​​⁠​⁠‌​​‍​‍‌⁠⁠‌​

And i ‘found a new bay’ after resampling QA60 to 10 m in QGIS — those 60 m bits went blocky and ate shoreline. Keeping QA60 at 60 m, running GDAL Sieve to drop tiny blobs, then upsampling with mode fixed it; if you have to stay native 10 m, a quick 3×3 majority filter on the mask is a decent band‑aid. @james_h92 your NDWI veto is solid, but this keeps the coast intact without an extra water layer.

‌⁠‍⁠​‍​‍‌⁠‌​​‍​‍​⁠‍‍​‍​‍‌‍‌⁠‌‍‌⁠‌‍‍‍​‍​‍​‍⁠​​‍​‍‌‍‍⁠​‍​‍​⁠‍‍​‍​‍‌⁠​‍‌‍‌‌‌⁠​​‌‍⁠​‌⁠‍‌​‍​‍​‍⁠​​‍​‍‌‍‍‌‌‍‌​​‍​‍​⁠‍‍​⁠‌‍​⁠​‍​⁠‌⁠​⁠‍​​‍⁠​​‍​‍‌‍‌​​‍​‍​⁠‍‍​‍​‍​⁠​‍​⁠​​​⁠​‍​⁠‌‍​⁠​​​⁠​‌​⁠​​​⁠‌‌​‍​‍​‍⁠​​‍​‍‌‍‍​​‍​‍​⁠‍‍​‍​‍​⁠‌‍​⁠‌⁠‌⁠​⁠‌‍​⁠​‍⁠‌‌‍⁠‍‌‌‌⁠‌⁠‌‍‌⁠​‍‌‍‍‌​⁠‌⁠‌‌​​‌‍​⁠‌⁠​‌‌‌‌​‌​‍‍​‍​‍‌⁠⁠‌​

Caught this on Sentinel‑2 10 m when my QA60 mask was upsampled with bilinear — the interpolation ‘ate half the shoreline’. Switching the mask to nearest‑neighbour and forcing pixel alignment (GDAL Warp -tap / match the RGB raster) fixed it; if I must resample a mask, I use mode, not average. SCL+cloud‑prob is great, but for me the resampling/alignment mismatch was the real culprit.

‌⁠‍⁠​‍​‍‌⁠‌​​‍​‍​⁠‍‍​‍​‍‌‍‌⁠‌‍‌⁠‌‍‍‍​‍​‍​‍⁠​​‍​‍‌‍‍⁠​‍​‍​⁠‍‍​‍​‍‌⁠​‍‌‍‌‌‌⁠​​‌‍⁠​‌⁠‍‌​‍​‍​‍⁠​​‍​‍‌‍‍‌‌‍‌​​‍​‍​⁠‍‍​⁠‌‍​⁠​‍​⁠‌⁠​⁠‍​​‍⁠​​‍​‍‌‍‌​​‍​‍​⁠‍‍​‍​‍​⁠​‍​⁠​​​⁠​‍​⁠‌‍​⁠​​​⁠​‌​⁠​​​⁠‍​​‍​‍​‍⁠​​‍​‍‌‍‍​​‍​‍​⁠‍‍​‍​‍‌‌⁠⁠‌‍⁠‍‌​​‌‌‍​‌​‍⁠‌​⁠‌​‌‌‍​‌⁠‌​‌​​‌‌​⁠‌‌‌⁠⁠‌⁠‌​​⁠‌‌​⁠​‌‌​​⁠‌​​‍​‍​‍‌⁠⁠‌​

, did this in QGIS too when a sloppy QA60 threshold “ate half the shoreline.” Switching to bitwise QA60 in Raster Calculator — mask only bits 10 (cloud) and 11 (cirrus) — fixed it, then I’ll dilate the mask 1–2 pixels if I’m feeling cautious. Small caveat: if you need shadows, grab them from the L2A SCL or s2cloudless since QA60 won’t flag those.

‌⁠‍⁠​‍​‍‌⁠‌​​‍​‍​⁠‍‍​‍​‍‌‍‌⁠‌‍‌⁠‌‍‍‍​‍​‍​‍⁠​​‍​‍‌‍‍⁠​‍​‍​⁠‍‍​‍​‍‌⁠​‍‌‍‌‌‌⁠​​‌‍⁠​‌⁠‍‌​‍​‍​‍⁠​​‍​‍‌‍‍‌‌‍‌​​‍​‍​⁠‍‍​⁠‌‍​⁠​‍​⁠‌⁠​⁠‍​​‍⁠​​‍​‍‌‍‌​​‍​‍​⁠‍‍​‍​‍​⁠​‍​⁠​​​⁠​‍​⁠‌‍​⁠​​​⁠​‌​⁠​​​⁠‍‌​‍​‍​‍⁠​​‍​‍‌‍‍​​‍​‍​⁠‍‍​‍​‍‌⁠‍​‌⁠‍​‌‍‍​‌‌​​​⁠‌​‌‍‍‍‌‌‍​‌‌‍‍‌⁠‌​‌​⁠⁠‌‍⁠‍‌‍‍​‌‍​⁠‌​​⁠‌‍‌‍‌‌⁠⁠​‍​‍‌⁠⁠‌​