RadGEEToolbox 1.6.4__py3-none-any.whl → 1.6.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- RadGEEToolbox/CollectionStitch.py +20 -10
- RadGEEToolbox/GetPalette.py +109 -15
- RadGEEToolbox/LandsatCollection.py +924 -359
- RadGEEToolbox/Sentinel1Collection.py +568 -292
- RadGEEToolbox/Sentinel2Collection.py +685 -258
- RadGEEToolbox/VisParams.py +83 -83
- RadGEEToolbox/__init__.py +2 -2
- {radgeetoolbox-1.6.4.dist-info → radgeetoolbox-1.6.6.dist-info}/METADATA +25 -6
- radgeetoolbox-1.6.6.dist-info/RECORD +12 -0
- radgeetoolbox-1.6.4.dist-info/RECORD +0 -12
- {radgeetoolbox-1.6.4.dist-info → radgeetoolbox-1.6.6.dist-info}/WHEEL +0 -0
- {radgeetoolbox-1.6.4.dist-info → radgeetoolbox-1.6.6.dist-info}/licenses/LICENSE.txt +0 -0
- {radgeetoolbox-1.6.4.dist-info → radgeetoolbox-1.6.6.dist-info}/top_level.txt +0 -0
|
@@ -2,6 +2,8 @@ import ee
|
|
|
2
2
|
import math
|
|
3
3
|
import pandas as pd
|
|
4
4
|
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
5
7
|
class Sentinel1Collection:
|
|
6
8
|
"""
|
|
7
9
|
Represents a user-defined collection of ESA Sentinel-1 C-band Synthetic Aperture Radar (SAR) GRD data at 10 m/px resolution from Google Earth Engine (GEE). Units of backscatter are in decibels (dB) by default.
|
|
@@ -28,7 +30,7 @@ class Sentinel1Collection:
|
|
|
28
30
|
collection (ee.ImageCollection, optional): A pre-filtered Sentinel-1 ee.ImageCollection object to be converted to a Sentinel1Collection object. Overrides all other filters.
|
|
29
31
|
|
|
30
32
|
Attributes:
|
|
31
|
-
collection (ee.ImageCollection): The filtered or user-supplied image collection converted to an ee.ImageCollection object.
|
|
33
|
+
collection (ee.ImageCollection): The filtered or user-supplied image collection converted to an ee.ImageCollection object.
|
|
32
34
|
|
|
33
35
|
Raises:
|
|
34
36
|
ValueError: Raised if required filter parameters are missing, or if both `collection` and other filters are provided.
|
|
@@ -58,11 +60,35 @@ class Sentinel1Collection:
|
|
|
58
60
|
>>> latest_image = SAR_collection.image_grab(-1)
|
|
59
61
|
>>> mean_SAR_backscatter = SAR_collection.mean
|
|
60
62
|
"""
|
|
61
|
-
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
start_date=None,
|
|
67
|
+
end_date=None,
|
|
68
|
+
relative_orbit_start=None,
|
|
69
|
+
relative_orbit_stop=None,
|
|
70
|
+
instrument_mode=None,
|
|
71
|
+
polarization=None,
|
|
72
|
+
bands=None,
|
|
73
|
+
orbit_direction=None,
|
|
74
|
+
boundary=None,
|
|
75
|
+
resolution=None,
|
|
76
|
+
resolution_meters=None,
|
|
77
|
+
collection=None,
|
|
78
|
+
):
|
|
62
79
|
if collection is None and (start_date is None or end_date is None):
|
|
63
|
-
raise ValueError(
|
|
64
|
-
|
|
65
|
-
|
|
80
|
+
raise ValueError(
|
|
81
|
+
"Either provide all required fields (start_date, end_date, tile_row, tile_path ; or boundary in place of tiles) or provide a collection."
|
|
82
|
+
)
|
|
83
|
+
if (
|
|
84
|
+
relative_orbit_start is None
|
|
85
|
+
and relative_orbit_stop is None
|
|
86
|
+
and boundary is None
|
|
87
|
+
and collection is None
|
|
88
|
+
):
|
|
89
|
+
raise ValueError(
|
|
90
|
+
"Provide either tile or boundary/geometry specifications to filter the image collection"
|
|
91
|
+
)
|
|
66
92
|
if collection is None:
|
|
67
93
|
self.start_date = start_date
|
|
68
94
|
self.end_date = end_date
|
|
@@ -77,8 +103,8 @@ class Sentinel1Collection:
|
|
|
77
103
|
self.bands = bands
|
|
78
104
|
|
|
79
105
|
if resolution is None:
|
|
80
|
-
self.resolution =
|
|
81
|
-
elif resolution not in [
|
|
106
|
+
self.resolution = "H"
|
|
107
|
+
elif resolution not in ["H", "M"]:
|
|
82
108
|
raise ValueError("Resolution must be either 'H' or 'M'")
|
|
83
109
|
else:
|
|
84
110
|
pass
|
|
@@ -91,32 +117,36 @@ class Sentinel1Collection:
|
|
|
91
117
|
else:
|
|
92
118
|
self.resolution_meters = resolution_meters
|
|
93
119
|
else:
|
|
94
|
-
pass
|
|
120
|
+
pass
|
|
95
121
|
|
|
96
122
|
if orbit_direction is None:
|
|
97
|
-
self.orbit_direction = [
|
|
98
|
-
elif orbit_direction == [
|
|
123
|
+
self.orbit_direction = ["ASCENDING", "DESCENDING"]
|
|
124
|
+
elif orbit_direction == ["ASCENDING", "DESCENDING"]:
|
|
99
125
|
self.orbit_direction = orbit_direction
|
|
100
|
-
elif orbit_direction not in [
|
|
101
|
-
raise ValueError(
|
|
126
|
+
elif orbit_direction not in ["ASCENDING", "DESCENDING"]:
|
|
127
|
+
raise ValueError(
|
|
128
|
+
"Orbit direction must be either 'ASCENDING' or 'DESCENDING', or '['ASCENDING', 'DESCENDING']' "
|
|
129
|
+
)
|
|
102
130
|
else:
|
|
103
131
|
pass
|
|
104
132
|
|
|
105
133
|
if instrument_mode is None:
|
|
106
|
-
self.instrument_mode =
|
|
107
|
-
elif instrument_mode not in [
|
|
134
|
+
self.instrument_mode = "IW"
|
|
135
|
+
elif instrument_mode not in ["IW", "EW", "SM"]:
|
|
108
136
|
raise ValueError("Instrument mode must be either 'IW', 'EW', or 'SM'")
|
|
109
137
|
else:
|
|
110
138
|
pass
|
|
111
139
|
|
|
112
140
|
if polarization is None:
|
|
113
|
-
self.polarization = [
|
|
114
|
-
elif polarization not in [[
|
|
115
|
-
raise ValueError(
|
|
141
|
+
self.polarization = ["VV", "VH"]
|
|
142
|
+
elif polarization not in [["VV"], ["HH"], ["VV", "VH"], ["HH", "HV"]]:
|
|
143
|
+
raise ValueError(
|
|
144
|
+
"Polarization must be either ['VV'], ['HH'], ['VV, VH'], or ['HH, HV']"
|
|
145
|
+
)
|
|
116
146
|
else:
|
|
117
147
|
pass
|
|
118
148
|
|
|
119
|
-
valid_bands = [
|
|
149
|
+
valid_bands = ["HH", "HV", "VV", "VH", "angle"]
|
|
120
150
|
|
|
121
151
|
if bands is not None and isinstance(bands, str):
|
|
122
152
|
bands = [bands]
|
|
@@ -124,9 +154,14 @@ class Sentinel1Collection:
|
|
|
124
154
|
if bands is None:
|
|
125
155
|
self.bands = self.polarization
|
|
126
156
|
elif not all(band in valid_bands for band in bands):
|
|
127
|
-
raise ValueError(
|
|
157
|
+
raise ValueError(
|
|
158
|
+
"Band must be either 'HH', 'HV', 'VV', 'VH', or 'angle'"
|
|
159
|
+
)
|
|
128
160
|
elif not all(band in self.polarization for band in bands):
|
|
129
|
-
raise ValueError(
|
|
161
|
+
raise ValueError(
|
|
162
|
+
"Band must be associated with chosen polarization type, currently: "
|
|
163
|
+
+ str(self.polarization)
|
|
164
|
+
)
|
|
130
165
|
else:
|
|
131
166
|
self.bands = bands
|
|
132
167
|
|
|
@@ -160,7 +195,6 @@ class Sentinel1Collection:
|
|
|
160
195
|
else:
|
|
161
196
|
self.bands = [self.bands]
|
|
162
197
|
|
|
163
|
-
|
|
164
198
|
# Filter the collection
|
|
165
199
|
if boundary and relative_orbit_start and relative_orbit_start is not None:
|
|
166
200
|
self.collection = self.get_boundary_and_orbit_filtered_collection()
|
|
@@ -171,7 +205,6 @@ class Sentinel1Collection:
|
|
|
171
205
|
else:
|
|
172
206
|
self.collection = collection
|
|
173
207
|
|
|
174
|
-
|
|
175
208
|
self._dates_list = None
|
|
176
209
|
self._dates = None
|
|
177
210
|
self._geometry_masked_collection = None
|
|
@@ -192,18 +225,19 @@ class Sentinel1Collection:
|
|
|
192
225
|
"""
|
|
193
226
|
Adds date to image properties as 'Date_Filter'.
|
|
194
227
|
|
|
195
|
-
Args:
|
|
228
|
+
Args:
|
|
196
229
|
image (ee.Image): Input image
|
|
197
230
|
|
|
198
|
-
Returns:
|
|
231
|
+
Returns:
|
|
199
232
|
ee.Image: Image with date in properties.
|
|
200
233
|
"""
|
|
201
|
-
date = ee.Number(image.date().format(
|
|
202
|
-
return image.set({
|
|
203
|
-
|
|
204
|
-
|
|
234
|
+
date = ee.Number(image.date().format("YYYY-MM-dd"))
|
|
235
|
+
return image.set({"Date_Filter": date})
|
|
236
|
+
|
|
205
237
|
@staticmethod
|
|
206
|
-
def PixelAreaSum(
|
|
238
|
+
def PixelAreaSum(
|
|
239
|
+
image, band_name, geometry, threshold=-1, scale=30, maxPixels=1e12
|
|
240
|
+
):
|
|
207
241
|
"""
|
|
208
242
|
Function to calculate the summation of area for pixels of interest (above a specific threshold) in a geometry
|
|
209
243
|
and store the value as image property (matching name of chosen band).
|
|
@@ -215,26 +249,34 @@ class Sentinel1Collection:
|
|
|
215
249
|
threshold (float): integer threshold to specify masking of pixels below threshold (defaults to -1)
|
|
216
250
|
scale (int): integer scale of image resolution (meters) (defaults to 30)
|
|
217
251
|
maxPixels (int): integer denoting maximum number of pixels for calculations
|
|
218
|
-
|
|
219
|
-
Returns:
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
220
254
|
ee.Image: ee.Image with area calculation stored as property matching name of band
|
|
221
255
|
"""
|
|
222
256
|
area_image = ee.Image.pixelArea()
|
|
223
257
|
mask = image.select(band_name).gte(threshold)
|
|
224
258
|
final = image.addBands(area_image)
|
|
225
|
-
stats =
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
259
|
+
stats = (
|
|
260
|
+
final.select("area")
|
|
261
|
+
.updateMask(mask)
|
|
262
|
+
.rename(band_name)
|
|
263
|
+
.reduceRegion(
|
|
264
|
+
reducer=ee.Reducer.sum(),
|
|
265
|
+
geometry=geometry,
|
|
266
|
+
scale=scale,
|
|
267
|
+
maxPixels=maxPixels,
|
|
268
|
+
)
|
|
269
|
+
)
|
|
230
270
|
return image.set(band_name, stats.get(band_name))
|
|
231
|
-
|
|
232
|
-
def PixelAreaSumCollection(
|
|
271
|
+
|
|
272
|
+
def PixelAreaSumCollection(
|
|
273
|
+
self, band_name, geometry, threshold=-1, scale=30, maxPixels=1e12
|
|
274
|
+
):
|
|
233
275
|
"""
|
|
234
|
-
Function to calculate the summation of area for pixels of interest (above a specific threshold)
|
|
276
|
+
Function to calculate the summation of area for pixels of interest (above a specific threshold)
|
|
235
277
|
within a geometry and store the value as image property (matching name of chosen band) for an entire
|
|
236
278
|
image collection.
|
|
237
|
-
The resulting value has units of square meters.
|
|
279
|
+
The resulting value has units of square meters.
|
|
238
280
|
|
|
239
281
|
Args:
|
|
240
282
|
band_name (str): name of band (string) for calculating area.
|
|
@@ -242,26 +284,41 @@ class Sentinel1Collection:
|
|
|
242
284
|
threshold (int): integer threshold to specify masking of pixels below threshold (defaults to -1).
|
|
243
285
|
scale (int): integer scale of image resolution (meters) (defaults to 30).
|
|
244
286
|
maxPixels (int): integer denoting maximum number of pixels for calculations.
|
|
245
|
-
|
|
246
|
-
Returns:
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
247
289
|
ee.Image: Image with area calculation stored as property matching name of band.
|
|
248
290
|
"""
|
|
249
291
|
if self._PixelAreaSumCollection is None:
|
|
250
292
|
collection = self.collection
|
|
251
|
-
AreaCollection = collection.map(
|
|
293
|
+
AreaCollection = collection.map(
|
|
294
|
+
lambda image: Sentinel1Collection.PixelAreaSum(
|
|
295
|
+
image,
|
|
296
|
+
band_name=band_name,
|
|
297
|
+
geometry=geometry,
|
|
298
|
+
threshold=threshold,
|
|
299
|
+
scale=scale,
|
|
300
|
+
maxPixels=maxPixels,
|
|
301
|
+
)
|
|
302
|
+
)
|
|
252
303
|
self._PixelAreaSumCollection = AreaCollection
|
|
253
304
|
return self._PixelAreaSumCollection
|
|
254
|
-
|
|
305
|
+
|
|
255
306
|
@staticmethod
|
|
256
307
|
def multilook_fn(image, looks):
|
|
257
308
|
if looks not in [1, 2, 3, 4]:
|
|
258
|
-
raise ValueError(
|
|
309
|
+
raise ValueError(
|
|
310
|
+
"Looks must be either 1, 2, 3, or 4, corresponding to 1x1, 2x2, 3x3, or 4x4 multilooking"
|
|
311
|
+
)
|
|
259
312
|
|
|
260
313
|
default_projection = image.projection()
|
|
261
314
|
image = image.setDefaultProjection(default_projection)
|
|
262
|
-
looked_image = image.reduceResolution(
|
|
315
|
+
looked_image = image.reduceResolution(
|
|
316
|
+
reducer=ee.Reducer.mean(), maxPixels=1024
|
|
317
|
+
).reproject(crs=default_projection, scale=10 * looks)
|
|
263
318
|
|
|
264
|
-
return looked_image.copyProperties(image).set(
|
|
319
|
+
return looked_image.copyProperties(image).set(
|
|
320
|
+
"number_of_processed_looks", looks
|
|
321
|
+
)
|
|
265
322
|
|
|
266
323
|
def multilook(self, looks):
|
|
267
324
|
"""
|
|
@@ -274,21 +331,25 @@ class Sentinel1Collection:
|
|
|
274
331
|
Sentinel1Collection: Sentinel1Collection image collection
|
|
275
332
|
"""
|
|
276
333
|
if looks not in [1, 2, 3, 4]:
|
|
277
|
-
raise ValueError(
|
|
334
|
+
raise ValueError(
|
|
335
|
+
"Looks must be either 1, 2, 3, or 4, corresponding to 1x1, 2x2, 3x3, or 4x4 multilooking"
|
|
336
|
+
)
|
|
278
337
|
else:
|
|
279
338
|
pass
|
|
280
339
|
if self._multilook is None:
|
|
281
340
|
collection = self.collection
|
|
282
341
|
looks = looks
|
|
283
|
-
multilook_collection = collection.map(
|
|
342
|
+
multilook_collection = collection.map(
|
|
343
|
+
lambda image: Sentinel1Collection.multilook_fn(image, looks=looks)
|
|
344
|
+
)
|
|
284
345
|
self._multilook = multilook_collection
|
|
285
346
|
return Sentinel1Collection(collection=self._multilook)
|
|
286
347
|
|
|
287
348
|
@staticmethod
|
|
288
349
|
def leesigma(image, KERNEL_SIZE, geometry=None, Tk=7, sigma=0.9, looks=1):
|
|
289
350
|
"""
|
|
290
|
-
Implements the improved lee sigma filter for speckle filtering, adapted from https://github.com/adugnag/gee_s1_ard (by Dr. Adugna Mullissa).
|
|
291
|
-
See: Lee, J.-S. Wen, J.-H. Ainsworth, T.L. Chen, K.-S. Chen, A.J. Improved sigma filter for speckle filtering of SAR imagery.
|
|
351
|
+
Implements the improved lee sigma filter for speckle filtering, adapted from https://github.com/adugnag/gee_s1_ard (by Dr. Adugna Mullissa).
|
|
352
|
+
See: Lee, J.-S. Wen, J.-H. Ainsworth, T.L. Chen, K.-S. Chen, A.J. Improved sigma filter for speckle filtering of SAR imagery.
|
|
292
353
|
IEEE Trans. Geosci. Remote Sens. 2009, 47, 202–213.
|
|
293
354
|
|
|
294
355
|
Args:
|
|
@@ -299,137 +360,157 @@ class Sentinel1Collection:
|
|
|
299
360
|
sigma (float): noise standard deviation (default is 0.9)
|
|
300
361
|
looks (int): number of looks (1, 2, 3, or 4) corresponding to the input image (default is 1). This does NOT perform multilooking, but rather is used to determine the sigma range for filtering.
|
|
301
362
|
|
|
302
|
-
Returns:
|
|
363
|
+
Returns:
|
|
303
364
|
ee.Image: Speckle filtered image
|
|
304
365
|
|
|
305
366
|
"""
|
|
306
367
|
|
|
307
|
-
#parameters
|
|
308
|
-
Tk = ee.Image.constant(Tk)
|
|
368
|
+
# parameters
|
|
369
|
+
Tk = ee.Image.constant(Tk) # number of bright pixels in a 3x3 window
|
|
309
370
|
sigma = 0.9
|
|
310
371
|
enl = 4
|
|
311
372
|
target_kernel = 3
|
|
312
|
-
bandNames = image.bandNames().remove(
|
|
373
|
+
bandNames = image.bandNames().remove("angle")
|
|
313
374
|
|
|
314
375
|
# Use image bounds as default geometry
|
|
315
376
|
if geometry is None:
|
|
316
377
|
geometry = image.geometry()
|
|
317
|
-
|
|
318
|
-
#compute the 98 percentile intensity
|
|
319
|
-
z98 = ee.Dictionary(
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
378
|
+
|
|
379
|
+
# compute the 98 percentile intensity
|
|
380
|
+
z98 = ee.Dictionary(
|
|
381
|
+
image.select(bandNames).reduceRegion(
|
|
382
|
+
reducer=ee.Reducer.percentile([98]),
|
|
383
|
+
geometry=geometry,
|
|
384
|
+
scale=10,
|
|
385
|
+
maxPixels=1e13,
|
|
386
|
+
)
|
|
387
|
+
).toImage()
|
|
388
|
+
|
|
389
|
+
# select the strong scatterers to retain
|
|
328
390
|
brightPixel = image.select(bandNames).gte(z98)
|
|
329
|
-
K = brightPixel.reduceNeighborhood(
|
|
330
|
-
|
|
391
|
+
K = brightPixel.reduceNeighborhood(
|
|
392
|
+
ee.Reducer.countDistinctNonNull(), ee.Kernel.square(target_kernel / 2)
|
|
393
|
+
)
|
|
331
394
|
retainPixel = K.gte(Tk)
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
#
|
|
335
|
-
|
|
336
|
-
eta = 1.0/math.sqrt(enl)
|
|
395
|
+
|
|
396
|
+
# compute the a-priori mean within a 3x3 local window
|
|
397
|
+
# original noise standard deviation since the data is 5 look
|
|
398
|
+
eta = 1.0 / math.sqrt(enl)
|
|
337
399
|
eta = ee.Image.constant(eta)
|
|
338
|
-
#MMSE applied to estimate the apriori mean
|
|
339
|
-
reducers = ee.Reducer.mean().combine(
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
meanBand = bandNames.map(lambda bandName: ee.String(bandName).cat(
|
|
348
|
-
varBand = bandNames.map(lambda bandName:
|
|
349
|
-
|
|
400
|
+
# MMSE applied to estimate the apriori mean
|
|
401
|
+
reducers = ee.Reducer.mean().combine(
|
|
402
|
+
reducer2=ee.Reducer.variance(), sharedInputs=True
|
|
403
|
+
)
|
|
404
|
+
stats = image.select(bandNames).reduceNeighborhood(
|
|
405
|
+
reducer=reducers,
|
|
406
|
+
kernel=ee.Kernel.square(target_kernel / 2, "pixels"),
|
|
407
|
+
optimization="window",
|
|
408
|
+
)
|
|
409
|
+
meanBand = bandNames.map(lambda bandName: ee.String(bandName).cat("_mean"))
|
|
410
|
+
varBand = bandNames.map(lambda bandName: ee.String(bandName).cat("_variance"))
|
|
411
|
+
|
|
350
412
|
z_bar = stats.select(meanBand)
|
|
351
413
|
varz = stats.select(varBand)
|
|
352
|
-
|
|
414
|
+
|
|
353
415
|
oneImg = ee.Image.constant(1)
|
|
354
|
-
varx = (varz.subtract(z_bar.abs().pow(2).multiply(eta.pow(2)))).divide(
|
|
416
|
+
varx = (varz.subtract(z_bar.abs().pow(2).multiply(eta.pow(2)))).divide(
|
|
417
|
+
oneImg.add(eta.pow(2))
|
|
418
|
+
)
|
|
355
419
|
b = varx.divide(varz)
|
|
356
|
-
xTilde =
|
|
357
|
-
|
|
358
|
-
|
|
420
|
+
xTilde = (
|
|
421
|
+
oneImg.subtract(b)
|
|
422
|
+
.multiply(z_bar.abs())
|
|
423
|
+
.add(b.multiply(image.select(bandNames)))
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
# step 3: compute the sigma range using lookup tables (J.S.Lee et al 2009) for range and eta values for intensity
|
|
359
427
|
if looks == 1:
|
|
360
|
-
LUT = ee.Dictionary(
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
428
|
+
LUT = ee.Dictionary(
|
|
429
|
+
{
|
|
430
|
+
0.5: ee.Dictionary({"I1": 0.436, "I2": 1.92, "eta": 0.4057}),
|
|
431
|
+
0.6: ee.Dictionary({"I1": 0.343, "I2": 2.21, "eta": 0.4954}),
|
|
432
|
+
0.7: ee.Dictionary({"I1": 0.254, "I2": 2.582, "eta": 0.5911}),
|
|
433
|
+
0.8: ee.Dictionary({"I1": 0.168, "I2": 3.094, "eta": 0.6966}),
|
|
434
|
+
0.9: ee.Dictionary({"I1": 0.084, "I2": 3.941, "eta": 0.8191}),
|
|
435
|
+
0.95: ee.Dictionary({"I1": 0.043, "I2": 4.840, "eta": 0.8599}),
|
|
436
|
+
}
|
|
437
|
+
)
|
|
368
438
|
elif looks == 2:
|
|
369
|
-
LUT = ee.Dictionary(
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
439
|
+
LUT = ee.Dictionary(
|
|
440
|
+
{
|
|
441
|
+
0.5: ee.Dictionary({"I1": 0.582, "I2": 1.584, "eta": 0.2763}),
|
|
442
|
+
0.6: ee.Dictionary({"I1": 0.501, "I2": 1.755, "eta": 0.3388}),
|
|
443
|
+
0.7: ee.Dictionary({"I1": 0.418, "I2": 1.972, "eta": 0.4062}),
|
|
444
|
+
0.8: ee.Dictionary({"I1": 0.327, "I2": 2.260, "eta": 0.4810}),
|
|
445
|
+
0.9: ee.Dictionary({"I1": 0.221, "I2": 2.744, "eta": 0.5699}),
|
|
446
|
+
0.95: ee.Dictionary({"I1": 0.152, "I2": 3.206, "eta": 0.6254}),
|
|
447
|
+
}
|
|
448
|
+
)
|
|
377
449
|
elif looks == 3:
|
|
378
|
-
LUT = ee.Dictionary(
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
450
|
+
LUT = ee.Dictionary(
|
|
451
|
+
{
|
|
452
|
+
0.5: ee.Dictionary({"I1": 0.652, "I2": 1.458, "eta": 0.2222}),
|
|
453
|
+
0.6: ee.Dictionary({"I1": 0.580, "I2": 1.586, "eta": 0.2736}),
|
|
454
|
+
0.7: ee.Dictionary({"I1": 0.505, "I2": 1.751, "eta": 0.3280}),
|
|
455
|
+
0.8: ee.Dictionary({"I1": 0.419, "I2": 1.965, "eta": 0.3892}),
|
|
456
|
+
0.9: ee.Dictionary({"I1": 0.313, "I2": 2.320, "eta": 0.4624}),
|
|
457
|
+
0.95: ee.Dictionary({"I1": 0.238, "I2": 2.656, "eta": 0.5084}),
|
|
458
|
+
}
|
|
459
|
+
)
|
|
386
460
|
elif looks == 4:
|
|
387
|
-
LUT = ee.Dictionary(
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
461
|
+
LUT = ee.Dictionary(
|
|
462
|
+
{
|
|
463
|
+
0.5: ee.Dictionary({"I1": 0.694, "I2": 1.385, "eta": 0.1921}),
|
|
464
|
+
0.6: ee.Dictionary({"I1": 0.630, "I2": 1.495, "eta": 0.2348}),
|
|
465
|
+
0.7: ee.Dictionary({"I1": 0.560, "I2": 1.627, "eta": 0.2825}),
|
|
466
|
+
0.8: ee.Dictionary({"I1": 0.480, "I2": 1.804, "eta": 0.3354}),
|
|
467
|
+
0.9: ee.Dictionary({"I1": 0.378, "I2": 2.094, "eta": 0.3991}),
|
|
468
|
+
0.95: ee.Dictionary({"I1": 0.302, "I2": 2.360, "eta": 0.4391}),
|
|
469
|
+
}
|
|
470
|
+
)
|
|
395
471
|
else:
|
|
396
|
-
raise ValueError(
|
|
472
|
+
raise ValueError(
|
|
473
|
+
"Invalid number of looks. Please choose from 1, 2, 3, or 4."
|
|
474
|
+
)
|
|
397
475
|
|
|
398
|
-
|
|
399
|
-
#extract data from lookup
|
|
476
|
+
# extract data from lookup
|
|
400
477
|
sigmaImage = ee.Dictionary(LUT.get(str(sigma))).toImage()
|
|
401
|
-
I1 = sigmaImage.select(
|
|
402
|
-
I2 = sigmaImage.select(
|
|
403
|
-
#new speckle sigma
|
|
404
|
-
nEta = sigmaImage.select(
|
|
405
|
-
#establish the sigma ranges
|
|
478
|
+
I1 = sigmaImage.select("I1")
|
|
479
|
+
I2 = sigmaImage.select("I2")
|
|
480
|
+
# new speckle sigma
|
|
481
|
+
nEta = sigmaImage.select("eta")
|
|
482
|
+
# establish the sigma ranges
|
|
406
483
|
I1 = I1.multiply(xTilde)
|
|
407
484
|
I2 = I2.multiply(xTilde)
|
|
408
|
-
|
|
409
|
-
#step 3: apply MMSE filter for pixels in the sigma range
|
|
410
|
-
#MMSE estimator
|
|
485
|
+
|
|
486
|
+
# step 3: apply MMSE filter for pixels in the sigma range
|
|
487
|
+
# MMSE estimator
|
|
411
488
|
mask = image.select(bandNames).gte(I1).Or(image.select(bandNames).lte(I2))
|
|
412
489
|
z = image.select(bandNames).updateMask(mask)
|
|
413
|
-
|
|
414
|
-
stats = z.reduceNeighborhood(
|
|
415
|
-
|
|
490
|
+
|
|
491
|
+
stats = z.reduceNeighborhood(
|
|
492
|
+
reducer=reducers,
|
|
493
|
+
kernel=ee.Kernel.square(KERNEL_SIZE / 2, "pixels"),
|
|
494
|
+
optimization="window",
|
|
495
|
+
)
|
|
496
|
+
|
|
416
497
|
z_bar = stats.select(meanBand)
|
|
417
498
|
varz = stats.select(varBand)
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
499
|
+
|
|
500
|
+
varx = (varz.subtract(z_bar.abs().pow(2).multiply(nEta.pow(2)))).divide(
|
|
501
|
+
oneImg.add(nEta.pow(2))
|
|
502
|
+
)
|
|
421
503
|
b = varx.divide(varz)
|
|
422
|
-
#if b is negative set it to zero
|
|
504
|
+
# if b is negative set it to zero
|
|
423
505
|
new_b = b.where(b.lt(0), 0)
|
|
424
506
|
xHat = oneImg.subtract(new_b).multiply(z_bar.abs()).add(new_b.multiply(z))
|
|
425
|
-
|
|
426
|
-
#remove the applied masks and merge the retained pixels and the filtered pixels
|
|
507
|
+
|
|
508
|
+
# remove the applied masks and merge the retained pixels and the filtered pixels
|
|
427
509
|
xHat = image.select(bandNames).updateMask(retainPixel).unmask(xHat)
|
|
428
510
|
output = ee.Image(xHat).rename(bandNames)
|
|
429
511
|
# return image.addBands(output, None, True)
|
|
430
512
|
return output.copyProperties(image)
|
|
431
|
-
|
|
432
|
-
|
|
513
|
+
|
|
433
514
|
def speckle_filter(self, KERNEL_SIZE, geometry=None, Tk=7, sigma=0.9, looks=1):
|
|
434
515
|
"""
|
|
435
516
|
Property attribute function to apply speckle filter to entire image collection. Results are calculated once per class object then cached for future use.
|
|
@@ -446,10 +527,19 @@ class Sentinel1Collection:
|
|
|
446
527
|
"""
|
|
447
528
|
if self._speckle_filter is None:
|
|
448
529
|
collection = self.collection
|
|
449
|
-
speckle_filtered_collection = collection.map(
|
|
530
|
+
speckle_filtered_collection = collection.map(
|
|
531
|
+
lambda image: Sentinel1Collection.leesigma(
|
|
532
|
+
image,
|
|
533
|
+
KERNEL_SIZE,
|
|
534
|
+
geometry=geometry,
|
|
535
|
+
Tk=Tk,
|
|
536
|
+
sigma=sigma,
|
|
537
|
+
looks=looks,
|
|
538
|
+
)
|
|
539
|
+
)
|
|
450
540
|
self._speckle_filter = speckle_filtered_collection
|
|
451
541
|
return Sentinel1Collection(collection=self._speckle_filter)
|
|
452
|
-
|
|
542
|
+
|
|
453
543
|
@property
|
|
454
544
|
def Sigma0FromDb(self):
|
|
455
545
|
"""
|
|
@@ -458,10 +548,16 @@ class Sentinel1Collection:
|
|
|
458
548
|
Returns:
|
|
459
549
|
Sentinel1Collection: Sentinel1Collection image collection
|
|
460
550
|
"""
|
|
551
|
+
|
|
461
552
|
def conversion(image):
|
|
462
553
|
image = ee.Image(image)
|
|
463
554
|
band_names = image.bandNames()
|
|
464
|
-
sigma_nought =
|
|
555
|
+
sigma_nought = (
|
|
556
|
+
ee.Image(10)
|
|
557
|
+
.pow(image.divide(ee.Image(10)))
|
|
558
|
+
.rename(band_names)
|
|
559
|
+
.copyProperties(image)
|
|
560
|
+
)
|
|
465
561
|
return sigma_nought
|
|
466
562
|
|
|
467
563
|
if self._Sigma0FromDb is None:
|
|
@@ -469,7 +565,7 @@ class Sentinel1Collection:
|
|
|
469
565
|
sigma0_collection = collection.map(conversion)
|
|
470
566
|
self._Sigma0FromDb = sigma0_collection
|
|
471
567
|
return Sentinel1Collection(collection=self._Sigma0FromDb)
|
|
472
|
-
|
|
568
|
+
|
|
473
569
|
@property
|
|
474
570
|
def DbFromSigma0(self):
|
|
475
571
|
"""
|
|
@@ -478,18 +574,24 @@ class Sentinel1Collection:
|
|
|
478
574
|
Returns:
|
|
479
575
|
Sentinel1Collection: Sentinel1Collection image collection
|
|
480
576
|
"""
|
|
577
|
+
|
|
481
578
|
def conversion(image):
|
|
482
579
|
image = ee.Image(image)
|
|
483
580
|
band_names = image.bandNames()
|
|
484
|
-
dB =
|
|
581
|
+
dB = (
|
|
582
|
+
ee.Image(10)
|
|
583
|
+
.multiply(image.log10())
|
|
584
|
+
.rename(band_names)
|
|
585
|
+
.copyProperties(image)
|
|
586
|
+
)
|
|
485
587
|
return dB
|
|
486
588
|
|
|
487
|
-
if self.
|
|
589
|
+
if self._DbFromSigma0 is None:
|
|
488
590
|
collection = self.collection
|
|
489
591
|
dB_collection = collection.map(conversion)
|
|
490
592
|
self._DbFromSigma0 = dB_collection
|
|
491
593
|
return Sentinel1Collection(collection=self._DbFromSigma0)
|
|
492
|
-
|
|
594
|
+
|
|
493
595
|
@property
|
|
494
596
|
def dates_list(self):
|
|
495
597
|
"""
|
|
@@ -499,7 +601,7 @@ class Sentinel1Collection:
|
|
|
499
601
|
ee.List: Server-side ee.List of dates.
|
|
500
602
|
"""
|
|
501
603
|
if self._dates_list is None:
|
|
502
|
-
dates = self.collection.aggregate_array(
|
|
604
|
+
dates = self.collection.aggregate_array("Date_Filter")
|
|
503
605
|
self._dates_list = dates
|
|
504
606
|
return self._dates_list
|
|
505
607
|
|
|
@@ -512,7 +614,7 @@ class Sentinel1Collection:
|
|
|
512
614
|
list: list of date strings.
|
|
513
615
|
"""
|
|
514
616
|
if self._dates_list is None:
|
|
515
|
-
dates = self.collection.aggregate_array(
|
|
617
|
+
dates = self.collection.aggregate_array("Date_Filter")
|
|
516
618
|
self._dates_list = dates
|
|
517
619
|
if self._dates is None:
|
|
518
620
|
dates = self._dates_list.getInfo()
|
|
@@ -526,15 +628,29 @@ class Sentinel1Collection:
|
|
|
526
628
|
Returns:
|
|
527
629
|
ee.ImageCollection: Filtered image collection - used for subsequent analyses or to acquire ee.ImageCollection from Sentinel1Collection object
|
|
528
630
|
"""
|
|
529
|
-
# filtered_collection = ee.ImageCollection("COPERNICUS/S1_GRD").filterDate(self.start_date, self.end_date).filter(ee.Filter.inList('instrumentMode', self.instrument_mode)).filter(ee.Filter.And(ee.Filter.inList('relativeOrbitNumber_start', self.relative_orbit_stop),
|
|
530
|
-
# ee.Filter.inList('relativeOrbitNumber_stop', self.relative_orbit_stop))).filter(ee.Filter.inList('orbitProperties_pass', self.orbit_direction)).filter(ee.Filter.inList('transmitterReceiverPolarisation',
|
|
531
|
-
# self.polarization)).filter(ee.Filter.eq('resolution', self.resolution)).map(self.image_dater).select(self.band)
|
|
532
631
|
|
|
533
|
-
filtered_collection =
|
|
534
|
-
|
|
535
|
-
|
|
632
|
+
filtered_collection = (
|
|
633
|
+
ee.ImageCollection("COPERNICUS/S1_GRD")
|
|
634
|
+
.filterDate(self.start_date, self.end_date)
|
|
635
|
+
.filter(ee.Filter.inList("instrumentMode", self.instrument_mode))
|
|
636
|
+
.filter(
|
|
637
|
+
ee.Filter.And(
|
|
638
|
+
ee.Filter.inList(
|
|
639
|
+
"relativeOrbitNumber_start", self.relative_orbit_start
|
|
640
|
+
),
|
|
641
|
+
ee.Filter.inList(
|
|
642
|
+
"relativeOrbitNumber_stop", self.relative_orbit_stop
|
|
643
|
+
),
|
|
644
|
+
)
|
|
645
|
+
)
|
|
646
|
+
.filter(ee.Filter.inList("orbitProperties_pass", self.orbit_direction))
|
|
647
|
+
.filter(ee.Filter.eq("transmitterReceiverPolarisation", self.polarization))
|
|
648
|
+
.filter(ee.Filter.eq("resolution_meters", self.resolution_meters))
|
|
649
|
+
.map(self.image_dater)
|
|
650
|
+
.select(self.bands)
|
|
651
|
+
)
|
|
536
652
|
return filtered_collection
|
|
537
|
-
|
|
653
|
+
|
|
538
654
|
def get_boundary_filtered_collection(self):
|
|
539
655
|
"""
|
|
540
656
|
Function to filter and mask image collection based on Sentinel1Collection class arguments. Automatically calculated when using collection method, depending on provided class arguments (when boundary info is provided).
|
|
@@ -543,10 +659,19 @@ class Sentinel1Collection:
|
|
|
543
659
|
ee.ImageCollection: Filtered image collection - used for subsequent analyses or to acquire ee.ImageCollection from Sentinel1Collection object
|
|
544
660
|
|
|
545
661
|
"""
|
|
546
|
-
filtered_collection =
|
|
547
|
-
|
|
662
|
+
filtered_collection = (
|
|
663
|
+
ee.ImageCollection("COPERNICUS/S1_GRD")
|
|
664
|
+
.filterDate(self.start_date, self.end_date)
|
|
665
|
+
.filterBounds(self.boundary)
|
|
666
|
+
.filter(ee.Filter.inList("instrumentMode", self.instrument_mode))
|
|
667
|
+
.filter(ee.Filter.inList("orbitProperties_pass", self.orbit_direction))
|
|
668
|
+
.filter(ee.Filter.eq("transmitterReceiverPolarisation", self.polarization))
|
|
669
|
+
.filter(ee.Filter.eq("resolution_meters", self.resolution_meters))
|
|
670
|
+
.map(self.image_dater)
|
|
671
|
+
.select(self.bands)
|
|
672
|
+
)
|
|
548
673
|
return filtered_collection
|
|
549
|
-
|
|
674
|
+
|
|
550
675
|
def get_boundary_and_orbit_filtered_collection(self):
|
|
551
676
|
"""
|
|
552
677
|
Function to filter image collection based on Sentinel1Collection class arguments. Automatically calculated when using collection method, depending on provided class arguments (when tile info is provided).
|
|
@@ -555,33 +680,51 @@ class Sentinel1Collection:
|
|
|
555
680
|
ee.ImageCollection: Filtered image collection - used for subsequent analyses or to acquire ee.ImageCollection from Sentinel1Collection object
|
|
556
681
|
"""
|
|
557
682
|
# filtered_collection = ee.ImageCollection("COPERNICUS/S1_GRD").filterDate(self.start_date, self.end_date).filter(ee.Filter.inList('instrumentMode', self.instrument_mode)).filter(ee.Filter.And(ee.Filter.inList('relativeOrbitNumber_start', self.relative_orbit_stop),
|
|
558
|
-
# ee.Filter.inList('relativeOrbitNumber_stop', self.relative_orbit_stop))).filter(ee.Filter.inList('orbitProperties_pass', self.orbit_direction)).filter(ee.Filter.inList('transmitterReceiverPolarisation',
|
|
683
|
+
# ee.Filter.inList('relativeOrbitNumber_stop', self.relative_orbit_stop))).filter(ee.Filter.inList('orbitProperties_pass', self.orbit_direction)).filter(ee.Filter.inList('transmitterReceiverPolarisation',
|
|
559
684
|
# self.polarization)).filter(ee.Filter.eq('resolution', self.resolution)).map(self.image_dater).select(self.band)
|
|
560
685
|
|
|
561
|
-
filtered_collection =
|
|
562
|
-
|
|
563
|
-
|
|
686
|
+
filtered_collection = (
|
|
687
|
+
ee.ImageCollection("COPERNICUS/S1_GRD")
|
|
688
|
+
.filterDate(self.start_date, self.end_date)
|
|
689
|
+
.filter(ee.Filter.inList("instrumentMode", self.instrument_mode))
|
|
690
|
+
.filterBounds(self.boundary)
|
|
691
|
+
.filter(
|
|
692
|
+
ee.Filter.And(
|
|
693
|
+
ee.Filter.inList(
|
|
694
|
+
"relativeOrbitNumber_start", self.relative_orbit_start
|
|
695
|
+
),
|
|
696
|
+
ee.Filter.inList(
|
|
697
|
+
"relativeOrbitNumber_stop", self.relative_orbit_stop
|
|
698
|
+
),
|
|
699
|
+
)
|
|
700
|
+
)
|
|
701
|
+
.filter(ee.Filter.inList("orbitProperties_pass", self.orbit_direction))
|
|
702
|
+
.filter(ee.Filter.eq("transmitterReceiverPolarisation", self.polarization))
|
|
703
|
+
.filter(ee.Filter.eq("resolution_meters", self.resolution_meters))
|
|
704
|
+
.map(self.image_dater)
|
|
705
|
+
.select(self.bands)
|
|
706
|
+
)
|
|
564
707
|
return filtered_collection
|
|
565
|
-
|
|
708
|
+
|
|
566
709
|
@property
|
|
567
710
|
def median(self):
|
|
568
711
|
"""
|
|
569
712
|
Property attribute function to calculate median image from image collection. Results are calculated once per class object then cached for future use.
|
|
570
713
|
|
|
571
|
-
Returns:
|
|
714
|
+
Returns:
|
|
572
715
|
ee.Image: median image from entire collection.
|
|
573
716
|
"""
|
|
574
717
|
if self._median is None:
|
|
575
718
|
col = self.collection.median()
|
|
576
719
|
self._median = col
|
|
577
720
|
return self._median
|
|
578
|
-
|
|
721
|
+
|
|
579
722
|
@property
|
|
580
723
|
def mean(self):
|
|
581
724
|
"""
|
|
582
725
|
Property attribute function to calculate mean image from image collection. Results are calculated once per class object then cached for future use.
|
|
583
726
|
|
|
584
|
-
Returns:
|
|
727
|
+
Returns:
|
|
585
728
|
ee.Image: mean image from entire collection.
|
|
586
729
|
|
|
587
730
|
"""
|
|
@@ -589,33 +732,33 @@ class Sentinel1Collection:
|
|
|
589
732
|
col = self.collection.mean()
|
|
590
733
|
self._mean = col
|
|
591
734
|
return self._mean
|
|
592
|
-
|
|
735
|
+
|
|
593
736
|
@property
|
|
594
737
|
def max(self):
|
|
595
738
|
"""
|
|
596
739
|
Property attribute function to calculate max image from image collection. Results are calculated once per class object then cached for future use.
|
|
597
740
|
|
|
598
|
-
Returns:
|
|
741
|
+
Returns:
|
|
599
742
|
ee.Image: max image from entire collection.
|
|
600
743
|
"""
|
|
601
744
|
if self._max is None:
|
|
602
745
|
col = self.collection.max()
|
|
603
746
|
self._max = col
|
|
604
747
|
return self._max
|
|
605
|
-
|
|
748
|
+
|
|
606
749
|
@property
|
|
607
750
|
def min(self):
|
|
608
751
|
"""
|
|
609
752
|
Property attribute function to calculate min image from image collection. Results are calculated once per class object then cached for future use.
|
|
610
|
-
|
|
611
|
-
Returns:
|
|
753
|
+
|
|
754
|
+
Returns:
|
|
612
755
|
ee.Image: min image from entire collection.
|
|
613
756
|
"""
|
|
614
757
|
if self._min is None:
|
|
615
758
|
col = self.collection.min()
|
|
616
759
|
self._min = col
|
|
617
760
|
return self._min
|
|
618
|
-
|
|
761
|
+
|
|
619
762
|
def mask_to_polygon(self, polygon):
|
|
620
763
|
"""
|
|
621
764
|
Function to mask Sentinel1Collection image collection by a polygon (ee.Geometry), where pixels outside the polygon are masked out.
|
|
@@ -625,21 +768,23 @@ class Sentinel1Collection:
|
|
|
625
768
|
|
|
626
769
|
Returns:
|
|
627
770
|
Sentinel1Collection: masked Sentinel1Collection image collection
|
|
628
|
-
|
|
771
|
+
|
|
629
772
|
"""
|
|
630
773
|
if self._geometry_masked_collection is None:
|
|
631
774
|
# Convert the polygon to a mask
|
|
632
775
|
mask = ee.Image.constant(1).clip(polygon)
|
|
633
|
-
|
|
776
|
+
|
|
634
777
|
# Update the mask of each image in the collection
|
|
635
778
|
masked_collection = self.collection.map(lambda img: img.updateMask(mask))
|
|
636
|
-
|
|
779
|
+
|
|
637
780
|
# Update the internal collection state
|
|
638
|
-
self._geometry_masked_collection = Sentinel1Collection(
|
|
639
|
-
|
|
781
|
+
self._geometry_masked_collection = Sentinel1Collection(
|
|
782
|
+
collection=masked_collection
|
|
783
|
+
)
|
|
784
|
+
|
|
640
785
|
# Return the updated object
|
|
641
786
|
return self._geometry_masked_collection
|
|
642
|
-
|
|
787
|
+
|
|
643
788
|
def mask_out_polygon(self, polygon):
|
|
644
789
|
"""
|
|
645
790
|
Function to mask Sentinel1Collection image collection by a polygon (ee.Geometry), where pixels inside the polygon are masked out.
|
|
@@ -649,7 +794,7 @@ class Sentinel1Collection:
|
|
|
649
794
|
|
|
650
795
|
Returns:
|
|
651
796
|
Sentinel1Collection: masked Sentinel1Collection image collection
|
|
652
|
-
|
|
797
|
+
|
|
653
798
|
"""
|
|
654
799
|
if self._geometry_masked_out_collection is None:
|
|
655
800
|
# Convert the polygon to a mask
|
|
@@ -657,17 +802,17 @@ class Sentinel1Collection:
|
|
|
657
802
|
|
|
658
803
|
# Use paint to set pixels inside polygon as 0
|
|
659
804
|
area = full_mask.paint(polygon, 0)
|
|
660
|
-
|
|
805
|
+
|
|
661
806
|
# Update the mask of each image in the collection
|
|
662
807
|
masked_collection = self.collection.map(lambda img: img.updateMask(area))
|
|
663
|
-
|
|
808
|
+
|
|
664
809
|
# Update the internal collection state
|
|
665
|
-
self._geometry_masked_out_collection = Sentinel1Collection(
|
|
666
|
-
|
|
810
|
+
self._geometry_masked_out_collection = Sentinel1Collection(
|
|
811
|
+
collection=masked_collection
|
|
812
|
+
)
|
|
813
|
+
|
|
667
814
|
# Return the updated object
|
|
668
815
|
return self._geometry_masked_out_collection
|
|
669
|
-
|
|
670
|
-
|
|
671
816
|
|
|
672
817
|
def image_grab(self, img_selector):
|
|
673
818
|
"""
|
|
@@ -675,8 +820,8 @@ class Sentinel1Collection:
|
|
|
675
820
|
|
|
676
821
|
Args:
|
|
677
822
|
img_selector (int): index of image in the collection for which user seeks to select/"grab".
|
|
678
|
-
|
|
679
|
-
Returns:
|
|
823
|
+
|
|
824
|
+
Returns:
|
|
680
825
|
ee.Image: ee.Image of selected image
|
|
681
826
|
"""
|
|
682
827
|
# Convert the collection to a list
|
|
@@ -694,8 +839,8 @@ class Sentinel1Collection:
|
|
|
694
839
|
Args:
|
|
695
840
|
img_col (ee.ImageCollection): ee.ImageCollection with same dates as another Sentinel1Collection image collection object.
|
|
696
841
|
img_selector (int): index of image in list of dates for which user seeks to "select".
|
|
697
|
-
|
|
698
|
-
Returns:
|
|
842
|
+
|
|
843
|
+
Returns:
|
|
699
844
|
ee.Image: ee.Image of selected image
|
|
700
845
|
"""
|
|
701
846
|
# Convert the collection to a list
|
|
@@ -705,7 +850,7 @@ class Sentinel1Collection:
|
|
|
705
850
|
image = ee.Image(image_list.get(img_selector))
|
|
706
851
|
|
|
707
852
|
return image
|
|
708
|
-
|
|
853
|
+
|
|
709
854
|
def image_pick(self, img_date):
|
|
710
855
|
"""
|
|
711
856
|
Function to select ("grab") image of a specific date in format of 'YYYY-MM-DD' - will not work correctly if collection is composed of multiple images of the same date.
|
|
@@ -713,16 +858,16 @@ class Sentinel1Collection:
|
|
|
713
858
|
Args:
|
|
714
859
|
img_date (str): date of image to select from collection, in format of 'YYYY-MM-DD'
|
|
715
860
|
|
|
716
|
-
Returns:
|
|
861
|
+
Returns:
|
|
717
862
|
ee.Image: ee.Image of selected image
|
|
718
863
|
"""
|
|
719
|
-
new_col = self.collection.filter(ee.Filter.eq(
|
|
864
|
+
new_col = self.collection.filter(ee.Filter.eq("Date_Filter", img_date))
|
|
720
865
|
return new_col.first()
|
|
721
866
|
|
|
722
867
|
def CollectionStitch(self, img_col2):
|
|
723
868
|
"""
|
|
724
|
-
Function to mosaic two Sentinel1Collection objects which share image dates.
|
|
725
|
-
Mosaics are only formed for dates where both image collections have images.
|
|
869
|
+
Function to mosaic two Sentinel1Collection objects which share image dates.
|
|
870
|
+
Mosaics are only formed for dates where both image collections have images.
|
|
726
871
|
Image properties are copied from the primary collection. Server-side friendly.
|
|
727
872
|
|
|
728
873
|
Args:
|
|
@@ -731,26 +876,38 @@ class Sentinel1Collection:
|
|
|
731
876
|
Returns:
|
|
732
877
|
Sentinel1Collection: Sentinel1Collection image collection
|
|
733
878
|
"""
|
|
734
|
-
dates_list =
|
|
879
|
+
dates_list = (
|
|
880
|
+
ee.List(self._dates_list).cat(ee.List(img_col2.dates_list)).distinct()
|
|
881
|
+
)
|
|
735
882
|
filtered_dates1 = self._dates_list
|
|
736
883
|
filtered_dates2 = img_col2._dates_list
|
|
737
884
|
|
|
738
|
-
filtered_col2 = img_col2.collection.filter(
|
|
739
|
-
|
|
885
|
+
filtered_col2 = img_col2.collection.filter(
|
|
886
|
+
ee.Filter.inList("Date_Filter", filtered_dates1)
|
|
887
|
+
)
|
|
888
|
+
filtered_col1 = self.collection.filter(
|
|
889
|
+
ee.Filter.inList(
|
|
890
|
+
"Date_Filter", filtered_col2.aggregate_array("Date_Filter")
|
|
891
|
+
)
|
|
892
|
+
)
|
|
740
893
|
|
|
741
894
|
# Create a function that will be mapped over filtered_col1
|
|
742
895
|
def mosaic_images(img):
|
|
743
896
|
# Get the date of the image
|
|
744
|
-
date = img.get(
|
|
745
|
-
|
|
897
|
+
date = img.get("Date_Filter")
|
|
898
|
+
|
|
746
899
|
# Get the corresponding image from filtered_col2
|
|
747
|
-
img2 = filtered_col2.filter(ee.Filter.equals(
|
|
900
|
+
img2 = filtered_col2.filter(ee.Filter.equals("Date_Filter", date)).first()
|
|
748
901
|
|
|
749
902
|
# Create a mosaic of the two images
|
|
750
903
|
mosaic = ee.ImageCollection.fromImages([img, img2]).mosaic()
|
|
751
904
|
|
|
752
905
|
# Copy properties from the first image and set the 'Date_Filter' property
|
|
753
|
-
mosaic =
|
|
906
|
+
mosaic = (
|
|
907
|
+
mosaic.copyProperties(img)
|
|
908
|
+
.set("Date_Filter", date)
|
|
909
|
+
.set("system:time_start", img.get("system:time_start"))
|
|
910
|
+
)
|
|
754
911
|
|
|
755
912
|
return mosaic
|
|
756
913
|
|
|
@@ -759,13 +916,13 @@ class Sentinel1Collection:
|
|
|
759
916
|
|
|
760
917
|
# Return a Sentinel1Collection instance
|
|
761
918
|
return Sentinel1Collection(collection=new_col)
|
|
762
|
-
|
|
919
|
+
|
|
763
920
|
@property
|
|
764
921
|
def MosaicByDate(self):
|
|
765
922
|
"""
|
|
766
|
-
Property attribute function to mosaic collection images that share the same date.
|
|
767
|
-
The property CLOUD_COVER for each image is used to calculate an overall mean,
|
|
768
|
-
which replaces the CLOUD_COVER property for each mosaiced image.
|
|
923
|
+
Property attribute function to mosaic collection images that share the same date.
|
|
924
|
+
The property CLOUD_COVER for each image is used to calculate an overall mean,
|
|
925
|
+
which replaces the CLOUD_COVER property for each mosaiced image.
|
|
769
926
|
Server-side friendly. NOTE: if images are removed from the collection from cloud filtering, you may have mosaics composed of only one image.
|
|
770
927
|
|
|
771
928
|
Returns:
|
|
@@ -773,11 +930,12 @@ class Sentinel1Collection:
|
|
|
773
930
|
"""
|
|
774
931
|
if self._MosaicByDate is None:
|
|
775
932
|
input_collection = self.collection
|
|
933
|
+
|
|
776
934
|
# Function to mosaic images of the same date and accumulate them
|
|
777
935
|
def mosaic_and_accumulate(date, list_accumulator):
|
|
778
936
|
|
|
779
937
|
list_accumulator = ee.List(list_accumulator)
|
|
780
|
-
date_filter = ee.Filter.eq(
|
|
938
|
+
date_filter = ee.Filter.eq("Date_Filter", date)
|
|
781
939
|
date_collection = input_collection.filter(date_filter)
|
|
782
940
|
# Convert the collection to a list
|
|
783
941
|
image_list = date_collection.toList(date_collection.size())
|
|
@@ -785,16 +943,29 @@ class Sentinel1Collection:
|
|
|
785
943
|
# Get the image at the specified index
|
|
786
944
|
first_image = ee.Image(image_list.get(0))
|
|
787
945
|
# Create mosaic
|
|
788
|
-
mosaic = date_collection.mosaic().set(
|
|
789
|
-
|
|
790
|
-
props_of_interest = [
|
|
791
|
-
|
|
792
|
-
|
|
946
|
+
mosaic = date_collection.mosaic().set("Date_Filter", date)
|
|
947
|
+
|
|
948
|
+
props_of_interest = [
|
|
949
|
+
"platform_number",
|
|
950
|
+
"instrument",
|
|
951
|
+
"instrumentMode",
|
|
952
|
+
"orbitNumber_start",
|
|
953
|
+
"orbitNumber_stop",
|
|
954
|
+
"orbitProperties_pass",
|
|
955
|
+
"resolution_meters",
|
|
956
|
+
"transmitterReceiverPolarisation",
|
|
957
|
+
"system:time_start",
|
|
958
|
+
"crs",
|
|
959
|
+
]
|
|
960
|
+
|
|
961
|
+
mosaic = mosaic.setDefaultProjection(
|
|
962
|
+
first_image.projection()
|
|
963
|
+
).copyProperties(first_image, props_of_interest)
|
|
793
964
|
|
|
794
965
|
return list_accumulator.add(mosaic)
|
|
795
966
|
|
|
796
967
|
# Get distinct dates
|
|
797
|
-
distinct_dates = input_collection.aggregate_array(
|
|
968
|
+
distinct_dates = input_collection.aggregate_array("Date_Filter").distinct()
|
|
798
969
|
|
|
799
970
|
# Initialize an empty list as the accumulator
|
|
800
971
|
initial = ee.List([])
|
|
@@ -808,9 +979,11 @@ class Sentinel1Collection:
|
|
|
808
979
|
|
|
809
980
|
# Convert the list of mosaics to an ImageCollection
|
|
810
981
|
return self._MosaicByDate
|
|
811
|
-
|
|
982
|
+
|
|
812
983
|
@staticmethod
|
|
813
|
-
def ee_to_df(
|
|
984
|
+
def ee_to_df(
|
|
985
|
+
ee_object, columns=None, remove_geom=True, sort_columns=False, **kwargs
|
|
986
|
+
):
|
|
814
987
|
"""Converts an ee.FeatureCollection to pandas dataframe. Adapted from the geemap package (https://geemap.org/common/#geemap.common.ee_to_df)
|
|
815
988
|
|
|
816
989
|
Args:
|
|
@@ -860,8 +1033,19 @@ class Sentinel1Collection:
|
|
|
860
1033
|
raise Exception(e)
|
|
861
1034
|
|
|
862
1035
|
@staticmethod
|
|
863
|
-
def extract_transect(
|
|
864
|
-
|
|
1036
|
+
def extract_transect(
|
|
1037
|
+
image,
|
|
1038
|
+
line,
|
|
1039
|
+
reducer="mean",
|
|
1040
|
+
n_segments=100,
|
|
1041
|
+
dist_interval=None,
|
|
1042
|
+
scale=None,
|
|
1043
|
+
crs=None,
|
|
1044
|
+
crsTransform=None,
|
|
1045
|
+
tileScale=1.0,
|
|
1046
|
+
to_pandas=False,
|
|
1047
|
+
**kwargs,
|
|
1048
|
+
):
|
|
865
1049
|
"""Extracts transect from an image. Adapted from the geemap package (https://geemap.org/common/#geemap.common.extract_transect). Exists as an alternative to RadGEEToolbox 'transect' function.
|
|
866
1050
|
|
|
867
1051
|
Args:
|
|
@@ -925,9 +1109,17 @@ class Sentinel1Collection:
|
|
|
925
1109
|
|
|
926
1110
|
except Exception as e:
|
|
927
1111
|
raise Exception(e)
|
|
928
|
-
|
|
1112
|
+
|
|
929
1113
|
@staticmethod
|
|
930
|
-
def transect(
|
|
1114
|
+
def transect(
|
|
1115
|
+
image,
|
|
1116
|
+
lines,
|
|
1117
|
+
line_names,
|
|
1118
|
+
reducer="mean",
|
|
1119
|
+
n_segments=None,
|
|
1120
|
+
dist_interval=10,
|
|
1121
|
+
to_pandas=True,
|
|
1122
|
+
):
|
|
931
1123
|
"""Computes and stores the values along a transect for each line in a list of lines. Builds off of the extract_transect function from the geemap package
|
|
932
1124
|
where checks are ran to ensure that the reducer column is present in the transect data. If the reducer column is not present, a column of NaNs is created.
|
|
933
1125
|
An ee reducer is used to aggregate the values along the transect, depending on the number of segments or distance interval specified. Defaults to 'mean' reducer.
|
|
@@ -944,46 +1136,71 @@ class Sentinel1Collection:
|
|
|
944
1136
|
Returns:
|
|
945
1137
|
pd.DataFrame or ee.FeatureCollection: organized list of values along the transect(s)
|
|
946
1138
|
"""
|
|
947
|
-
#Create empty dataframe
|
|
1139
|
+
# Create empty dataframe
|
|
948
1140
|
transects_df = pd.DataFrame()
|
|
949
1141
|
|
|
950
|
-
#Check if line is a list of lines or a single line - if single line, convert to list
|
|
1142
|
+
# Check if line is a list of lines or a single line - if single line, convert to list
|
|
951
1143
|
if isinstance(lines, list):
|
|
952
1144
|
pass
|
|
953
1145
|
else:
|
|
954
1146
|
lines = [lines]
|
|
955
|
-
|
|
1147
|
+
|
|
956
1148
|
for i, line in enumerate(lines):
|
|
957
1149
|
if n_segments is None:
|
|
958
|
-
transect_data = Sentinel1Collection.extract_transect(
|
|
1150
|
+
transect_data = Sentinel1Collection.extract_transect(
|
|
1151
|
+
image=image,
|
|
1152
|
+
line=line,
|
|
1153
|
+
reducer=reducer,
|
|
1154
|
+
dist_interval=dist_interval,
|
|
1155
|
+
to_pandas=to_pandas,
|
|
1156
|
+
)
|
|
959
1157
|
if reducer in transect_data.columns:
|
|
960
1158
|
# Extract the 'mean' column and rename it
|
|
961
|
-
mean_column = transect_data[[
|
|
1159
|
+
mean_column = transect_data[["mean"]]
|
|
962
1160
|
else:
|
|
963
1161
|
# Handle the case where 'mean' column is not present
|
|
964
|
-
print(
|
|
1162
|
+
print(
|
|
1163
|
+
f"{reducer} column not found in transect data for line {line_names[i]}"
|
|
1164
|
+
)
|
|
965
1165
|
# Create a column of NaNs with the same length as the longest column in transects_df
|
|
966
1166
|
max_length = max(transects_df.shape[0], transect_data.shape[0])
|
|
967
1167
|
mean_column = pd.Series([np.nan] * max_length)
|
|
968
1168
|
else:
|
|
969
|
-
transect_data = Sentinel1Collection.extract_transect(
|
|
1169
|
+
transect_data = Sentinel1Collection.extract_transect(
|
|
1170
|
+
image=image,
|
|
1171
|
+
line=line,
|
|
1172
|
+
reducer=reducer,
|
|
1173
|
+
n_segments=n_segments,
|
|
1174
|
+
to_pandas=to_pandas,
|
|
1175
|
+
)
|
|
970
1176
|
if reducer in transect_data.columns:
|
|
971
1177
|
# Extract the 'mean' column and rename it
|
|
972
|
-
mean_column = transect_data[[
|
|
1178
|
+
mean_column = transect_data[["mean"]]
|
|
973
1179
|
else:
|
|
974
1180
|
# Handle the case where 'mean' column is not present
|
|
975
|
-
print(
|
|
1181
|
+
print(
|
|
1182
|
+
f"{reducer} column not found in transect data for line {line_names[i]}"
|
|
1183
|
+
)
|
|
976
1184
|
# Create a column of NaNs with the same length as the longest column in transects_df
|
|
977
1185
|
max_length = max(transects_df.shape[0], transect_data.shape[0])
|
|
978
1186
|
mean_column = pd.Series([np.nan] * max_length)
|
|
979
|
-
|
|
1187
|
+
|
|
980
1188
|
transects_df = pd.concat([transects_df, mean_column], axis=1)
|
|
981
1189
|
|
|
982
1190
|
transects_df.columns = line_names
|
|
983
|
-
|
|
1191
|
+
|
|
984
1192
|
return transects_df
|
|
985
|
-
|
|
986
|
-
def transect_iterator(
|
|
1193
|
+
|
|
1194
|
+
def transect_iterator(
|
|
1195
|
+
self,
|
|
1196
|
+
lines,
|
|
1197
|
+
line_names,
|
|
1198
|
+
save_folder_path,
|
|
1199
|
+
reducer="mean",
|
|
1200
|
+
n_segments=None,
|
|
1201
|
+
dist_interval=10,
|
|
1202
|
+
to_pandas=True,
|
|
1203
|
+
):
|
|
987
1204
|
"""Computes and stores the values along a transect for each line in a list of lines for each image in a Sentinel1Collection image collection, then saves the data for each image to a csv file. Builds off of the extract_transect function from the geemap package
|
|
988
1205
|
where checks are ran to ensure that the reducer column is present in the transect data. If the reducer column is not present, a column of NaNs is created.
|
|
989
1206
|
An ee reducer is used to aggregate the values along the transect, depending on the number of segments or distance interval specified. Defaults to 'mean' reducer.
|
|
@@ -1004,21 +1221,37 @@ class Sentinel1Collection:
|
|
|
1004
1221
|
Returns:
|
|
1005
1222
|
csv file: file for each image with an organized list of values along the transect(s)
|
|
1006
1223
|
"""
|
|
1007
|
-
image_collection = self
|
|
1224
|
+
image_collection = self
|
|
1008
1225
|
image_collection_dates = self.dates
|
|
1009
1226
|
for i, date in enumerate(image_collection_dates):
|
|
1010
1227
|
try:
|
|
1011
1228
|
print(f"Processing image {i+1}/{len(image_collection_dates)}: {date}")
|
|
1012
1229
|
image = image_collection.image_grab(i)
|
|
1013
|
-
transects_df = Sentinel1Collection.transect(
|
|
1230
|
+
transects_df = Sentinel1Collection.transect(
|
|
1231
|
+
image,
|
|
1232
|
+
lines,
|
|
1233
|
+
line_names,
|
|
1234
|
+
reducer=reducer,
|
|
1235
|
+
n_segments=n_segments,
|
|
1236
|
+
dist_interval=dist_interval,
|
|
1237
|
+
to_pandas=to_pandas,
|
|
1238
|
+
)
|
|
1014
1239
|
image_id = date
|
|
1015
|
-
transects_df.to_csv(f
|
|
1016
|
-
print(f
|
|
1240
|
+
transects_df.to_csv(f"{save_folder_path}{image_id}_transects.csv")
|
|
1241
|
+
print(f"{image_id}_transects saved to csv")
|
|
1017
1242
|
except Exception as e:
|
|
1018
1243
|
print(f"An error occurred while processing image {i+1}: {e}")
|
|
1019
1244
|
|
|
1020
1245
|
@staticmethod
|
|
1021
|
-
def extract_zonal_stats_from_buffer(
|
|
1246
|
+
def extract_zonal_stats_from_buffer(
|
|
1247
|
+
image,
|
|
1248
|
+
coordinates,
|
|
1249
|
+
buffer_size=1,
|
|
1250
|
+
reducer_type="mean",
|
|
1251
|
+
scale=40,
|
|
1252
|
+
tileScale=1,
|
|
1253
|
+
coordinate_names=None,
|
|
1254
|
+
):
|
|
1022
1255
|
"""
|
|
1023
1256
|
Function to extract spatial statistics from an image for a list of coordinates, providing individual statistics for each location.
|
|
1024
1257
|
A radial buffer is applied around each coordinate to extract the statistics, which defaults to 1 meter.
|
|
@@ -1040,15 +1273,26 @@ class Sentinel1Collection:
|
|
|
1040
1273
|
# Check if coordinates is a single tuple and convert it to a list of tuples if necessary
|
|
1041
1274
|
if isinstance(coordinates, tuple) and len(coordinates) == 2:
|
|
1042
1275
|
coordinates = [coordinates]
|
|
1043
|
-
elif not (
|
|
1044
|
-
|
|
1045
|
-
|
|
1276
|
+
elif not (
|
|
1277
|
+
isinstance(coordinates, list)
|
|
1278
|
+
and all(
|
|
1279
|
+
isinstance(coord, tuple) and len(coord) == 2 for coord in coordinates
|
|
1280
|
+
)
|
|
1281
|
+
):
|
|
1282
|
+
raise ValueError(
|
|
1283
|
+
"Coordinates must be a list of tuples with two elements each (latitude, longitude)."
|
|
1284
|
+
)
|
|
1285
|
+
|
|
1046
1286
|
# Check if coordinate_names is a list of strings
|
|
1047
1287
|
if coordinate_names is not None:
|
|
1048
|
-
if not isinstance(coordinate_names, list) or not all(
|
|
1288
|
+
if not isinstance(coordinate_names, list) or not all(
|
|
1289
|
+
isinstance(name, str) for name in coordinate_names
|
|
1290
|
+
):
|
|
1049
1291
|
raise ValueError("coordinate_names must be a list of strings.")
|
|
1050
1292
|
if len(coordinate_names) != len(coordinates):
|
|
1051
|
-
raise ValueError(
|
|
1293
|
+
raise ValueError(
|
|
1294
|
+
"coordinate_names must have the same length as the coordinates list."
|
|
1295
|
+
)
|
|
1052
1296
|
else:
|
|
1053
1297
|
coordinate_names = [f"Location {i+1}" for i in range(len(coordinates))]
|
|
1054
1298
|
|
|
@@ -1060,75 +1304,97 @@ class Sentinel1Collection:
|
|
|
1060
1304
|
# Check if the image is a singleband image
|
|
1061
1305
|
image = ee.Image(check_singleband(image))
|
|
1062
1306
|
|
|
1063
|
-
#Convert coordinates to ee.Geometry.Point, buffer them, and add label/name to feature
|
|
1064
|
-
points = [
|
|
1307
|
+
# Convert coordinates to ee.Geometry.Point, buffer them, and add label/name to feature
|
|
1308
|
+
points = [
|
|
1309
|
+
ee.Feature(
|
|
1310
|
+
ee.Geometry.Point([coord[0], coord[1]]).buffer(buffer_size),
|
|
1311
|
+
{"name": str(coordinate_names[i])},
|
|
1312
|
+
)
|
|
1313
|
+
for i, coord in enumerate(coordinates)
|
|
1314
|
+
]
|
|
1065
1315
|
# Create a feature collection from the buffered points
|
|
1066
1316
|
features = ee.FeatureCollection(points)
|
|
1067
1317
|
# Reduce the image to the buffered points - handle different reducer types
|
|
1068
|
-
if reducer_type ==
|
|
1318
|
+
if reducer_type == "mean":
|
|
1069
1319
|
img_stats = image.reduceRegions(
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1320
|
+
collection=features,
|
|
1321
|
+
reducer=ee.Reducer.mean(),
|
|
1322
|
+
scale=scale,
|
|
1323
|
+
tileScale=tileScale,
|
|
1324
|
+
)
|
|
1074
1325
|
mean_values = img_stats.getInfo()
|
|
1075
1326
|
means = []
|
|
1076
1327
|
names = []
|
|
1077
|
-
for feature in mean_values[
|
|
1078
|
-
names.append(feature[
|
|
1079
|
-
means.append(feature[
|
|
1328
|
+
for feature in mean_values["features"]:
|
|
1329
|
+
names.append(feature["properties"]["name"])
|
|
1330
|
+
means.append(feature["properties"]["mean"])
|
|
1080
1331
|
organized_values = pd.DataFrame([means], columns=names)
|
|
1081
|
-
elif reducer_type ==
|
|
1332
|
+
elif reducer_type == "median":
|
|
1082
1333
|
img_stats = image.reduceRegions(
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1334
|
+
collection=features,
|
|
1335
|
+
reducer=ee.Reducer.median(),
|
|
1336
|
+
scale=scale,
|
|
1337
|
+
tileScale=tileScale,
|
|
1338
|
+
)
|
|
1087
1339
|
median_values = img_stats.getInfo()
|
|
1088
1340
|
medians = []
|
|
1089
1341
|
names = []
|
|
1090
|
-
for feature in median_values[
|
|
1091
|
-
names.append(feature[
|
|
1092
|
-
medians.append(feature[
|
|
1342
|
+
for feature in median_values["features"]:
|
|
1343
|
+
names.append(feature["properties"]["name"])
|
|
1344
|
+
medians.append(feature["properties"]["median"])
|
|
1093
1345
|
organized_values = pd.DataFrame([medians], columns=names)
|
|
1094
|
-
elif reducer_type ==
|
|
1346
|
+
elif reducer_type == "min":
|
|
1095
1347
|
img_stats = image.reduceRegions(
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1348
|
+
collection=features,
|
|
1349
|
+
reducer=ee.Reducer.min(),
|
|
1350
|
+
scale=scale,
|
|
1351
|
+
tileScale=tileScale,
|
|
1352
|
+
)
|
|
1100
1353
|
min_values = img_stats.getInfo()
|
|
1101
1354
|
mins = []
|
|
1102
1355
|
names = []
|
|
1103
|
-
for feature in min_values[
|
|
1104
|
-
names.append(feature[
|
|
1105
|
-
mins.append(feature[
|
|
1356
|
+
for feature in min_values["features"]:
|
|
1357
|
+
names.append(feature["properties"]["name"])
|
|
1358
|
+
mins.append(feature["properties"]["min"])
|
|
1106
1359
|
organized_values = pd.DataFrame([mins], columns=names)
|
|
1107
|
-
elif reducer_type ==
|
|
1360
|
+
elif reducer_type == "max":
|
|
1108
1361
|
img_stats = image.reduceRegions(
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1362
|
+
collection=features,
|
|
1363
|
+
reducer=ee.Reducer.max(),
|
|
1364
|
+
scale=scale,
|
|
1365
|
+
tileScale=tileScale,
|
|
1366
|
+
)
|
|
1113
1367
|
max_values = img_stats.getInfo()
|
|
1114
1368
|
maxs = []
|
|
1115
1369
|
names = []
|
|
1116
|
-
for feature in max_values[
|
|
1117
|
-
names.append(feature[
|
|
1118
|
-
maxs.append(feature[
|
|
1370
|
+
for feature in max_values["features"]:
|
|
1371
|
+
names.append(feature["properties"]["name"])
|
|
1372
|
+
maxs.append(feature["properties"]["max"])
|
|
1119
1373
|
organized_values = pd.DataFrame([maxs], columns=names)
|
|
1120
1374
|
else:
|
|
1121
|
-
raise ValueError(
|
|
1375
|
+
raise ValueError(
|
|
1376
|
+
"reducer_type must be one of 'mean', 'median', 'min', or 'max'."
|
|
1377
|
+
)
|
|
1122
1378
|
return organized_values
|
|
1123
1379
|
|
|
1124
|
-
def iterate_zonal_stats(
|
|
1380
|
+
def iterate_zonal_stats(
|
|
1381
|
+
self,
|
|
1382
|
+
coordinates,
|
|
1383
|
+
buffer_size=1,
|
|
1384
|
+
reducer_type="mean",
|
|
1385
|
+
scale=40,
|
|
1386
|
+
tileScale=1,
|
|
1387
|
+
coordinate_names=None,
|
|
1388
|
+
file_path=None,
|
|
1389
|
+
dates=None,
|
|
1390
|
+
):
|
|
1125
1391
|
"""
|
|
1126
1392
|
Function to iterate over a collection of images and extract spatial statistics for a list of coordinates (defaults to mean). Individual statistics are provided for each location.
|
|
1127
1393
|
A radial buffer is applied around each coordinate to extract the statistics, which defaults to 1 meter.
|
|
1128
1394
|
The function returns a pandas DataFrame with the statistics for each coordinate and date, or optionally exports the data to a table in .csv format.
|
|
1129
1395
|
|
|
1130
1396
|
NOTE: The input RadGEEToolbox class object but be a collection of singleband images or else resulting values will all be zero!
|
|
1131
|
-
|
|
1397
|
+
|
|
1132
1398
|
Args:
|
|
1133
1399
|
coordinates (list): Single tuple or a list of tuples with the coordinates as decimal degrees in the format of (longitude, latitude) for which to extract the statistics. NOTE the format needs to be [(x1, y1), (x2, y2), ...].
|
|
1134
1400
|
buffer_size (int, optional): The radial buffer size in meters around the coordinates. Defaults to 1.
|
|
@@ -1144,22 +1410,32 @@ class Sentinel1Collection:
|
|
|
1144
1410
|
.csv file: Optionally exports the data to a table in .csv format. If file_path is None, the function returns the DataFrame - otherwise the function will only export the csv file.
|
|
1145
1411
|
"""
|
|
1146
1412
|
img_collection = self
|
|
1147
|
-
#Create empty DataFrame to accumulate results
|
|
1413
|
+
# Create empty DataFrame to accumulate results
|
|
1148
1414
|
accumulated_df = pd.DataFrame()
|
|
1149
|
-
#Check if dates is None, if not use the dates provided
|
|
1415
|
+
# Check if dates is None, if not use the dates provided
|
|
1150
1416
|
if dates is None:
|
|
1151
1417
|
dates = img_collection.dates
|
|
1152
1418
|
else:
|
|
1153
1419
|
dates = dates
|
|
1154
|
-
#Iterate over the dates and extract the zonal statistics for each date
|
|
1420
|
+
# Iterate over the dates and extract the zonal statistics for each date
|
|
1155
1421
|
for date in dates:
|
|
1156
|
-
image = img_collection.collection.filter(
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
single_df.
|
|
1422
|
+
image = img_collection.collection.filter(
|
|
1423
|
+
ee.Filter.eq("Date_Filter", date)
|
|
1424
|
+
).first()
|
|
1425
|
+
single_df = Sentinel1Collection.extract_zonal_stats_from_buffer(
|
|
1426
|
+
image,
|
|
1427
|
+
coordinates,
|
|
1428
|
+
buffer_size=buffer_size,
|
|
1429
|
+
reducer_type=reducer_type,
|
|
1430
|
+
scale=scale,
|
|
1431
|
+
tileScale=tileScale,
|
|
1432
|
+
coordinate_names=coordinate_names,
|
|
1433
|
+
)
|
|
1434
|
+
single_df["Date"] = date
|
|
1435
|
+
single_df.set_index("Date", inplace=True)
|
|
1160
1436
|
accumulated_df = pd.concat([accumulated_df, single_df])
|
|
1161
|
-
#Return the DataFrame or export the data to a .csv file
|
|
1437
|
+
# Return the DataFrame or export the data to a .csv file
|
|
1162
1438
|
if file_path is None:
|
|
1163
1439
|
return accumulated_df
|
|
1164
1440
|
else:
|
|
1165
|
-
return accumulated_df.to_csv(f
|
|
1441
|
+
return accumulated_df.to_csv(f"{file_path}.csv")
|