eoml 0.9.0__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.
- eoml/__init__.py +74 -0
- eoml/automation/__init__.py +7 -0
- eoml/automation/configuration.py +105 -0
- eoml/automation/dag.py +233 -0
- eoml/automation/experience.py +618 -0
- eoml/automation/tasks.py +825 -0
- eoml/bin/__init__.py +6 -0
- eoml/bin/clean_checkpoint.py +146 -0
- eoml/bin/land_cover_mapping_toml.py +435 -0
- eoml/bin/mosaic_images.py +137 -0
- eoml/data/__init__.py +7 -0
- eoml/data/basic_geo_data.py +214 -0
- eoml/data/dataset_utils.py +98 -0
- eoml/data/persistence/__init__.py +7 -0
- eoml/data/persistence/generic.py +253 -0
- eoml/data/persistence/lmdb.py +379 -0
- eoml/data/persistence/serializer.py +82 -0
- eoml/raster/__init__.py +7 -0
- eoml/raster/band.py +141 -0
- eoml/raster/dataset/__init__.py +6 -0
- eoml/raster/dataset/extractor.py +604 -0
- eoml/raster/raster_reader.py +602 -0
- eoml/raster/raster_utils.py +116 -0
- eoml/torch/__init__.py +7 -0
- eoml/torch/cnn/__init__.py +7 -0
- eoml/torch/cnn/augmentation.py +150 -0
- eoml/torch/cnn/dataset_evaluator.py +68 -0
- eoml/torch/cnn/db_dataset.py +605 -0
- eoml/torch/cnn/map_dataset.py +579 -0
- eoml/torch/cnn/map_dataset_const_mem.py +135 -0
- eoml/torch/cnn/outputs_transformer.py +130 -0
- eoml/torch/cnn/torch_utils.py +404 -0
- eoml/torch/cnn/training_dataset.py +241 -0
- eoml/torch/cnn/windows_dataset.py +120 -0
- eoml/torch/dataset/__init__.py +6 -0
- eoml/torch/dataset/shade_dataset_tester.py +46 -0
- eoml/torch/dataset/shade_tree_dataset_creators.py +537 -0
- eoml/torch/model_low_use.py +507 -0
- eoml/torch/models.py +282 -0
- eoml/torch/resnet.py +437 -0
- eoml/torch/sample_statistic.py +260 -0
- eoml/torch/trainer.py +782 -0
- eoml/torch/trainer_v2.py +253 -0
- eoml-0.9.0.dist-info/METADATA +93 -0
- eoml-0.9.0.dist-info/RECORD +47 -0
- eoml-0.9.0.dist-info/WHEEL +4 -0
- eoml-0.9.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
"""Raster reader module for geospatial raster data access.
|
|
2
|
+
|
|
3
|
+
This module provides wrapper classes around rasterio for reading and processing
|
|
4
|
+
geotiff raster data. It supports reading single rasters and multiple aligned
|
|
5
|
+
rasters with different resolutions and coordinate systems.
|
|
6
|
+
|
|
7
|
+
The readers are designed to be thread-safe when properly configured and support
|
|
8
|
+
various operations like windowed reading, coordinate-based extraction, and
|
|
9
|
+
on-the-fly resampling.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import copy
|
|
13
|
+
import math
|
|
14
|
+
from abc import ABC, abstractmethod
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import List, Union
|
|
17
|
+
|
|
18
|
+
import numpy
|
|
19
|
+
import rasterio
|
|
20
|
+
from rasterio.transform import TransformMethodsMixin
|
|
21
|
+
from rasterio.windows import Window, WindowMethodsMixin
|
|
22
|
+
|
|
23
|
+
from eoml import get_read_profile
|
|
24
|
+
from eoml.raster.band import Band
|
|
25
|
+
|
|
26
|
+
from rasterio.enums import Resampling
|
|
27
|
+
|
|
28
|
+
from eoml.raster.raster_utils import RasterInfo
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def append_raster_reader(r_list,
|
|
32
|
+
reference_index: int = 0,
|
|
33
|
+
read_profile=None,
|
|
34
|
+
sharing=False):
|
|
35
|
+
"""Combine multiple raster readers into a single MultiRasterReader.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
r_list: List of RasterReader or MultiRasterReader objects to combine.
|
|
39
|
+
reference_index: Index of the reader to use as spatial reference. Defaults to 0.
|
|
40
|
+
read_profile: Rasterio read profile configuration. Defaults to None.
|
|
41
|
+
sharing: Whether to enable file sharing mode. Defaults to False.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
MultiRasterReader: Combined reader for all input rasters.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
paths = []
|
|
48
|
+
bands = []
|
|
49
|
+
transformer = []
|
|
50
|
+
interpolation = []
|
|
51
|
+
|
|
52
|
+
for r in r_list:
|
|
53
|
+
|
|
54
|
+
if isinstance(r, RasterReader):
|
|
55
|
+
paths.append(r.path)
|
|
56
|
+
bands.append(r.bands_list)
|
|
57
|
+
transformer.append(r.transformer)
|
|
58
|
+
interpolation.append(r.interpolation)
|
|
59
|
+
|
|
60
|
+
else:
|
|
61
|
+
paths.extend(r.path)
|
|
62
|
+
bands.extend(r.bands_list)
|
|
63
|
+
transformer.extend(r.transformer)
|
|
64
|
+
interpolation.extend(r.interpolation)
|
|
65
|
+
|
|
66
|
+
return MultiRasterReader(paths, bands, transformer, interpolation, reference_index, read_profile, sharing)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class AbstractRasterReader(ABC):
|
|
70
|
+
"""Abstract base class for raster readers.
|
|
71
|
+
|
|
72
|
+
Provides common interface for reading geotiff rasters with support for
|
|
73
|
+
band selection, transformation, and various read operations.
|
|
74
|
+
|
|
75
|
+
Warning:
|
|
76
|
+
This class should either be thread-safe or properly copied for parallel mapping.
|
|
77
|
+
|
|
78
|
+
Attributes:
|
|
79
|
+
bands_list: Band selection configuration.
|
|
80
|
+
transformer: Function to apply to read data.
|
|
81
|
+
interpolation: Resampling method for reading.
|
|
82
|
+
read_profile: Rasterio read configuration.
|
|
83
|
+
path: Path to raster file(s).
|
|
84
|
+
sharing: Whether file sharing is enabled.
|
|
85
|
+
src_info: Raster metadata information.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
@abstractmethod
|
|
89
|
+
def __init__(self,
|
|
90
|
+
path,
|
|
91
|
+
bands_list: Band,
|
|
92
|
+
transformer,
|
|
93
|
+
interpolation=None,
|
|
94
|
+
read_profile=None,
|
|
95
|
+
sharing=False):
|
|
96
|
+
"""Initialize abstract raster reader.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
path: Path to raster file.
|
|
100
|
+
bands_list: Band configuration object.
|
|
101
|
+
transformer: Optional function to transform read data.
|
|
102
|
+
interpolation: Resampling method. Defaults to None (nearest neighbor).
|
|
103
|
+
read_profile: Rasterio read profile. Defaults to None.
|
|
104
|
+
sharing: Enable file sharing mode. Defaults to False.
|
|
105
|
+
"""
|
|
106
|
+
self.bands_list = bands_list
|
|
107
|
+
self.transformer = transformer
|
|
108
|
+
|
|
109
|
+
if interpolation is None:
|
|
110
|
+
interpolation = Resampling.nearest
|
|
111
|
+
|
|
112
|
+
self.interpolation = interpolation
|
|
113
|
+
self.read_profile = read_profile
|
|
114
|
+
self.path = path
|
|
115
|
+
|
|
116
|
+
self.sharing = sharing
|
|
117
|
+
|
|
118
|
+
self.src_info = RasterInfo.from_file(path)
|
|
119
|
+
|
|
120
|
+
def __repr__(self):
|
|
121
|
+
return f"AbstractRasterReader({self.path}, {self.bands_list}, {self.transformer}, {self.sharing})"
|
|
122
|
+
|
|
123
|
+
def __copy__(self):
|
|
124
|
+
cls = self.__class__
|
|
125
|
+
result = cls.__new__(cls)
|
|
126
|
+
result.__init__(self.path, self.bands_list, self.transformer, self.interpolation, self.read_profile, self.sharing)
|
|
127
|
+
|
|
128
|
+
return result
|
|
129
|
+
|
|
130
|
+
def __deepcopy__(self, memo):
|
|
131
|
+
cls = self.__class__
|
|
132
|
+
result = cls.__new__(cls)
|
|
133
|
+
|
|
134
|
+
path = copy.deepcopy(self.path)
|
|
135
|
+
bands_list = copy.deepcopy(self.bands_list)
|
|
136
|
+
transformer = copy.deepcopy(self.transformer)
|
|
137
|
+
interpolation = copy.deepcopy(self.interpolation)
|
|
138
|
+
read_profile = copy.deepcopy(self.read_profile)
|
|
139
|
+
result.__init__(path, bands_list, transformer, interpolation, read_profile, self.sharing)
|
|
140
|
+
|
|
141
|
+
return result
|
|
142
|
+
|
|
143
|
+
@abstractmethod
|
|
144
|
+
def ref_raster(self):
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
@abstractmethod
|
|
148
|
+
def ref_raster_info(self) -> RasterInfo:
|
|
149
|
+
pass
|
|
150
|
+
|
|
151
|
+
@abstractmethod
|
|
152
|
+
def read_windows(self, source_window):
|
|
153
|
+
pass
|
|
154
|
+
@abstractmethod
|
|
155
|
+
def read_windows_around_coordinate(self, center_x, center_y, size, op=math.floor):
|
|
156
|
+
pass
|
|
157
|
+
|
|
158
|
+
@abstractmethod
|
|
159
|
+
def read_bound(self, bounds):
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
def is_inside(self, windows: Window):
|
|
163
|
+
"""Check if a window is fully contained within the raster bounds.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
windows: Rasterio window to check.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
bool: True if window is fully inside raster bounds, False otherwise.
|
|
170
|
+
"""
|
|
171
|
+
return windows.row_off >= 0 and\
|
|
172
|
+
windows.col_off >= 0 and\
|
|
173
|
+
windows.col_off + windows.width <= self.ref_raster_info().width and\
|
|
174
|
+
windows.row_off + windows.height <= self.ref_raster_info().height
|
|
175
|
+
|
|
176
|
+
def windows_for_center(self, center_x, center_y, size, op=math.floor) -> Window:
|
|
177
|
+
"""Compute rasterio window centered on specified coordinates.
|
|
178
|
+
|
|
179
|
+
By default uses floor operation, meaning the returned window contains
|
|
180
|
+
the pixel at the specified point.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
center_x: X coordinate (longitude/easting) of center point.
|
|
184
|
+
center_y: Y coordinate (latitude/northing) of center point.
|
|
185
|
+
size: Size of window in pixels (square window).
|
|
186
|
+
op: Operation to apply when converting coordinates to pixels. Defaults to math.floor.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
Window: Rasterio window centered on the specified coordinates.
|
|
190
|
+
"""
|
|
191
|
+
row, col = self.ref_raster_info().index(center_x, center_y, op=op)
|
|
192
|
+
radius = math.floor(size / 2)
|
|
193
|
+
|
|
194
|
+
col = col - radius
|
|
195
|
+
row = row - radius
|
|
196
|
+
|
|
197
|
+
return Window(col, row, size, size)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class RasterReader(AbstractRasterReader):
|
|
201
|
+
"""Single raster file reader with band selection and transformation.
|
|
202
|
+
|
|
203
|
+
Wrapper around rasterio for reading geotiff files with support for
|
|
204
|
+
band selection, data transformation, and various reading modes.
|
|
205
|
+
|
|
206
|
+
Warning:
|
|
207
|
+
This class should either be thread-safe or properly copied for parallel mapping.
|
|
208
|
+
|
|
209
|
+
Attributes:
|
|
210
|
+
path: Path to the geotiff file.
|
|
211
|
+
bands_list: Band selection configuration.
|
|
212
|
+
transformer: Function to transform read data.
|
|
213
|
+
interpolation: Resampling method for reading.
|
|
214
|
+
read_profile: Rasterio read configuration.
|
|
215
|
+
n_band: Number of bands to read.
|
|
216
|
+
src_info: Raster metadata information.
|
|
217
|
+
sharing: Whether file sharing is enabled.
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
def __init__(self,
|
|
221
|
+
path: Path,
|
|
222
|
+
bands_list: Band,
|
|
223
|
+
transformer=None,
|
|
224
|
+
interpolation: Union[Resampling, None] = None,
|
|
225
|
+
read_profile=None,
|
|
226
|
+
sharing=False):
|
|
227
|
+
"""Initialize single raster reader.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
path: Path to the geotiff file.
|
|
231
|
+
bands_list: Band configuration object specifying which bands to read.
|
|
232
|
+
transformer: Optional function to apply to read data. Defaults to None.
|
|
233
|
+
interpolation: Resampling method for reading. Defaults to None (nearest neighbor).
|
|
234
|
+
read_profile: Rasterio read profile configuration. Defaults to None.
|
|
235
|
+
sharing: Enable file sharing mode. Defaults to False.
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
self.path = path
|
|
239
|
+
|
|
240
|
+
self.transformer = transformer
|
|
241
|
+
|
|
242
|
+
if interpolation is None:
|
|
243
|
+
interpolation = Resampling.nearest
|
|
244
|
+
|
|
245
|
+
self.interpolation = interpolation
|
|
246
|
+
|
|
247
|
+
if read_profile is None:
|
|
248
|
+
self.read_profile = get_read_profile()
|
|
249
|
+
else:
|
|
250
|
+
self.read_profile = read_profile
|
|
251
|
+
|
|
252
|
+
self.n_band = 0
|
|
253
|
+
self.bands_list = bands_list
|
|
254
|
+
|
|
255
|
+
if self.bands_list.selected is None:
|
|
256
|
+
with rasterio.open(path, 'r', **self.read_profile) as reader:
|
|
257
|
+
self.n_band = reader.count
|
|
258
|
+
else:
|
|
259
|
+
self.n_band = len(bands_list)
|
|
260
|
+
|
|
261
|
+
self.src_info = RasterInfo.from_file(path)
|
|
262
|
+
|
|
263
|
+
self.sharing = sharing
|
|
264
|
+
# self.reader = None
|
|
265
|
+
|
|
266
|
+
def __repr__(self):
|
|
267
|
+
return f"RasterReader({self.path}, {self.bands_list}, {self.transformer}, {self.sharing})"
|
|
268
|
+
|
|
269
|
+
def __enter__(self):
|
|
270
|
+
self.reader = rasterio.open(self.path, 'r', **self.read_profile, sharing= self.sharing)
|
|
271
|
+
return self
|
|
272
|
+
|
|
273
|
+
def __exit__(self, exc_type, exc_value, exc_tb):
|
|
274
|
+
self.reader.close()
|
|
275
|
+
# set the reader to none as we can not copy it
|
|
276
|
+
self.reader = None
|
|
277
|
+
|
|
278
|
+
def ref_raster(self):
|
|
279
|
+
"""Get the path of the reference raster.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
str: Path to the raster file.
|
|
283
|
+
"""
|
|
284
|
+
return self.path
|
|
285
|
+
|
|
286
|
+
def ref_raster_info(self):
|
|
287
|
+
"""Get metadata information of the reference raster.
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
RasterInfo: Raster metadata including CRS, transform, dimensions.
|
|
291
|
+
"""
|
|
292
|
+
return self.src_info
|
|
293
|
+
|
|
294
|
+
def read_windows(self, source_window):
|
|
295
|
+
"""Read data from a specific window of the raster.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
source_window: Rasterio window defining the area to read.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
numpy.ndarray: Raster data for the specified window, optionally transformed.
|
|
302
|
+
"""
|
|
303
|
+
if self.bands_list.selected1 is not None:
|
|
304
|
+
data = self.reader.read(self.bands_list.selected1, window=source_window, boundless=True)
|
|
305
|
+
else:
|
|
306
|
+
data = self.reader.read(window=source_window, boundless=True)
|
|
307
|
+
|
|
308
|
+
if self.transformer is not None:
|
|
309
|
+
self.transformer(data)
|
|
310
|
+
|
|
311
|
+
return data
|
|
312
|
+
|
|
313
|
+
def read_windows_around_coordinate(self, center_x, center_y, size, op=math.floor):
|
|
314
|
+
"""Read a window centered on specified coordinates.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
center_x: X coordinate (longitude/easting) of center point.
|
|
318
|
+
center_y: Y coordinate (latitude/northing) of center point.
|
|
319
|
+
size: Size of window in pixels (square window).
|
|
320
|
+
op: Operation to apply when converting coordinates to pixels. Defaults to math.floor.
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
numpy.ndarray: Raster data for the window around the specified coordinates.
|
|
324
|
+
"""
|
|
325
|
+
return self.read_windows(self.windows_for_center(center_x, center_y, size, op))
|
|
326
|
+
|
|
327
|
+
def read_bound(self, bounds):
|
|
328
|
+
"""Read data from a bounding box.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
bounds: Tuple of (left, bottom, right, top) coordinates.
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
numpy.ndarray: Raster data for the specified bounding box.
|
|
335
|
+
"""
|
|
336
|
+
|
|
337
|
+
windows = self.src_info.window(*bounds)
|
|
338
|
+
return self.read_windows(windows)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
class MultiRasterReader(AbstractRasterReader):
|
|
342
|
+
"""Multi-raster file reader for reading multiple aligned rasters.
|
|
343
|
+
|
|
344
|
+
Reads multiple raster files as a single combined dataset, with support for
|
|
345
|
+
different resolutions and coordinate systems. Automatically handles
|
|
346
|
+
reprojection and resampling to align all rasters to a reference grid.
|
|
347
|
+
|
|
348
|
+
Warning:
|
|
349
|
+
This class should either be thread-safe or properly copied for parallel mapping.
|
|
350
|
+
|
|
351
|
+
Todo:
|
|
352
|
+
- Check VRT implementation
|
|
353
|
+
- Improve handling of different projections
|
|
354
|
+
|
|
355
|
+
Attributes:
|
|
356
|
+
path: List of paths to geotiff files.
|
|
357
|
+
bands_list: List of band selection configurations for each raster.
|
|
358
|
+
transformer: List of transformation functions for each raster.
|
|
359
|
+
interpolation: List of resampling methods for each raster.
|
|
360
|
+
n_raster: Number of rasters being read.
|
|
361
|
+
reference_index: Index of the reference raster for spatial alignment.
|
|
362
|
+
reader: List of rasterio file readers.
|
|
363
|
+
src_info: List of raster metadata for each file.
|
|
364
|
+
read_profile: Rasterio read configuration.
|
|
365
|
+
n_band: Total number of bands across all rasters.
|
|
366
|
+
sharing: Whether file sharing is enabled.
|
|
367
|
+
"""
|
|
368
|
+
|
|
369
|
+
def __init__(self, paths: List[str],
|
|
370
|
+
bands_list: Union[List[Band], None],
|
|
371
|
+
transformer,
|
|
372
|
+
interpolation: Union[List[Union[Resampling, None]], None] = None,
|
|
373
|
+
reference_index: int = 0,
|
|
374
|
+
read_profile=None,
|
|
375
|
+
sharing = False):
|
|
376
|
+
"""Initialize multi-raster reader.
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
paths: List of paths to geotiff files to read.
|
|
380
|
+
bands_list: List of band configurations, one per raster file.
|
|
381
|
+
transformer: List of transformation functions, one per raster.
|
|
382
|
+
interpolation: List of resampling methods for each raster. Defaults to None (nearest neighbor).
|
|
383
|
+
reference_index: Index of raster to use as spatial reference. Defaults to 0.
|
|
384
|
+
read_profile: Rasterio read profile configuration. Defaults to None.
|
|
385
|
+
sharing: Enable file sharing mode. Defaults to False.
|
|
386
|
+
"""
|
|
387
|
+
|
|
388
|
+
super().__init__(paths[reference_index],
|
|
389
|
+
bands_list[reference_index],
|
|
390
|
+
transformer[reference_index],
|
|
391
|
+
interpolation[reference_index],
|
|
392
|
+
read_profile,
|
|
393
|
+
sharing)
|
|
394
|
+
|
|
395
|
+
self.n_raster = len(paths)
|
|
396
|
+
|
|
397
|
+
if interpolation is None:
|
|
398
|
+
interpolation = [Resampling.nearest for _ in range(self.n_raster)]
|
|
399
|
+
else:
|
|
400
|
+
interpolation = [Resampling.nearest if r is None else r for r in interpolation]
|
|
401
|
+
|
|
402
|
+
self.interpolation = interpolation
|
|
403
|
+
|
|
404
|
+
self.bands_list = MultiRasterReader.validate_list(bands_list, "in_bands_list", self.n_raster)
|
|
405
|
+
self.transformer = MultiRasterReader.validate_list(transformer, "normalizer", self.n_raster)
|
|
406
|
+
self.interpolation: Union[List[Union[Resampling, None]], None] = interpolation
|
|
407
|
+
|
|
408
|
+
self.path = paths
|
|
409
|
+
|
|
410
|
+
self.reader = None
|
|
411
|
+
self.reader: list = [None for _ in range(len(paths))]
|
|
412
|
+
|
|
413
|
+
self.src_info = []
|
|
414
|
+
for p in paths:
|
|
415
|
+
self.src_info.append(RasterInfo.from_file(p))
|
|
416
|
+
|
|
417
|
+
if read_profile is None:
|
|
418
|
+
self.read_profile = get_read_profile()
|
|
419
|
+
else:
|
|
420
|
+
self.read_profile = read_profile
|
|
421
|
+
|
|
422
|
+
self.n_band = 0
|
|
423
|
+
self.bands_list = bands_list
|
|
424
|
+
for p, b in zip(paths, self.bands_list):
|
|
425
|
+
if b is None:
|
|
426
|
+
with rasterio.open(p, 'r', **self.read_profile) as reader:
|
|
427
|
+
self.n_band += reader.count
|
|
428
|
+
else:
|
|
429
|
+
self.n_band += len(b)
|
|
430
|
+
|
|
431
|
+
self.reference_index = reference_index
|
|
432
|
+
|
|
433
|
+
def __repr__(self):
|
|
434
|
+
return f"MultiRasterReader({self.path}, {self.bands_list}, {self.transformer}, {self.sharing})"
|
|
435
|
+
|
|
436
|
+
def __copy__(self):
|
|
437
|
+
cls = self.__class__
|
|
438
|
+
result = cls.__new__(cls)
|
|
439
|
+
result.__init__(self.path,
|
|
440
|
+
self.bands_list,
|
|
441
|
+
self.transformer,
|
|
442
|
+
self.interpolation,
|
|
443
|
+
self.reference_index,
|
|
444
|
+
self.read_profile,
|
|
445
|
+
self.sharing)
|
|
446
|
+
|
|
447
|
+
return result
|
|
448
|
+
|
|
449
|
+
def __deepcopy__(self, memo):
|
|
450
|
+
cls = self.__class__
|
|
451
|
+
result = cls.__new__(cls)
|
|
452
|
+
|
|
453
|
+
path = copy.deepcopy(self.path)
|
|
454
|
+
bands_list = copy.deepcopy(self.bands_list)
|
|
455
|
+
transformer = copy.deepcopy(self.transformer)
|
|
456
|
+
interpolation = copy.deepcopy(self.interpolation),
|
|
457
|
+
read_profile = copy.deepcopy(self.read_profile)
|
|
458
|
+
result.__init__(path, bands_list, transformer, interpolation, self.reference_index, read_profile, self.sharing)
|
|
459
|
+
|
|
460
|
+
return result
|
|
461
|
+
|
|
462
|
+
def __enter__(self):
|
|
463
|
+
self.reader = [rasterio.open(p, 'r', **self.read_profile, sharing= self.sharing) for p in self.path]
|
|
464
|
+
return self
|
|
465
|
+
|
|
466
|
+
def __exit__(self, exc_type, exc_value, exc_tb):
|
|
467
|
+
for r in self.reader:
|
|
468
|
+
r.close()
|
|
469
|
+
self.reader = None
|
|
470
|
+
|
|
471
|
+
def ref_raster(self):
|
|
472
|
+
"""Get the path of the reference raster.
|
|
473
|
+
|
|
474
|
+
Returns:
|
|
475
|
+
str: Path to the reference raster file.
|
|
476
|
+
"""
|
|
477
|
+
return self.path[self.reference_index]
|
|
478
|
+
|
|
479
|
+
def ref_raster_info(self):
|
|
480
|
+
"""Get metadata information of the reference raster.
|
|
481
|
+
|
|
482
|
+
Returns:
|
|
483
|
+
RasterInfo: Raster metadata including CRS, transform, dimensions.
|
|
484
|
+
"""
|
|
485
|
+
return self.src_info[self.reference_index]
|
|
486
|
+
|
|
487
|
+
@staticmethod
|
|
488
|
+
def validate_list(var, var_name, num):
|
|
489
|
+
"""Validate list parameter has correct size.
|
|
490
|
+
|
|
491
|
+
Args:
|
|
492
|
+
var: Variable to validate (list, None, or single value).
|
|
493
|
+
var_name: Name of variable for error messages.
|
|
494
|
+
num: Expected list size.
|
|
495
|
+
|
|
496
|
+
Returns:
|
|
497
|
+
list: Validated list of correct size.
|
|
498
|
+
|
|
499
|
+
Raises:
|
|
500
|
+
ValueError: If var is a list of wrong size or invalid type.
|
|
501
|
+
"""
|
|
502
|
+
if hasattr(var, '__len__'):
|
|
503
|
+
if len(var) != num:
|
|
504
|
+
raise ValueError(f"{var_name} should be None or a list of size {num},"
|
|
505
|
+
f" {len(var)} found instead")
|
|
506
|
+
else:
|
|
507
|
+
if var is None:
|
|
508
|
+
var = [None for _ in range(num)]
|
|
509
|
+
else:
|
|
510
|
+
raise ValueError({"None or list expected for the selected band"})
|
|
511
|
+
|
|
512
|
+
return var
|
|
513
|
+
|
|
514
|
+
def read_windows_around_coordinate(self, center_x, center_y, size, op= math.floor):
|
|
515
|
+
"""Read a window centered on specified coordinates from all rasters.
|
|
516
|
+
|
|
517
|
+
Args:
|
|
518
|
+
center_x: X coordinate (longitude/easting) of center point.
|
|
519
|
+
center_y: Y coordinate (latitude/northing) of center point.
|
|
520
|
+
size: Size of window in pixels (square window).
|
|
521
|
+
op: Operation to apply when converting coordinates to pixels. Defaults to math.floor.
|
|
522
|
+
|
|
523
|
+
Returns:
|
|
524
|
+
numpy.ndarray: Combined raster data for the window around the specified coordinates.
|
|
525
|
+
"""
|
|
526
|
+
return self.read_windows(self.windows_for_center(center_x, center_y, size, math.floor))
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def read_windows(self, window: Window):
|
|
530
|
+
"""Read data from a specific window across all rasters.
|
|
531
|
+
|
|
532
|
+
Args:
|
|
533
|
+
window: Rasterio window defining the area to read.
|
|
534
|
+
|
|
535
|
+
Returns:
|
|
536
|
+
numpy.ndarray: Combined raster data for the specified window.
|
|
537
|
+
"""
|
|
538
|
+
bounds = self.ref_raster_info().window_bounds(window)
|
|
539
|
+
return self.read_bound(bounds)
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def _read_windows(self, windows: List[Window]):
|
|
543
|
+
"""Internal method to read from multiple windows across all rasters.
|
|
544
|
+
|
|
545
|
+
Args:
|
|
546
|
+
windows: List of windows, one per raster file.
|
|
547
|
+
|
|
548
|
+
Returns:
|
|
549
|
+
numpy.ndarray: Combined and stacked raster data.
|
|
550
|
+
"""
|
|
551
|
+
# TODO manage manage resampling
|
|
552
|
+
#rasterio.windows.from_bounds
|
|
553
|
+
|
|
554
|
+
# we read the real window, but change the resolution to "size"
|
|
555
|
+
ref_size = (round(windows[self.reference_index].height), round(windows[self.reference_index].width))
|
|
556
|
+
|
|
557
|
+
data = []
|
|
558
|
+
|
|
559
|
+
for r, b, norm, interpol, source_window in\
|
|
560
|
+
zip(self.reader, self.bands_list, self.transformer, self.interpolation, windows):
|
|
561
|
+
if b.selected1 is not None:
|
|
562
|
+
d = r.read(b.selected1, window=source_window, boundless=True,
|
|
563
|
+
out_shape=ref_size, resampling=interpol)
|
|
564
|
+
else:
|
|
565
|
+
d = r.read(window=source_window, boundless=True,
|
|
566
|
+
out_shape=ref_size, resampling=interpol)
|
|
567
|
+
|
|
568
|
+
if norm is not None:
|
|
569
|
+
norm(d)
|
|
570
|
+
|
|
571
|
+
data.append(d)
|
|
572
|
+
|
|
573
|
+
data = numpy.vstack(data)
|
|
574
|
+
|
|
575
|
+
return data
|
|
576
|
+
|
|
577
|
+
def read_bound(self, bounds):
|
|
578
|
+
"""Read data from a bounding box across all rasters.
|
|
579
|
+
|
|
580
|
+
Automatically handles reprojection if rasters have different coordinate
|
|
581
|
+
reference systems.
|
|
582
|
+
|
|
583
|
+
Args:
|
|
584
|
+
bounds: Tuple of (left, bottom, right, top) coordinates in reference CRS.
|
|
585
|
+
|
|
586
|
+
Returns:
|
|
587
|
+
numpy.ndarray: Combined raster data for the specified bounding box.
|
|
588
|
+
"""
|
|
589
|
+
|
|
590
|
+
windows = []
|
|
591
|
+
for info in self.src_info:
|
|
592
|
+
if self.ref_raster_info().crs == info.crs:
|
|
593
|
+
other_bounds = bounds
|
|
594
|
+
else:
|
|
595
|
+
other_bounds = rasterio.warp.transform_bounds(self.ref_raster_info().crs, info.crs, *bounds)
|
|
596
|
+
|
|
597
|
+
windows.append(info.window(*other_bounds))
|
|
598
|
+
|
|
599
|
+
return self._read_windows(windows)
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
|