Visualization / Statistical Info

Dear colleagues.

I have a couple of questios that I would be pleased to solve. First of all, I am Forestry Engineer without knowledge on IT; just a basic knowledge. Sorry in advance.

Question 1
I am managing to visualize a script, found at Github web, on a color scale, such as red (lower values) to green (higher values). The script is made for grey scale (dark for lower values and light to higher). The script is: https://github.com/sentinel-hub/custom-scripts/tree/master/sentinel-2/fapar

How could I modify the script to get a red to green visualization?

Question 2

Another question is regard to how to get statistic info (graphs) at EO-BROWSER. What are the system requirements to enable it? I guess that I need to make changes at script. I realized that statistical info is available when I type the script as custom instead use a layer.

Thanks in advance.

For red/green visualization of FAPAR you need to change two parts of the script:

  1. Change output settings from “1-band” (e.g. gray scale) to “3-band” (red,green,blue).
    You do this by changing
    componentCount: 1
    to
    componentCount: 2
    in setup part.

  2. Use colorBlend function for the output

I am not familiar with FAPAR algorithm as such so I do not know, what are typical output values. But assuming that you would like to map “0” to red and “1” to green (and everything in between proportionally), you have to replace
return {
default: [fapar]
}

with
return {
default: colorBlend(fapar, [0, 1], [[1,0,0],[0,1,0]]);
}

See this page for more information:
https://www.sentinel-hub.com/develop/documentation/api/custom-evaluation-script

Hi unap,

as of your 2nd question:

To get statistical info for a layer in EO Browser a corresponding “FIS” layer needs to be configured as explained here: https://www.sentinel-hub.com/develop/documentation/faq#t34n451

For your own (i.e. custom) layers, you will have to configure FIS layers in your Configuration Utility.

For some of the default layers in EO Browser, we have configured FIS layers (where we thought it makes sense to display statistical info). If you miss statistical info for any of the default layers, please let us know for which particular and we will check and add it.

I have a question when using custom layers FIS.

According to your instructions they display the values for the NDVI, but how ca i specify another INDEX layer?
I want to get statistical information from the PSRI layer, what code do I need to put in my FIS layer?

Thanks!

Hi nilsfernquist,

based on https://www.indexdatabase.de/db/i-single.php?id=69 , we assume that Sentinel-2’s bands B02, B04 and B06 (note that we used to use B05 in the predefined product) shall be used to calculate Plant Senescence Reflectance Index (PSRI) as:

PSRI = (B04 - B02) / B06

Custom script for a corresponding FIS layer shall then be:

return [(B06 > 0) ? (B04 - B02) / B06 : JAVA_DOUBLE_MAX_VAL];

If prefer custom scripts v2 (https://www.sentinel-hub.com/develop/documentation/api/custom-evaluation-script) you could use the code below:

//VERSION=2
function setup(ds) {
return {
        components: [ds.B02, ds.B04, ds.B06], 
        output: [ 
            {
                id: "default",
                sampleType: SampleType.AUTO,
                componentCount: 1
            }
         ],
        normalization: false 
    };
}

function evaluatePixel(samples) {
    return {
        default: [(samples[0].B06 > 0) ? (samples[0].B04 - samples[0].B02) / samples[0].B06 : JAVA_DOUBLE_MAX_VAL],   
    }; 
}

Both scripts shall return identical results.

Thank you for replying.

Im do not have much experience with scripting and such, how would you write the script in detail?

function index(x, y) { return (x - y) / (x + y); } const NGDR = index(B03, B04); const bRatio = (B03 - 0.175) / (0.39 - 0.175); const isCloud = bRatio > 1 || (bRatio > 0 && NGDR > 0); const ndvi = index(B08, B04); return isCloud ? [ndvi, 1] : [ndvi, 0];

Sorry about the layout on the quote

Hi,

my approach is to first ask what inputs do I need (in this example: B03, B04 and B08) and what outputs I’d like (in this example: an array of two values). Based on this I write a setup function as:

//VERSION=2
function setup(ds) {
return {
        components: [ds.B03, ds.B04, ds.B08], 
        output: [ 
            {
                id: "default",
                sampleType: SampleType.AUTO,
                componentCount: 2
            }
         ]
    };
}

All the calculation shall be done in evaluatePixel function. Besides moving everything inside the evaluatePixel function, one needs to make sure 1) to prefix the bands with samples[0]. and 2) that return statement consist of the outputs as specified in setup function. Something like:

