RadGEEToolbox 1.6.8__py3-none-any.whl → 1.6.10__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.
@@ -346,12 +346,12 @@ class Sentinel1Collection:
346
346
  else:
347
347
  raise ValueError("output_type must be 'ImageCollection' or 'Sentinel1Collection'")
348
348
 
349
- def merge(self, other):
349
+ def combine(self, other):
350
350
  """
351
- Merges the current Sentinel1Collection with another Sentinel1Collection, where images/bands with the same date are combined to a single multiband image.
351
+ Combines the current Sentinel1Collection with another Sentinel1Collection, using the `combine` method.
352
352
 
353
353
  Args:
354
- other (Sentinel1Collection): Another Sentinel1Collection to merge with current collection.
354
+ other (Sentinel1Collection): Another Sentinel1Collection to combine with current collection.
355
355
 
356
356
  Returns:
357
357
  Sentinel1Collection: A new Sentinel1Collection containing images from both collections.
@@ -364,6 +364,73 @@ class Sentinel1Collection:
364
364
  merged_collection = self.collection.combine(other.collection)
365
365
  return Sentinel1Collection(collection=merged_collection)
366
366
 
367
+ def merge(self, collections=None, multiband_collection=None, date_key='Date_Filter'):
368
+ """
369
+ Merge many singleband Sentinel1Collection products into the parent collection,
370
+ or merge a single multiband collection with parent collection,
371
+ pairing images by exact Date_Filter and returning one multiband image per date.
372
+
373
+ NOTE: if you want to merge two multiband collections, use the `combine` method instead.
374
+
375
+ Args:
376
+ collections (list): List of singleband collections to merge with parent collection, effectively adds one band per collection to each image in parent
377
+ multiband_collection (Sentinel1Collection, optional): A multiband collection to merge with parent. Specifying a collection here will override `collections`.
378
+ date_key (str): image property key for exact pairing (default 'Date_Filter')
379
+
380
+ Returns:
381
+ Sentinel1Collection: parent with extra single bands attached (one image per date)
382
+ """
383
+
384
+ if collections is None and multiband_collection is not None:
385
+ # Exact-date inner-join merge of two collections (adds ALL bands from 'other').
386
+ join = ee.Join.inner()
387
+ flt = ee.Filter.equals(leftField=date_key, rightField=date_key)
388
+ paired = join.apply(self.collection, multiband_collection.collection, flt)
389
+
390
+ def _pair_two(f):
391
+ f = ee.Feature(f)
392
+ a = ee.Image(f.get('primary'))
393
+ b = ee.Image(f.get('secondary'))
394
+ # Overwrite on name collision
395
+ merged = a.addBands(b, None, True)
396
+ # Keep parent props + date key
397
+ merged = merged.copyProperties(a, a.propertyNames())
398
+ merged = merged.set(date_key, a.get(date_key))
399
+ return ee.Image(merged)
400
+
401
+ return Sentinel1Collection(collection=ee.ImageCollection(paired.map(_pair_two)))
402
+
403
+ # Preferred path: merge many singleband products into the parent
404
+ if not isinstance(collections, list) or len(collections) == 0:
405
+ raise ValueError("Provide a non-empty list of Sentinel1Collection objects in `collections`.")
406
+
407
+ result = self.collection
408
+ for extra in collections:
409
+ if not isinstance(extra, Sentinel1Collection):
410
+ raise ValueError("All items in `collections` must be Sentinel1Collection objects.")
411
+
412
+ join = ee.Join.inner()
413
+ flt = ee.Filter.equals(leftField=date_key, rightField=date_key)
414
+ paired = join.apply(result, extra.collection, flt)
415
+
416
+ def _attach_one(f):
417
+ f = ee.Feature(f)
418
+ parent = ee.Image(f.get('primary'))
419
+ sb = ee.Image(f.get('secondary'))
420
+ # Assume singleband product; grab its first band name server-side
421
+ bname = ee.String(sb.bandNames().get(0))
422
+ # Add the single band; overwrite if the name already exists in parent
423
+ merged = parent.addBands(sb.select([bname]).rename([bname]), None, True)
424
+ # Preserve parent props + date key
425
+ merged = merged.copyProperties(parent, parent.propertyNames())
426
+ merged = merged.set(date_key, parent.get(date_key))
427
+ return ee.Image(merged)
428
+
429
+ result = ee.ImageCollection(paired.map(_attach_one))
430
+
431
+ return Sentinel1Collection(collection=result)
432
+
433
+
367
434
  @staticmethod
368
435
  def multilook_fn(image, looks):
369
436
  if looks not in [1, 2, 3, 4]:
@@ -1634,6 +1701,7 @@ class Sentinel1Collection:
1634
1701
  def iterate_zonal_stats(
1635
1702
  self,
1636
1703
  geometries,
1704
+ band=None,
1637
1705
  reducer_type="mean",
1638
1706
  scale=10,
1639
1707
  geometry_names=None,
@@ -1649,6 +1717,7 @@ class Sentinel1Collection:
1649
1717
 
1650
1718
  Args:
1651
1719
  geometries (ee.Geometry, ee.Feature, ee.FeatureCollection, list, or tuple): Input geometries for which to extract statistics. Can be a single ee.Geometry, an ee.Feature, an ee.FeatureCollection, a list of (lon, lat) tuples, or a list of ee.Geometry objects. Be careful to NOT provide coordinates as (lat, lon)!
1720
+ band (str, optional): The name of the band to use for statistics. If None, the first band is used. Defaults to None.
1652
1721
  reducer_type (str, optional): The ee.Reducer to use, e.g., 'mean', 'median', 'max', 'sum'. Defaults to 'mean'. Any ee.Reducer method can be used.
1653
1722
  scale (int, optional): Pixel scale in meters for the reduction. Defaults to 10.
1654
1723
  geometry_names (list, optional): A list of string names for the geometries. If provided, must match the number of geometries. Defaults to None.
@@ -1666,6 +1735,12 @@ class Sentinel1Collection:
1666
1735
  TypeError: If geometries input type is unsupported.
1667
1736
  """
