eotdl 2023.7.19.post4__py3-none-any.whl → 2023.9.14__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.
@@ -2,11 +2,24 @@
2
2
  Module for STAC extensions objects
3
3
  """
4
4
 
5
+ import rasterio
6
+ import json
5
7
  import pystac
8
+
9
+ from os.path import join, dirname
6
10
  from pystac.extensions.sar import SarExtension
7
11
  from pystac.extensions.sar import FrequencyBand, Polarization
8
12
  from pystac.extensions.eo import Band, EOExtension
9
- from typing import Union
13
+ from pystac.extensions.label import (LabelClasses, LabelExtension, SummariesLabelExtension)
14
+ from pystac.extensions.raster import RasterExtension, RasterBand
15
+ from pystac.extensions.projection import ProjectionExtension
16
+ from typing import Union, List, Optional, Dict
17
+
18
+ import pandas as pd
19
+ from typing import List
20
+
21
+
22
+ SUPPORTED_EXTENSIONS = ('eo', 'sar', 'proj', 'raster')
10
23
 
11
24
 
12
25
  class STACExtensionObject:
@@ -15,12 +28,14 @@ class STACExtensionObject:
15
28
  self.properties = dict()
16
29
 
17
30
  def add_extension_to_object(
18
- self, obj: Union[pystac.Item, pystac.Asset]
31
+ self, obj: Union[pystac.Item, pystac.Asset],
32
+ obj_info: Optional[pd.DataFrame] = None
19
33
  ) -> Union[pystac.Item, pystac.Asset]:
20
34
  """
21
35
  Add the extension to the given object
22
36
 
23
37
  :param obj: object to add the extension
38
+ :param obj_info: object info from the STACDataFrame
24
39
  """
25
40
  pass
26
41
 
@@ -28,22 +43,31 @@ class STACExtensionObject:
28
43
  class SarExtensionObject(STACExtensionObject):
29
44
  def __init__(self) -> None:
30
45
  super().__init__()
31
- pass
46
+ self.polarizations = [Polarization.VV, Polarization.VH]
47
+ self.polarizations_dict = {"VV": Polarization.VV, "VH": Polarization.VH}
32
48
 
33
49
  def add_extension_to_object(
34
- self, obj: Union[pystac.Item, pystac.Asset]
50
+ self, obj: Union[pystac.Item, pystac.Asset],
51
+ obj_info: Optional[pd.DataFrame] = None
35
52
  ) -> Union[pystac.Item, pystac.Asset]:
36
53
  """
37
54
  Add the extension to the given object
38
55
 
39
56
  :param obj: object to add the extension
