NDVI images with custom interval and color pallete

Is it possible to retrieve NDVI images with 0.05 intervals between index values ​​and with a custom color palette?

Good morning Tetri,

Yes, this is very easy to do using the custom scripts functionality:

Below is an example of a script for use with Sentinel 2 L1C data. The intervals are set at 0.5 and the color palette is set in RGB format using values between 0 and 1. For example 0,0,0 would be black and 1,1,1 would be white.

//VERSION=3
function setup() {
return {
input: [{
bands:[“B04”, “B08”],
}],
output: {
id: “default”,
bands: 3,
}
}
}
function evaluatePixel(sample) {
let ndvi = (sample.B08 - sample.B04) / (sample.B08 + sample.B04)

if (ndvi<-0) return [0.05,0.05,0.05]
else if (ndvi<0.05) return [0.75,0.75,0.75]
else if (ndvi<0.1) return [0.86,0.86,0.86]
else if (ndvi<0.15) return [0.92,0.92,0.92]
else if (ndvi<0.2) return [1,0.98,0.8]
else if (ndvi<0.25) return [0.93,0.91,0.71]
else if (ndvi<0.3) return [0.87,0.85,0.61]
else if (ndvi<0.35) return [0.8,0.78,0.51]
else if (ndvi<0.4) return [0.74,0.72,0.42]
else if (ndvi<0.45) return [0.69,0.76,0.38]
else if (ndvi<0.5) return [0.64,0.8,0.35]
else if (ndvi<0.55) return [0.57,0.75,0.32]
else if (ndvi<0.6) return [0.5,0.7,0.28]
else if (ndvi<0.65) return [0.44,0.64,0.25]
else if (ndvi<0.7) return [0.38,0.59,0.21]
else if (ndvi<0.75) return [0.31,0.54,0.18]
else if (ndvi<0.8) return [0.25,0.49,0.14]
else if (ndvi<0.85) return [0.19,0.43,0.11]
else if (ndvi<0.9) return [0.13,0.38,0.07]
else if (ndvi<0.95) return [0.06,0.33,0.04]
else return [0,0.27,0]

}

This example uses a yellow to green color gradient but if you want to create your own custom palette then using the ColorBrewer website is a good starting point:

Using the tool, you can pick a suitable color palette and specify the number of classes too. To convert the RGB values (0 to 255) to something friendly for the above custom script, just divide the numbers by 255. For example, 255, 51, 0 would convert into 1, 0.2, 0.

Let me know if you need any more clarification on this, happy to help!

Will

1 Like

Perfect. And can I make this request through the API through my C# code?

Hi Tetri,

I’m just trying to find out more from colleagues. Can you clarify what your workflow is / would be please?

Thanks

Will

The idea is to request the NDVI images of polygons formed by the farms that I have registered in my system.

I found examples of requesting images in the link https://docs.sentinel-hub.com/api/latest/data/sentinel-2-l1c/examples/ and implemented the call in my code, but I was not successful in recovering the images.

Hi Tetri,

Do you want to share the curl request you have written and I can try and help you to debug it?

Will

When testing the link requests that I passed above, using Postman, the answer in all of them is the error:

{
    "error": {
        "status": 500,
        "reason": "Internal Server Error",
        "message": "MessageBodyReader not found for media type=application/octet-stream, type=class io.jsonwebtoken.impl.DefaultClaims, genericType=class io.jsonwebtoken.impl.DefaultClaims.",
        "code": "RENDERER_EXCEPTION"
    }
}

Is it something I’m doing wrong?

Hi Tetri,

It looks like you need to configure the Authentication tab in Postman. You will need to create a client ID and client secret on the Sentinel Hub Dashboard and you can use these to obtain a token in Postman.

Once that is done, you can follow the below instructions:

In the Postman request Authorization tab set the Type to OAuth 2.0 and Add the authorization data to Request Headers . Then click the Get New Access Token button. Set the Grant Type to Client Credentials , the access token URL to the token endpoint, then set the Client ID and Client Secret to the values of your OAuth Client. Scope can be blank. Keep Client Authentication as Send As Basic Auth Header . Click Request Token . You should get a new one immediately. To use this token to authorize your request, click Use Token . For more information see the Postman authorization documentation

All the information you need can be found here: https://docs.sentinel-hub.com/api/latest/api/overview/authentication/

Let me know if you run into any problems :slight_smile:

Will

1 Like

I got the access token from your instructions.
But the error 500 in the request continues to be displayed.

Hi Tetri,

Are you able to share the curl request with me? (make sure to redact anything sensitive)

I’m pretty sure it will just be an error in your syntax that is causing the error.

Will

Hello tetri.neto,

Did you managed to solve this error? If yes, how?

Best Regards,
Stelios

Hi @stelios.neophytides ,

Could you please share your curl request and the error message?

Hello chung.horng,

Following find my curl request

curl -v https://services.sentinel-hub.com/api/v1/process --insecure --header “Authorization : Bearer 4a71959b-c3ad-453d-a3c2-e91b30b3b387” --header “Content-Type : multipart/form-data” --header “Accept: application/tar” --form ‘request = {“input”:{“bounds”:{“properties”:{“crs”:“http://www.opengis.net/def/crs/OGC/1.3/CRS84"},“bbox”:[32.262420654296875,34.57316742269859,34.005889892578125,35.14953465791943]},“data”:[{“type”:“sentinel-2-l2a”,“dataFilter”:{“timeRange”:{“from”:“2022-01-01T00:00:00Z”,“to”:“2022-01-10T00:00:00Z”}}}]},“output”:{“width”:512,“height”:512,“responses”:[{“identifier”:“default”,“format”:{“type”:“image/tiff”}},{“identifier”:“true_color_8bit”,“format”:{“type”:"image/png”}}]}}’ --form ‘evalscript=//VERSION=3
function setup(){ return { input: [“B02”, “B03”, “B04”], mosaicking: Mosaicking.ORBIT, output: { id:“default”, bands: 3} }} function updateOutputMetadata(scenes, inputMetadata, outputMetadata){outputMetadata.userData = { “scenes”: scenes.orbits }} function evaluatePixel(samples){return [ 2.5 * samples[0].B04, 2.5 * samples[0].B03, 2.5 * samples[0].B02 ]}’