1668
1737
  img_collection_obj = self
1738
+ if band:
1739
+ img_collection_obj = Sentinel1Collection(collection=img_collection_obj.collection.select(band))
1740
+ else:
1741
+ first_image = img_collection_obj.image_grab(0)
1742
+ first_band = first_image.bandNames().get(0)
1743
+ img_collection_obj = Sentinel1Collection(collection=img_collection_obj.collection.select([first_band]))
1669
1744
  # Filter collection by dates if provided
1670
1745
  if dates:
1671
1746
  img_collection_obj = Sentinel1Collection(
@@ -1748,18 +1823,33 @@ class Sentinel1Collection:
1748
1823
  stats_fc = image.reduceRegions(
1749
1824
  collection=features, reducer=reducer, scale=scale, tileScale=tileScale
1750
1825
  )
1751
- return stats_fc.map(lambda f: f.set('image_date', image_date))
1826
+
1827
+ def guarantee_reducer_property(f):
1828
+ has_property = f.propertyNames().contains(reducer_type)
1829
+ return ee.Algorithms.If(has_property, f, f.set(reducer_type, -9999))
1830
+ fixed_stats_fc = stats_fc.map(guarantee_reducer_property)
1831
+
1832
+ return fixed_stats_fc.map(lambda f: f.set('image_date', image_date))
1752
1833
 
1753
1834
  results_fc = ee.FeatureCollection(img_collection_obj.collection.map(calculate_stats_for_image)).flatten()
1754
1835
  df = Sentinel1Collection.ee_to_df(results_fc, remove_geom=True)
1755
1836
 
1756
1837
  # Checking for issues
1757
1838
  if df.empty:
1758
- 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.")
1759
- return df
1839
+ # 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.")
1840
+ # return df
1841
+ 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.")
1760
1842
  if reducer_type not in df.columns:
1761
1843
  print(f"Warning: Reducer '{reducer_type}' not found in results.")
1762
- return df
1844
+ # return df
1845
+
1846
+ # Get the number of rows before dropping nulls for a helpful message
1847
+ initial_rows = len(df)
1848
+ df.dropna(subset=[reducer_type], inplace=True)
1849
+ df = df[df[reducer_type] != -9999]
1850
+ dropped_rows = initial_rows - len(df)
1851
+ if dropped_rows > 0:
1852
+ print(f"Warning: Discarded {dropped_rows} results due to failed reductions (e.g., no valid pixels in geometry).")
1763
1853
 
1764
1854
  # Reshape DataFrame to have dates as index and geometry names as columns
1765
1855
  pivot_df = df.pivot(index='image_date', columns='geo_name', values=reducer_type)
@@ -1772,3 +1862,59 @@ class Sentinel1Collection:
1772
1862
  print(f"Zonal stats saved to {file_path}.csv")
1773
1863
  return
1774
1864
  return pivot_df
1865
+
1866
+ def export_to_asset_collection(
1867
+ self,
1868
+ asset_collection_path,
1869
+ region,
1870
+ scale,
1871
+ dates=None,
1872
+ filename_prefix="",
1873
+ crs=None,
1874
+ max_pixels=int(1e13),
1875
+ description_prefix="export"
1876
+ ):
1877
+ """
1878
+ Exports an image collection to a Google Earth Engine asset collection. The asset collection will be created if it does not already exist,
1879
+ and each image exported will be named according to the provided filename prefix and date.
1880
+
1881
+ Args:
1882
+ asset_collection_path (str): The path to the asset collection.
1883
+ region (ee.Geometry): The region to export.
1884
+ scale (int): The scale of the export.
1885
+ dates (list, optional): The dates to export. Defaults to None.
1886
+ filename_prefix (str, optional): The filename prefix. Defaults to "", i.e. blank.
1887
+ crs (str, optional): The coordinate reference system. Defaults to None, which will use the image's CRS.
1888
+ max_pixels (int, optional): The maximum number of pixels. Defaults to int(1e13).
1889
+ description_prefix (str, optional): The description prefix. Defaults to "export".
1890
+
1891
+ Returns:
1892
+ None: (queues export tasks)
1893
+ """
1894
+ ic = self.collection
1895
+ if dates is None:
1896
+ dates = self.dates
1897
+ try:
1898
+ ee.data.createAsset({'type': 'ImageCollection'}, asset_collection_path)
1899
+ except Exception:
1900
+ pass
1901
+
1902
+ for date_str in dates:
1903
+ img = ee.Image(ic.filter(ee.Filter.eq('Date_Filter', date_str)).first())
1904
+ asset_id = asset_collection_path + "/" + filename_prefix + date_str
1905
+ desc = description_prefix + "_" + filename_prefix + date_str
1906
+
1907
+ params = {
1908
+ 'image': img,
1909
+ 'description': desc,
1910
+ 'assetId': asset_id,
1911
+ 'region': region,
1912
+ 'scale': scale,
1913
+ 'maxPixels': max_pixels
1914
+ }
1915
+ if crs:
1916
+ params['crs'] = crs
1917
+
1918
+ ee.batch.Export.image.toAsset(**params).start()
1919
+
1920
+ print("Queued", len(dates), "export tasks to", asset_collection_path)