57
+ :param obj_info: object info from the STACDataFrame
40
58
  """
59
+ # Add SAR extension to the item
41
60
  sar_ext = SarExtension.ext(obj, add_if_missing=True)
42
- if isinstance(obj, pystac.Item):
43
- polarizations = [Polarization.VV, Polarization.VH]
44
- elif isinstance(obj, pystac.Asset):
45
- polarizations_dict = {"VV": Polarization.VV, "VH": Polarization.VH}
46
- polarizations = [polarizations_dict[obj.title]]
61
+ if isinstance(obj, pystac.Item) or (
62
+ isinstance(obj, pystac.Asset)
63
+ and obj.title not in self.polarizations_dict.keys()
64
+ ):
65
+ polarizations = self.polarizations
66
+ elif (
67
+ isinstance(obj, pystac.Asset)
68
+ and obj.title in self.polarizations_dict.keys()
69
+ ):
70
+ polarizations = [self.polarizations_dict[obj.title]]
47
71
  sar_ext.apply(
48
72
  instrument_mode="EW",
49
73
  polarizations=polarizations,
@@ -57,93 +81,102 @@ class SarExtensionObject(STACExtensionObject):
57
81
  class EOS2ExtensionObject(STACExtensionObject):
58
82
  def __init__(self) -> None:
59
83
  super().__init__()
60
- self.bands = [
61
- Band.create(
62
- name="Aerosols",
84
+ self.bands_dict = {
85
+ "B01": Band.create(
86
+ name="B01",
63
87
  description="Coastal aerosol, 442.7 nm (S2A), 442.3 nm (S2B)",
64
88
  common_name="coastal",
65
89
  ),
66
- Band.create(
67
- name="Blue",
90
+ "B02": Band.create(
91
+ name="B02",
68
92
  description="Blue, 492.4 nm (S2A), 492.1 nm (S2B)",
69
93
  common_name="blue",
70
94
  ),
71
- Band.create(
72
- name="Green",
95
+ "B03": Band.create(
96
+ name="B03",
73
97
  description="Green, 559.8 nm (S2A), 559.0 nm (S2B)",
74
98
  common_name="green",
75
99
  ),
76
- Band.create(
77
- name="Red",
100
+ "B04": Band.create(
101
+ name="B04",
78
102
  description="Red, 664.6 nm (S2A), 665.0 nm (S2B)",
79
103
  common_name="red",
80
104
  ),
81
- Band.create(
82
- name="Red edge 1",
105
+ "B05": Band.create(
106
+ name="B05",
83
107
  description="Vegetation red edge, 704.1 nm (S2A), 703.8 nm (S2B)",
84
108
  common_name="rededge",
85
109
  ),
86
- Band.create(
87
- name="Red edge 2",
110
+ "B06": Band.create(
111
+ name="B06",
88
112
  description="Vegetation red edge, 740.5 nm (S2A), 739.1 nm (S2B)",
89
113
  common_name="rededge",
90
114
  ),
91
- Band.create(
92
- name="Red edge 3",
115
+ "B07": Band.create(
116
+ name="B07",
93
117
  description="Vegetation red edge, 782.8 nm (S2A), 779.7 nm (S2B)",
94
118
  common_name="rededge",
95
119
  ),
96
- Band.create(
97
- name="NIR",
120
+ "B08": Band.create(
121
+ name="B08",
98
122
  description="NIR, 832.8 nm (S2A), 833.0 nm (S2B)",
99
123
  common_name="nir",
100
124
  ),
101
- Band.create(
102
- name="Red edge 4",
125
+ "B08a": Band.create(
126
+ name="B08a",
103
127
  description="Narrow NIR, 864.7 nm (S2A), 864.0 nm (S2B)",
104
128
  common_name="nir08",
105
129
  ),
106
- Band.create(
107
- name="Water vapour",
130
+ "B09": Band.create(
131
+ name="B09",
108
132
  description="Water vapour, 945.1 nm (S2A), 943.2 nm (S2B)",
109
133
  common_name="nir09",
110
134
  ),
111
- Band.create(
112
- name="Cirrus",
135
+ "B10": Band.create(
136
+ name="B10",
113
137
  description="SWIR – Cirrus, 1373.5 nm (S2A), 1376.9 nm (S2B)",
114
138
  common_name="cirrus",
115
139
  ),
116
- Band.create(
117
- name="SWIR1",
140
+ "B11": Band.create(
141
+ name="B11",
118
142
  description="SWIR, 1613.7 nm (S2A), 1610.4 nm (S2B)",
119
143
  common_name="swir16",
120
144
  ),
121
- Band.create(
122
- name="SWIR2",
145
+ "B12": Band.create(
146
+ name="B12",
123
147
  description="SWIR, 2202.4 nm (S2A), 2185.7 nm (S2B)",
124
148
  common_name="swir22",
125
149
  ),
126
- ]
150
+ }
127
151
 
128
152
  def add_extension_to_object(
129
- self, obj: Union[pystac.Item, pystac.Asset]
153
+ self, obj: Union[pystac.Item, pystac.Asset],
154
+ obj_info: pd.DataFrame
130
155
  ) -> Union[pystac.Item, pystac.Asset]:
131
156
  """
132
157
  Add the extension to the given object
133
158
 