and the error message

  • Trying 3.64.176.69:443…
  • Connected to services .sentinel-hub. com (3.64.176.69) port 443 (#0)
  • ALPN: offers h2
  • ALPN: offers http /1.1
  • TLSv1.3 (OUT), TLS handshake, Client hello (1):
  • TLSv1.3 (IN), TLS handshake, Server hello (2):
  • TLSv1.2 (IN), TLS handshake, Certificate (11):
  • TLSv1.2 (IN), TLS handshake, Server key exchange (12):
  • TLSv1.2 (IN), TLS handshake, Server finished (14):
  • TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
  • TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
  • TLSv1.2 (OUT), TLS handshake, Finished (20):
  • TLSv1.2 (IN), TLS handshake, Finished (20):
  • SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
  • ALPN: server accepted h2
  • Server certificate:
  • subject: C=SI; ST=Ljubljana; O=Sinergise d.o.o.; CN=* . sentinel - hub . com
  • start date: Feb 23 00:00:00 2023 GMT
  • expire date: Mar 25 23:59:59 2024 GMT
  • issuer: C=GB; ST=Greater Manchester; L=Salford; O=Sectigo Limited; CN=Sectigo RSA Organization Validation Secure Server CA
  • SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
  • Using HTTP2, server supports multiplexing
  • Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
  • h2h3 [:method: POST]
  • h2h3 [:path: /api/v1/process]
  • h2h3 [:scheme: https]
  • h2h3 [:authority: services. sentinel-hub .com]
  • h2h3 [user-agent: curl/7.84.0]
  • h2h3 [authorization : Bearer 4a71959b-c3ad-453d-a3c2-e91b30b3b387]
  • h2h3 [content-type : multipart/form-data]
  • h2h3 [accept: application/tar]
  • h2h3 [content-length: 607]
  • h2h3 [content-type: multipart/form-data; boundary=------------------------62ea758e706bb984]
  • Using Stream ID: 1 (easy handle 0x191dd40)

POST /api/v1/process HTTP/2
Host: services . sentinel-hub . com
user-agent: curl/7.84.0
authorization : Bearer 4a71959b-c3ad-453d-a3c2-e91b30b3b387
content-type : multipart/form-data
accept: application/tar
content-length: 607
content-type: multipart/form-data; boundary=------------------------62ea758e706bb984

  • Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
  • We are completely uploaded and fine
    < HTTP/2 400
    < date: Wed, 12 Apr 2023 12:18:27 GMT
    < content-type: text/html
    < content-length: 90
    < cache-control: no-cache
    <

400 Bad request

Your browser sent an invalid request. * Connection #0 to host services .sentinel-hub .com left intact

Thank you in advance,
Stelios

P.S. I broke out some links in the error as i am not allowed to send more than 2.

Hi @stelios.neophytides ,

There are a couple of errors in your request.

First of all, you set two responses (default and true_color_8bit) in your payload, but there’s only one output in your Evalscript. I would recommend watching the Processing API webinar to get a general idea how the API works. There’s also a beginner’s guide which provides a step-by-step guide to get the first imagery with our services.

Secondly, I guess you’re trying to obtain metadata of the tile as you’re using the updateOutputMetadata function to collect the metadata. To get the metadata, you need to set a response with an identifier userdata in your request.

Below is an example request to get a true color image as JPG and the tile’s metadata as JSON. You could simply copy the request and paste it to Requests Builder to get the result.

curl -X POST https://services.sentinel-hub.com/api/v1/process \
 -H 'Content-Type: application/json' \
 -H 'Authorization: Bearer <your_token>' \
 -H 'Accept: application/tar' \
 -d '{
  "input": {
    "bounds": {
      "bbox": [
        32.262420654296875,
        34.57316742269859,
        34.005889892578125,
        35.14953465791943
      ]
    },
    "data": [
      {
        "dataFilter": {
          "timeRange": {
            "from": "2022-01-01T00:00:00Z",
            "to": "2022-01-10T23:59:59Z"
          }
        },
        "type": "sentinel-2-l2a"
      }
    ]
  },
  "output": {
    "width": 512,
    "height": 205.565,
    "responses": [
      {
        "identifier": "default",
        "format": {
          "type": "image/jpeg"
        }
      },
      {
        "identifier": "userdata",
        "format": {
          "type": "application/json"
        }
      }
    ]
  },
  "evalscript": "//VERSION=3\nfunction setup() { \n  return { \n    input: [\"B02\", \"B03\", \"B04\"], \n    mosaicking: Mosaicking.ORBIT, \n    output: { bands: 3} \n  }\n} \n\nfunction updateOutputMetadata(scenes, inputMetadata, outputMetadata) {\n  outputMetadata.userData = { \"scenes\": scenes.orbits }\n} \n\nfunction evaluatePixel(samples) {\n  return [ 2.5 * samples[0].B04, 2.5 * samples[0].B03, 2.5 * samples[0].B02 ]\n}"
}'