Skip to content

earthlib.CloudMask

Functions for cloud masking earth engine images.

Landsat4578(img)

Cloud-masks Landsat images.

See gis.stackexchange.com/questions/349371/creating-cloud-free-images-out-of-a-mod09a1-modis-image-in-gee Previously: gis.stackexchange.com/questions/425159/how-to-make-a-cloud-free-composite-for-landsat-8-collection-2-surface-reflectanc/425160

Parameters:

Name Type Description Default
img Image

the ee.Image to mask. Must have a Landsat "QA_PIXEL" band.

required

Returns:

Type Description
Image

the same input image with an updated mask.

Source code in earthlib/CloudMask.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
def Landsat4578(img: ee.Image) -> ee.Image:
    """Cloud-masks Landsat images.

    See https://gis.stackexchange.com/questions/349371/creating-cloud-free-images-out-of-a-mod09a1-modis-image-in-gee
    Previously: https://gis.stackexchange.com/questions/425159/how-to-make-a-cloud-free-composite-for-landsat-8-collection-2-surface-reflectanc/425160

    Args:
        img: the ee.Image to mask. Must have a Landsat "QA_PIXEL" band.

    Returns:
        the same input image with an updated mask.
    """
    qa = img.select("QA_PIXEL")
    sat = img.select("QA_RADSAT")

    dilatedCloud = bitwiseSelect(qa, 1).eq(0)
    cirrus = bitwiseSelect(qa, 2).eq(0)
    cloud = bitwiseSelect(qa, 3).eq(0)
    cloudShadow = bitwiseSelect(qa, 4).eq(0)
    snow = bitwiseSelect(qa, 5).eq(0)
    qaMask = dilatedCloud.And(cirrus).And(cloud).And(cloudShadow).And(snow)

    satMask = sat.eq(0)
    jointMask = qaMask.And(satMask)

    return img.updateMask(jointMask)

MODIS(img)

Dummy function for MODIS images.

This function just returns the original image because the MODIS collection already applies a cloud mask to all pixels. It only exists so as to not break other processing chains that use .bySensor() methods.

Parameters:

Name Type Description Default
img Image

an ee.Image.

required

Returns:

Type Description
Image

the input image.

Source code in earthlib/CloudMask.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
def MODIS(img: ee.Image) -> ee.Image:
    """Dummy function for MODIS images.

    This function just returns the original image because the MODIS collection
        already applies a cloud mask to all pixels. It only exists so as to
        not break other processing chains that use .bySensor() methods.

    Args:
        img: an ee.Image.

    Returns:
        the input image.
    """
    return img

Opening(img, iterations=3)

Apply a morphological opening filter to an image mask.

Parameters:

Name Type Description Default
img Image

the ee.Image to mask. Must have a mask band set.

required
iterations int

the number of sequential dilate/erode operations.

3

Returns:

Type Description
Image

the input image with an updated, opened mask.

Source code in earthlib/CloudMask.py
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
def Opening(img: ee.Image, iterations: int = 3) -> ee.Image:
    """Apply a morphological opening filter to an image mask.

    Args:
        img: the ee.Image to mask. Must have a mask band set.
        iterations: the number of sequential dilate/erode operations.

    Returns:
        the input image with an updated, opened mask.
    """
    mask = img.mask()
    kernel = ee.Kernel.circle(iterations)
    dilateOpts = {"kernel": kernel, "iterations": iterations}
    erodeOpts = {"kernel": kernel, "iterations": iterations}
    openedMask = mask.focalMin(**dilateOpts).focalMax(**erodeOpts)

    return img.updateMask(openedMask)

Sentinel2(img, use_qa=True, use_scl=True)

Mask Sentinel2 images using multiple masking approaches.

Parameters:

Name Type Description Default
img Image

the ee.Image to mask.

required
use_qa bool

apply QA band cloud masking. img must have a "QA60" band.

True
use_scl bool

apply SCL band cloud masking. img must have a "SCL" band.

True

Returns:

Type Description
Image

the same input image with an updated mask.

Source code in earthlib/CloudMask.py
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
def Sentinel2(img: ee.Image, use_qa: bool = True, use_scl: bool = True) -> ee.Image:
    """Mask Sentinel2 images using multiple masking approaches.

    Args:
        img: the ee.Image to mask.
        use_qa: apply QA band cloud masking. img must have a "QA60" band.
        use_scl: apply SCL band cloud masking. img must have a "SCL" band.

    Returns:
        the same input image with an updated mask.
    """
    if use_qa:
        img = Sentinel2QA(img)

    if use_scl:
        img = Sentinel2SCL(img)

    return img

Sentinel2QA(img)

Masks Sentinel2 images using the QA band.

Parameters:

Name Type Description Default
img Image

the ee.Image to mask. Must have a Sentinel "QA60" band.

required

Returns:

Type Description
Image

the same input image with an updated mask.

Source code in earthlib/CloudMask.py
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
def Sentinel2QA(img: ee.Image) -> ee.Image:
    """Masks Sentinel2 images using the QA band.

    Args:
        img: the ee.Image to mask. Must have a Sentinel "QA60" band.

    Returns:
        the same input image with an updated mask.
    """
    qa = img.select("QA60")
    cloud = bitwiseSelect(qa, 10).eq(0)
    cirrus = bitwiseSelect(qa, 11).eq(0)
    mask = cloud.And(cirrus)

    return img.updateMask(mask)