function evaluatePixel(samples) {
	function index(x, y) { 
	    return (x - y) / (x + y); 
    } 
	const NGDR = index(samples[0].B03, samples[0].B04); 
	const bRatio = (samples[0].B03 - 0.175) / (0.39 - 0.175); 
	const isCloud = bRatio > 1 || (bRatio > 0 && NGDR > 0); 
	const ndvi = index(samples[0].B08, samples[0].B04);
    
	return {
        default:  isCloud ? [ndvi, 1] : [ndvi, 0]  
    }; 
}

Both parts shall be used when defining your FIS layer.

P.S.: I use ``` before and after the code (must be in a separate line) and it renders nicely. Most of the time:)

Thank you so much for the information provided; it was very hepfull.
Regarding to fAPAR index (https://github.com/sentinel-hub/custom-scripts/blob/master/sentinel-2/fapar/script.js), I am trying to get values by using JSON, but the output is null (http://services.sentinel-hub.com/ogc/fis/<INSTANCE_ID>?LAYER=FAPAR-INDEX&CRS=EPSG:4326&TIME=2016-05-01%2F2019-02-08&BBOX=39.56245,-4.71769,39.5629,-4.717107&RESOLUTION=5m&MAXCC=5&STYLE=INDEX). When I use this URL to get values of another indexs from the same configuration I get sucessfull values.
What I am doing wrong?
Thank you in advance.

(Edited to remove instance ID)

Using the request above, I get error:

java.util.concurrent.ExecutionException: java.lang.IllegalArgumentException: No enum constant com.sinergise.sentinel.s2.model.S2Constants.Band.viewZenithMean

This gives me a feeling that one of the dates in the period has corrupted meta-data.
We will look into it and report back, once we find a culprit. However, it might take a while. Until then the workaround is to limit the time period to newer dates. Also, try to avoid using our “run-time optimized” atmospheric correction.

Thank you, starting to understad parts of it.
Tried to copy code into FIS layer but get:
Error: Failed to evaluate script! ReferenceError: SampleType is not defined
when clicking the statistics tool in EO

Thanks. I’ll try to avoid it.

Thanks so much for your interest.

Can you paste the code here as well?

Hi,
I double checked the script and it works ok for me.
From the error you reported it seems that the system doesn’t recognize your script as version 2. Does your script start with //VERSION=2 (this shall be the first line of the script)?
If yes, then we will need more information to investigate further; please copy-paste the script you are using.

Yes, the script starts with //VERSION=2". Next, after “”//"", you can find it:

//VERSION=2
var degToRad = Math.PI / 180;

function evaluatePixel(samples) {
var sample = samples[0];
var b03_norm = normalize(sample.B03, 0, 0.253061520471542);
var b04_norm = normalize(sample.B04, 0, 0.290393577911328);
var b05_norm = normalize(sample.B05, 0, 0.305398915248555);
var b06_norm = normalize(sample.B06, 0.006637972542253, 0.608900395797889);
var b07_norm = normalize(sample.B07, 0.013972727018939, 0.753827384322927);
var b8a_norm = normalize(sample.B8A, 0.026690138082061, 0.782011770669178);
var b11_norm = normalize(sample.B11, 0.016388074192258, 0.493761397883092);
var b12_norm = normalize(sample.B12, 0, 0.493025984460231);
var viewZen_norm = normalize(Math.cos(sample.viewZenithMean * degToRad), 0.918595400582046, 1);
var sunZen_norm = normalize(Math.cos(sample.sunZenithAngles * degToRad), 0.342022871159208, 0.936206429175402);
var relAzim_norm = Math.cos((sample.sunAzimuthAngles - sample.viewAzimuthMean) * degToRad)

var n1 = neuron1(b03_norm,b04_norm,b05_norm,b06_norm,b07_norm,b8a_norm,b11_norm,b12_norm, viewZen_norm,sunZen_norm,relAzim_norm);
var n2 = neuron2(b03_norm,b04_norm,b05_norm,b06_norm,b07_norm,b8a_norm,b11_norm,b12_norm, viewZen_norm,sunZen_norm,relAzim_norm);
var n3 = neuron3(b03_norm,b04_norm,b05_norm,b06_norm,b07_norm,b8a_norm,b11_norm,b12_norm, viewZen_norm,sunZen_norm,relAzim_norm);
var n4 = neuron4(b03_norm,b04_norm,b05_norm,b06_norm,b07_norm,b8a_norm,b11_norm,b12_norm, viewZen_norm,sunZen_norm,relAzim_norm);
var n5 = neuron5(b03_norm,b04_norm,b05_norm,b06_norm,b07_norm,b8a_norm,b11_norm,b12_norm, viewZen_norm,sunZen_norm,relAzim_norm);

var l2 = layer2(n1, n2, n3, n4, n5);

var fapar = denormalize(l2, 0.000153013463222, 0.977135096979553);
return {
default: [fapar]
}
}

function neuron1(b03_norm,b04_norm,b05_norm,b06_norm,b07_norm,b8a_norm,b11_norm,b12_norm, viewZen_norm,sunZen_norm,relAzim_norm) {
var sum =
- 0.887068364040280
+ 0.268714454733421 * b03_norm
- 0.205473108029835 * b04_norm
+ 0.281765694196018 * b05_norm
+ 1.337443412255980 * b06_norm
+ 0.390319212938497 * b07_norm
- 3.612714342203350 * b8a_norm
+ 0.222530960987244 * b11_norm
+ 0.821790549667255 * b12_norm
- 0.093664567310731 * viewZen_norm
+ 0.019290146147447 * sunZen_norm
+ 0.037364446377188 * relAzim_norm;

return tansig(sum);
}

function neuron2(b03_norm,b04_norm,b05_norm,b06_norm,b07_norm,b8a_norm,b11_norm,b12_norm, viewZen_norm,sunZen_norm,relAzim_norm) {
var sum =
+ 0.320126471197199
- 0.248998054599707 * b03_norm
- 0.571461305473124 * b04_norm
- 0.369957603466673 * b05_norm
+ 0.246031694650909 * b06_norm
+ 0.332536215252841 * b07_norm
+ 0.438269896208887 * b8a_norm
+ 0.819000551890450 * b11_norm
- 0.934931499059310 * b12_norm
+ 0.082716247651866 * viewZen_norm
- 0.286978634108328 * sunZen_norm
- 0.035890968351662 * relAzim_norm;

return tansig(sum);
}

function neuron3(b03_norm,b04_norm,b05_norm,b06_norm,b07_norm,b8a_norm,b11_norm,b12_norm, viewZen_norm,sunZen_norm,relAzim_norm) {
var sum =
+ 0.610523702500117
- 0.164063575315880 * b03_norm
- 0.126303285737763 * b04_norm
- 0.253670784366822 * b05_norm
- 0.321162835049381 * b06_norm
+ 0.067082287973580 * b07_norm
+ 2.029832288655260 * b8a_norm
- 0.023141228827722 * b11_norm
- 0.553176625657559 * b12_norm
+ 0.059285451897783 * viewZen_norm
- 0.034334454541432 * sunZen_norm
- 0.031776704097009 * relAzim_norm;

return tansig(sum);
}

function neuron4(b03_norm,b04_norm,b05_norm,b06_norm,b07_norm,b8a_norm,b11_norm,b12_norm, viewZen_norm,sunZen_norm,relAzim_norm) {
var sum =
- 0.379156190833946
+ 0.130240753003835 * b03_norm
+ 0.236781035723321 * b04_norm
+ 0.131811664093253 * b05_norm
- 0.250181799267664 * b06_norm
- 0.011364149953286 * b07_norm
- 1.857573214633520 * b8a_norm
- 0.146860751013916 * b11_norm
+ 0.528008831372352 * b12_norm
- 0.046230769098303 * viewZen_norm
- 0.034509608392235 * sunZen_norm
+ 0.031884395036004 * relAzim_norm;

return tansig(sum);
}

function neuron5(b03_norm,b04_norm,b05_norm,b06_norm,b07_norm,b8a_norm,b11_norm,b12_norm, viewZen_norm,sunZen_norm,relAzim_norm) {
var sum =
+ 1.353023396690570
- 0.029929946166941 * b03_norm
+ 0.795804414040809 * b04_norm
+ 0.348025317624568 * b05_norm
+ 0.943567007518504 * b06_norm
- 0.276341670431501 * b07_norm
- 2.946594180142590 * b8a_norm
+ 0.289483073507500 * b11_norm
+ 1.044006950440180 * b12_norm
- 0.000413031960419 * viewZen_norm
+ 0.403331114840215 * sunZen_norm
+ 0.068427130526696 * relAzim_norm;

return tansig(sum);
}

function layer2(neuron1, neuron2, neuron3, neuron4, neuron5) {
var sum =
- 0.336431283973339
+ 2.126038811064490 * neuron1
- 0.632044932794919 * neuron2
+ 5.598995787206250 * neuron3
+ 1.770444140578970 * neuron4
- 0.267879583604849 * neuron5;

return sum;
}

function normalize(unnormalized, min, max) {
return 2 * (unnormalized - min) / (max - min) - 1;
}
function denormalize(normalized, min, max) {
return 0.5 * (normalized + 1) * (max - min) + min;
}
function tansig(input) {
return 2 / (1 + Math.exp(-2 * input)) - 1;
}

function setup(ds) {
return {
components: [ds.B03, ds.B04, ds.B05, ds.B06, ds.B07, ds.B8A, ds.B11, ds.B12, ds.viewZenithMean, ds.viewAzimuthMean, ds.sunZenithAngles, ds.sunAzimuthAngles],
output: [
{
id: “default”,
sampleType: SampleType.AUTO,
componentCount: 1
}
]
}
}

Hi @unap,
I am not sure, what exactly is a problem on your side.
I took a liberty to create a new instance on your account, named LAI (be210f19-04ee-…) and inside LAI layer. I copied the content from GitHub

and Statistical API works:

http://services.sentinel-hub.com/ogc/fis/be210f19-04ee-MASKED?LAYER=LAI&TIME=2018-06-27/2018-07-27&CRS=EPSG:3857&BBOX=-11545048.75219302%2C3717897.0557909743%2C-11505912.99371101%2C3757032.8142729844&RESOLUTION=100m
(you have to replace the instance with an appropriate one)

One thing to note is that this script is very long one and if you pass it as EVALSCRIPT parameter directly to the service, the call will be too long. Therefore you have to store it in Configuration utility.

Thank you.
The reported error is based on fapar index (https://github.com/sentinel-hub/custom-scripts/blob/master/sentinel-2/fapar/script.js), which is configurated as a layer (ID:FAPAR-INDEX) within a configuration named “Humedad combustible Sentinel L1C” (6d2a6bed-88d5-MASKED). I am trying to use a URL to get values: http://services.sentinel-hub.com/ogc/fis/6d2a6bed-MASKED?LAYER=FAPAR-INDEX&CRS=EPSG:4326&TIME=2016-05-01%2F2019-02-08&BBOX=39.56245,-4.71769,39.5629,-4.717107&RESOLUTION=5m&MAXCC=5&STYLE=INDEX
but my JSON converter (https://json-csv.com/) give me back an error (“null”). Similar URL is working with another inedexes, even though at the same configuration.

Actually, I do not know what you mean with last paragraph. I need to apologize for my limited knowledge in scripts.

Thank you.

Aha, this issue is the same as discussed above (Visualization / Statistical Info). There seems to be a problem when using atmospheric correction for some old data. If you configure it without it, the request will work.

Thank you very much. Having changed atomosferic correctio to DOS1 type, getting values is working.

Kind regards.