How to eliminate cloudy pixels from tiff

For the evalscript posted below, i introduced the method cloudy() to eliminate the cloudy pixels from the tiff. in other words, i want have a tiff file contains ndvi values where
the pixels values in the tiff are not covered with clouds.

at the run time, i receive empty tiff.
can you please point out what i am doing wronG?

  // Script to extract a time series of NDVI values using 
  // Sentinel 2 Level 2A data and  metadata file.
  function setup() {
	return {
	  input: [{
		bands: ["B04", "B08", "CLD"],
		units: "DN"
	  output: [
	  id: "default",
	  bands: 1,
	  sampleType: "FLOAT32",
	  nodataValue: NaN,
	  mosaicking: Mosaicking.ORBIT

  function cloudy(samples) {
	let isCloudy = false;
	samples.forEach((sample) => {
	  if (sample.CLD > 0) isCloudy = true;
	return isCloudy;

  function evaluatePixel(samples) {
	if (samples.length < 1) return [NaN];
	if (cloudy(samples)) return [NaN];

	// Precompute an array to contain NDVI observations
	var n_observations = samples.length;
	let ndvi = new Array(n_observations).fill(NaN);
  //Fill the array with NDVI values
	samples.forEach((sample, index) => {
	  ndvi[index] = (sample.B08 - sample.B04) / (sample.B08 + sample.B04) ;
	// get average over the collected ndvi values:
	let sum = 0;
	for(let i = 0;i < ndvi.length; i++) {
	  sum += ndvi[i];
	let avg = sum / ndvi.length;
	return [avg];

How you are doing it right now, the cloudy() function returns False if just one sample is cloudy. This excludes the whole pixel from the calculation. And since all pixels likely have one cloudy acquisition in the time series you don’t get anything back.

To fix that, exclude observations on a per acquisition basis, for example like this:

function isClear(sample) {
    return sample.CLD == 0;

function evaluatePixel(samples) {
   const clearTimeSeries = samples.filter(isClear)
   ... rest of your script, using clearTimeSeries

is eliminating the cloudy pixel programmatically through introducing some methods eg. isClear() serves as a substitute for specifying the maxCloudCoverage attribute in the json request? what is the difference please between specifying maxCloudCoverage as an attribute in the json request and the way mentioned in the evalscript above

maxCloudCoverage will exclude full acquisitions on a tile basis. So assume a full Sentinel 2 tile which is 50% cloud covered, but you specify a smaller subset of the tile as area of interest which isn’t cloudy. If you set the maximum cloud coverage to below 50% the tile will get excluded, even though the pixels for the subset you selected might be clear.

Doing it in the evalscript will look at the actual pixel values and only include single pixels which are cloudy and not full tiles. This way you preserve a lot more data.

1 Like