14
14
import logging
15
15
import warnings
16
16
from abc import ABCMeta
17
- from typing import BinaryIO , Optional , Union
17
+ from typing import BinaryIO , List , Optional , Tuple , Union
18
18
19
19
import dateutil
20
20
import fs
@@ -421,7 +421,7 @@ def _get_session(self) -> AWSSession:
421
421
aws_session_token = self .config .aws_session_token or None ,
422
422
)
423
423
424
- def _load_from_image (self , path : str , filesystem : FS , bbox : Optional [BBox ]) -> np .ndarray :
424
+ def _load_from_image (self , path : str , filesystem : FS , bbox : Optional [BBox ]) -> Tuple [ np .ndarray , Optional [ BBox ]] :
425
425
"""The method decides in what way data will be loaded the image.
426
426
427
427
The method always uses `rasterio.Env` to suppress any low-level warnings. In case of a local filesystem
@@ -445,39 +445,65 @@ def _load_from_image(self, path: str, filesystem: FS, bbox: Optional[BBox]) -> n
445
445
with filesystem .openbin (path , "r" ) as file_handle :
446
446
return self ._read_image (file_handle , bbox )
447
447
448
- def _read_image (self , file_object : Union [str , BinaryIO ], bbox : Optional [BBox ]) -> np .ndarray :
448
+ def _read_image (self , file_object : Union [str , BinaryIO ], bbox : Optional [BBox ]) -> Tuple [ np .ndarray , Optional [ BBox ]] :
449
449
"""Reads data from the image."""
450
450
with rasterio .open (file_object ) as src :
451
451
src : DatasetReader
452
452
453
- read_window = self ._get_reading_window (src , bbox )
453
+ read_window , read_bbox = self ._get_reading_window_and_bbox (src , bbox )
454
454
boundless_reading = read_window is not None
455
- return src .read (window = read_window , boundless = boundless_reading , fill_value = self .no_data_value )
455
+ return src .read (window = read_window , boundless = boundless_reading , fill_value = self .no_data_value ), read_bbox
456
456
457
457
@staticmethod
458
- def _get_reading_window (reader : DatasetReader , bbox : Optional [BBox ]) -> Optional [Window ]:
458
+ def _get_reading_window_and_bbox (
459
+ reader : DatasetReader , bbox : Optional [BBox ]
460
+ ) -> Tuple [Optional [Window ], Optional [BBox ]]:
459
461
"""Provides a reading window for which data will be read from image. If it returns `None` this means that the
460
462
whole image should be read. Those cases are when bbox is not defined, image is not geo-referenced, or
461
- bbox coordinates exactly match image coordinates."""
462
- if bbox is None :
463
- return None
464
-
463
+ bbox coordinates exactly match image coordinates. Additionally, it provides a bounding box of reading window
464
+ if an image is geo-referenced."""
465
465
image_crs = reader .crs
466
466
image_transform = reader .transform
467
467
image_bounds = reader .bounds
468
468
if image_crs is None or image_transform is None or image_bounds is None :
469
- return None
469
+ return None , bbox
470
470
471
- if bbox .crs .epsg is not image_crs .to_epsg ():
471
+ image_bbox = BBox (list (image_bounds ), crs = image_crs .to_epsg ())
472
+ if bbox is None :
473
+ return None , image_bbox
474
+
475
+ original_bbox = bbox
476
+ if bbox .crs is not image_bbox .crs :
472
477
bbox = bbox .transform (image_crs .to_epsg ())
473
478
474
- bbox_coords = list (bbox )
475
- if bbox_coords == list (image_bounds ):
476
- return None
479
+ if bbox == image_bbox :
480
+ return None , original_bbox
481
+
482
+ return from_bounds (* iter (bbox ), transform = image_transform ), original_bbox
477
483
478
- return from_bounds (* bbox_coords , transform = image_transform )
484
+ def _load_data (
485
+ self , filename_paths : List [str ], filesystem : FS , initial_bbox : Optional [BBox ]
486
+ ) -> Tuple [np .ndarray , Optional [BBox ]]:
487
+ """Load data from images, join them, and provide their bounding box."""
488
+ data_per_path : List [np .ndarray ] = []
489
+ final_bbox : Optional [BBox ] = None
479
490
480
- def execute (self , eopatch = None , * , filename = None ):
491
+ for path in filename_paths :
492
+ data , bbox = self ._load_from_image (path , filesystem , initial_bbox )
493
+ data_per_path .append (data )
494
+
495
+ if bbox is None :
496
+ continue
497
+ if final_bbox and bbox != final_bbox :
498
+ raise RuntimeError (
499
+ "Given images have different geo-references. They can't be imported into an EOPatch that"
500
+ " doesn't have a bounding box."
501
+ )
502
+ final_bbox = bbox
503
+
504
+ return np .concatenate (data_per_path , axis = 0 ), final_bbox
505
+
506
+ def execute (self , eopatch : Optional [EOPatch ] = None , * , filename : Optional [str ] = None ) -> EOPatch :
481
507
"""Execute method which adds a new feature to the EOPatch
482
508
483
509
:param eopatch: input EOPatch or None if a new EOPatch should be created
@@ -493,8 +519,10 @@ def execute(self, eopatch=None, *, filename=None):
493
519
494
520
filesystem , filename_paths = self ._get_filesystem_and_paths (filename , eopatch .timestamp , create_paths = False )
495
521
496
- data = [self ._load_from_image (path , filesystem , eopatch .bbox ) for path in filename_paths ]
497
- data = np .concatenate (data , axis = 0 )
522
+ data , bbox = self ._load_data (filename_paths , filesystem , eopatch .bbox )
523
+
524
+ if eopatch .bbox is None :
525
+ eopatch .bbox = bbox
498
526
499
527
if self .image_dtype is not None :
500
528
data = data .astype (self .image_dtype )
0 commit comments