Using this code, I am able to get a cloud-free composite, but it still includes shadows in my image.

var collection = ee.ImageCollection("COPERNICUS/S2")
  .filterBounds(ee.Geometry.Rectangle(-85.14404296875, 46.9502622421856, 
  -71.87255859375, 41.60722821271717));
var S2_cloud = collection.min();

How do I create a Sentinel-2 cloud free, cloud-shadow free composite or scene on Earth Engine (GEE)?

  • 1
    Can you include screenshots of areas in question? – MaryBeth Feb 21 '18 at 20:57
  • You can find an example for cloud masking Sentinel-2 in the Code Editor Examples. Scripts tab > Examples > Cloud Masking – Nicholas Clinton Feb 26 '18 at 2:54

As far as I know, Sentinel 2 has no computed cloud-shadow mask, so I ommit that part. But for the rest, I think you should at least mask out clouds, and filter a little.

I've made public a repo I have to perform this: users/fitoprincipe/geetools

First the core:

var computeQAbits = function(image, start, end, newName) {
    var pattern = 0;

    for (var i=start; i<=end; i++) {
        pattern += Math.pow(2, i);

    return image.select([0], [newName]).bitwiseAnd(pattern).rightShift(start);

var sentinel2 = function(image) {

  var cloud_mask = image.select("QA60");
  var opaque = computeQAbits(cloud_mask, 10, 10, "opaque");
  var cirrus = computeQAbits(cloud_mask, 11, 11, "cirrus");
  var mask = opaque.or(cirrus);

  return image.updateMask(mask.not());

You can use the function sentinel2 in a map function.

to your question:

Using min(), which may not be the best approach to generate a composite, (but that's another story so I am not diggin on it), this would be a complete 'solution':

var s2mask = require('users/fitoprincipe/geetools:cloud_masks').sentinel2;

var AOI = ee.Geometry.Rectangle(-85.14404296875, 46.9502622421856, 
  -71.87255859375, 41.60722821271717);

var collection = ee.ImageCollection("COPERNICUS/S2")
  .filterMetadata('CLOUDY_PIXEL_PERCENTAGE', 'less_than', 70)

var image = ee.Image(collection.min());
Map.addLayer(image, {bands:['B8', 'B12', 'B4'], min:0, max:3000});

I am quite new to Google Earth Engine but here is the solution I came up nonetheless. Instead of using min() function to composite image, I use percentile. This should reduce shadow captured using min() function while the overall image should not be too choppy (If in the time interval, there is a section where there is no clouds)

One thing I want to improve in the future is to somehow smooth out the mosaic effect on RGB image. But currently, I am out of idea. Another one is rgb_p might not represent the true color as each channel is selected based on its percentile separately. Better method is to group RGB and select it whole

The code are the following

// Setup
var s2mask = require('users/fitoprincipe/geetools:cloud_masks').sentinel2;
var cloud_threshold = 80; // Filter image from collection to have cloud less than 80 percent
var rgb_threshold = 0.23; // Cut-off, Probably unmasked cloud
var rgb_percentile = 35; // Select RGB at 35th percentile
var ndvi_percentile = 90; // Select NDVI at 90th percentile

// Define helpers
function get_collection(start_date, end_date) {  
    var collection = ee.ImageCollection("COPERNICUS/S2")
        .filterDate(start_date, end_date)
        .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', cloud_threshold))
        .map(function (image) {
            return image.divide(10000);
    return collection.map(function(image) {
        var ndvi = image.normalizedDifference(['B8', 'B4']).rename('NDVI');
        image = image.addBands(ndvi.toFloat());
        return image.toFloat();

function get_final(collection) {
    var final = collection.max(); // Baseline value
    // Select value
    collection = collection.map(function(img) {
        return img.updateMask(
    var rgb_p = collection.select(["B2", "B3", "B4"])
    rgb_p = rgb_p.select([
                            "B2_p" + rgb_percentile,
                            "B3_p" + rgb_percentile,
                            "B4_p" + rgb_percentile,
                        ], ["B2", "B3", "B4"])
    var ndvi_p = collection.select(["NDVI"])
                                    "NDVI_p" + ndvi_percentile
                                ], ["NDVI"])
    // Replace old bands
    final = final.addBands({
        srcImg: rgb_p.select("B2").toFloat().rename("B2"),
        overwrite: true
    final = final.addBands({
        srcImg: rgb_p.select("B3").toFloat().rename("B3"),
        overwrite: true
    final = final.addBands({
        srcImg: rgb_p.select("B4").toFloat().rename("B4"),
        overwrite: true
    final = final.addBands({
        srcImg: ndvi_p.select("NDVI").toFloat().rename("NDVI"),
        overwrite: true
    return final.toFloat();

// Operation
var collection = get_collection("2018-01-01", "2018-03-01");
var final = get_final(collection).toFloat();

// Visualize
Map.addLayer(final.select(["B4", "B3", "B2"]), {
    min: 0,
    max: 0.3
}, "RGB");
Map.addLayer(final.select(["NDVI"]), {
    min: -1,
    max: 1,
    palette: ['blue', 'white', 'green']
}, "NDVI");

For shadow-free image, I am not sure. But I think would go something like this. For every pixel threshold outlier first and then select bands using percentile

As of right now in Google Earth Engine, in my opinion it is much better to use "S2_SR" image collections as its corrected for atmospheric correction

If I am missing something, please feel free to comment. Thank you



Landsat and sentinel Cloudness mosaic

Cloudness mask is similar as obtained with Principe`s Script, but using median instead min().

We're looking for long answers that provide some explanation and context. Don't just give a one-line answer; explain why your answer is right, ideally with citations. Answers that don't include explanations may be removed.

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.