134
159
  :param obj: object to add the extension
160
+ :param obj_info: object info from the STACDataFrame
135
161
  """
136
- if isinstance(obj, pystac.Asset):
137
- return
138
162
  # Add EO extension
139
163
  eo_ext = EOExtension.ext(obj, add_if_missing=True)
140
- # Add the existing bands from the rasters assets list
141
- eo_ext.apply(bands=self.bands)
142
164
  # Add common metadata
143
- obj.common_metadata.constellation = "Sentinel-2"
144
- obj.common_metadata.platform = "Sentinel-2"
145
- obj.common_metadata.instruments = ["Sentinel-2"]
146
- obj.common_metadata.gsd = 10 # TODO Where to obtain it?
165
+ if isinstance(obj, pystac.Item) or (
166
+ isinstance(obj, pystac.Asset) and obj.title not in self.bands_dict.keys()
167
+ ):
168
+ obj.common_metadata.constellation = "Sentinel-2"
169
+ obj.common_metadata.platform = "Sentinel-2"
170
+ obj.common_metadata.instruments = ["Sentinel-2"]
171
+ obj.common_metadata.gsd = 10
172
+ # Add bands
173
+ bands = obj_info["bands"].values
174
+ bands = bands[0] if bands else None
175
+ bands_list = [self.bands_dict[band] for band in bands] if bands else None
176
+ eo_ext.apply(bands=bands_list)
177
+
178
+ elif isinstance(obj, pystac.Asset):
179
+ eo_ext.apply(bands=[self.bands_dict[obj.title]])
147
180
 
148
181
  return obj
149
182
 
@@ -158,8 +191,224 @@ class DEMExtensionObject(STACExtensionObject):
158
191
  super().__init__()
159
192
 
160
193
 
194
+ class LabelExtensionObject(STACExtensionObject):
195
+ def __init__(self) -> None:
196
+ super().__init__()
197
+
198
+ @classmethod
199
+ def add_extension_to_item(
200
+ self,
201
+ obj: pystac.Item,
202
+ label_names: List[str],
203
+ label_classes: List[str],
204
+ label_properties: list,
205
+ label_description: str,
206
+ label_methods: list,
207
+ label_tasks: List[str],
208
+ label_type: str
209
+ ) -> Union[pystac.Item, pystac.Asset]:
210
+ """
211
+ Add the extension to the given object
212
+
213
+ :param obj: object to add the extension
214
+ :param label_names: list of label names
215
+ :param label_classes: list of label classes of the item
216
+ :param label_classes_list: list of all possible label classes
217
+ :param label_properties: list of label properties
218
+ :param label_description: label description
219
+ :param label_methods: list of labeling methods
220
+ :param label_tasks: list of label tasks
221
+ :param label_type: label type
222
+
223
+ :return: the item with the label extension
224
+ """
225
+ label_item = pystac.Item(id=obj.id,
226
+ geometry=obj.geometry,
227
+ bbox=obj.bbox,
228
+ properties=dict(),
229
+ datetime=obj.datetime
230
+ )
231
+
232
+ # Add the label extension to the item
233
+ LabelExtension.add_to(label_item)
234
+
235
+ # Access the label extension
236
+ label_ext = LabelExtension.ext(label_item)
237
+
238
+ # Add the label classes
239
+ for name, classes in zip(label_names, label_classes):
240
+ label_classes = LabelClasses.create(
241
+ name=name,
242
+ classes=classes,
243
+ )
244
+ label_ext.label_classes = [label_classes]
245
+
246
+ # TODO kwargs
247
+ # Add the label properties
248
+ label_ext.label_properties = label_properties
249
+ # Add the label description
250
+ label_ext.label_description = label_description
251
+ # Add the label methods
252
+ label_ext.label_methods = label_methods
253
+ # Add the label type
254
+ label_ext.label_type = label_type
255
+ # Add the label tasks
256
+ label_ext.label_tasks = label_tasks
257
+ # Add the source
258
+ label_ext.add_source(obj)
259
+
260
+ return label_item
261
+
262
+ @classmethod
263
+ def add_geojson_to_items(self,
264
+ collection: pystac.Collection,
265
+ df: pd.DataFrame
266
+ ) -> None:
267
+ """
268
+ """
269
+ for item in collection.get_all_items():
270
+ label_type = item.properties['label:type']
271
+ file_name = 'vector_labels' if label_type == 'vector' else 'raster_labels'
272
+ geojson_path = join(dirname(item.get_self_href()), f'{file_name}.geojson')
273
+
274
+ properties = {'roles': ['labels', f'labels-{label_type}']}
275
+
276
+ # TODO depending on the tasks, there must be extra fields
277
+ # TODO https://github.com/stac-extensions/label#assets
278
+ tasks = item.properties['label:tasks']
279
+ if 'tile_regression' in tasks:
280
+ pass
281
+ elif any(task in tasks for task in ('tile_classification', 'object_detection', 'segmentation')):
282
+ pass
283
+
284
+ label_ext = LabelExtension.ext(item)
285
+ label_ext.add_geojson_labels(href=geojson_path,
286
+ title='Label',
287
+ properties=properties)
288
+ item.make_asset_hrefs_relative()
289
+
290
+ item_id = item.id
291
+ geometry = item.geometry
292
+ labels = [df[df['id'] == item_id]['label'].values[0]]
293
+ # There is data like DEM data that does not have datetime but start and end datetime
294
+ datetime = item.datetime.isoformat() if item.datetime else (item.properties.start_datetime.isoformat(),
295
+ item.properties.end_datetime.isoformat())
296
+ labels_properties = dict(zip(item.properties['label:properties'], labels))
297
+ labels_properties['datetime'] = datetime
298
+
299
+ geojson = {
300
+ "type": "FeatureCollection",
301
+ "features": [
302
+ {
303
+ "type": "Feature",
304
+ "geometry": geometry,
305
+ "properties": labels_properties,
306
+ }
307
+ ],
308
+ }
309
+
310
+ with open(geojson_path, "w") as f:
311
+ json.dump(geojson, f)
312
+
313
+ @classmethod
314
+ def add_extension_to_collection(
315
+ self,
316
+ obj: pystac.Collection,
317
+ label_names: List[str],
318
+ label_classes: List[Union[list, tuple]],
319
+ label_type: str
320
+ ) -> None:
321
+ """
322
+ Add the label extension to the given collection
323
+
324
+ :param obj: object to add the extension
325
+ :param label_names: list of label names
326
+ :param label_classes: list of label classes
327
+ :param label_type: label type
328
+ """
329
+ LabelExtension.add_to(obj)
330
+
331
+ # Add the label extension to the collection
332
+ label_ext = SummariesLabelExtension(obj)
333
+
334
+ # Add the label classes
335
+ for name, classes in zip(label_names, label_classes):
336
+ label_classes = LabelClasses.create(
337
+ name=name,
338
+ classes=classes,
339
+ )
340
+ label_ext.label_classes = [label_classes]
341
+
342
+ # Add the label type
343
+ label_ext.label_type = label_type
344
+
345
+
346
+ class RasterExtensionObject(STACExtensionObject):
347
+ def __init__(self) -> None:
348
+ super().__init__()
349
+
350
+ def add_extension_to_object(
351
+ self, obj: Union[pystac.Item, pystac.Asset],
352
+ obj_info: Optional[pd.DataFrame] = None
353
+ ) -> Union[pystac.Item, pystac.Asset]:
354
+ """
355
+ Add the extension to the given object
356
+
357
+ :param obj: object to add the extension
358
+ :param obj_info: object info from the STACDataFrame
359
+ """
360
+ if not isinstance(obj, pystac.Asset):
361
+ return obj
362
+ else:
363
+ raster_ext = RasterExtension.ext(obj, add_if_missing=True)
364
+ src = rasterio.open(obj.href)
365
+ bands = list()
366
+ for band in src.indexes:
367
+ bands.append(RasterBand.create(
368
+ nodata=src.nodatavals[band - 1],
369
+ data_type=src.dtypes[band - 1],
370
+ spatial_resolution=src.res) if src.nodatavals else RasterBand.create(
371
+ data_type=src.dtypes[band - 1],
372
+ spatial_resolution=src.res))
373
+ raster_ext.apply(bands=bands)
374
+
375
+ return obj
376
+
377
+
378
+ class ProjExtensionObject(STACExtensionObject):
379
+ def __init__(self) -> None:
380
+ super().__init__()
381
+
382
+ def add_extension_to_object(
383
+ self, obj: Union[pystac.Item, pystac.Asset],
384
+ obj_info: pd.DataFrame
385
+ ) -> Union[pystac.Item, pystac.Asset]:
386
+ """
387
+ Add the extension to the given object
388
+
389
+ :param obj: object to add the extension
390
+ :param obj_info: object info from the STACDataFrame
391
+ """
392
+ # Add raster extension to the item
393
+ if isinstance(obj, pystac.Asset):
394
+ return obj
395
+ elif isinstance(obj, pystac.Item):
396
+ proj_ext = ProjectionExtension.ext(obj, add_if_missing=True)
397
+ ds = rasterio.open(obj_info['image'].values[0])
398
+ # Assume all the bands have the same projection
399
+ proj_ext.apply(
400
+ epsg=ds.crs.to_epsg(),
401
+ transform=ds.transform,
402
+ shape=ds.shape,
403
+ )
404
+
405
+ return obj
406
+
407
+
161
408
  type_stac_extensions_dict = {
162
409
  "sar": SarExtensionObject(),
163
410
  "eo": EOS2ExtensionObject(),
164
411
  "dem": DEMExtensionObject(),
412
+ "raster": RasterExtensionObject(),
413
+ "proj": ProjExtensionObject()
165
414
  }
@@ -0,0 +1,130 @@
1
+ '''
2
+ Module for STAC extent
3
+ '''
4
+
5
+ import pystac
6
+ from datetime import datetime
7
+ import rasterio
8
+ import json
9
+
10
+ from glob import glob
11
+ from os.path import dirname
12
+
13
+ from typing import List
14
+
15
+
16
+ def get_dem_temporal_interval() -> pystac.TemporalExtent:
17
+ """
18
+ Get a temporal interval for DEM data
19
+ """
20
+ min_date = datetime.strptime('2011-01-01', '%Y-%m-%d')
21
+ max_date = datetime.strptime('2015-01-07', '%Y-%m-%d')
22
+
23
+ return pystac.TemporalExtent([(min_date, max_date)])
24
+
25
+ def get_unknow_temporal_interval() -> pystac.TemporalExtent:
26
+ """
27
+ Get an unknown temporal interval
28
+ """
29
+ min_date = datetime.strptime('2000-01-01', '%Y-%m-%d')
30
+ max_date = datetime.strptime('2023-12-31', '%Y-%m-%d')
31
+
32
+ return pystac.TemporalExtent([(min_date, max_date)])
33
+
34
+ def get_unknow_extent() -> pystac.Extent:
35
+ """
36
+ """
37
+ return pystac.Extent(spatial=pystac.SpatialExtent([[0, 0, 0, 0]]),
38
+ temporal=pystac.TemporalExtent([(datetime.strptime('2000-01-01', '%Y-%m-%d'),
39
+ datetime.strptime('2023-12-31', '%Y-%m-%d')
40
+ )]))
41
+
42
+
43
+ def get_collection_extent(rasters: List[str]) -> pystac.Extent:
44
+ """
45
+ Get the extent of a collection
46
+
47
+ :param rasters: list of rasters
48
+ """
49
+ # Get the spatial extent of the collection
50
+ spatial_extent = get_collection_spatial_extent(rasters)
51
+ # Get the temporal interval of the collection
52
+ temporal_interval = get_collection_temporal_interval(rasters)
53
+ # Create the Extent object
54
+ extent = pystac.Extent(spatial=spatial_extent, temporal=temporal_interval)
55
+
56
+ return extent
57
+
58
+ def get_collection_spatial_extent(rasters: List[str]) -> pystac.SpatialExtent:
59
+ """
60
+ Get the spatial extent of a collection
61
+
62
+ :param path: path to the directory
63
+ """
64
+ # Get the bounding boxes of all the given rasters
65
+ bboxes = list()
66
+ for raster in rasters:
67
+ with rasterio.open(raster) as ds:
68
+ bounds = ds.bounds
69
+ dst_crs = 'EPSG:4326'
70
+ try:
71
+ left, bottom, right, top = rasterio.warp.transform_bounds(ds.crs, dst_crs, *bounds)
72
+ bbox = [left, bottom, right, top]
73
+ except rasterio.errors.CRSError:
74
+ spatial_extent = pystac.SpatialExtent([[0, 0, 0, 0]])
75
+ return spatial_extent
76
+ bboxes.append(bbox)
77
+ # Get the minimum and maximum values of the bounding boxes
78
+ try:
79
+ left = min([bbox[0] for bbox in bboxes])
80
+ bottom = min([bbox[1] for bbox in bboxes])
81
+ right = max([bbox[2] for bbox in bboxes])
82
+ top = max([bbox[3] for bbox in bboxes])
83
+ spatial_extent = pystac.SpatialExtent([[left, bottom, right, top]])
84
+ except ValueError:
85
+ spatial_extent = pystac.SpatialExtent([[0, 0, 0, 0]])
86
+ finally:
87
+ return spatial_extent
88
+
89
+ def get_collection_temporal_interval(rasters: List[str]) -> pystac.TemporalExtent:
90
+ """
91
+ Get the temporal interval of a collection
92
+
93
+ :param path: path to the directory
94
+ """
95
+ # Get all the metadata.json files in the directory of all the given rasters
96
+ metadata_json_files = list()
97
+ for raster in rasters:
98
+ metadata_json_files += glob(f'{dirname(raster)}/*.json', recursive=True)
99
+
100
+ if not metadata_json_files:
101
+ return get_unknow_temporal_interval() # If there is no metadata, set a generic temporal interval
102
+
103
+ # Get the temporal interval of every metadata.json file and the type of the data
104
+ data_types = list()
105
+ temporal_intervals = list()
106
+ for metadata_json_file in metadata_json_files:
107
+ with open(metadata_json_file, 'r') as f:
108
+ metadata = json.load(f)
109
+ # Append the temporal interval to the list as a datetime object
110
+ temporal_intervals.append(metadata['date-adquired']) if metadata['date-adquired'] else None
111
+ # Append the data type to the list
112
+ data_types.append(metadata['type']) if metadata['type'] else None
113
+
114
+ if temporal_intervals:
115
+ try:
116
+ # Get the minimum and maximum values of the temporal intervals
117
+ min_date = min([datetime.strptime(interval, '%Y-%m-%d') for interval in temporal_intervals])
118
+ max_date = max([datetime.strptime(interval, '%Y-%m-%d') for interval in temporal_intervals])
119
+ except ValueError:
120
+ min_date = datetime.strptime('2000-01-01', '%Y-%m-%d')
121
+ max_date = datetime.strptime('2023-12-31', '%Y-%m-%d')
122
+ finally:
123
+ # Create the temporal interval
124
+ return pystac.TemporalExtent([(min_date, max_date)])
125
+ else:
126
+ # Check if the collection is composed by DEM data. If not, set a generic temporal interval
127
+ if set(data_types) == {'dem'} or set(data_types) == {'DEM'} or set(data_types) == {'dem', 'DEM'}:
128
+ return get_dem_temporal_interval()
129
+ else:
130
+ return get_unknow_temporal_interval()