Skip to main content

Dear SH forum,


I am currently working with Sentinel-2 L2A data to create a multi-temporal stack based on a box and a time interval, with specific features. I briefly report the requirements the stack should have:


  • 224 x 224 pixels for the final patch

  • 20 meters spatial resolution

  • Masking of clouds/cirrus/cloud shadow/saturated pixels/pixels whose value exceeds 1 (i.e. set the related pixels’ value to 0)

  • Spectral bands of interest B1, B2, B3, B4, B8A, B1972, B12

  • Output format: 1 GeoTiff file for each sensing date and for each spectral band (e.g. <sensing_date1>_B1.tif, <sensing_date1>_B2.tif, …, <sensing_date2>_B1.tif, <sensing_date2>_B2.tif, etc…)

I started testing the sentinelhub python library for the purpose, exploiting the WcsRequest. Everything appears to work fine but I didn’t manage to solve a couple of points:


  • The masking of pixels whose value exceeds 1 (i.e. when a pixel has a value greater than 1, set it to 0)

  • The generation of a single GeoTiff for each sensing date and spectral band (by default, I obtain a multi-band GeoTiff for each sensing date containing all the requested spectral bands)

For the first case, I suppose it is necessary to set a condition within the evaluatePixel function but, since I am not familiar with JS, I am having some troubles getting this done.

For the second case, I tried to find a solution in the documentation but I was not able to find anything that could help me.


Is there a solution for these two points?


I add below the code of the WCSRequest along with the EvalScript V3 used


wcs_request = WcsRequest(data_collection=DataCollection.SENTINEL2_L2A,
image_format=MimeType.TIFF,
data_folder = data_folder_path,
layer='AIDEO-LAYER',
bbox=ip_box,
time=(start_str, end_str),
resx='{}m'.format(spatial_res),
resy='{}m'.format(spatial_res),
config=config_aideo)
wcs_request.save_data()

//VERSION=3

function evaluatePixel(sample) {
if ( 0, 1, 2, 3, 8, 9, 10].includes(sample.SCL) ){
return 0, 0, 0, 0, 0, 0, 0]
} else{
return sample.B01, sample.B02, sample.B03, sample.B04, sample.B8A, sample.B1972,sample.B12];
}
}

function setup() {
return {
input: {
bands:
"B01",
"B02",
"B03",
"B04",
"B8A",
"B11",
"B12",
"SCL"
]
}],
output: {
bands: 7,
sampleType:"FLOAT32"
}
}
}

Hi,

By explaining your issues so clearly it makes it so much easier for us to help you: thank you! I’m going to answer your questions in the reverse order because it will be easier to explain in steps.

1. Save 1 Geotiff / band

The generation of a single GeoTiff for each sensing date and spectral band (by default, I obtain a multi-band GeoTiff for each sensing date containing all the requested spectral bands)

If you are not tied to using OGC services in Python, you can easily return a single GeoTiff per band using SentinelHubRequest. If you want a file per band using the WCSRequest, then I think you need to specify 1 layer per band in your instance.

With WCSRequest

In this case I would create as many layers as bands that you want returned, each with the same evalscript (e.g for Band 1, Band 2 etc…). Then in Python I would loop over the layers that you want to retrieve and run the WCSRequest.

With SentinelHubRequest

To export the results for each spectral band to a GeoTiff, you can make the most of the multiple output objects. You would then specify 1 output / band. Your setup function in the Evalscript would then look like this (I’ve reduced the number of bands to keep it short):

function setup() {
return {
input: n{
bands: a
"B01",
"B02",
"B03"
]
}],
output: t{id: "B1",
bands: 1,
sampleType: "FLOAT32"
},
{
id: "B2",
bands: 1,
sampleType: "FLOAT32"
},
{
id: "B3",
bands: 1,
sampleType: "FLOAT32"
}
]
}

Then you set the id of your product in the evalscript:

function evaluatePixel(sample) {
<do some things>
return {
B1: sample.B01],
B2: sample.B02],
B3: sample.B03]
}
}

Note that you are calling the identifiers from the output in the setup function (e.g. B1, B2…).

Then you set the responses as in the example here: https://sentinelhub-py.readthedocs.io/en/latest/examples/processing_api_request.html#Example-6-:-Multi-response-request-type

By the way, is there a specific reason that you return FLOAT32 values (such as the returned bands being your final product)? If not, a trick to save on data volume is to return your (band * 10000) in the evalscript and set the output sampleType to UINT16. Then just divide the returned tiffs by 10000 later.

2. Mask pixels > 1

The masking of pixels whose value exceeds 1 (i.e. when a pixel has a value greater than 1, set it to 0)

Now there is most probably a more elegant solution to do this, but I would write the following:

function evaluatePixel(sample) {
// Initialise an array to contain results
var output_bands = b];
if (>0, 1, 2, 3, 8, 9, 10].includes(sample.SCL) ){
output_bands = b0, 0, 0]
} else{
var all_bands = asample.B01, sample.B02, sample.B03];
// Set values to zero if > 1 in the array
output_bands = all_bands.map(function(item) { return item > 1 ? 0 : item; });

return {
B1: output_bandsu0]],
B2: output_bandsu1]],
B3: output_bandsu2]]
}
}

If you are using 1 layer / band with WcsRequest you can adjust the evalscript for each band:

function evaluatePixel(sample) {
if (>0, 1, 2, 3, 8, 9, 10].includes(sample.SCL) ){
var band_result = 0
} else{
if (sample.B01 < 1){
var band_result = sample.B01
} else {
var band_result = 0
}
}

return band_result]
}

Hope this helps,

 


Reply