Sentinel2SCL(img)

Masks Sentinel2 images using scene classification labels.

Parameters:

Name Type Description Default
img Image

the ee.Image to mask. Must have a Sentinel "SCL" class band.

required

Returns:

Type Description
Image

the same input image with an updated mask.

Source code in earthlib/CloudMask.py
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
def Sentinel2SCL(img: ee.Image) -> ee.Image:
    """Masks Sentinel2 images using scene classification labels.

    Args:
        img: the ee.Image to mask. Must have a Sentinel "SCL" class band.

    Returns:
        the same input image with an updated mask.
    """
    scl = img.select("SCL")

    # class labels
    sat = scl.neq(1)
    shadow = scl.neq(3)
    cloudLow = scl.neq(7)
    cloudMed = scl.neq(8)
    cloudHigh = scl.neq(9)
    cirrus = scl.neq(10)
    bareSoil = scl.eq(5)
    baseMask = sat.And(shadow).And(cloudLow).And(cloudMed).And(cloudHigh).And(cirrus)

    # apply morphological closing to clean up one/two pixel cloud predictions
    cleanupKernel = ee.Kernel.circle(2)
    focalOpts = {"kernel": cleanupKernel, "iterations": 1}
    erodedMask = baseMask.focalMax(**focalOpts).focalMin(**focalOpts)

    # include a search radius to include false positive bare soil predictions near clouds
    euclidean = ee.Kernel.euclidean(1000, "meters")
    distToCloud = erodedMask.subtract(1).distance(euclidean).unmask()
    notBare = ee.Image(1).subtract(bareSoil.And(distToCloud.lte(100)))
    cloudBareMask = erodedMask.And(notBare)

    return img.updateMask(cloudBareMask)

VIIRS(img)

Masks VIIRS images.

Parameters:

Name Type Description Default
img Image

the ee.Image to mask. Must have "QF1" and "QF2" bands.

required

Returns:

Type Description
Image

the same input image with an updated mask.

Source code in earthlib/CloudMask.py
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
def VIIRS(img: ee.Image) -> ee.Image:
    """Masks VIIRS images.

    Args:
        img: the ee.Image to mask. Must have "QF1" and "QF2" bands.

    Returns:
        the same input image with an updated mask.
    """
    qf1 = img.select("QF1")
    qf2 = img.select("QF2")
    qf7 = img.select("QF7")

    clearMask = bitwiseSelect(qf1, 2, 3).eq(0)
    dayMask = bitwiseSelect(qf1, 4).eq(0)
    shadowMask = bitwiseSelect(qf2, 3).eq(0)
    snowMask = bitwiseSelect(qf2, 5).eq(0)
    cirrus1Mask = bitwiseSelect(qf2, 6).eq(0)
    cirrus2Mask = bitwiseSelect(qf2, 7).eq(0)
    adjacentMask = bitwiseSelect(qf7, 1).eq(0)
    thinCirrusMask = bitwiseSelect(qf7, 4).eq(0)

    mask = (
        clearMask.And(dayMask)
        .And(shadowMask)
        .And(snowMask)
        .And(cirrus1Mask)
        .And(cirrus2Mask)
        .And(adjacentMask)
        .And(thinCirrusMask)
    )

    return img.updateMask(mask)

bitwiseSelect(img, fromBit, toBit=None)

Filter QA bit masks.

Parameters:

Name Type Description Default
img Image

the QA band image

required
fromBit int

QA start bit

required
toBit int

QA end bit

None

Returns:

Type Description
Image

encoded bitmap for the passed QA bits

Source code in earthlib/CloudMask.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def bitwiseSelect(img: ee.Image, fromBit: int, toBit: int = None) -> ee.Image:
    """Filter QA bit masks.

    Args:
        img: the QA band image
        fromBit: QA start bit
        toBit: QA end bit

    Returns:
        encoded bitmap for the passed QA bits
    """
    toBit = fromBit if toBit is None else toBit
    size = ee.Number(1).add(toBit).subtract(fromBit)
    mask = ee.Number(1).leftShift(size).subtract(1)
    return img.rightShift(fromBit).bitwiseAnd(mask)

bySensor(sensor)

Returns the appropriate cloud mask function to use by sensor type.

Parameters:

Name Type Description Default
sensor str

string with the sensor name to return (e.g. "Landsat8", "Sentinel2").

required

Returns:

Type Description
Callable

the mask function associated with a sensor to pass to an ee .map() call

Source code in earthlib/CloudMask.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def bySensor(sensor: str) -> Callable:
    """Returns the appropriate cloud mask function to use by sensor type.

    Args:
        sensor: string with the sensor name to return (e.g. "Landsat8", "Sentinel2").

    Returns:
        the mask function associated with a sensor to pass to an ee .map() call
    """
    lookup = {
        "Landsat4": Landsat4578,
        "Landsat5": Landsat4578,
        "Landsat7": Landsat4578,
        "Landsat8": Landsat4578,
        "Sentinel2": Sentinel2,
        "MODIS": MODIS,
        "VIIRS": VIIRS,
    }
    try:
        function = lookup[sensor]
        return function
    except KeyError:
        supported = ", ".join(lookup.keys())
        raise SensorError(
            f"Cloud masking not supported for '{sensor}'. Supported: {supported}"
        )