SampleType: what’s all the fuss about?
AUTO, UINT8, UINT16, FLOAT32… Strategically set the SampleType for Sentinel Hub requests.
The difference between a float and an integer may seem straightforward at a first glance. However, ever since I joined Sentinel Hub Austria and started helping users, I have regularly come across people puzzled by the
sampleType parameter in their Evalscripts. Today, I’m going to give out a few tips and tricks to master the dreaded
sampleType whilst saving precious Processing Units! As you will see, it’s no rocket science, so don’t hesitate to spread the word!
Finding a clear and comprehensive website describing numeric types is like looking for a needle in a haystack… although I am sure I will be very quickly corrected in the comments (and I look forward to it). Sentinel Hub’s documentation and FAQ pages contain valuable information, but you need to know what you are looking for. The goal of this article is to provide a clear overview of the subject, with all the links and explanations needed to make you an expert in Evalscript writing.
A good place to start with a basic explanation of the
sampleTypes supported by Sentinel Hub is the documentation page on how to write Evalscripts. The dedicated section specifies that the following
sampleTypes can be used:
Quick tip: integers in Sentinel Hub are unsigned. This has nothing to do with autographs:
INT types are signed, meaning that they can either be negative or positive, whereas unsigned integers can only hold positive values.
What do these parameters mean exactly?
UINT8is an unsigned 8-bits integer, with values ranging from 0 to 255.
UINT16is an unsigned 16-bits integer, with values ranging from 0 to 65535.
FLOAT32is a signed 32-bit floating point number, with values ranging from ~1.1e-³⁸ to ~3.4e³⁸. Otherwise put: it will support any number you need, including negative values.
AUTOis not a standard numeric type, but is specific to Sentinel Hub. Values ranging between 0 and 1 are automatically stretched over the 0–255 range and returned as
UINT8. This means that negative values are clamped to 0 and values larger than 1 all become 255. If
sampleTypeis not set in the Evalscript, this parameter will be taken by default.
Next, let’s take a closer look at what the use of
sampleType really entails in an Evalscript. Luckily for you, Sentinel Hub people have already compiled a detailed description of how output values are returned by Sentinel Hub according to the selected
sampleType. The table below, heavily inspired by the documentation page just mentioned, demonstrates a few cases highlighting the effects of using different
A case study
Let’s imagine we are querying Reflectance (real numbers) from a single Sentinel-2 band. Note that this example is also valid for derived products whose values lie between 0 and 1, such as FAPAR. Typically our Evalscript would look like the following:
Referring to the table of values further up, where the green column represents possible reflectance values (rows 2 to 5), we can see that to retrieve the “raw” real numbers representing reflectance, we would need to set
There are cases where we may want to return data in
UINT16 rather than
FLOAT32: for example if we want to display the image in EO Browser or in an app that doesn’t support
FLOAT32, or if we want to return the image as a jpeg/png. However, if we simply modify the sample type in the Evalscript to
UINT, we will end up with only zeros and ones due to rounding, as shown by the third and forth columns of the table further up. To retrieve meaningful data, there are several solutions possible:
UINT16: we could stretch the reflectance over the range of values supported by
UINT16 (0–65535). A simple solution would be to change to the return statement to:
return [sample.B02 * 65535]
in combination with
sampleType: "UINT16", meaning that reflectance values of 1 will become 65535, 0.5 will become 32768 and 0.25 will become 16384. Using this method will allow us to display the data in EO Browser, or download a png file. However, jpeg doesn’t support
UINT16, for this format we will need to use
UINT8: an even simpler solution in our case, where we have initial values between 0 and 1, is to return the original value and set
sampleType: "AUTO", letting the system convert the values to
UINT8 for you. If you want to perform the conversion manually, you can follow the previous example for
UINT16 and simply replace the multiplication factor by 255.
Quick tip: owing to most surface on Earth being quite dark, you will often find a multiplication factor in Evalscripts (e.g.
[2.5 * sample.B02] which stretches the image from 0–0.4 to 0–255), allowing to “brighten” the image for better visualisation of features. You should avoid this step if your goal is data-analysis!
EO Browser is a specific case that justifies a separate description. Indeed, when you use the Download Image tool, you are faced with the choice of the Image format. This parameter overrides the
sampleType present in the Evalscript, meaning that if you are downloading an image with an Evalscript bearing
sampleType, you can still access the
FLOAT32 values using the option
TIFF (32-bit float)… and vice-versa.
Tips and tricks for cheaper scripts
Based on the section above, it seems quite easy: if we want to retrieve the “raw” values of reflectance or index values for data analysis, we just need to set the
FLOAT32. Not so fast! By being savvy with your
sampleType you can save quite a lot of data transfer, disk space and Processing Units. Monja Šebela has already covered this point in her article on how to make faster, cheaper, better Custom Scripts, but here I will expand the subject a little by providing some concrete examples.
Example 1: Reflectance
As shown in our previous example, we can request reflectance bands using
FLOAT32 to retrieve the original values, or use
UINT8/16 to display the images. However, it is possible to save precious resources (
UINT is lighter than
FLOAT, keep reading to find out how much) by requesting the reflectance in
UINT16 and later retrieving the original values from the requested images.
All we need to do is apply a multiplication factor to the values, request the image in
UINT16 and divide the resulting image by the same factor. The choice of the factor will depend on the precision needed and the range of values of your original data. Indeed, the larger the multiplication factor, the more precision you will maintain, but beware not to end up with values larger than 65535 if you don’t want your range to be cropped!
For reflectance, I recommend a factor of 10000, as shown in the example Evalscript below. This factor allows you to preserve the entire range of values, including reflectance slightly larger than 1 (yes, reflectance can be larger than 1), whilst retrieving the original values with a 4-digit precision.
The sketch below helps understand to what precision we can retrieve reflectance locally after going through the multiplication and division process. Please keep in mind how meaningful the values are going to be in regards to instrument accuracy though: does that fourth decimal digit really matter?
Example 2: Indices
Not all satellite products have values that range between 0 and 1. The use of normalised spectral indices, as you can find in the Custom Scripts Repository, are very common in the world of Remote Sensing, and more often than not, values lie be between -1 and 1. With negative values in play, suddenly the tips I previously presented don’t work anymore: all negative values would be returned as 0. The trick in this case is to use an offset in combination with the multiplication that will allow us to get back to the negative values later. In this example, I will also use 10000 as an offset. The following Evalscript returns Sentinel-2 NDVI as
Quick tip: to save having to write the formula for calculating normalised indices in Sentinel Hub (difference divided by sum of bands), simply use the in-built index function!
The NDVI values returned by the example above will range between 0 and 20000. Once the images are downloaded, you can easily rescale back to the range -1 to 1 by applying the following operation to your image:
ndvi_float = (ndvi-10000)/10000.
Just as we did for reflectance, let’s see how the operations transform the data for each of the steps with a few examples:
A trade-off: savings vs ease of use.
You may wonder just how much data or processing costs you will save with the tricks I showed you above.
Is it really worth the hassle?
The answer to this question, as often, is: “it depends”. A lot of factors come into play when considering savings: the extent of the request, how many bands you are querying, how many times you will run the request, etc…
To get a rough idea nevertheless, I compiled a table of the savings for a couple of examples based on the Processing Units (PU) definition page and some test runs using Requests Builder. In the following table, we will compare the size of files downloaded and the number of PUs required for 2 different areas based on a Sentinel-2 image. The number of bands is set to 3 to represent an RGB composite.
For a small area (26 km², roughly the size of Tuvalu), requesting an image in
UINT16 is probably not worth the hassle of having to convert the reflectance back to
FLOAT locally, as you will only save 0.5 MB and 1 PU per request. Although it might be worth it if you are running the request often, given that you are saving 30% data volume, which adds up in the long run. However, as your request size grows, requesting data in
UINT16 starts to become very advantageous. For an area of 625 km² (you could cover Saint Lucia), you would be saving 8.5 MB and 24 PUs per request. And in all cases, just switching from
UINT16 cuts the PU consumption in half. Imagine the savings when running large-scale applications!
Of course, if you don’t want to bother and access the values straight out of Sentinel Hub, you can just set the
FLOAT32 and forget about all the above.
Do you apply any tricks to reduce your data consumption? Make sure to give your tips in the comments below or share your wisdom in our Sentinel Hub forum!
Now let’s get scripting…