Using a function inside of evaluatePixel in StatisticalAPI

I’m quite new to StatisticalAPI. I’m using it via the Python API.
I would get an NDVI mean and cloud coverage (via a custom function) over my AOI.
The cloud coverage is calculated from a custom function I saw here.

To get only the NDVI I use:

sn2_ndvi_evalscript = “”"
//VERSION=3

function setup() {
return {
input: [{bands: [“B04”,“B08”,“dataMask”]}],
output: [{id:“ndvi”,bands: 1},{id: “dataMask”,bands: 1}]
} }

function evaluatePixel(samples) {
return {
ndvi: [index(samples.B08, samples.B04)],
dataMask: [samples.dataMask]
};
}
“”"

The statisticalAPI (Python) syntax is:

sn2_ndvi_request = SentinelHubStatistical(
aggregation=SentinelHubStatistical.aggregation(
evalscript=sn2_ndvi_evalscript,
time_interval=(“2020-06-07”, “2020-09-13”),
aggregation_interval=“P1D”,
resolution=(0.0001, 0.0001)
),
input_data=[SentinelHubStatistical.input_data(DataCollection.SENTINEL2_L2A)],
geometry=poly,
config=config,
)

And this works well.

However, when I want to add the cloud coverage function I get an error.
The cloud function is:

> 
> function cloud_free(samples) {
>   var scl = samples.SCL;
>   var clm = samples.CLM;
> 
>   if (clm === 1 || clm === 255) {
>         return 1;
>   } else if (scl === 1) { // SC_SATURATED_DEFECTIVE
>         return 1;
>   } else if (scl === 3) { // SC_CLOUD_SHADOW
>         return 1;
>   } else if (scl === 8) { // SC_CLOUD_MEDIUM_PROBA
>         return 1;
>   } else if (scl === 9) { // SC_CLOUD_HIGH_PROBA
>         return 1;
>   } else if (scl === 10) { // SC_THIN_CIRRUS
>         return 1;
>   } else if (scl === 11) { // SC_SNOW_ICE
>     return 1;
>   }  else {
>   return 0;
>   }
> }

To add this cloud function to the previous function:

sn2_ndvi_evalscript = “”"
//VERSION=3

function setup() {
return {
input: [“B04”,“B08”,“dataMask”,“SCL”,“CLM”],
output: [{id:“ndvi”,bands:1},
{id:“dataMask”,bands:1}],
{id:“cc_p”,bands:1}]
} }

function cloud_free(samples) {
var scl = samples.SCL;
var clm = samples.CLM;

if (clm === 1 || clm === 255) {
return 1;
} else if (scl === 1) { // SC_SATURATED_DEFECTIVE
return 1;
} else if (scl === 3) { // SC_CLOUD_SHADOW
return 1;
} else if (scl === 8) { // SC_CLOUD_MEDIUM_PROBA
return 1;
} else if (scl === 9) { // SC_CLOUD_HIGH_PROBA
return 1;
} else if (scl === 10) { // SC_THIN_CIRRUS
return 1;
} else if (scl === 11) { // SC_SNOW_ICE
return 1;
} else {
return 0;
}
}

function evaluatePixel(samples) {
return {
ndvi: [index(samples.B08, samples.B04)],
dataMask: [samples.dataMask],
cc_p: [cloud_free(samples)]
};
}
“”"

Then I get (this is only 1 example, I didn’t want to paste here the same error for all dates):

{‘interval’: {‘from’: ‘2020-07-04T00:00:00Z’, ‘to’: ‘2020-07-05T00:00:00Z’},
‘error’: {‘type’: ‘BAD_REQUEST’,
‘message’: ‘Failed to evaluate script!\nevalscript.js:9: SyntaxError: Unexpected token {\n {id:“cc_p”,bands:1}]\n ^\n’}}

The desired result should have NDVI aggregation (min, max, mean, etc.) and the same for the clouds function which outputs either 0 or 1 per pixel.

Is this possible via the statistical API?

Hi,

The function you were trying to write is a little more complex than maybe you thought. Also, before inserting multiple functions into an evalscript, I would always test them separately first :slight_smile: this makes debugging your scripts much easier!

Below I have given you the example of the evalscript that shows you how to achieve what you want to do:

//VERSION=3
function setup() {
  return {
    input: ["CLM","CLP","B04", "B08", "dataMask"],
    output: { bands: 2 }
  };
}

function cloud_detect(samples) {
  var scl = samples.SCL;
  var clm = samples.CLM;

  if (clm === 1 || clm === 255) {
        return false;
  } else if (scl === 1) { // SC_SATURATED_DEFECTIVE
        return false;
  } else if (scl === 3) { // SC_CLOUD_SHADOW
        return false;
  } else if (scl === 8) { // SC_CLOUD_MEDIUM_PROBA
        return false;
  } else if (scl === 9) { // SC_CLOUD_HIGH_PROBA
        return false;
  } else if (scl === 10) { // SC_THIN_CIRRUS
        return false;
  } else if (scl === 11) { // SC_SNOW_ICE
    return false;
  }  else {
  return true;
  }
}
function evaluatePixel(sample) {
  let ndvi = (sample.B08 - sample.B04) / (sample.B08 + sample.B04)
  var cloud_free = cloud_detect(sample)
  if (!cloud_free){
    return [1,0,0,1]
  } else {
    return [ndvi, sample.dataMask];
}
}

If you need some further help, then please get back in touch.

Hi @william.ray - thank you, but I’m afraid it does not work for me.

I pasted your example follow by:

sn2_ndvi_request = SentinelHubStatistical(
                                        aggregation=SentinelHubStatistical.aggregation(
                                        evalscript=sn2_ndvi_evalscript,
                                        time_interval=("2020-06-07", "2020-09-13"),
                                        aggregation_interval="P1D",
                                         resolution=(0.0001, 0.0001)
                                        ),
                                        input_data=[SentinelHubStatistical.input_data(DataCollection.SENTINEL2_L2A)],
                                        geometry=poly,
                                        config=config,
                                            )

sn2_ndvi_stats = sn2_ndvi_request.get_data()[0]

and an output example is:

sn2_ndvi_stats['data'][5]
>>>{'interval': {'from': '2020-07-04T00:00:00Z', 'to': '2020-07-05T00:00:00Z'},
 'error': {'type': 'EXECUTION_ERROR',
  'message': 'Output dataMask requested but missing from function setup()'}}

Also, I think the “CLP” is unnecessary (I don’t use it) and if I understand your code then the function does not return both NDVI and the cloud_detect output, rather the function returns only one of them, correct?

Hi,

You are correct that CLP in our example is not needed. An important thing to add is that outputs have to be explicitly defined in Statistical API evalscripts. More about this can be found in the documentation.

I have put together the below example for you that will return statistics for NDVI and for cloud cover. You can see that the outputs are defined in the setup function and then again in the return of the evaluate pixel function.

//VERSION=3
function setup() {
  return {
    input: ["CLM","B04", "B08", "dataMask"],
    output: [
      {
        id: "ndvi",
        bands: 1,
        sampleType: "FLOAT32"
      },
      {
        id: "cloud_cover",
        bands: 1,
        sampleType: "UINT8"
      },  
      {
        id: "dataMask",
        bands: 1,
        sampleType: "UINT8"
      }],
  };
}

function cloud_detect(samples) {
  var scl = samples.SCL;
  var clm = samples.CLM;

  if (clm === 1 || clm === 255) {
        return 1;
  } else if (scl === 1) { // SC_SATURATED_DEFECTIVE
        return 1;
  } else if (scl === 3) { // SC_CLOUD_SHADOW
        return 1;
  } else if (scl === 8) { // SC_CLOUD_MEDIUM_PROBA
        return 1;
  } else if (scl === 9) { // SC_CLOUD_HIGH_PROBA
        return 1;
  } else if (scl === 10) { // SC_THIN_CIRRUS
        return 1;
  } else if (scl === 11) { // SC_SNOW_ICE
    return 1;
  }  else {
  return 0;
  }
}

function evaluatePixel(sample) {
  let ndvi = (sample.B08 - sample.B04) / (sample.B08 + sample.B04)
  var cloud_cover = cloud_detect(sample)
return {
        ndvi: [ndvi],
        cloud_cover: [cloud_cover],
        dataMask: [sample.dataMask]
        }
}

I hope this clears things up for you. Have a good weekend.

1 Like

Works!
Thank you for the help