My goal is to create an image collection that has 100% free cloud over a small region of interest, for example a lake.

This script filters landsat 8 images based on location and cloud cover:

var nocloudimages =  landsat8.filterBounds(ROI)
                     .filter(ee.Filter.lt('CLOUD_COVER', value))
                     .sort('system:time_start', true)

However, as we know, the 'CLOUD_COVER' accounted for the whole landsat 8 image percentage of cloud, not a particular Region of Interest (ROI) cloud cover.

Is there a method to achieve that?

3 Answers 3


It's going to be something like this, but you'll need to play with the threshold (10 in this example) to meet your needs. Watch out for ROIs that overlap a scene's footprint, but do not contain any valid pixels. Also watch out for ROIs that are very large or span multiple WRS cells.

var ic = ee.ImageCollection("LANDSAT/LC08/C01/T1_RT_TOA");

// A polygon representing the roi.
var geometry = ee.Geometry.Polygon(
        [[[-121.85897778617999, 37.70881514186375],
          [-121.83975284337708, 37.76202899390253],
          [-121.94137041100134, 37.759857750255144]]]);

var c = ic.filterBounds(geometry);

var withCloudiness = c.map(function(image) {
  var cloud = ee.Algorithms.Landsat.simpleCloudScore(image).select('cloud');
  var cloudiness = cloud.reduceRegion({
    reducer: 'mean', 
    geometry: geometry, 
    scale: 30,
  return image.set(cloudiness);

var filteredCollection = withCloudiness.filter(ee.Filter.lt('cloud', 10));

There is standard example in the code editor that is pretty close to what you want. https://code.earthengine.google.com/1be28850c6c7880d8fcd5f1e0a808986

// SimpleCloudScore, an example of computing a cloud-free composite with L8
// by selecting the least-cloudy pixel from the collection.

// A mapping from a common name to the sensor-specific bands.
var LC8_BANDS = ['B2',   'B3',    'B4',  'B5',  'B6',    'B7',    'B10'];
var STD_NAMES = ['blue', 'green', 'red', 'nir', 'swir1', 'swir2', 'temp'];

// Compute a cloud score.  This expects the input image to have the common
// band names: ["red", "blue", etc], so it can work across sensors.
var cloudScore = function(img) {
  // A helper to apply an expression and linearly rescale the output.
  var rescale = function(img, exp, thresholds) {
    return img.expression(exp, {img: img})
        .subtract(thresholds[0]).divide(thresholds[1] - thresholds[0]);

  // Compute several indicators of cloudyness and take the minimum of them.
  var score = ee.Image(1.0);
  // Clouds are reasonably bright in the blue band.
  score = score.min(rescale(img, 'img.blue', [0.1, 0.3]));

  // Clouds are reasonably bright in all visible bands.
  score = score.min(rescale(img, 'img.red + img.green + img.blue', [0.2, 0.8]));

  // Clouds are reasonably bright in all infrared bands.
  score = score.min(
      rescale(img, 'img.nir + img.swir1 + img.swir2', [0.3, 0.8]));

  // Clouds are reasonably cool in temperature.
  score = score.min(rescale(img, 'img.temp', [300, 290]));

  // However, clouds are not snow.
  var ndsi = img.normalizedDifference(['green', 'swir1']);
  return score.min(rescale(ndsi, 'img', [0.8, 0.6]));

// Filter the TOA collection to a time-range and add the cloudscore band.
var collection = ee.ImageCollection('LC8_L1T_TOA')
    .filterDate('2013-05-01', '2013-07-01')
    .map(function(img) {
      // Invert the cloudscore so 1 is least cloudy, and rename the band.
      var score = cloudScore(img.select(LC8_BANDS, STD_NAMES));
      score = ee.Image(1).subtract(score).select([0], ['cloudscore']);
      return img.addBands(score);

// Define visualization parameters for a true color image.
var vizParams = {'bands': ['B4', 'B3', 'B2'], 'max': 0.4, 'gamma': 1.6};
Map.setCenter(-120.24487, 37.52280, 8);
Map.addLayer(collection.qualityMosaic('cloudscore'), vizParams);

I've created the following function to add the cloud+shadow+snow percentage for a particular region as a new image property so you can filter the image collection based on that property. The 'region' in the function refers to your area of interest or aoi.

    // Function to add the cloud, shadow and snow percentge as a new property for each image. It uses 'QA_PIXEL' band
function add_cloudCover(img){
  // Bit 3 - Cloud
  // Bit 4 - Cloud Shadow
  // Bit 5 - Snow
  var cloudsBitMask = 1 << 3;
  var cloudShadowBitMask = 1 << 4;
  var snowBitMask = 1 << 5;
  var qa = img.select('QA_PIXEL').clip(region);
  var unmasked = qa.gt(0)
  var unmasked_area = unmasked.multiply(ee.Image.pixelArea());
  var mask = qa.bitwiseAnd(cloudShadowBitMask).eq(0)
  var masked_area = mask.multiply(ee.Image.pixelArea());
  var u_area = unmasked_area.reduceRegion({
  reducer: ee.Reducer.sum(),
  geometry: region.geometry(),
  scale: 30,
  maxPixels: 1e10
  var area = masked_area.reduceRegion({
  reducer: ee.Reducer.sum(),
  geometry: region.geometry(),
  scale: 30,
  maxPixels: 1e10
  var landCover = ee.Number(area.get('QA_PIXEL')).multiply(100).divide(ee.Number(u_area.get('QA_PIXEL')));
  var cloudCover = ee.Number(100).subtract(landCover).round();
  return img.set('CLOUD_SHADOW_SNOW_COVER_REGION', cloudCover);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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