RadGEEToolbox 1.7.0__py3-none-any.whl → 1.7.1__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/GenericCollection.py +119 -31
- RadGEEToolbox/GetPalette.py +136 -87
- RadGEEToolbox/LandsatCollection.py +461 -36
- RadGEEToolbox/Sentinel1Collection.py +603 -18
- RadGEEToolbox/Sentinel2Collection.py +464 -30
- RadGEEToolbox/VisParams.py +112 -194
- RadGEEToolbox/__init__.py +1 -1
- {radgeetoolbox-1.7.0.dist-info → radgeetoolbox-1.7.1.dist-info}/METADATA +8 -6
- radgeetoolbox-1.7.1.dist-info/RECORD +13 -0
- radgeetoolbox-1.7.0.dist-info/RECORD +0 -13
- {radgeetoolbox-1.7.0.dist-info → radgeetoolbox-1.7.1.dist-info}/WHEEL +0 -0
- {radgeetoolbox-1.7.0.dist-info → radgeetoolbox-1.7.1.dist-info}/licenses/LICENSE.txt +0 -0
- {radgeetoolbox-1.7.0.dist-info → radgeetoolbox-1.7.1.dist-info}/top_level.txt +0 -0
|
@@ -104,7 +104,7 @@ class GenericCollection:
|
|
|
104
104
|
|
|
105
105
|
|
|
106
106
|
@staticmethod
|
|
107
|
-
def anomaly_fn(image, geometry, band_name=None, anomaly_band_name=None, replace=True):
|
|
107
|
+
def anomaly_fn(image, geometry, band_name=None, anomaly_band_name=None, replace=True, scale=None):
|
|
108
108
|
"""
|
|
109
109
|
Calculates the anomaly of a singleband image compared to the mean of the singleband image.
|
|
110
110
|
|
|
@@ -132,16 +132,25 @@ class GenericCollection:
|
|
|
132
132
|
|
|
133
133
|
image_to_process = image.select([band_name])
|
|
134
134
|
|
|
135
|
-
|
|
135
|
+
image_scale = image_to_process.projection().nominalScale()
|
|
136
|
+
|
|
137
|
+
# If the user supplies a numeric scale (int/float), keep it; otherwise default to image projection scale.
|
|
138
|
+
scale_value = scale if scale is not None else image_scale # Can be Python number or ee.Number
|
|
139
|
+
|
|
140
|
+
# Compute mean over geometry at chosen scale (scale_value may be ee.Number).
|
|
136
141
|
mean_image = image_to_process.reduceRegion(
|
|
137
142
|
reducer=ee.Reducer.mean(),
|
|
138
143
|
geometry=geometry,
|
|
139
|
-
scale=
|
|
144
|
+
scale=scale_value,
|
|
140
145
|
maxPixels=1e13
|
|
141
146
|
).toImage()
|
|
142
147
|
|
|
143
148
|
# Compute the anomaly by subtracting the mean image from the input image.
|
|
144
|
-
|
|
149
|
+
if scale is None:
|
|
150
|
+
anomaly_image = image_to_process.subtract(mean_image)
|
|
151
|
+
else:
|
|
152
|
+
anomaly_image = image_to_process.reproject(crs=image_to_process.projection(), scale=scale_value).subtract(mean_image)
|
|
153
|
+
|
|
145
154
|
if anomaly_band_name is None:
|
|
146
155
|
if band_name:
|
|
147
156
|
anomaly_image = anomaly_image.rename(band_name)
|
|
@@ -152,9 +161,9 @@ class GenericCollection:
|
|
|
152
161
|
anomaly_image = anomaly_image.rename(anomaly_band_name)
|
|
153
162
|
# return anomaly_image
|
|
154
163
|
if replace:
|
|
155
|
-
return anomaly_image.copyProperties(image)
|
|
164
|
+
return anomaly_image.copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
156
165
|
else:
|
|
157
|
-
return image.addBands(anomaly_image, overwrite=True)
|
|
166
|
+
return image.addBands(anomaly_image, overwrite=True).copyProperties(image)
|
|
158
167
|
|
|
159
168
|
@staticmethod
|
|
160
169
|
def mask_via_band_fn(image, band_to_mask, band_for_mask, threshold, mask_above=False, add_band_to_original_image=False):
|
|
@@ -315,7 +324,7 @@ class GenericCollection:
|
|
|
315
324
|
|
|
316
325
|
# Call to iterate the calculate_and_set_area function over the list of bands, starting with the original image
|
|
317
326
|
final_image = ee.Image(bands.iterate(calculate_and_set_area, image))
|
|
318
|
-
return final_image
|
|
327
|
+
return final_image #.set('system:time_start', image.get('system:time_start'))
|
|
319
328
|
|
|
320
329
|
def PixelAreaSumCollection(
|
|
321
330
|
self, band_name, geometry, threshold=-1, scale=30, maxPixels=1e12, output_type='ImageCollection', area_data_export_path=None
|
|
@@ -329,11 +338,11 @@ class GenericCollection:
|
|
|
329
338
|
|
|
330
339
|
Args:
|
|
331
340
|
band_name (string or list of strings): name of band(s) (string) for calculating area. If providing multiple band names, pass as a list of strings.
|
|
332
|
-
geometry (ee.Geometry): ee.Geometry object denoting area to clip to for area calculation
|
|
341
|
+
geometry (ee.Geometry): ee.Geometry object denoting area to clip to for area calculation.
|
|
333
342
|
threshold (float): integer threshold to specify masking of pixels below threshold (defaults to -1). If providing multiple band names, the same threshold will be applied to all bands. Best practice in this case is to mask the bands prior to passing to this function and leave threshold at default of -1.
|
|
334
|
-
scale (int): integer scale of image resolution (meters) (defaults to 30)
|
|
335
|
-
maxPixels (int): integer denoting maximum number of pixels for calculations
|
|
336
|
-
output_type (str): 'ImageCollection' to return an ee.ImageCollection, 'GenericCollection' to return a GenericCollection object (defaults to 'ImageCollection')
|
|
343
|
+
scale (int): integer scale of image resolution (meters) (defaults to 30).
|
|
344
|
+
maxPixels (int): integer denoting maximum number of pixels for calculations.
|
|
345
|
+
output_type (str): 'ImageCollection' or 'ee.ImageCollection' to return an ee.ImageCollection, 'GenericCollection' to return a GenericCollection object, or 'DataFrame', 'Pandas', 'pd', 'dataframe', 'df' to return a pandas DataFrame (defaults to 'ImageCollection').
|
|
337
346
|
area_data_export_path (str, optional): If provided, the function will save the resulting area data to a CSV file at the specified path.
|
|
338
347
|
|
|
339
348
|
Returns:
|
|
@@ -358,15 +367,41 @@ class GenericCollection:
|
|
|
358
367
|
|
|
359
368
|
# If an export path is provided, the area data will be exported to a CSV file
|
|
360
369
|
if area_data_export_path:
|
|
361
|
-
GenericCollection(collection=self._PixelAreaSumCollection).ExportProperties(property_names=band_name, file_path=area_data_export_path+'.csv')
|
|
370
|
+
GenericCollection(collection=self._PixelAreaSumCollection).ExportProperties(property_names=[band_name], file_path=area_data_export_path+'.csv')
|
|
362
371
|
|
|
363
372
|
# Returning the result in the desired format based on output_type argument or raising an error for invalid input
|
|
364
|
-
if output_type == 'ImageCollection':
|
|
373
|
+
if output_type == 'ImageCollection' or output_type == 'ee.ImageCollection':
|
|
365
374
|
return self._PixelAreaSumCollection
|
|
366
375
|
elif output_type == 'GenericCollection':
|
|
367
376
|
return GenericCollection(collection=self._PixelAreaSumCollection)
|
|
377
|
+
elif output_type == 'DataFrame' or output_type == 'Pandas' or output_type == 'pd' or output_type == 'dataframe' or output_type == 'df':
|
|
378
|
+
return GenericCollection(collection=self._PixelAreaSumCollection).ExportProperties(property_names=[band_name])
|
|
368
379
|
else:
|
|
369
|
-
raise ValueError("output_type must be 'ImageCollection'
|
|
380
|
+
raise ValueError("Incorrect `output_type`. The `output_type` argument must be one of the following: 'ImageCollection', 'ee.ImageCollection', 'GenericCollection', 'DataFrame', 'Pandas', 'pd', 'dataframe', or 'df'.")
|
|
381
|
+
|
|
382
|
+
@staticmethod
|
|
383
|
+
def add_month_property_fn(image):
|
|
384
|
+
"""
|
|
385
|
+
Adds a numeric 'month' property to the image based on its date.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
image (ee.Image): Input image.
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
ee.Image: Image with the 'month' property added.
|
|
392
|
+
"""
|
|
393
|
+
return image.set('month', image.date().get('month'))
|
|
394
|
+
|
|
395
|
+
@property
|
|
396
|
+
def add_month_property(self):
|
|
397
|
+
"""
|
|
398
|
+
Adds a numeric 'month' property to each image in the collection.
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
GenericCollection: A GenericCollection image collection with the 'month' property added to each image.
|
|
402
|
+
"""
|
|
403
|
+
col = self.collection.map(GenericCollection.add_month_property_fn)
|
|
404
|
+
return GenericCollection(collection=col)
|
|
370
405
|
|
|
371
406
|
def combine(self, other):
|
|
372
407
|
"""
|
|
@@ -480,6 +515,28 @@ class GenericCollection:
|
|
|
480
515
|
dates = self._dates_list.getInfo()
|
|
481
516
|
self._dates = dates
|
|
482
517
|
return self._dates
|
|
518
|
+
|
|
519
|
+
def remove_duplicate_dates(self, sort_by='system:time_start', ascending=True):
|
|
520
|
+
"""
|
|
521
|
+
Removes duplicate images that share the same date, keeping only the first one encountered.
|
|
522
|
+
Useful for handling duplicate acquisitions or overlapping path/rows.
|
|
523
|
+
|
|
524
|
+
Args:
|
|
525
|
+
sort_by (str): Property to sort by before filtering distinct dates. Defaults to 'system:time_start' which is a global property.
|
|
526
|
+
Take care to provide a property that exists in all images if using a custom property.
|
|
527
|
+
ascending (bool): Sort order. Defaults to True.
|
|
528
|
+
|
|
529
|
+
Returns:
|
|
530
|
+
GenericCollection: A new GenericCollection object with distinct dates.
|
|
531
|
+
"""
|
|
532
|
+
|
|
533
|
+
# Sort the collection to ensure the "best" image comes first (e.g. least cloudy)
|
|
534
|
+
sorted_col = self.collection.sort(sort_by, ascending)
|
|
535
|
+
|
|
536
|
+
# distinct() retains the first image for each unique value of the specified property
|
|
537
|
+
distinct_col = sorted_col.distinct('Date_Filter')
|
|
538
|
+
|
|
539
|
+
return GenericCollection(collection=distinct_col)
|
|
483
540
|
|
|
484
541
|
def ExportProperties(self, property_names, file_path=None):
|
|
485
542
|
"""
|
|
@@ -1565,24 +1622,24 @@ class GenericCollection:
|
|
|
1565
1622
|
if classify_above_threshold:
|
|
1566
1623
|
if mask_zeros:
|
|
1567
1624
|
col = self.collection.map(
|
|
1568
|
-
lambda image: image.select(band_name).gte(threshold).rename(band_name).updateMask(image.select(band_name).gt(0)).copyProperties(image)
|
|
1625
|
+
lambda image: image.select(band_name).gte(threshold).rename(band_name).updateMask(image.select(band_name).gt(0)).copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
1569
1626
|
)
|
|
1570
1627
|
else:
|
|
1571
1628
|
col = self.collection.map(
|
|
1572
|
-
lambda image: image.select(band_name).gte(threshold).rename(band_name).copyProperties(image)
|
|
1629
|
+
lambda image: image.select(band_name).gte(threshold).rename(band_name).copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
1573
1630
|
)
|
|
1574
1631
|
else:
|
|
1575
1632
|
if mask_zeros:
|
|
1576
1633
|
col = self.collection.map(
|
|
1577
|
-
lambda image: image.select(band_name).lte(threshold).rename(band_name).updateMask(image.select(band_name).gt(0)).copyProperties(image)
|
|
1634
|
+
lambda image: image.select(band_name).lte(threshold).rename(band_name).updateMask(image.select(band_name).gt(0)).copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
1578
1635
|
)
|
|
1579
1636
|
else:
|
|
1580
1637
|
col = self.collection.map(
|
|
1581
|
-
lambda image: image.select(band_name).lte(threshold).rename(band_name).copyProperties(image)
|
|
1638
|
+
lambda image: image.select(band_name).lte(threshold).rename(band_name).copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
1582
1639
|
)
|
|
1583
1640
|
return GenericCollection(collection=col)
|
|
1584
1641
|
|
|
1585
|
-
def anomaly(self, geometry, band_name=None, anomaly_band_name=None, replace=True):
|
|
1642
|
+
def anomaly(self, geometry, band_name=None, anomaly_band_name=None, replace=True, scale=None):
|
|
1586
1643
|
"""
|
|
1587
1644
|
Calculates the anomaly of each image in a collection compared to the mean of each image.
|
|
1588
1645
|
|
|
@@ -1596,6 +1653,7 @@ class GenericCollection:
|
|
|
1596
1653
|
band_name (str, optional): A string representing the band name to be used for the output anomaly image. If not provided, the band name of the first band of the input image will be used.
|
|
1597
1654
|
anomaly_band_name (str, optional): A string representing the band name to be used for the output anomaly image. If not provided, the band name of the first band of the input image will be used.
|
|
1598
1655
|
replace (bool, optional): A boolean indicating whether to replace the original band with the anomaly band. If True, the output image will only contain the anomaly band. If False, the output image will retain all original bands and add the anomaly band. Default is True.
|
|
1656
|
+
scale (int, optional): The scale (in meters) to use for the image reduction. If not provided, the nominal scale of the image will be used.
|
|
1599
1657
|
|
|
1600
1658
|
Returns:
|
|
1601
1659
|
GenericCollection: A GenericCollection where each image represents the anomaly (deviation from
|
|
@@ -1614,7 +1672,7 @@ class GenericCollection:
|
|
|
1614
1672
|
else:
|
|
1615
1673
|
band_name = band_names.get(0).getInfo()
|
|
1616
1674
|
|
|
1617
|
-
col = self.collection.map(lambda image: GenericCollection.anomaly_fn(image, geometry=geometry, band_name=band_name, anomaly_band_name=anomaly_band_name, replace=replace))
|
|
1675
|
+
col = self.collection.map(lambda image: GenericCollection.anomaly_fn(image, geometry=geometry, band_name=band_name, anomaly_band_name=anomaly_band_name, replace=replace, scale=scale))
|
|
1618
1676
|
return GenericCollection(collection=col)
|
|
1619
1677
|
|
|
1620
1678
|
def mask_via_band(self, band_to_mask, band_for_mask, threshold=-1, mask_above=True, add_band_to_original_image=False):
|
|
@@ -2434,24 +2492,30 @@ class GenericCollection:
|
|
|
2434
2492
|
ValueError: If input parameters are invalid.
|
|
2435
2493
|
TypeError: If geometries input type is unsupported.
|
|
2436
2494
|
"""
|
|
2495
|
+
# Create a local reference to the collection object to allow for modifications (like band selection) without altering the original instance
|
|
2437
2496
|
img_collection_obj = self
|
|
2497
|
+
|
|
2498
|
+
# If a specific band is requested, select only that band
|
|
2438
2499
|
if band:
|
|
2439
2500
|
img_collection_obj = GenericCollection(collection=img_collection_obj.collection.select(band))
|
|
2440
2501
|
else:
|
|
2502
|
+
# If no band is specified, default to using the first band of the first image in the collection
|
|
2441
2503
|
first_image = img_collection_obj.image_grab(0)
|
|
2442
2504
|
first_band = first_image.bandNames().get(0)
|
|
2443
2505
|
img_collection_obj = GenericCollection(collection=img_collection_obj.collection.select([first_band]))
|
|
2444
|
-
|
|
2506
|
+
|
|
2507
|
+
# If a list of dates is provided, filter the collection to include only images matching those dates
|
|
2445
2508
|
if dates:
|
|
2446
2509
|
img_collection_obj = GenericCollection(
|
|
2447
2510
|
collection=self.collection.filter(ee.Filter.inList('Date_Filter', dates))
|
|
2448
2511
|
)
|
|
2449
2512
|
|
|
2450
|
-
# Initialize variables
|
|
2513
|
+
# Initialize variables to hold the standardized feature collection and coordinates
|
|
2451
2514
|
features = None
|
|
2452
2515
|
validated_coordinates = []
|
|
2453
2516
|
|
|
2454
|
-
#
|
|
2517
|
+
# Define a helper function to ensure every feature has a standardized 'geo_name' property
|
|
2518
|
+
# This handles features that might have different existing name properties or none at all
|
|
2455
2519
|
def set_standard_name(feature):
|
|
2456
2520
|
has_geo_name = feature.get('geo_name')
|
|
2457
2521
|
has_name = feature.get('name')
|
|
@@ -2462,33 +2526,38 @@ class GenericCollection:
|
|
|
2462
2526
|
ee.Algorithms.If(has_index, has_index, 'unnamed_geometry')))
|
|
2463
2527
|
return feature.set({'geo_name': new_name})
|
|
2464
2528
|
|
|
2529
|
+
# Handle input: FeatureCollection or single Feature
|
|
2465
2530
|
if isinstance(geometries, (ee.FeatureCollection, ee.Feature)):
|
|
2466
2531
|
features = ee.FeatureCollection(geometries)
|
|
2467
2532
|
if geometry_names:
|
|
2468
2533
|
print("Warning: 'geometry_names' are ignored when the input is an ee.Feature or ee.FeatureCollection.")
|
|
2469
2534
|
|
|
2535
|
+
# Handle input: Single ee.Geometry
|
|
2470
2536
|
elif isinstance(geometries, ee.Geometry):
|
|
2471
2537
|
name = geometry_names[0] if (geometry_names and geometry_names[0]) else 'unnamed_geometry'
|
|
2472
2538
|
features = ee.FeatureCollection([ee.Feature(geometries).set('geo_name', name)])
|
|
2473
2539
|
|
|
2540
|
+
# Handle input: List (could be coordinates or ee.Geometry objects)
|
|
2474
2541
|
elif isinstance(geometries, list):
|
|
2475
2542
|
if not geometries: # Handle empty list case
|
|
2476
2543
|
raise ValueError("'geometries' list cannot be empty.")
|
|
2477
2544
|
|
|
2478
|
-
# Case: List of coordinates
|
|
2545
|
+
# Case: List of tuples (coordinates)
|
|
2479
2546
|
if all(isinstance(i, tuple) for i in geometries):
|
|
2480
2547
|
validated_coordinates = geometries
|
|
2548
|
+
# Generate default names if none provided
|
|
2481
2549
|
if geometry_names is None:
|
|
2482
2550
|
geometry_names = [f"Location_{i+1}" for i in range(len(validated_coordinates))]
|
|
2483
2551
|
elif len(geometry_names) != len(validated_coordinates):
|
|
2484
2552
|
raise ValueError("geometry_names must have the same length as the coordinates list.")
|
|
2553
|
+
# Create features with buffers around the coordinates
|
|
2485
2554
|
points = [
|
|
2486
2555
|
ee.Feature(ee.Geometry.Point(coord).buffer(buffer_size), {'geo_name': str(name)})
|
|
2487
2556
|
for coord, name in zip(validated_coordinates, geometry_names)
|
|
2488
2557
|
]
|
|
2489
2558
|
features = ee.FeatureCollection(points)
|
|
2490
2559
|
|
|
2491
|
-
# Case: List of
|
|
2560
|
+
# Case: List of ee.Geometry objects
|
|
2492
2561
|
elif all(isinstance(i, ee.Geometry) for i in geometries):
|
|
2493
2562
|
if geometry_names is None:
|
|
2494
2563
|
geometry_names = [f"Geometry_{i+1}" for i in range(len(geometries))]
|
|
@@ -2503,6 +2572,7 @@ class GenericCollection:
|
|
|
2503
2572
|
else:
|
|
2504
2573
|
raise TypeError("Input list must be a list of (lon, lat) tuples OR a list of ee.Geometry objects.")
|
|
2505
2574
|
|
|
2575
|
+
# Handle input: Single tuple (coordinate)
|
|
2506
2576
|
elif isinstance(geometries, tuple) and len(geometries) == 2:
|
|
2507
2577
|
name = geometry_names[0] if geometry_names else 'Location_1'
|
|
2508
2578
|
features = ee.FeatureCollection([
|
|
@@ -2511,39 +2581,48 @@ class GenericCollection:
|
|
|
2511
2581
|
else:
|
|
2512
2582
|
raise TypeError("Unsupported type for 'geometries'.")
|
|
2513
2583
|
|
|
2584
|
+
# Apply the naming standardization to the created FeatureCollection
|
|
2514
2585
|
features = features.map(set_standard_name)
|
|
2515
2586
|
|
|
2587
|
+
# Dynamically retrieve the Earth Engine reducer based on the string name provided
|
|
2516
2588
|
try:
|
|
2517
2589
|
reducer = getattr(ee.Reducer, reducer_type)()
|
|
2518
2590
|
except AttributeError:
|
|
2519
2591
|
raise ValueError(f"Unknown reducer_type: '{reducer_type}'.")
|
|
2520
2592
|
|
|
2593
|
+
# Define the function to map over the image collection
|
|
2521
2594
|
def calculate_stats_for_image(image):
|
|
2522
2595
|
image_date = image.get('Date_Filter')
|
|
2596
|
+
# Calculate statistics for all geometries in 'features' for this specific image
|
|
2523
2597
|
stats_fc = image.reduceRegions(
|
|
2524
2598
|
collection=features, reducer=reducer, scale=scale, tileScale=tileScale
|
|
2525
2599
|
)
|
|
2526
2600
|
|
|
2601
|
+
# Helper to ensure the result has the reducer property, even if masked
|
|
2602
|
+
# If the property is missing (e.g., all pixels masked), set it to a sentinel value (-9999)
|
|
2527
2603
|
def guarantee_reducer_property(f):
|
|
2528
2604
|
has_property = f.propertyNames().contains(reducer_type)
|
|
2529
2605
|
return ee.Algorithms.If(has_property, f, f.set(reducer_type, -9999))
|
|
2606
|
+
|
|
2607
|
+
# Apply the guarantee check
|
|
2530
2608
|
fixed_stats_fc = stats_fc.map(guarantee_reducer_property)
|
|
2531
2609
|
|
|
2610
|
+
# Attach the image date to every feature in the result so we know which image it came from
|
|
2532
2611
|
return fixed_stats_fc.map(lambda f: f.set('image_date', image_date))
|
|
2533
2612
|
|
|
2613
|
+
# Map the calculation over the image collection and flatten the resulting FeatureCollections into one
|
|
2534
2614
|
results_fc = ee.FeatureCollection(img_collection_obj.collection.map(calculate_stats_for_image)).flatten()
|
|
2615
|
+
|
|
2616
|
+
# Convert the Earth Engine FeatureCollection to a pandas DataFrame (client-side operation)
|
|
2535
2617
|
df = GenericCollection.ee_to_df(results_fc, remove_geom=True)
|
|
2536
2618
|
|
|
2537
|
-
#
|
|
2619
|
+
# Check for empty results or missing columns
|
|
2538
2620
|
if df.empty:
|
|
2539
|
-
# print("No results found for the given parameters. Check if the geometries intersect with the images, if the dates filter is too restrictive, or if the provided bands are empty.")
|
|
2540
|
-
# return df
|
|
2541
2621
|
raise ValueError("No results found for the given parameters. Check if the geometries intersect with the images, if the dates filter is too restrictive, or if the provided bands are empty.")
|
|
2542
2622
|
if reducer_type not in df.columns:
|
|
2543
2623
|
print(f"Warning: Reducer '{reducer_type}' not found in results.")
|
|
2544
|
-
# return df
|
|
2545
2624
|
|
|
2546
|
-
#
|
|
2625
|
+
# Filter out the sentinel values (-9999) which indicate failed reductions/masked pixels
|
|
2547
2626
|
initial_rows = len(df)
|
|
2548
2627
|
df.dropna(subset=[reducer_type], inplace=True)
|
|
2549
2628
|
df = df[df[reducer_type] != -9999]
|
|
@@ -2551,9 +2630,18 @@ class GenericCollection:
|
|
|
2551
2630
|
if dropped_rows > 0:
|
|
2552
2631
|
print(f"Warning: Discarded {dropped_rows} results due to failed reductions (e.g., no valid pixels in geometry).")
|
|
2553
2632
|
|
|
2554
|
-
#
|
|
2633
|
+
# Pivot the DataFrame so that each row represents a date and each column represents a geometry location
|
|
2555
2634
|
pivot_df = df.pivot(index='image_date', columns='geo_name', values=reducer_type)
|
|
2635
|
+
# Rename the column headers (geometry names) to include the reducer type
|
|
2636
|
+
pivot_df.columns = [f"{col}_{reducer_type}" for col in pivot_df.columns]
|
|
2637
|
+
# Rename the index axis to 'Date' so it is correctly labeled when moved to a column later
|
|
2556
2638
|
pivot_df.index.name = 'Date'
|
|
2639
|
+
# Remove the name of the columns axis (which defaults to 'geo_name') so it doesn't appear as a confusing label in the final output
|
|
2640
|
+
pivot_df.columns.name = None
|
|
2641
|
+
# Reset the index to move the 'Date' index into a regular column and create a standard numerical index (0, 1, 2...)
|
|
2642
|
+
pivot_df = pivot_df.reset_index(drop=False)
|
|
2643
|
+
|
|
2644
|
+
# If a file path is provided, save the resulting DataFrame to CSV
|
|
2557
2645
|
if file_path:
|
|
2558
2646
|
# Check if file_path ends with .csv and remove it if so for consistency
|
|
2559
2647
|
if file_path.endswith('.csv'):
|
RadGEEToolbox/GetPalette.py
CHANGED
|
@@ -3,118 +3,167 @@ def get_palette(name):
|
|
|
3
3
|
Returns the color palette associated with the given name.
|
|
4
4
|
|
|
5
5
|
Args:
|
|
6
|
-
name (str):
|
|
6
|
+
name (str): Options include:
|
|
7
|
+
- Scientific: 'viridis', 'plasma', 'inferno', 'magma', 'cividis', 'turbo', 'coolwarm', 'spectral'
|
|
8
|
+
- Sequential: 'blues', 'greens', 'reds', 'greys', 'oranges', 'purples'
|
|
9
|
+
- Diverging: 'rdylgn' (Red-Yellow-Green), 'rdylbu' (Red-Yellow-Blue), 'rdbu' (Red-White-Blue), 'brbg' (Brown-Blue-Green), 'piyg' (Pink-Yellow-Green)
|
|
10
|
+
- Domain Specific: 'dem' (Elevation), 'terrain' (Topography), 'ndvi' (Vegetation), 'ndwi' (Water), 'precipitation' (Rain/Snow), 'thermal' (Temperature), 'evapotranspiration' (ET)
|
|
11
|
+
- Custom/Legacy: 'algae', 'dense', 'haline', 'jet', 'matter', 'pubu', 'soft_blue_green_red', 'turbid', 'ylord', 'ocean'
|
|
7
12
|
|
|
8
13
|
Returns:
|
|
9
14
|
list: list of colors to be used for image visualization in GEE vis params
|
|
10
15
|
|
|
11
16
|
"""
|
|
12
17
|
palettes = {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"#
|
|
16
|
-
"#
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"#
|
|
20
|
-
"#
|
|
18
|
+
# --- Scientific / Perceptually Uniform ---
|
|
19
|
+
"viridis": [
|
|
20
|
+
"#440154", "#482475", "#414487", "#355f8d", "#2a788e",
|
|
21
|
+
"#21918c", "#22a884", "#44bf70", "#7ad151", "#bddf26", "#fde725"
|
|
22
|
+
],
|
|
23
|
+
"plasma": [
|
|
24
|
+
"#0d0887", "#46039f", "#7201a8", "#9c179e", "#bd3786",
|
|
25
|
+
"#d8576b", "#ed7953", "#fb9f3a", "#fdc924", "#f0f921"
|
|
21
26
|
],
|
|
22
|
-
"soft_blue_green_red": ["#deeaee", "#b1cbbb", "#eea29a", "#c94c4c"],
|
|
23
27
|
"inferno": [
|
|
24
|
-
"#000004",
|
|
25
|
-
"#
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"#
|
|
29
|
-
"#
|
|
30
|
-
|
|
28
|
+
"#000004", "#160b39", "#420a68", "#6a176e", "#932667",
|
|
29
|
+
"#bc3754", "#dd513a", "#f37819", "#fca50a", "#f6d746", "#fcffa4"
|
|
30
|
+
],
|
|
31
|
+
"magma": [
|
|
32
|
+
"#000004", "#140e36", "#3b0f70", "#641a80", "#8c2981",
|
|
33
|
+
"#b73779", "#de4968", "#f7705c", "#fe9f6d", "#fecf92", "#fcfdbf"
|
|
34
|
+
],
|
|
35
|
+
"cividis": [
|
|
36
|
+
"#00204d", "#002c69", "#003989", "#184a8c", "#3f5b8a",
|
|
37
|
+
"#5d6d85", "#78807f", "#969576", "#b4ab6a", "#d4c359", "#fdea45"
|
|
38
|
+
],
|
|
39
|
+
"turbo": [
|
|
40
|
+
"#30123b", "#466be3", "#28bbec", "#32f298", "#a2fc3c",
|
|
41
|
+
"#f2ea33", "#fe9b2d", "#e4460a", "#7a0403"
|
|
42
|
+
],
|
|
43
|
+
"coolwarm": [
|
|
44
|
+
"#3d4c8a", "#6282ea", "#99baff", "#cdd9ec", "#eaf0e4",
|
|
45
|
+
"#f4dcb8", "#e8ac80", "#d4654d", "#b2182b"
|
|
46
|
+
],
|
|
47
|
+
|
|
48
|
+
# --- Sequential (ColorBrewer & Standard) ---
|
|
49
|
+
"blues": [
|
|
50
|
+
"#f7fbff", "#deebf7", "#c6dbef", "#9ecae1", "#6baed6",
|
|
51
|
+
"#4292c6", "#2171b5", "#08519c", "#08306b"
|
|
52
|
+
],
|
|
53
|
+
"greens": [
|
|
54
|
+
"#f7fcf5", "#e5f5e0", "#c7e9c0", "#a1d99b", "#74c476",
|
|
55
|
+
"#41ab5d", "#238b45", "#006d2c", "#00441b"
|
|
56
|
+
],
|
|
57
|
+
"reds": [
|
|
58
|
+
"#fff5f0", "#fee0d2", "#fcbba1", "#fc9272", "#fb6a4a",
|
|
59
|
+
"#ef3b2c", "#cb181d", "#a50f15", "#67000d"
|
|
60
|
+
],
|
|
61
|
+
"greys": [
|
|
62
|
+
"#ffffff", "#f0f0f0", "#d9d9d9", "#bdbdbd", "#969696",
|
|
63
|
+
"#737373", "#525252", "#252525", "#000000"
|
|
64
|
+
],
|
|
65
|
+
"oranges": [
|
|
66
|
+
"#fff5eb", "#fee6ce", "#fdd0a2", "#fdae6b", "#fd8d3c",
|
|
67
|
+
"#f16913", "#d94801", "#a63603", "#7f2704"
|
|
31
68
|
],
|
|
69
|
+
"purples": [
|
|
70
|
+
"#fcfbfd", "#efedf5", "#dadaeb", "#bcbddc", "#9e9ac8",
|
|
71
|
+
"#807dba", "#6a51a3", "#54278f", "#3f007d"
|
|
72
|
+
],
|
|
73
|
+
|
|
74
|
+
# --- Diverging ---
|
|
75
|
+
"spectral": [
|
|
76
|
+
"#9e0142", "#d53e4f", "#f46d43", "#fdae61", "#fee08b",
|
|
77
|
+
"#ffffbf", "#e6f598", "#abdda4", "#66c2a5", "#3288bd", "#5e4fa2"
|
|
78
|
+
],
|
|
79
|
+
"rdylgn": [
|
|
80
|
+
"#a50026", "#d73027", "#f46d43", "#fdae61", "#fee08b",
|
|
81
|
+
"#ffffbf", "#d9ef8b", "#a6d96a", "#66bd63", "#1a9850", "#006837"
|
|
82
|
+
],
|
|
83
|
+
"rdylbu": [
|
|
84
|
+
"#a50026", "#d73027", "#f46d43", "#fdae61", "#fee08b",
|
|
85
|
+
"#ffffbf", "#e0f3f8", "#abd9e9", "#74add1", "#4575b4", "#313695"
|
|
86
|
+
],
|
|
87
|
+
"rdbu": [
|
|
88
|
+
"#67001f", "#b2182b", "#d6604d", "#f4a582", "#fddbc7",
|
|
89
|
+
"#f7f7f7", "#d1e5f0", "#92c5de", "#4393c3", "#2166ac", "#053061"
|
|
90
|
+
],
|
|
91
|
+
"brbg": [
|
|
92
|
+
"#543005", "#8c510a", "#bf812d", "#dfc27d", "#f6e8c3",
|
|
93
|
+
"#f5f5f5", "#c7eae5", "#80cdc1", "#35978f", "#01665e", "#003c30"
|
|
94
|
+
],
|
|
95
|
+
"piyg": [
|
|
96
|
+
"#8e0152", "#c51b7d", "#de77ae", "#f1b6da", "#fde0ef",
|
|
97
|
+
"#f7f7f7", "#e6f5d0", "#b8e186", "#7fbc41", "#4d9221", "#276419"
|
|
98
|
+
],
|
|
99
|
+
|
|
100
|
+
# --- Domain Specific ---
|
|
101
|
+
"dem": [
|
|
102
|
+
"#006600", "#002200", "#fff700", "#ab7634", "#c4d0ff", "#ffffff"
|
|
103
|
+
], # Classic Green-Brown-White Elevation
|
|
104
|
+
"terrain": [
|
|
105
|
+
"#00A600", "#63C600", "#E6E600", "#E9BD3A", "#ECB176",
|
|
106
|
+
"#EFC2B3", "#F2F2F2"
|
|
107
|
+
], # Alternative Terrain
|
|
108
|
+
"ndvi": [
|
|
109
|
+
"#FFFFFF", "#CE7E45", "#DF923D", "#F1B555", "#FCD163", "#99B718",
|
|
110
|
+
"#74A901", "#66A000", "#529400", "#3E8601", "#207401", "#056201",
|
|
111
|
+
"#004C00", "#023B01", "#012E01", "#011D01", "#011301"
|
|
112
|
+
], # Standard MODIS/Landsat NDVI ramp
|
|
113
|
+
"ndwi": [
|
|
114
|
+
"#ece7f2", "#d0d1e6", "#a6bddb", "#74a9cf", "#3690c0",
|
|
115
|
+
"#0570b0", "#045a8d", "#023858"
|
|
116
|
+
], # Blue ramp for water
|
|
117
|
+
"precipitation": [
|
|
118
|
+
"#ffffff", "#00ffff", "#0000ff", "#00ff00", "#ffff00",
|
|
119
|
+
"#ff0000", "#ff00ff"
|
|
120
|
+
], # Classic Precip: White-Blue-Green-Yellow-Red-Purple
|
|
121
|
+
"evapotranspiration": [
|
|
122
|
+
"#ffffff", "#fcd163", "#99b718", "#74a901", "#66a000",
|
|
123
|
+
"#529400", "#3e8601", "#207401", "#056201", "#004c00"
|
|
124
|
+
], # Modeled on NDVI/Vegetation water use
|
|
32
125
|
"thermal": [
|
|
33
|
-
"#042333",
|
|
34
|
-
"#
|
|
35
|
-
"#744992",
|
|
36
|
-
"#b15f82",
|
|
37
|
-
"#eb7958",
|
|
38
|
-
"#fbb43d",
|
|
39
|
-
"#e8fa5b",
|
|
126
|
+
"#042333", "#2c3395", "#744992", "#b15f82", "#eb7958",
|
|
127
|
+
"#fbb43d", "#e8fa5b"
|
|
40
128
|
],
|
|
129
|
+
|
|
130
|
+
# --- Custom / Legacy from Original ---
|
|
131
|
+
"jet": [
|
|
132
|
+
"#00007F", "#002AFF", "#00D4FF", "#7FFF7F", "#FFD400",
|
|
133
|
+
"#FF2A00", "#7F0000"
|
|
134
|
+
],
|
|
135
|
+
"soft_blue_green_red": ["#deeaee", "#b1cbbb", "#eea29a", "#c94c4c"],
|
|
41
136
|
"algae": [
|
|
42
|
-
"#d7f9d0",
|
|
43
|
-
"#
|
|
44
|
-
"#64b463",
|
|
45
|
-
"#129450",
|
|
46
|
-
"#126e45",
|
|
47
|
-
"#1a482f",
|
|
48
|
-
"#122414",
|
|
137
|
+
"#d7f9d0", "#a2d595", "#64b463", "#129450", "#126e45",
|
|
138
|
+
"#1a482f", "#122414"
|
|
49
139
|
],
|
|
50
140
|
"turbid": [
|
|
51
|
-
"#e9f6ab",
|
|
52
|
-
"#
|
|
53
|
-
"#bf9747",
|
|
54
|
-
"#a1703b",
|
|
55
|
-
"#795338",
|
|
56
|
-
"#4d392d",
|
|
57
|
-
"#221f1b",
|
|
141
|
+
"#e9f6ab", "#d3c671", "#bf9747", "#a1703b", "#795338",
|
|
142
|
+
"#4d392d", "#221f1b"
|
|
58
143
|
],
|
|
59
144
|
"dense": [
|
|
60
|
-
"#e6f1f1",
|
|
61
|
-
"#
|
|
62
|
-
"#76a4e5",
|
|
63
|
-
"#7871d5",
|
|
64
|
-
"#7642a5",
|
|
65
|
-
"#621d62",
|
|
66
|
-
"#360e24",
|
|
145
|
+
"#e6f1f1", "#a2cee2", "#76a4e5", "#7871d5", "#7642a5",
|
|
146
|
+
"#621d62", "#360e24"
|
|
67
147
|
],
|
|
68
148
|
"matter": [
|
|
69
|
-
"#feedb0",
|
|
70
|
-
"#
|
|
71
|
-
"#eb7858",
|
|
72
|
-
"#ce4356",
|
|
73
|
-
"#9f2462",
|
|
74
|
-
"#66185c",
|
|
75
|
-
"#2f0f3e",
|
|
149
|
+
"#feedb0", "#f7b37c", "#eb7858", "#ce4356", "#9f2462",
|
|
150
|
+
"#66185c", "#2f0f3e"
|
|
76
151
|
],
|
|
77
152
|
"haline": [
|
|
78
|
-
"#2a186c",
|
|
79
|
-
"
|
|
80
|
-
"#206e8b",
|
|
81
|
-
"#3c9387",
|
|
82
|
-
"#5ab978",
|
|
83
|
-
"#aad85c",
|
|
84
|
-
"#fdef9a",
|
|
153
|
+
"#2a186c", "14439c", "#206e8b", "#3c9387", "#5ab978",
|
|
154
|
+
"#aad85c", "#fdef9a"
|
|
85
155
|
],
|
|
86
156
|
"ylord": [
|
|
87
|
-
"#ffffcc",
|
|
88
|
-
"#
|
|
89
|
-
"#fed976",
|
|
90
|
-
"#feb24c",
|
|
91
|
-
"#fd8d3c",
|
|
92
|
-
"#fc4e2a",
|
|
93
|
-
"#e31a1c",
|
|
94
|
-
"#bd0026",
|
|
95
|
-
"#800026",
|
|
157
|
+
"#ffffcc", "#ffeda0", "#fed976", "#feb24c", "#fd8d3c",
|
|
158
|
+
"#fc4e2a", "#e31a1c", "#bd0026", "#800026"
|
|
96
159
|
],
|
|
97
160
|
"pubu": [
|
|
98
|
-
"#fff7fb",
|
|
99
|
-
"#
|
|
100
|
-
"#d0d1e6",
|
|
101
|
-
"#a6bddb",
|
|
102
|
-
"#74a9cf",
|
|
103
|
-
"#3690c0",
|
|
104
|
-
"#0570b0",
|
|
105
|
-
"#045a8d",
|
|
106
|
-
"#023858",
|
|
161
|
+
"#fff7fb", "#ece7f2", "#d0d1e6", "#a6bddb", "#74a9cf",
|
|
162
|
+
"#3690c0", "#0570b0", "#045a8d", "#023858"
|
|
107
163
|
][::-1],
|
|
108
|
-
"
|
|
109
|
-
"#
|
|
110
|
-
"#
|
|
111
|
-
"#c7e9c0",
|
|
112
|
-
"#a1d99b",
|
|
113
|
-
"#74c476",
|
|
114
|
-
"#41ab5d",
|
|
115
|
-
"#238b45",
|
|
116
|
-
"#006d2c",
|
|
117
|
-
"#00441b",
|
|
164
|
+
"ocean": [
|
|
165
|
+
"#ffffd9", "#edf8b1", "#c7e9b4", "#7fcdbb", "#41b6c4",
|
|
166
|
+
"#1d91c0", "#225ea8", "#253494", "#081d58"
|
|
118
167
|
],
|
|
119
168
|
}
|
|
120
169
|
return palettes.get(name, None)
|