Statistical API for Calculating Average NDVI Values

Hi Team,

I am working on obtaining average NDVI values from sentinel-2 images for my parcels over a given date range using the Statistical API. I want to exclude cloudy pixels by masking them with bands such as clm and scl before calculating the average.

Do you have any sample evalscripts or suggestions on how to achieve this?

Thank you in advance for your help!

Hi, you can find a selection of examples using Statistical API in the documentation here. You are able to use the dataMask to exclude pixels from your calculations as documented here. An example of how to use it can be found here and you should be able to adapt the logic to use cloudy pixels instead water pixels that are used in the example.

In addition, you can use the cloud filter in your request too. This will help remove cloudy scenes from your statistical requests before you apply the cloud masking as documented in the API Reference docs.

Hi @william.ray,
Thank you for your quick reponse.
I have tried to create a script and request using the clm and scl band combination to assign cloudy pixels to dataMask, as outlined in the mentioned documents. I am working on calculating statistical values over clear pixels for each incoming image. The script seems to be running smoothly, but could you please check if there is any place I might have missed or if there is a mistake?

time_interval = "2023-07-01", "2023-08-01"

ndvi_evalscript = """
//VERSION=3
function cloud_free(sample) {
  var scl = sample.SCL;
  var clm = sample.CLM;

  if (clm === 1 || clm === 255) {
    return false;
  } else if (scl === 1 || scl === 3 || scl === 8 || scl === 9 || scl === 10 || scl === 11) {
    return false;
  } else {
    return true;
  }
}

function setup() {
  return {
    input: [{
      bands: [
        "B04",
        "B08",
        "SCL",
        "CLM",
        "dataMask"
      ]
    }],
    mosaicking: "ORBIT",
    output: [
      {
        id: "data",
        bands: ["daily_max_ndvi"]
      },
      {
        id: "dataMask",
        bands: 1
      }
    ]
  };
}

function evaluatePixel(samples, scenes) {
  var max = 0;
  var hasData = 0;

  for (var i = 0; i < samples.length; i++) {
    var sample = samples[i];

    if (cloud_free(sample) && sample.dataMask == 1 && sample.B04 + sample.B08 != 0) {
      hasData = 1;
      var ndvi = (sample.B08 - sample.B04) / (sample.B08 + sample.B04);
      max = ndvi > max ? ndvi : max;
    }
  }

  return {
    data: [max],
    dataMask: [hasData]
  };
}

"""

aggregation = SentinelHubStatistical.aggregation(
    evalscript=ndvi_evalscript, time_interval=time_interval, aggregation_interval="P1D", resolution=(10, 10)
)

input_data = SentinelHubStatistical.input_data(DataCollection.SENTINEL2_L2A,
                                              other_args={"dataFilter": {"maxCloudCoverage": 30}},
                                              )

Hi,

Everything looks in order I think. I’d recommend testing your cloud mask in Process API over your AOI, no cloud mask is perfect and always worth checking how successful it is in classifying clouds in your AOI and time range.