mask_with_scl

mask_with_scl(data, scl, mask_codes=None, fill_value=None)[source]

Apply SCL-based masking to a data array.

This function masks pixels in the data array based on the Scene Classification Layer (SCL) values. Unlike mask_scl which only returns a masked SCL array, this function applies the mask to actual data (e.g., spectral bands).

Supported array shapes: - 2D data (y, x) with 2D SCL (y, x) - 3D data (time, y, x) with 3D SCL (time, y, x) - 4D data (time, band, y, x) with 3D SCL (time, y, x) - SCL broadcast across bands

Parameters:
  • data (numpy.ndarray) – The data array to mask (2D, 3D, or 4D).

  • scl (numpy.ndarray) – The SCL array (2D or 3D). For 4D data, SCL should be 3D (time, y, x).

  • mask_codes (sequence of float, optional) – SCL codes to mask (set to fill_value). Defaults to clouds/shadows/etc: [0, 1, 2, 3, 8, 9, 10] (no data, saturated, dark, shadow, cloud med/high, cirrus).

  • fill_value (float, optional) – Value to assign to masked pixels. Defaults to NaN.

Returns:

Data array with masked pixels replaced by fill_value.

Return type:

numpy.ndarray

Examples

>>> import numpy as np
>>> from eo_processor import mask_with_scl
>>> # 3D example: (time=2, y=3, x=3)
>>> data = np.ones((2, 3, 3), dtype=np.float64)
>>> scl = np.array([[[4, 4, 9], [4, 8, 4], [4, 4, 4]],
...                 [[4, 4, 4], [3, 4, 4], [4, 4, 10]]], dtype=np.float64)
>>> result = mask_with_scl(data, scl)
>>> # Pixels with SCL codes 9, 8, 3, 10 are now NaN
>>> np.isnan(result[0, 0, 2])  # SCL=9 (cloud high)
True

Overview

mask_with_scl applies SCL-based masking directly to a data array (e.g., spectral bands). Unlike mask_scl which only filters the SCL array itself, this function uses the SCL as a classification layer to mask corresponding pixels in the actual data.

This is particularly useful when:

  • Processing multi-band imagery where clouds/shadows need to be removed

  • Working with time-series data where the SCL mask should be applied consistently

  • Building composites that require pre-masking of invalid pixels

Supported Array Shapes

The function supports the following combinations:

Data Shape

SCL Shape

2D: (y, x)

2D: (y, x)

3D: (time, y, x)

3D: (time, y, x)

4D: (time, band, y, x)

3D: (time, y, x)

For 4D data with 3D SCL, the mask is broadcast across all bands, ensuring that if a pixel is cloudy at time t, it is masked in all spectral bands at that time step.

Default Mask Codes

If mask_codes is not provided, the function masks the following SCL classes:

  • 0: No Data

  • 1: Saturated / Defective

  • 2: Dark Area Pixels

  • 3: Cloud Shadows

  • 8: Cloud (Medium Probability)

  • 9: Cloud (High Probability)

  • 10: Thin Cirrus

This leaves valid surface pixels (vegetation, bare soil, water, snow/ice) intact.

Typical Sentinel-2 SCL Codes

For reference:

Code

Meaning

Action

0 1 2 3 4 5 6 7 8 9 10 11

No Data Saturated / Defective Dark Area Pixels Cloud Shadows Vegetation Not Vegetated Water Unclassified Cloud (Medium Probability) Cloud (High Probability) Thin Cirrus Snow / Ice

Mask Mask Mask Mask Keep Keep Keep Keep Mask Mask Mask Keep

Parameters

  • data (numpy.ndarray): The data array to mask (2D, 3D, or 4D).

  • scl (numpy.ndarray): The SCL classification layer (2D or 3D).

  • mask_codes (Sequence[float] | None): SCL codes to mask. Defaults to [0, 1, 2, 3, 8, 9, 10].

  • fill_value (float | None): Value for masked pixels. Defaults to NaN.

Returns

numpy.ndarray (float64): Data array with masked pixels replaced.

Examples

Basic 3D Usage

import numpy as np
from eo_processor import mask_with_scl

# Sentinel-2 bands: shape (time=10, y=100, x=100)
red_band = np.random.rand(10, 100, 100)
scl = np.random.choice([4, 5, 6, 8, 9], size=(10, 100, 100))

# Mask cloud pixels (8, 9) in the red band
masked_red = mask_with_scl(red_band, scl)

4D Multi-Band Usage

import numpy as np
from eo_processor import mask_with_scl

# Multi-band data: shape (time=10, band=4, y=100, x=100)
# Bands: B02 (Blue), B03 (Green), B04 (Red), B08 (NIR)
data = np.random.rand(10, 4, 100, 100)
scl = np.random.choice([4, 5, 6, 8, 9], size=(10, 100, 100))

# Mask clouds across all bands
masked_data = mask_with_scl(data, scl)

Custom Mask Codes

# Only mask high-probability clouds and shadows
masked = mask_with_scl(data, scl, mask_codes=[3, 9])

Custom Fill Value

# Use -9999 as nodata instead of NaN
masked = mask_with_scl(data, scl, fill_value=-9999.0)

Integration with XArray/Dask

import xarray as xr
from eo_processor import mask_with_scl

# Apply via xr.apply_ufunc for chunked processing
masked_data = xr.apply_ufunc(
    mask_with_scl,
    data_array,    # 4D: (time, band, y, x)
    scl_array,     # 3D: (time, y, x)
    dask="parallelized",
    output_dtypes=[float],
)

See Also