Using the Cloud and QA Masks | Hydrosat Docs

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

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
plot_mask(cloud_mask, title=f'Item ID: {item.id}', colorbar_label='Cloud mask value')

1.3 Interpret the cloud mask pixel values

def decimal_to_binary(value, bit_length=16):
    return format(value, f'0{bit_length}b')
cloud_mask_bit_meanings = {
    0: "No data",
    1: "Cloud"
}
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
0.0 0000000000000000 No No
1.0 0000000000000001 Yes No
2.0 0000000000000010 No Yes

Access the QA mask asset

qa_mask = rxr.open_rasterio(item.assets['quality_assurance'].href).squeeze()
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"
}
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

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

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