using the cloud and qa masks.md

For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to page URLs; this page is available as Markdown.

Using the Cloud and QA Masks

1. Explore the cloud mask asset

1.1 Access the cloud mask COG

import rioxarray as rxr
from matplotlib import pyplot as plt
import matplotlib.colors as mcolors
import seaborn as sns
import numpy as np

cloud_mask = rxr.open_rasterio(item.assets['CLOUD_MASK'].href)

1.2 Visualize the cloud mask

Here, we'll define and use a function to plot the mask with a discrete colormap.

def plot_mask(data, set_min=True, title=None, cmap='cubehelix', vmin=None, vmax=None, colorbar_label=None):
    '''
    data: a DataArray containing quality or cloud mask data
    set_min: boolean, if True, sets the color for the minimum value to white
    title: None or string representing the title of the plot
    cmap: colormap to be used with plt.imshow()
    vmin, vmax: min and max values for the colormap
    colorbar_label: None or string
    '''
    data = data.squeeze() # Remove extra dimension if present

min_val = int(data.min().values)
    max_val = int(data.max().values)
    n_colors = max_val - min_val +1

cmap = sns.color_palette(cmap, n_colors) # Discrete cmap with breaks at every integer
    if set_min: cmap[0] = 'whitesmoke' # set specific color for min
    cmap = mcolors.ListedColormap(cmap)

bounds = np.arange(min_val, max_val+2)
    norm = mcolors.BoundaryNorm(bounds, cmap.N)

plt.figure(figsize=(12,10))
    plot = plt.imshow(data, cmap=cmap, norm=norm)
    if colorbar_label is not None:
        max_ticks = 10
        values = np.arange(min_val, max_val + 1)
        tick_values = values if len(values) <= max_ticks else np.linspace(min_val, max_val, max_ticks, dtype=int)
        cbar = plt.colorbar(
            plot,
            label=colorbar_label,
            ticks=tick_values + 0.5
        )
        cbar.ax.set_yticklabels(tick_values)
        
    plt.xticks([])
    plt.yticks([])
    plt.title(title, weight='bold')
    plt.tight_layout()
    plt.show()
    return

Let's plot the cloud mask for the item of interest.

plot_mask(cloud_mask, title=f'Item ID: {item.id}', colorbar_label='Cloud mask value')

The cloud mask includes bit-packed values, so we'll need to unpack them to understand what they represent.

1.3 Interpret the cloud mask pixel values

First, we'll need to write a function to convert a given binary value to a 16-bit binary string.

def decimal_to_binary(value, bit_length=16):
    return format(value, f'0{bit_length}b')

Next, we'll create a dict with information about each bit's meaning. This information is available on the FAQs page and in our product guide.

# Interpretation when set to True
cloud_mask_bit_meanings = {
    0: "No data",
    1: "Cloud"
}

Finally, we'll display a lookup table for each unique pixel value from the cloud mask.

This function takes the cloud mask and bit definitions as input and outputs a table of pixel values mapped to their meanings.

import pandas as pd

def make_lookup_table(mask, meanings):

unique_decimal_values = np.unique(mask)
    binary_strings = [decimal_to_binary(int(val)) for val in unique_decimal_values]

df = pd.DataFrame({
        "Decimal": unique_decimal_values,
        "Binary": binary_strings
    })

# Add Yes/No columns for each bit
    for bit, bit_name in meanings.items():
        df[bit_name] = ["Yes" if binary[-(bit + 1)] == "1" else "No" for binary in binary_strings] # -(bit + 1) ensures we are reading the string from right to left

return df
cloud_LUT = make_lookup_table(cloud_mask, cloud_mask_bit_meanings)
cloud_LUT
Decimal Binary No data Cloud Cloud shadow Snow or ice
0.0 0000000000000000 No No No No
1.0 0000000000000001 Yes No No No
2.0 0000000000000010 No Yes No No

There we go! Each pixel value represents a specific set of conditions. For example, a value of 2 indicates cloudy conditions; a value of 1 represents no data; and a value of 0 represents clear conditions.

Access the QA mask asset

The QA mask provides information about radiometric saturation in the VNIR and LWIR bands. Similar to the cloud mask, it contains bit-packed values.

First, we'll use rioxarray to access the QA mask data.

qa_mask = rxr.open_rasterio(item.assets['quality_assurance'].href).squeeze()

The following dict contains information about each bit's meaning.

# Interpretation of bits 0-8 when set to True
qa_mask_bit_meanings = {
    0: "Blue band saturation",
    1: "Green band saturation",
    2: "Red band saturation",
    3: "Red edge 1 band saturation",
    4: "Red edge 2 band saturation",
    5: "Red edge 3 band saturation",
    6: "NIR band saturation",
    7: "LWIR 1 band saturation",
    8: "LWIR 2 band saturation"
}

Let's plot the QA mask to understand the variability in pixel values.

plot_mask(qa_mask, set_min=False, cmap='rainbow', title=f'Item ID: {item.id}', colorbar_label='QA mask value')

Interpret the QA mask pixel values

Now, we'll use the same functions we wrote previously to interpret the pixel values from the QA mask.

qa_LUT = make_lookup_table(qa_mask, qa_mask_bit_meanings)
qa_LUT
Decimal Binary Blue band saturation Green band saturation Red band saturation Red edge 1 band saturation Red edge 2 band saturation Red edge 3 band saturation NIR band saturation LWIR 1 band saturation LWIR 2 band saturation
0 0000000000000000 No No No No No No No No No
1 0000000000000001 Yes No No No No No No No No
2 0000000000000010 No Yes No No No No No No No
3 0000000000000011 Yes Yes No No No No No No No
4 0000000000000100 No No Yes No No No No No No
... ... ... ... ... ... ... ... ... ... ...
67 0000000001000011 Yes Yes No No No No Yes No No
68 0000000001000100 No No Yes No No No Yes No No
69 0000000001000101 Yes No Yes No No No Yes No No
70 0000000001000110 No Yes Yes No No No Yes No No
71 0000000001000111 Yes Yes Yes No No No Yes No No

The bottom line is that a pixel value of 0 means that no bands are saturated, while a nonzero pixel value indicates some saturation.