SampleType: what’s all the fuss about?

Maxim Lamare
Sentinel Hub Blog
Published in
8 min readFeb 15, 2022

--

AUTO, UINT8, UINT16, FLOAT32… Strategically set the SampleType for Sentinel Hub requests.

Image: Markus Spiske / Unsplash.

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!

The basics

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: UINT8, UINT16, FLOAT32, and AUTO.

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?

  • UINT8 is an unsigned 8-bits integer, with values ranging from 0 to 255.
  • UINT16 is an unsigned 16-bits integer, with values ranging from 0 to 65535.
  • FLOAT32 is 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.
  • AUTO is 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 sampleType is 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 sampleType settings.

Output values returned by Sentinel Hub for a given input value (Calculated Value). Find more examples and explanations in the FAQ.

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 sampleType: "FLOAT32".

There are cases where we may want to return data in UINT8 or 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.

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!

Sentinel-2 B2 (Blue) visualised in UINT16 over Manicouagan Reservoir, Quebec, Canada on 21st February 2021. (🌐 EO Browser)

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 AUTO, UINT8, or UINT16 as sampleType, you can still access the FLOAT32 values using the option TIFF (32-bit float)… and vice-versa.

Download dialog box from EO Browser showing the different options that can be set.

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 sampleType to FLOAT32. Not so fast! By being savvy with your sampleType you can save quite a lot of data transfer, disk space and Processing Units.

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.

Sentinel-2 B2 (Blue) visualised in UINT8 with a multiplication factor over Salt flat, Bolivia on 17th July 2021. (🌐 EO Browser)

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.

Example Evalscript that returns reflectance multiplied by a factor of 10000.

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?

On the left: 3 original reflectance values. In the middle: these same values when multiplied by 10000 and rounded to UINT16. On the right: the FLOAT32 values retrieved by dividing the results from your Sentinel Hub request by 10000. Note the rounding past 4 digits.

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 UINT16.

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 FLOAT32 to 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 sampleType to FLOAT32 and forget about all the above.

Normalized Difference Moisture Index over Tierras Bajas Project in Bolivia, seen by Landsat-8 on 20th July 2021 (🌐 EO Browser)

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…

--

--