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.