4

I am trying to create and map an image collection (not image or mosaic if that is even possible), where QA60 defines the Cloud-mask. I have written the code below - however, it seems the Jan2016 images do not correspond the cloud mask, even though the pixel inspector shows the same image time stamp for all layers. Hence, "NoCLouds" layer contains many pixels with clouds and has many other without masked. Maybe my mask is wrong?

var Jan2016 = ee.ImageCollection('COPERNICUS/S2')
      .filterBounds(NSW)
      .filterDate('2016-01-01', '2016-01-30');

var trueColor = {bands: ['B4', 'B3', 'B2'], min: 0, max: 3000};
var falseColor= {bands: ['B8', 'B3', 'B4'], min: 0, max: 3000};
//Define the CLoud layer (here: QA60 is MSI2's quality mask)
var Clouds = {bands: ['QA60']};

//Add the cloud mask as layer; PROBLEM: SEEMS QA60 is taken from different images every time..
//Clouds is just a band, do i need to specify the threshold?
Map.addLayer(Jan2016, Clouds, "CloudsJan16");

// Mask clouds
var noclouds = Jan2016.map(function(img) {
              var mask = 
img.select(['QA60']).neq(11); 
//.neq(xx) doesn't seem to make a difference
                   return img.updateMask(mask);

});

//Add the layer/imagery to the map with variable, display type, and name  
Map.addLayer(noclouds, trueColor, "NoClouds");
Map.addLayer(Jan2016, falseColor, "Jan2016false");
1
  • You should define NSW to make this example reproducible – aldo_tapia Dec 27 '17 at 11:16
8

Here is an example of cloud masking for Sentinel-2:

var s2 = ee.ImageCollection("COPERNICUS/S2");
var pt = ee.Geometry.Point([-122.41653442382812, 37.77180027337861]);

var cloudyImage = ee.Image('COPERNICUS/S2/20151207T190417_20151207T221905_T10SEG');
Map.centerObject(cloudyImage, 10);
Map.addLayer(cloudyImage, {bands: ['B4', 'B3', 'B2'], max: 2000}, 'cloudy image');

// Bits 10 and 11 are clouds and cirrus, respectively.
var cloudBitMask = ee.Number(2).pow(10).int();
var cirrusBitMask = ee.Number(2).pow(11).int();

var qa = cloudyImage.select('QA60');

Map.addLayer(qa.bitwiseAnd(cloudBitMask).neq(0), {}, 'clouds');
Map.addLayer(qa.bitwiseAnd(cirrusBitMask).neq(0), {}, 'cirrus');

function maskS2clouds(image) {
  var qa = image.select('QA60');
  // Both flags should be set to zero, indicating clear conditions.
  var mask = qa.bitwiseAnd(cloudBitMask).eq(0).and(
             qa.bitwiseAnd(cirrusBitMask).eq(0));
  return image.updateMask(mask);
}

var cloudMasked = s2.filterBounds(pt).map(maskS2clouds);

var median = cloudMasked.median();
Map.addLayer(median, {bands: ['B4', 'B3', 'B2'], max: 2000}, 'median');
1
  • 1
    Nick, there are some accuracy problems when using QA60 band to mask clouds. Do you suggest some alternative when computing cloud and shadows actually on S2 data? Maybe the Gennadii shadow code, or something like it? – Etore Marcari Jr. Dec 4 '18 at 12:12
2

This is an old question, but for the sake of completeness, here is comprehensive tutorial on how to mask clouds and their shadows over an entire Sentinel-2 image collection: https://developers.google.com/earth-engine/tutorials/community/sentinel-2-s2cloudless

It uses the Sentinel-2 Cloud Probability image collection to mask clouds (rather than the QA60 band), which is a separate but compatible dataset.

The tutorial is in Python, but I've re-written it for those interested in a JavaScript version: https://code.earthengine.google.com/f0fa2a9ae2f478b89f9f8ba8aab644a5

There's a minor addition to the JS version: for those wanting to use the Top Of Atmosphere (TOA) instead of surface reflectance, it won't work because line 53 requires the SCL (Scene CLassification map) to remove water pixels when detecting cloud shadows, and SCL is absent from the TOA dataset. Instead, use line 54 which thresholds NDWI to identify water pixels.

3
  • Thanks for you reply @david. The final result 'cloud transform' masks out all the clouds and shadows, right? When I use Inspector on the white areas I expected no values if they are masked out, but it is not the case. So I have a doubt about how to use this final result. Also do you know the difference between this script and the 'maskS2clouds' function using QA60 band? Which one is preferred to use? – Paris Apr 23 at 10:22
  • 1
    I think the final result you want is the 'cloud mask', as that excludes both the clouds and their shadows. This script used the cloud probably data set developers.google.com/earth-engine/datasets/catalog/… , which is a separate data set which has been resampled to match the 10m resolution of S2. The QA60 band is a coarser resolution and, as mentioned above, has some accuracy issues so suggest you use this script instead. – David Kinsler Apr 24 at 12:33
  • Thanks @David Kinsler. I will use the script you provided. – Paris Apr 28 at 7:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.